Spaces:
Build error
Build error
undo
Browse files- frontend/src/lib/DrawingCanvas.svelte +103 -40
- frontend/src/lib/Icons/Undo.svelte +12 -0
- frontend/src/lib/TemplateGallery.svelte +18 -3
- frontend/src/lib/store.ts +3 -2
- frontend/src/types.ts +12 -0
frontend/src/lib/DrawingCanvas.svelte
CHANGED
@@ -1,15 +1,17 @@
|
|
1 |
<script lang="ts">
|
|
|
2 |
import PxBrush from 'px-brush';
|
3 |
import { onMount } from 'svelte';
|
4 |
import type { Brush } from '../types';
|
5 |
-
import
|
|
|
|
|
6 |
|
7 |
let canvas: HTMLCanvasElement;
|
8 |
let brush: HTMLCanvasElement;
|
9 |
|
10 |
let brushCtx: CanvasRenderingContext2D;
|
11 |
let ctx: CanvasRenderingContext2D;
|
12 |
-
let pxBrush: PxBrush;
|
13 |
let startPosition: { x: number; y: number } = { x: 0, y: 0 };
|
14 |
|
15 |
$: {
|
@@ -20,28 +22,22 @@
|
|
20 |
}
|
21 |
}
|
22 |
$: {
|
23 |
-
if ($
|
24 |
-
drawImage(ctx, $
|
|
|
25 |
}
|
26 |
}
|
|
|
27 |
onMount(() => {
|
28 |
ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
29 |
brushCtx = brush.getContext('2d') as CanvasRenderingContext2D;
|
30 |
window.devicePixelRatio = 1;
|
31 |
-
pxBrush = new PxBrush(canvas);
|
32 |
$currentCanvas = canvas;
|
33 |
clearCanvas(ctx);
|
34 |
});
|
35 |
|
36 |
-
const drawImage = (ctx: CanvasRenderingContext2D, blob: Blob) => {
|
37 |
-
const img = new Image();
|
38 |
-
img.onload = function () {
|
39 |
-
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
|
40 |
-
};
|
41 |
-
img.src = URL.createObjectURL(blob);
|
42 |
-
};
|
43 |
-
|
44 |
let mouseDown: boolean = false;
|
|
|
45 |
function pointerEnter() {
|
46 |
// brush.hidden = false;
|
47 |
}
|
@@ -53,12 +49,22 @@
|
|
53 |
function pointerDown(e: MouseEvent) {
|
54 |
mouseDown = true;
|
55 |
startPosition = getPosition(canvas, e);
|
56 |
-
pxBrush.draw({
|
57 |
-
|
58 |
-
|
59 |
-
|
60 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
});
|
|
|
62 |
}
|
63 |
function pointerMove(e: MouseEvent) {
|
64 |
const position = getPosition(canvas, e);
|
@@ -67,13 +73,23 @@
|
|
67 |
if (!mouseDown) {
|
68 |
return;
|
69 |
}
|
70 |
-
pxBrush.draw({
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
75 |
});
|
76 |
startPosition = position;
|
|
|
77 |
}
|
78 |
function getPosition(canvas: HTMLCanvasElement, event: MouseEvent) {
|
79 |
const rect = canvas.getBoundingClientRect();
|
@@ -98,25 +114,72 @@
|
|
98 |
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
99 |
ctx.fill();
|
100 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
</script>
|
102 |
|
103 |
-
<div
|
104 |
-
<
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
</div>
|
121 |
|
122 |
<style lang="postcss" scoped>
|
|
|
1 |
<script lang="ts">
|
2 |
+
import { nanoid } from 'nanoid';
|
3 |
import PxBrush from 'px-brush';
|
4 |
import { onMount } from 'svelte';
|
5 |
import type { Brush } from '../types';
|
6 |
+
import UndoIcon from '$lib/Icons/Undo.svelte';
|
7 |
+
|
8 |
+
import { selectedBrush, selectedImage, currentCanvas, drawingLayers } from '$lib/store';
|
9 |
|
10 |
let canvas: HTMLCanvasElement;
|
11 |
let brush: HTMLCanvasElement;
|
12 |
|
13 |
let brushCtx: CanvasRenderingContext2D;
|
14 |
let ctx: CanvasRenderingContext2D;
|
|
|
15 |
let startPosition: { x: number; y: number } = { x: 0, y: 0 };
|
16 |
|
17 |
$: {
|
|
|
22 |
}
|
23 |
}
|
24 |
$: {
|
25 |
+
if ($selectedImage) {
|
26 |
+
drawImage(ctx, $selectedImage);
|
27 |
+
$drawingLayers = new Map();
|
28 |
}
|
29 |
}
|
30 |
+
|
31 |
onMount(() => {
|
32 |
ctx = canvas.getContext('2d') as CanvasRenderingContext2D;
|
33 |
brushCtx = brush.getContext('2d') as CanvasRenderingContext2D;
|
34 |
window.devicePixelRatio = 1;
|
|
|
35 |
$currentCanvas = canvas;
|
36 |
clearCanvas(ctx);
|
37 |
});
|
38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
39 |
let mouseDown: boolean = false;
|
40 |
+
let currLayerId: string;
|
41 |
function pointerEnter() {
|
42 |
// brush.hidden = false;
|
43 |
}
|
|
|
49 |
function pointerDown(e: MouseEvent) {
|
50 |
mouseDown = true;
|
51 |
startPosition = getPosition(canvas, e);
|
52 |
+
// pxBrush.draw({
|
53 |
+
// from: startPosition,
|
54 |
+
// to: startPosition,
|
55 |
+
// size: $selectedBrush.size,
|
56 |
+
// color: $selectedBrush.color
|
57 |
+
// });
|
58 |
+
|
59 |
+
currLayerId = nanoid();
|
60 |
+
drawingLayers.update((map) => {
|
61 |
+
map.set(currLayerId, {
|
62 |
+
brush: $selectedBrush,
|
63 |
+
points: [{ from: startPosition, to: startPosition }]
|
64 |
+
});
|
65 |
+
return map;
|
66 |
});
|
67 |
+
drawLayers();
|
68 |
}
|
69 |
function pointerMove(e: MouseEvent) {
|
70 |
const position = getPosition(canvas, e);
|
|
|
73 |
if (!mouseDown) {
|
74 |
return;
|
75 |
}
|
76 |
+
// pxBrush.draw({
|
77 |
+
// from: startPosition,
|
78 |
+
// to: position,
|
79 |
+
// size: $selectedBrush.size,
|
80 |
+
// color: $selectedBrush.color
|
81 |
+
// });
|
82 |
+
|
83 |
+
drawingLayers.update((map) => {
|
84 |
+
const currentLayer = map.get(currLayerId);
|
85 |
+
currentLayer?.points.push({
|
86 |
+
from: startPosition,
|
87 |
+
to: position
|
88 |
+
});
|
89 |
+
return map;
|
90 |
});
|
91 |
startPosition = position;
|
92 |
+
drawLayers();
|
93 |
}
|
94 |
function getPosition(canvas: HTMLCanvasElement, event: MouseEvent) {
|
95 |
const rect = canvas.getBoundingClientRect();
|
|
|
114 |
ctx.rect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
115 |
ctx.fill();
|
116 |
}
|
117 |
+
function drawImage(ctx: CanvasRenderingContext2D, img: HTMLImageElement | HTMLCanvasElement) {
|
118 |
+
ctx.drawImage(img, 0, 0, ctx.canvas.width, ctx.canvas.height);
|
119 |
+
}
|
120 |
+
function rollBack() {
|
121 |
+
if ($drawingLayers.size <= 0) {
|
122 |
+
return;
|
123 |
+
}
|
124 |
+
// clear current layer
|
125 |
+
const ids = Array.from($drawingLayers.keys());
|
126 |
+
drawingLayers.update((map) => {
|
127 |
+
map.delete(ids[ids.length - 1]);
|
128 |
+
return map;
|
129 |
+
});
|
130 |
+
drawLayers();
|
131 |
+
}
|
132 |
+
function drawLayers() {
|
133 |
+
const tempcanvas = document.createElement('canvas');
|
134 |
+
tempcanvas.width = 256;
|
135 |
+
tempcanvas.height = 512;
|
136 |
+
window.devicePixelRatio = 1;
|
137 |
+
const pxBrush = new PxBrush(tempcanvas);
|
138 |
+
clearCanvas(ctx);
|
139 |
+
if ($selectedImage) {
|
140 |
+
drawImage(ctx, $selectedImage);
|
141 |
+
}
|
142 |
+
Array.from($drawingLayers.values()).forEach((layer) => {
|
143 |
+
// clear temp canvas
|
144 |
+
layer.points.forEach((point, i) => {
|
145 |
+
pxBrush.draw({
|
146 |
+
from: point.from,
|
147 |
+
to: point.to,
|
148 |
+
size: layer.brush.size,
|
149 |
+
color: layer.brush.color
|
150 |
+
});
|
151 |
+
});
|
152 |
+
});
|
153 |
+
requestAnimationFrame(() => {
|
154 |
+
drawImage(ctx, tempcanvas);
|
155 |
+
});
|
156 |
+
}
|
157 |
</script>
|
158 |
|
159 |
+
<div>
|
160 |
+
<div class="inline-block relative overflow-clip">
|
161 |
+
<canvas
|
162 |
+
bind:this={canvas}
|
163 |
+
class="canvas"
|
164 |
+
width="256"
|
165 |
+
height="512"
|
166 |
+
on:touchmove={(e) => e.preventDefault()}
|
167 |
+
on:pointerenter={pointerEnter}
|
168 |
+
on:pointerup={pointerOut}
|
169 |
+
on:pointerleave={pointerOut}
|
170 |
+
on:pointercancel={pointerOut}
|
171 |
+
on:pointerout={pointerOut}
|
172 |
+
on:pointermove={pointerMove}
|
173 |
+
on:pointerdown={pointerDown}
|
174 |
+
/>
|
175 |
+
<canvas bind:this={brush} class="brush" width="10" height="10" />
|
176 |
+
<span class="label">{$selectedBrush?.label} </span>
|
177 |
+
<button
|
178 |
+
class="absolute bottom-1 left-1"
|
179 |
+
on:click|preventDefault={() => rollBack()}
|
180 |
+
disabled={$drawingLayers.size <= 0}><UndoIcon /></button
|
181 |
+
>
|
182 |
+
</div>
|
183 |
</div>
|
184 |
|
185 |
<style lang="postcss" scoped>
|
frontend/src/lib/Icons/Undo.svelte
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<script lang="ts">
|
2 |
+
export let classNames = '';
|
3 |
+
</script>
|
4 |
+
|
5 |
+
<svg xmlns="http://www.w3.org/2000/svg" width="20" viewBox="0 0 512 512" class={classNames}
|
6 |
+
><path
|
7 |
+
fill="white"
|
8 |
+
stroke="black"
|
9 |
+
stroke-width="30"
|
10 |
+
d="M480 256c0 123.4-100.5 223.9-223.9 223.9c-48.84 0-95.17-15.58-134.2-44.86c-14.12-10.59-16.97-30.66-6.375-44.81c10.59-14.12 30.62-16.94 44.81-6.375c27.84 20.91 61 31.94 95.88 31.94C344.3 415.8 416 344.1 416 256s-71.69-159.8-159.8-159.8c-37.46 0-73.09 13.49-101.3 36.64l45.12 45.14c17.01 17.02 4.955 46.1-19.1 46.1H35.17C24.58 224.1 16 215.5 16 204.9V59.04c0-24.04 29.07-36.08 46.07-19.07l47.6 47.63C149.9 52.71 201.5 32.11 256.1 32.11C379.5 32.11 480 132.6 480 256z"
|
11 |
+
/></svg
|
12 |
+
>
|
frontend/src/lib/TemplateGallery.svelte
CHANGED
@@ -1,16 +1,31 @@
|
|
1 |
<script lang="ts">
|
2 |
import { IMAGES_LIST } from '../data';
|
3 |
-
import {
|
4 |
import { base } from '$app/paths';
|
5 |
|
6 |
const submit = async (e: Event) => {
|
7 |
e.preventDefault();
|
8 |
const src = IMAGES_LIST[parseInt((e.target as HTMLInputElement).value)];
|
9 |
if (src) {
|
10 |
-
const blob = await fetch(base + src).then((res) => res.blob())
|
11 |
-
|
|
|
12 |
}
|
13 |
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
14 |
</script>
|
15 |
|
16 |
<div>
|
|
|
1 |
<script lang="ts">
|
2 |
import { IMAGES_LIST } from '../data';
|
3 |
+
import { selectedImage, generateHuman } from '$lib/store';
|
4 |
import { base } from '$app/paths';
|
5 |
|
6 |
const submit = async (e: Event) => {
|
7 |
e.preventDefault();
|
8 |
const src = IMAGES_LIST[parseInt((e.target as HTMLInputElement).value)];
|
9 |
if (src) {
|
10 |
+
const blob = await fetch(base + src).then((res) => res.blob())
|
11 |
+
const img = await getImage(blob);
|
12 |
+
$selectedImage = img;
|
13 |
}
|
14 |
};
|
15 |
+
|
16 |
+
async function getImage(blob: Blob): Promise<HTMLImageElement> {
|
17 |
+
return new Promise((resolve, reject) => {
|
18 |
+
const img = new Image();
|
19 |
+
img.onload = () => {
|
20 |
+
URL.revokeObjectURL(img.src);
|
21 |
+
resolve(img);
|
22 |
+
};
|
23 |
+
img.onerror = (err) => {
|
24 |
+
reject(err);
|
25 |
+
};
|
26 |
+
img.src = URL.createObjectURL(blob);
|
27 |
+
});
|
28 |
+
}
|
29 |
</script>
|
30 |
|
31 |
<div>
|
frontend/src/lib/store.ts
CHANGED
@@ -1,10 +1,11 @@
|
|
1 |
import { writable } from 'svelte/store';
|
2 |
-
import type { Brush, Params } from '../types';
|
3 |
import { randomSeed } from '$lib/utils';
|
4 |
|
|
|
5 |
export const resultImage = writable<string>();
|
6 |
export const currentCanvas = writable<HTMLCanvasElement>();
|
7 |
-
export const
|
8 |
export const selectedBrush = writable<Brush>();
|
9 |
export const selectedParams = writable<Params>({
|
10 |
texture: '',
|
|
|
1 |
import { writable } from 'svelte/store';
|
2 |
+
import type { Brush, Params, DrawingLayer } from '../types';
|
3 |
import { randomSeed } from '$lib/utils';
|
4 |
|
5 |
+
export const drawingLayers = writable<Map<string, DrawingLayer>>(new Map());
|
6 |
export const resultImage = writable<string>();
|
7 |
export const currentCanvas = writable<HTMLCanvasElement>();
|
8 |
+
export const selectedImage = writable<HTMLImageElement>();
|
9 |
export const selectedBrush = writable<Brush>();
|
10 |
export const selectedParams = writable<Params>({
|
11 |
texture: '',
|
frontend/src/types.ts
CHANGED
@@ -22,3 +22,15 @@ export interface FormElements extends HTMLCollection {
|
|
22 |
texture1: HTMLInputElement;
|
23 |
texture2: HTMLInputElement;
|
24 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
22 |
texture1: HTMLInputElement;
|
23 |
texture2: HTMLInputElement;
|
24 |
}
|
25 |
+
interface Point {
|
26 |
+
x: number;
|
27 |
+
y: number;
|
28 |
+
}
|
29 |
+
interface pxPoint {
|
30 |
+
from: Point;
|
31 |
+
to: Point;
|
32 |
+
}
|
33 |
+
export interface DrawingLayer {
|
34 |
+
brush: Brush;
|
35 |
+
points: pxPoint[];
|
36 |
+
}
|