@gradio/imageeditor 0.4.11 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/ImageEditor.stories.svelte +6 -2
- package/Index.svelte +39 -13
- package/package.json +9 -9
- package/shared/Controls.svelte +2 -2
- package/shared/ImageEditor.svelte +35 -12
- package/shared/InteractiveImageEditor.svelte +97 -20
- package/shared/layers/Layers.svelte +113 -56
- package/shared/layers/utils.ts +44 -4
- package/shared/tools/Brush.svelte +42 -51
- package/shared/tools/{BrushColor.svelte → BrushOptions.svelte} +73 -32
- package/shared/tools/BrushSize.svelte +13 -20
- package/shared/tools/ColorSwatch.svelte +66 -34
- package/shared/tools/Crop.svelte +12 -17
- package/shared/tools/Cropper.svelte +2 -0
- package/shared/tools/Handle.svelte +1 -1
- package/shared/tools/Sources.svelte +45 -14
- package/shared/tools/Tools.svelte +42 -57
- package/shared/tools/crop.ts +9 -4
- package/shared/utils/events.ts +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,28 @@
|
|
|
1
1
|
# @gradio/imageeditor
|
|
2
2
|
|
|
3
|
+
## 0.5.0
|
|
4
|
+
|
|
5
|
+
### Features
|
|
6
|
+
|
|
7
|
+
- [#8042](https://github.com/gradio-app/gradio/pull/8042) [`92139f3`](https://github.com/gradio-app/gradio/commit/92139f3d7d6b832b649ff1f6c10c87e6fb522cde) - refresh the `ImageEditor` UI. Thanks @pngwn!
|
|
8
|
+
- [#8059](https://github.com/gradio-app/gradio/pull/8059) [`074ce38`](https://github.com/gradio-app/gradio/commit/074ce3805ada6ba829c17da3af5f186d9f5f9865) - ensure the `ImageEditor` works correctly with layers and `change` events. Thanks @pngwn!
|
|
9
|
+
- [#7845](https://github.com/gradio-app/gradio/pull/7845) [`dbb7373`](https://github.com/gradio-app/gradio/commit/dbb7373dde69d4ed2741942b5a1898f8620cec24) - ensure `ImageEditor` events work as expected. Thanks @pngwn!
|
|
10
|
+
|
|
11
|
+
### Fixes
|
|
12
|
+
|
|
13
|
+
- [#8046](https://github.com/gradio-app/gradio/pull/8046) [`d6c289b`](https://github.com/gradio-app/gradio/commit/d6c289b34668f0011676737ee8edb4cb62b0620a) - round [x, y, w, h] before cropping to avoid unexpected interpolation on pixel values. Thanks @ernestchu!
|
|
14
|
+
- [#7959](https://github.com/gradio-app/gradio/pull/7959) [`2a5cb97`](https://github.com/gradio-app/gradio/commit/2a5cb978240960fd8b150a1eae6ae8328a5c0c59) - ensure `ImageEditor` always draws at the correct position. Thanks @hrrbay!
|
|
15
|
+
|
|
16
|
+
### Dependency updates
|
|
17
|
+
|
|
18
|
+
- @gradio/utils@0.3.2
|
|
19
|
+
- @gradio/statustracker@0.4.12
|
|
20
|
+
- @gradio/client@0.16.0
|
|
21
|
+
- @gradio/upload@0.8.5
|
|
22
|
+
- @gradio/atoms@0.7.0
|
|
23
|
+
- @gradio/icons@0.4.0
|
|
24
|
+
- @gradio/image@0.9.12
|
|
25
|
+
|
|
3
26
|
## 0.4.11
|
|
4
27
|
|
|
5
28
|
### Fixes
|
|
@@ -26,7 +26,11 @@
|
|
|
26
26
|
class="image-container"
|
|
27
27
|
style="width: 500px; position: relative;border-radius: var(--radius-lg);overflow: hidden;"
|
|
28
28
|
>
|
|
29
|
-
<ImageEditor
|
|
29
|
+
<ImageEditor
|
|
30
|
+
i18n={get(format)}
|
|
31
|
+
{...args}
|
|
32
|
+
server={{ accept_blobs: () => {} }}
|
|
33
|
+
/>
|
|
30
34
|
</div>
|
|
31
35
|
</Template>
|
|
32
36
|
|
|
@@ -118,7 +122,7 @@
|
|
|
118
122
|
coords: { clientX: 100, clientY: 100 }
|
|
119
123
|
});
|
|
120
124
|
|
|
121
|
-
await userEvent.click(canvas.getByLabelText("
|
|
125
|
+
await userEvent.click(canvas.getByLabelText("Draw button"));
|
|
122
126
|
|
|
123
127
|
var availableColors = document.querySelectorAll(
|
|
124
128
|
"button.color:not(.empty):not(.selected):not(.hidden)"
|
package/Index.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<svelte:options accessors={true} />
|
|
1
|
+
<svelte:options accessors={true} immutable={true} />
|
|
2
2
|
|
|
3
3
|
<script lang="ts">
|
|
4
4
|
import type { Brush, Eraser } from "./shared/tools/Brush.svelte";
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { Block } from "@gradio/atoms";
|
|
14
14
|
import { StatusTracker } from "@gradio/statustracker";
|
|
15
15
|
import type { LoadingStatus } from "@gradio/statustracker";
|
|
16
|
+
import { tick } from "svelte";
|
|
16
17
|
|
|
17
18
|
export let elem_id = "";
|
|
18
19
|
export let elem_classes: string[] = [];
|
|
@@ -26,6 +27,7 @@
|
|
|
26
27
|
export let show_label: boolean;
|
|
27
28
|
export let show_download_button: boolean;
|
|
28
29
|
export let root: string;
|
|
30
|
+
export let value_is_output = false;
|
|
29
31
|
|
|
30
32
|
export let height: number | undefined;
|
|
31
33
|
export let width: number | undefined;
|
|
@@ -47,16 +49,19 @@
|
|
|
47
49
|
export let eraser: Eraser;
|
|
48
50
|
export let crop_size: [number, number] | `${string}:${string}` | null = null;
|
|
49
51
|
export let transforms: "crop"[] = ["crop"];
|
|
50
|
-
|
|
52
|
+
export let layers = true;
|
|
51
53
|
export let attached_events: string[] = [];
|
|
54
|
+
export let server: {
|
|
55
|
+
accept_blobs: (a: any) => void;
|
|
56
|
+
};
|
|
52
57
|
|
|
53
58
|
export let gradio: Gradio<{
|
|
54
59
|
change: never;
|
|
55
60
|
error: string;
|
|
56
61
|
input: never;
|
|
57
62
|
edit: never;
|
|
58
|
-
stream: never;
|
|
59
63
|
drag: never;
|
|
64
|
+
apply: never;
|
|
60
65
|
upload: never;
|
|
61
66
|
clear: never;
|
|
62
67
|
select: SelectData;
|
|
@@ -64,13 +69,16 @@
|
|
|
64
69
|
}>;
|
|
65
70
|
|
|
66
71
|
let editor_instance: InteractiveImageEditor;
|
|
72
|
+
let image_id: null | string = null;
|
|
73
|
+
|
|
74
|
+
export async function get_value(): Promise<ImageBlobs | { id: string }> {
|
|
75
|
+
if (image_id) {
|
|
76
|
+
const val = { id: image_id };
|
|
77
|
+
image_id = null;
|
|
78
|
+
return val;
|
|
79
|
+
}
|
|
67
80
|
|
|
68
|
-
export async function get_value(): Promise<ImageBlobs> {
|
|
69
|
-
// @ts-ignore
|
|
70
|
-
loading_status = { status: "pending" };
|
|
71
81
|
const blobs = await editor_instance.get_data();
|
|
72
|
-
// @ts-ignore
|
|
73
|
-
loading_status = { status: "complete" };
|
|
74
82
|
|
|
75
83
|
return blobs;
|
|
76
84
|
}
|
|
@@ -79,7 +87,15 @@
|
|
|
79
87
|
|
|
80
88
|
$: value && handle_change();
|
|
81
89
|
|
|
82
|
-
function
|
|
90
|
+
function wait_for_next_frame(): Promise<void> {
|
|
91
|
+
return new Promise((resolve) => {
|
|
92
|
+
requestAnimationFrame(() => requestAnimationFrame(() => resolve()));
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function handle_change(): Promise<void> {
|
|
97
|
+
await wait_for_next_frame();
|
|
98
|
+
|
|
83
99
|
if (
|
|
84
100
|
value &&
|
|
85
101
|
(value.background || value.layers?.length || value.composite)
|
|
@@ -89,8 +105,15 @@
|
|
|
89
105
|
}
|
|
90
106
|
|
|
91
107
|
function handle_save(): void {
|
|
108
|
+
gradio.dispatch("apply");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function handle_history_change(): void {
|
|
92
112
|
gradio.dispatch("change");
|
|
93
|
-
|
|
113
|
+
if (!value_is_output) {
|
|
114
|
+
gradio.dispatch("input");
|
|
115
|
+
tick().then((_) => (value_is_output = false));
|
|
116
|
+
}
|
|
94
117
|
}
|
|
95
118
|
</script>
|
|
96
119
|
|
|
@@ -149,6 +172,8 @@
|
|
|
149
172
|
/>
|
|
150
173
|
|
|
151
174
|
<InteractiveImageEditor
|
|
175
|
+
on:change={() => handle_history_change()}
|
|
176
|
+
bind:image_id
|
|
152
177
|
{crop_size}
|
|
153
178
|
{value}
|
|
154
179
|
bind:this={editor_instance}
|
|
@@ -159,10 +184,8 @@
|
|
|
159
184
|
on:save={(e) => handle_save()}
|
|
160
185
|
on:edit={() => gradio.dispatch("edit")}
|
|
161
186
|
on:clear={() => gradio.dispatch("clear")}
|
|
162
|
-
on:stream={() => gradio.dispatch("stream")}
|
|
163
187
|
on:drag={({ detail }) => (dragging = detail)}
|
|
164
188
|
on:upload={() => gradio.dispatch("upload")}
|
|
165
|
-
on:select={({ detail }) => gradio.dispatch("select", detail)}
|
|
166
189
|
on:share={({ detail }) => gradio.dispatch("share", detail)}
|
|
167
190
|
on:error={({ detail }) => {
|
|
168
191
|
loading_status = loading_status || {};
|
|
@@ -172,9 +195,12 @@
|
|
|
172
195
|
on:error
|
|
173
196
|
{brush}
|
|
174
197
|
{eraser}
|
|
175
|
-
changeable={attached_events.includes("
|
|
198
|
+
changeable={attached_events.includes("apply")}
|
|
176
199
|
i18n={gradio.i18n}
|
|
177
200
|
{transforms}
|
|
201
|
+
accept_blobs={server.accept_blobs}
|
|
202
|
+
{layers}
|
|
203
|
+
status={loading_status?.status}
|
|
178
204
|
></InteractiveImageEditor>
|
|
179
205
|
</Block>
|
|
180
206
|
{/if}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -10,14 +10,14 @@
|
|
|
10
10
|
"@types/tinycolor2": "^1.4.6",
|
|
11
11
|
"pixi.js": "^7.3.2",
|
|
12
12
|
"tinycolor2": "^1.6.0",
|
|
13
|
-
"@gradio/atoms": "^0.
|
|
14
|
-
"@gradio/
|
|
15
|
-
"@gradio/
|
|
16
|
-
"@gradio/
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/
|
|
19
|
-
"@gradio/
|
|
20
|
-
"@gradio/
|
|
13
|
+
"@gradio/atoms": "^0.7.0",
|
|
14
|
+
"@gradio/client": "^0.16.0",
|
|
15
|
+
"@gradio/icons": "^0.4.0",
|
|
16
|
+
"@gradio/image": "^0.9.12",
|
|
17
|
+
"@gradio/statustracker": "^0.4.12",
|
|
18
|
+
"@gradio/upload": "^0.8.5",
|
|
19
|
+
"@gradio/utils": "^0.3.2",
|
|
20
|
+
"@gradio/wasm": "^0.10.0"
|
|
21
21
|
},
|
|
22
22
|
"main_changeset": true,
|
|
23
23
|
"main": "./Index.svelte",
|
package/shared/Controls.svelte
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { createEventDispatcher } from "svelte";
|
|
3
3
|
import { IconButton } from "@gradio/atoms";
|
|
4
|
-
import { Clear, Undo, Redo, Check } from "@gradio/icons";
|
|
4
|
+
import { Clear, Undo, Redo, Check, Trash } from "@gradio/icons";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Can the current image be undone?
|
|
@@ -56,7 +56,7 @@
|
|
|
56
56
|
}}
|
|
57
57
|
/>
|
|
58
58
|
<IconButton
|
|
59
|
-
Icon={
|
|
59
|
+
Icon={Trash}
|
|
60
60
|
label="Clear canvas"
|
|
61
61
|
on:click={(event) => {
|
|
62
62
|
dispatch("remove_image");
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
child_bottom: number;
|
|
29
29
|
}>;
|
|
30
30
|
active_tool: Writable<tool>;
|
|
31
|
+
toolbar_box: Writable<DOMRect>;
|
|
31
32
|
crop: Writable<[number, number, number, number]>;
|
|
32
33
|
position_spring: Spring<{
|
|
33
34
|
x: number;
|
|
@@ -69,10 +70,12 @@
|
|
|
69
70
|
const dispatch = createEventDispatcher<{
|
|
70
71
|
clear?: never;
|
|
71
72
|
save: void;
|
|
73
|
+
change: void;
|
|
72
74
|
}>();
|
|
73
75
|
export let crop_constraint = false;
|
|
74
76
|
|
|
75
77
|
let dimensions = writable(crop_size);
|
|
78
|
+
export let height = 0;
|
|
76
79
|
|
|
77
80
|
let editor_box: EditorContext["editor_box"] = writable({
|
|
78
81
|
parent_width: 0,
|
|
@@ -89,6 +92,8 @@
|
|
|
89
92
|
child_bottom: 0
|
|
90
93
|
});
|
|
91
94
|
|
|
95
|
+
$: height = $editor_box.child_height;
|
|
96
|
+
|
|
92
97
|
const crop = writable<[number, number, number, number]>([0, 0, 1, 1]);
|
|
93
98
|
const position_spring = spring(
|
|
94
99
|
{ x: 0, y: 0 },
|
|
@@ -103,6 +108,8 @@
|
|
|
103
108
|
|
|
104
109
|
const { can_redo, can_undo, current_history } = CommandManager;
|
|
105
110
|
|
|
111
|
+
$: $current_history.previous, dispatch("change");
|
|
112
|
+
|
|
106
113
|
$: {
|
|
107
114
|
history = !!$current_history.previous || $active_tool !== "bg";
|
|
108
115
|
}
|
|
@@ -114,6 +121,7 @@
|
|
|
114
121
|
PartialRecord<context_type, (dimensions?: typeof $dimensions) => void>
|
|
115
122
|
> = writable({});
|
|
116
123
|
const contexts: Writable<context_type[]> = writable([]);
|
|
124
|
+
const toolbar_box: Writable<DOMRect> = writable(new DOMRect());
|
|
117
125
|
|
|
118
126
|
const sort_order = ["bg", "layers", "crop", "draw", "erase"] as const;
|
|
119
127
|
const editor_context = setContext<EditorContext>(EDITOR_KEY, {
|
|
@@ -121,6 +129,7 @@
|
|
|
121
129
|
current_layer: writable(null),
|
|
122
130
|
dimensions,
|
|
123
131
|
editor_box,
|
|
132
|
+
toolbar_box,
|
|
124
133
|
active_tool,
|
|
125
134
|
crop,
|
|
126
135
|
position_spring,
|
|
@@ -238,10 +247,10 @@
|
|
|
238
247
|
return $pixi?.get_blobs(
|
|
239
248
|
$pixi.get_layers(),
|
|
240
249
|
new Rectangle(
|
|
241
|
-
l * $dimensions[0],
|
|
242
|
-
t * $dimensions[1],
|
|
243
|
-
w * $dimensions[0],
|
|
244
|
-
h * $dimensions[1]
|
|
250
|
+
Math.round(l * $dimensions[0]),
|
|
251
|
+
Math.round(t * $dimensions[1]),
|
|
252
|
+
Math.round(w * $dimensions[0]),
|
|
253
|
+
Math.round(h * $dimensions[1])
|
|
245
254
|
),
|
|
246
255
|
$dimensions
|
|
247
256
|
);
|
|
@@ -331,39 +340,53 @@
|
|
|
331
340
|
style:transform="translate({$position_spring.x}px, {$position_spring.y}px)"
|
|
332
341
|
></div>
|
|
333
342
|
</div>
|
|
334
|
-
<
|
|
343
|
+
<div class="tools-wrap">
|
|
344
|
+
<slot />
|
|
345
|
+
</div>
|
|
335
346
|
<div
|
|
336
347
|
class="border"
|
|
337
|
-
style:width="{$crop[2] * $editor_box.child_width}px"
|
|
338
|
-
style:height="{$crop[3] * $editor_box.child_height}px"
|
|
348
|
+
style:width="{$crop[2] * $editor_box.child_width + 1}px"
|
|
349
|
+
style:height="{$crop[3] * $editor_box.child_height + 1}px"
|
|
339
350
|
style:top="{$crop[1] * $editor_box.child_height +
|
|
340
|
-
($editor_box.child_top - $editor_box.parent_top)
|
|
351
|
+
($editor_box.child_top - $editor_box.parent_top) -
|
|
352
|
+
0.5}px"
|
|
341
353
|
style:left="{$crop[0] * $editor_box.child_width +
|
|
342
|
-
($editor_box.child_left - $editor_box.parent_left)
|
|
354
|
+
($editor_box.child_left - $editor_box.parent_left) -
|
|
355
|
+
0.5}px"
|
|
343
356
|
></div>
|
|
344
357
|
</div>
|
|
345
358
|
|
|
346
359
|
<style>
|
|
347
360
|
.wrap {
|
|
361
|
+
display: flex;
|
|
348
362
|
width: 100%;
|
|
349
363
|
height: 100%;
|
|
350
364
|
position: relative;
|
|
351
|
-
display: flex;
|
|
352
365
|
justify-content: center;
|
|
353
|
-
align-items:
|
|
366
|
+
align-items: flex-start;
|
|
354
367
|
}
|
|
355
368
|
.border {
|
|
356
369
|
position: absolute;
|
|
357
370
|
border: var(--block-border-color) 1px solid;
|
|
358
371
|
pointer-events: none;
|
|
372
|
+
border-radius: var(--radius-md);
|
|
359
373
|
}
|
|
360
374
|
|
|
361
375
|
.stage-wrap {
|
|
362
376
|
margin: var(--size-8);
|
|
363
377
|
margin-bottom: var(--size-1);
|
|
378
|
+
border-radius: var(--radius-md);
|
|
379
|
+
overflow: hidden;
|
|
364
380
|
}
|
|
365
381
|
|
|
366
|
-
.
|
|
382
|
+
.tools-wrap {
|
|
383
|
+
display: flex;
|
|
384
|
+
justify-content: center;
|
|
385
|
+
align-items: center;
|
|
386
|
+
padding: 0 var(--spacing-xl) 0 0;
|
|
387
|
+
border: 1px solid var(--block-border-color);
|
|
388
|
+
border-radius: var(--radius-sm);
|
|
389
|
+
margin: var(--spacing-xxl) 0 var(--spacing-xxl) 0;
|
|
367
390
|
}
|
|
368
391
|
|
|
369
392
|
.image-container {
|
|
@@ -22,12 +22,12 @@
|
|
|
22
22
|
import { type Brush as IBrush } from "./tools/Brush.svelte";
|
|
23
23
|
import { type Eraser } from "./tools/Brush.svelte";
|
|
24
24
|
|
|
25
|
-
export let brush: IBrush | null;
|
|
26
|
-
export let eraser: Eraser | null;
|
|
27
25
|
import { Tools, Crop, Brush, Sources } from "./tools";
|
|
28
26
|
import { BlockLabel } from "@gradio/atoms";
|
|
29
27
|
import { Image as ImageIcon } from "@gradio/icons";
|
|
30
28
|
|
|
29
|
+
export let brush: IBrush | null;
|
|
30
|
+
export let eraser: Eraser | null;
|
|
31
31
|
export let sources: ("clipboard" | "webcam" | "upload")[];
|
|
32
32
|
export let crop_size: [number, number] | `${string}:${string}` | null = null;
|
|
33
33
|
export let i18n: I18nFormatter;
|
|
@@ -41,9 +41,14 @@
|
|
|
41
41
|
composite: null
|
|
42
42
|
};
|
|
43
43
|
export let transforms: "crop"[] = ["crop"];
|
|
44
|
+
export let layers: boolean;
|
|
45
|
+
export let accept_blobs: (a: any) => void;
|
|
46
|
+
export let status: "pending" | "complete" | "error" = "complete";
|
|
44
47
|
|
|
45
48
|
const dispatch = createEventDispatcher<{
|
|
46
49
|
clear?: never;
|
|
50
|
+
upload?: never;
|
|
51
|
+
change?: never;
|
|
47
52
|
}>();
|
|
48
53
|
|
|
49
54
|
let editor: ImageEditor;
|
|
@@ -56,6 +61,8 @@
|
|
|
56
61
|
return !!o;
|
|
57
62
|
}
|
|
58
63
|
|
|
64
|
+
$: if (bg) dispatch("upload");
|
|
65
|
+
|
|
59
66
|
export async function get_data(): Promise<ImageBlobs> {
|
|
60
67
|
const blobs = await editor.get_blobs();
|
|
61
68
|
|
|
@@ -107,11 +114,83 @@
|
|
|
107
114
|
let bg = false;
|
|
108
115
|
let history = false;
|
|
109
116
|
|
|
117
|
+
export let image_id: null | string = null;
|
|
118
|
+
|
|
110
119
|
$: editor &&
|
|
111
120
|
editor.set_tool &&
|
|
112
121
|
(sources && sources.length
|
|
113
122
|
? editor.set_tool("bg")
|
|
114
123
|
: editor.set_tool("draw"));
|
|
124
|
+
|
|
125
|
+
type BinaryImages = [string, string, File, number | null][];
|
|
126
|
+
|
|
127
|
+
function nextframe(): Promise<void> {
|
|
128
|
+
return new Promise((resolve) => setTimeout(() => resolve(), 30));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let uploading = false;
|
|
132
|
+
let pending = false;
|
|
133
|
+
async function handle_change(e: CustomEvent<Blob | any>): Promise<void> {
|
|
134
|
+
if (uploading) {
|
|
135
|
+
pending = true;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
uploading = true;
|
|
140
|
+
|
|
141
|
+
await nextframe();
|
|
142
|
+
const blobs = await editor.get_blobs();
|
|
143
|
+
|
|
144
|
+
const images: BinaryImages = [];
|
|
145
|
+
|
|
146
|
+
let id = Math.random().toString(36).substring(2);
|
|
147
|
+
|
|
148
|
+
if (blobs.background)
|
|
149
|
+
images.push([
|
|
150
|
+
id,
|
|
151
|
+
"background",
|
|
152
|
+
new File([blobs.background], "background.png"),
|
|
153
|
+
null
|
|
154
|
+
]);
|
|
155
|
+
if (blobs.composite)
|
|
156
|
+
images.push([
|
|
157
|
+
id,
|
|
158
|
+
"composite",
|
|
159
|
+
new File([blobs.composite], "composite.png"),
|
|
160
|
+
null
|
|
161
|
+
]);
|
|
162
|
+
blobs.layers.forEach((layer, i) => {
|
|
163
|
+
if (layer)
|
|
164
|
+
images.push([
|
|
165
|
+
id as string,
|
|
166
|
+
`layer`,
|
|
167
|
+
new File([layer], `layer_${i}.png`),
|
|
168
|
+
i
|
|
169
|
+
]);
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
await Promise.all(
|
|
173
|
+
images.map(async ([image_id, type, data, index]) => {
|
|
174
|
+
return accept_blobs({
|
|
175
|
+
binary: true,
|
|
176
|
+
data: { file: data, id: image_id, type, index }
|
|
177
|
+
});
|
|
178
|
+
})
|
|
179
|
+
);
|
|
180
|
+
image_id = id;
|
|
181
|
+
dispatch("change");
|
|
182
|
+
|
|
183
|
+
await nextframe();
|
|
184
|
+
uploading = false;
|
|
185
|
+
if (pending) {
|
|
186
|
+
pending = false;
|
|
187
|
+
uploading = false;
|
|
188
|
+
handle_change(e);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
let active_mode: "webcam" | "color" | null = null;
|
|
193
|
+
let editor_height = 0;
|
|
115
194
|
</script>
|
|
116
195
|
|
|
117
196
|
<BlockLabel
|
|
@@ -121,8 +200,10 @@
|
|
|
121
200
|
/>
|
|
122
201
|
<ImageEditor
|
|
123
202
|
bind:this={editor}
|
|
203
|
+
bind:height={editor_height}
|
|
124
204
|
{changeable}
|
|
125
205
|
on:save
|
|
206
|
+
on:change={handle_change}
|
|
126
207
|
on:clear={() => dispatch("clear")}
|
|
127
208
|
bind:history
|
|
128
209
|
bind:bg
|
|
@@ -130,15 +211,17 @@
|
|
|
130
211
|
crop_constraint={!!crop_constraint}
|
|
131
212
|
>
|
|
132
213
|
<Tools {i18n}>
|
|
133
|
-
{
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
214
|
+
<Layers layer_files={value?.layers || null} enable_layers={layers} />
|
|
215
|
+
|
|
216
|
+
<Sources
|
|
217
|
+
{i18n}
|
|
218
|
+
{root}
|
|
219
|
+
{sources}
|
|
220
|
+
bind:bg
|
|
221
|
+
bind:active_mode
|
|
222
|
+
background_file={value?.background || value?.composite || null}
|
|
223
|
+
></Sources>
|
|
224
|
+
|
|
142
225
|
{#if transforms.includes("crop")}
|
|
143
226
|
<Crop {crop_constraint} />
|
|
144
227
|
{/if}
|
|
@@ -157,10 +240,8 @@
|
|
|
157
240
|
{/if}
|
|
158
241
|
</Tools>
|
|
159
242
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{#if !bg && !history}
|
|
163
|
-
<div class="empty wrap">
|
|
243
|
+
{#if !bg && !history && active_mode !== "webcam" && status !== "error"}
|
|
244
|
+
<div class="empty wrap" style:height={`${editor_height}px`}>
|
|
164
245
|
{#if sources && sources.length}
|
|
165
246
|
<div>Upload an image</div>
|
|
166
247
|
{/if}
|
|
@@ -181,7 +262,6 @@
|
|
|
181
262
|
flex-direction: column;
|
|
182
263
|
justify-content: center;
|
|
183
264
|
align-items: center;
|
|
184
|
-
|
|
185
265
|
position: absolute;
|
|
186
266
|
height: 100%;
|
|
187
267
|
width: 100%;
|
|
@@ -191,6 +271,7 @@
|
|
|
191
271
|
z-index: var(--layer-top);
|
|
192
272
|
text-align: center;
|
|
193
273
|
color: var(--body-text-color);
|
|
274
|
+
top: var(--size-8);
|
|
194
275
|
}
|
|
195
276
|
|
|
196
277
|
.wrap {
|
|
@@ -198,14 +279,10 @@
|
|
|
198
279
|
flex-direction: column;
|
|
199
280
|
justify-content: center;
|
|
200
281
|
align-items: center;
|
|
201
|
-
min-height: var(--size-60);
|
|
202
282
|
color: var(--block-label-text-color);
|
|
203
283
|
line-height: var(--line-md);
|
|
204
|
-
height: 100%;
|
|
205
|
-
padding-top: var(--size-3);
|
|
206
284
|
font-size: var(--text-lg);
|
|
207
285
|
pointer-events: none;
|
|
208
|
-
transform: translateY(-30px);
|
|
209
286
|
}
|
|
210
287
|
|
|
211
288
|
.or {
|