@gradio/imageeditor 0.9.0 → 0.10.1
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 +39 -0
- package/ImageEditor.stories.svelte +1 -0
- package/Index.svelte +7 -2
- package/dist/Example.svelte +41 -0
- package/dist/Example.svelte.d.ts +19 -0
- package/dist/Index.svelte +184 -0
- package/dist/Index.svelte.d.ts +192 -0
- package/dist/shared/Controls.svelte +73 -0
- package/dist/shared/Controls.svelte.d.ts +24 -0
- package/dist/shared/ImageEditor.svelte +329 -0
- package/dist/shared/ImageEditor.svelte.d.ts +79 -0
- package/dist/shared/InteractiveImageEditor.svelte +273 -0
- package/dist/shared/InteractiveImageEditor.svelte.d.ts +58 -0
- package/dist/shared/layers/Layers.svelte +216 -0
- package/dist/shared/layers/Layers.svelte.d.ts +18 -0
- package/dist/shared/layers/utils.d.ts +69 -0
- package/dist/shared/layers/utils.js +131 -0
- package/dist/shared/tools/Brush.svelte +208 -0
- package/dist/shared/tools/Brush.svelte.d.ts +29 -0
- package/dist/shared/tools/BrushOptions.svelte +162 -0
- package/dist/shared/tools/BrushOptions.svelte.d.ts +29 -0
- package/dist/shared/tools/BrushSize.svelte +60 -0
- package/dist/shared/tools/BrushSize.svelte.d.ts +20 -0
- package/dist/shared/tools/ColorField.svelte +159 -0
- package/dist/shared/tools/ColorField.svelte.d.ts +20 -0
- package/dist/shared/tools/ColorPicker.svelte +179 -0
- package/dist/shared/tools/ColorPicker.svelte.d.ts +16 -0
- package/dist/shared/tools/ColorSwatch.svelte +183 -0
- package/dist/shared/tools/ColorSwatch.svelte.d.ts +30 -0
- package/dist/shared/tools/Crop.svelte +155 -0
- package/dist/shared/tools/Crop.svelte.d.ts +16 -0
- package/dist/shared/tools/Cropper.svelte +271 -0
- package/dist/shared/tools/Cropper.svelte.d.ts +41 -0
- package/dist/shared/tools/Handle.svelte +260 -0
- package/dist/shared/tools/Handle.svelte.d.ts +29 -0
- package/dist/shared/tools/Sources.svelte +250 -0
- package/dist/shared/tools/Sources.svelte.d.ts +32 -0
- package/dist/shared/tools/Tools.svelte +101 -0
- package/dist/shared/tools/Tools.svelte.d.ts +48 -0
- package/dist/shared/tools/brush.d.ts +62 -0
- package/dist/shared/tools/brush.js +133 -0
- package/dist/shared/tools/crop.d.ts +15 -0
- package/dist/shared/tools/crop.js +154 -0
- package/dist/shared/tools/index.d.ts +5 -0
- package/dist/shared/tools/index.js +5 -0
- package/dist/shared/tools/sources.d.ts +40 -0
- package/dist/shared/tools/sources.js +69 -0
- package/dist/shared/tools/types.d.ts +1 -0
- package/dist/shared/tools/types.js +1 -0
- package/dist/shared/utils/commands.d.ts +96 -0
- package/dist/shared/utils/commands.js +65 -0
- package/dist/shared/utils/events.d.ts +7 -0
- package/dist/shared/utils/events.js +22 -0
- package/dist/shared/utils/parse_placeholder.d.ts +1 -0
- package/dist/shared/utils/parse_placeholder.js +19 -0
- package/dist/shared/utils/pixi.d.ts +76 -0
- package/dist/shared/utils/pixi.js +152 -0
- package/package.json +28 -12
- package/shared/ImageEditor.svelte +8 -4
- package/shared/InteractiveImageEditor.svelte +29 -5
- package/shared/tools/Brush.svelte +4 -6
- package/shared/tools/BrushOptions.svelte +1 -1
- package/shared/tools/Crop.svelte +2 -1
- package/shared/utils/parse_placeholder.ts +27 -0
- package/shared/utils/pixi.ts +0 -1
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
<script context="module">import {} from "./utils/pixi";
|
|
2
|
+
import {} from "./utils/commands";
|
|
3
|
+
export const EDITOR_KEY = Symbol("editor");
|
|
4
|
+
import {} from "./tools";
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<script>import { onMount, setContext, createEventDispatcher, tick } from "svelte";
|
|
8
|
+
import { writable } from "svelte/store";
|
|
9
|
+
import { spring } from "svelte/motion";
|
|
10
|
+
import { Rectangle } from "pixi.js";
|
|
11
|
+
import { command_manager } from "./utils/commands";
|
|
12
|
+
import {} from "./layers/utils";
|
|
13
|
+
import { create_pixi_app } from "./utils/pixi";
|
|
14
|
+
import Controls from "./Controls.svelte";
|
|
15
|
+
export let antialias = true;
|
|
16
|
+
export let crop_size;
|
|
17
|
+
export let changeable = false;
|
|
18
|
+
export let history;
|
|
19
|
+
export let bg = false;
|
|
20
|
+
export let sources;
|
|
21
|
+
const dispatch = createEventDispatcher();
|
|
22
|
+
export let crop_constraint = false;
|
|
23
|
+
export let canvas_size;
|
|
24
|
+
export let parent_height;
|
|
25
|
+
$:
|
|
26
|
+
orig_canvas_size = canvas_size;
|
|
27
|
+
const BASE_DIMENSIONS = canvas_size || [800, 600];
|
|
28
|
+
let dimensions = writable(BASE_DIMENSIONS);
|
|
29
|
+
export let height = 0;
|
|
30
|
+
let editor_box = writable({
|
|
31
|
+
parent_width: 0,
|
|
32
|
+
parent_height,
|
|
33
|
+
parent_top: 0,
|
|
34
|
+
parent_left: 0,
|
|
35
|
+
parent_right: 0,
|
|
36
|
+
parent_bottom: 0,
|
|
37
|
+
child_width: 0,
|
|
38
|
+
child_height: parent_height,
|
|
39
|
+
child_top: 0,
|
|
40
|
+
child_left: 0,
|
|
41
|
+
child_right: 0,
|
|
42
|
+
child_bottom: 0
|
|
43
|
+
});
|
|
44
|
+
$:
|
|
45
|
+
height = $editor_box.child_height;
|
|
46
|
+
const crop = writable([0, 0, 1, 1]);
|
|
47
|
+
const position_spring = spring(
|
|
48
|
+
{ x: 0, y: 0 },
|
|
49
|
+
{
|
|
50
|
+
stiffness: 0.1,
|
|
51
|
+
damping: 0.5
|
|
52
|
+
}
|
|
53
|
+
);
|
|
54
|
+
const pixi = writable(null);
|
|
55
|
+
const CommandManager = command_manager();
|
|
56
|
+
const { can_redo, can_undo, current_history } = CommandManager;
|
|
57
|
+
$:
|
|
58
|
+
$current_history.previous, dispatch("change");
|
|
59
|
+
$: {
|
|
60
|
+
history = !!$current_history.previous || $active_tool !== "bg";
|
|
61
|
+
}
|
|
62
|
+
const is_browser = typeof window !== "undefined";
|
|
63
|
+
const active_tool = writable("bg");
|
|
64
|
+
const reset_context = writable({});
|
|
65
|
+
const init_context = writable({});
|
|
66
|
+
const contexts = writable([]);
|
|
67
|
+
const toolbar_box = writable(
|
|
68
|
+
is_browser ? new DOMRect() : {}
|
|
69
|
+
);
|
|
70
|
+
const sort_order = ["bg", "layers", "crop", "draw", "erase"];
|
|
71
|
+
const editor_context = setContext(EDITOR_KEY, {
|
|
72
|
+
pixi,
|
|
73
|
+
current_layer: writable(null),
|
|
74
|
+
dimensions,
|
|
75
|
+
editor_box,
|
|
76
|
+
toolbar_box,
|
|
77
|
+
active_tool,
|
|
78
|
+
crop,
|
|
79
|
+
position_spring,
|
|
80
|
+
command_manager: CommandManager,
|
|
81
|
+
current_history,
|
|
82
|
+
register_context: (type, {
|
|
83
|
+
reset_fn,
|
|
84
|
+
init_fn
|
|
85
|
+
}) => {
|
|
86
|
+
contexts.update((c) => [...c, type]);
|
|
87
|
+
init_context.update((c) => ({ ...c, [type]: init_fn }));
|
|
88
|
+
reset_context.update((c) => ({ ...c, [type]: reset_fn }));
|
|
89
|
+
},
|
|
90
|
+
reset: (clear_image, dimensions2) => {
|
|
91
|
+
bg = false;
|
|
92
|
+
const _sorted_contexts = $contexts.sort((a, b) => {
|
|
93
|
+
return sort_order.indexOf(a) - sort_order.indexOf(b);
|
|
94
|
+
});
|
|
95
|
+
for (const k of _sorted_contexts) {
|
|
96
|
+
if (k in $reset_context && typeof $reset_context[k] === "function") {
|
|
97
|
+
$reset_context[k]?.();
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const k of _sorted_contexts) {
|
|
101
|
+
if (k in $init_context && typeof $init_context[k] === "function") {
|
|
102
|
+
if (k === "bg" && !clear_image) {
|
|
103
|
+
continue;
|
|
104
|
+
} else {
|
|
105
|
+
$init_context[k]?.(dimensions2);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
CommandManager.reset();
|
|
109
|
+
$pixi?.resize?.(...dimensions2);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
let pixi_target;
|
|
114
|
+
let canvas_wrap;
|
|
115
|
+
function get_dimensions(parent, child) {
|
|
116
|
+
if (!parent || !child)
|
|
117
|
+
return;
|
|
118
|
+
const {
|
|
119
|
+
width: parent_width,
|
|
120
|
+
height: parent_height2,
|
|
121
|
+
top: parent_top,
|
|
122
|
+
left: parent_left,
|
|
123
|
+
right: parent_right,
|
|
124
|
+
bottom: parent_bottom
|
|
125
|
+
} = canvas_wrap.getBoundingClientRect();
|
|
126
|
+
const {
|
|
127
|
+
width: child_width,
|
|
128
|
+
height: child_height,
|
|
129
|
+
top: child_top,
|
|
130
|
+
left: child_left,
|
|
131
|
+
right: child_right,
|
|
132
|
+
bottom: child_bottom
|
|
133
|
+
} = child.getBoundingClientRect();
|
|
134
|
+
editor_box.set({
|
|
135
|
+
child_width,
|
|
136
|
+
child_height,
|
|
137
|
+
child_left,
|
|
138
|
+
child_right,
|
|
139
|
+
child_top,
|
|
140
|
+
child_bottom,
|
|
141
|
+
parent_width,
|
|
142
|
+
parent_height: parent_height2,
|
|
143
|
+
parent_left,
|
|
144
|
+
parent_right,
|
|
145
|
+
parent_top,
|
|
146
|
+
parent_bottom
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
$:
|
|
150
|
+
if (crop_constraint && bg && !history) {
|
|
151
|
+
set_crop();
|
|
152
|
+
}
|
|
153
|
+
function set_crop() {
|
|
154
|
+
requestAnimationFrame(() => {
|
|
155
|
+
tick().then((v) => $active_tool = "crop");
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
function reposition_canvas() {
|
|
159
|
+
if (!$editor_box)
|
|
160
|
+
return;
|
|
161
|
+
const [l, t, w, h] = $crop;
|
|
162
|
+
const cx = l * $editor_box.child_width;
|
|
163
|
+
const cy = t * $editor_box.child_height;
|
|
164
|
+
const cw = w * $editor_box.child_width;
|
|
165
|
+
const ch = h * $editor_box.child_height;
|
|
166
|
+
const x = 0.5 * $editor_box.child_width - cx - cw / 2;
|
|
167
|
+
const y = 0.5 * $editor_box.child_height - cy - ch / 2;
|
|
168
|
+
position_spring.set({ x, y });
|
|
169
|
+
}
|
|
170
|
+
export async function get_blobs() {
|
|
171
|
+
if (!$pixi || !$pixi.get_layers)
|
|
172
|
+
return { background: null, layers: [], composite: null };
|
|
173
|
+
const [l, t, w, h] = $crop;
|
|
174
|
+
return $pixi?.get_blobs(
|
|
175
|
+
$pixi.get_layers(),
|
|
176
|
+
new Rectangle(
|
|
177
|
+
Math.round(l * $dimensions[0]),
|
|
178
|
+
Math.round(t * $dimensions[1]),
|
|
179
|
+
Math.round(w * $dimensions[0]),
|
|
180
|
+
Math.round(h * $dimensions[1])
|
|
181
|
+
),
|
|
182
|
+
$dimensions
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
$:
|
|
186
|
+
$crop && reposition_canvas();
|
|
187
|
+
$:
|
|
188
|
+
$position_spring && get_dimensions(canvas_wrap, pixi_target);
|
|
189
|
+
export function handle_remove() {
|
|
190
|
+
editor_context.reset(
|
|
191
|
+
true,
|
|
192
|
+
orig_canvas_size ? orig_canvas_size : $dimensions
|
|
193
|
+
);
|
|
194
|
+
if (!sources.length) {
|
|
195
|
+
set_tool("draw");
|
|
196
|
+
} else {
|
|
197
|
+
set_tool("bg");
|
|
198
|
+
}
|
|
199
|
+
dispatch("clear");
|
|
200
|
+
}
|
|
201
|
+
onMount(() => {
|
|
202
|
+
const _size = (canvas_size ? canvas_size : crop_size) || [800, 600];
|
|
203
|
+
const app = create_pixi_app({
|
|
204
|
+
target: pixi_target,
|
|
205
|
+
dimensions: _size,
|
|
206
|
+
antialias
|
|
207
|
+
});
|
|
208
|
+
function resize(width, height2) {
|
|
209
|
+
app.resize(width, height2);
|
|
210
|
+
dimensions.set([width, height2]);
|
|
211
|
+
}
|
|
212
|
+
pixi.set({ ...app, resize });
|
|
213
|
+
const resizer = new ResizeObserver((entries) => {
|
|
214
|
+
for (const entry of entries) {
|
|
215
|
+
get_dimensions(canvas_wrap, pixi_target);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
resizer.observe(canvas_wrap);
|
|
219
|
+
resizer.observe(pixi_target);
|
|
220
|
+
for (const k of $contexts) {
|
|
221
|
+
if (k in $init_context && typeof $init_context[k] === "function") {
|
|
222
|
+
$init_context[k]?.($dimensions);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
resize(...$dimensions);
|
|
226
|
+
return () => {
|
|
227
|
+
$pixi?.destroy();
|
|
228
|
+
resizer.disconnect();
|
|
229
|
+
for (const k of $contexts) {
|
|
230
|
+
if (k in $reset_context) {
|
|
231
|
+
$reset_context[k]?.();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
});
|
|
236
|
+
let saved_history = $current_history;
|
|
237
|
+
function handle_save() {
|
|
238
|
+
saved_history = $current_history;
|
|
239
|
+
dispatch("save");
|
|
240
|
+
}
|
|
241
|
+
export function set_tool(tool) {
|
|
242
|
+
$active_tool = tool;
|
|
243
|
+
}
|
|
244
|
+
</script>
|
|
245
|
+
|
|
246
|
+
<svelte:window on:scroll={() => get_dimensions(canvas_wrap, pixi_target)} />
|
|
247
|
+
|
|
248
|
+
<div data-testid="image" class="image-container">
|
|
249
|
+
<Controls
|
|
250
|
+
can_undo={$can_undo}
|
|
251
|
+
can_redo={$can_redo}
|
|
252
|
+
can_save={saved_history !== $current_history}
|
|
253
|
+
{changeable}
|
|
254
|
+
on:undo={CommandManager.undo}
|
|
255
|
+
on:redo={CommandManager.redo}
|
|
256
|
+
on:remove_image={handle_remove}
|
|
257
|
+
on:save={handle_save}
|
|
258
|
+
/>
|
|
259
|
+
<div class="wrap" bind:this={canvas_wrap}>
|
|
260
|
+
<div
|
|
261
|
+
bind:this={pixi_target}
|
|
262
|
+
class="stage-wrap"
|
|
263
|
+
class:bg={!bg}
|
|
264
|
+
style:transform="translate({$position_spring.x}px, {$position_spring.y}px)"
|
|
265
|
+
></div>
|
|
266
|
+
</div>
|
|
267
|
+
<div class="tools-wrap">
|
|
268
|
+
<slot />
|
|
269
|
+
</div>
|
|
270
|
+
<div
|
|
271
|
+
class="canvas"
|
|
272
|
+
class:no-border={!bg && $active_tool === "bg" && !history}
|
|
273
|
+
style:width="{$crop[2] * $editor_box.child_width + 1}px"
|
|
274
|
+
style:height="{$crop[3] * $editor_box.child_height + 1}px"
|
|
275
|
+
style:top="{$crop[1] * $editor_box.child_height +
|
|
276
|
+
($editor_box.child_top - $editor_box.parent_top) -
|
|
277
|
+
0.5}px"
|
|
278
|
+
style:left="{$crop[0] * $editor_box.child_width +
|
|
279
|
+
($editor_box.child_left - $editor_box.parent_left) -
|
|
280
|
+
0.5}px"
|
|
281
|
+
></div>
|
|
282
|
+
</div>
|
|
283
|
+
|
|
284
|
+
<style>
|
|
285
|
+
.wrap {
|
|
286
|
+
display: flex;
|
|
287
|
+
width: 100%;
|
|
288
|
+
height: 100%;
|
|
289
|
+
position: relative;
|
|
290
|
+
justify-content: center;
|
|
291
|
+
align-items: flex-start;
|
|
292
|
+
}
|
|
293
|
+
.canvas {
|
|
294
|
+
position: absolute;
|
|
295
|
+
border: var(--block-border-color) 1px solid;
|
|
296
|
+
pointer-events: none;
|
|
297
|
+
border-radius: var(--radius-md);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
.no-border {
|
|
301
|
+
border: none;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.stage-wrap {
|
|
305
|
+
margin: var(--size-8);
|
|
306
|
+
margin-bottom: var(--size-1);
|
|
307
|
+
border-radius: var(--radius-md);
|
|
308
|
+
overflow: hidden;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
.tools-wrap {
|
|
312
|
+
display: flex;
|
|
313
|
+
justify-content: center;
|
|
314
|
+
align-items: center;
|
|
315
|
+
padding: 0 var(--spacing-xl) 0 0;
|
|
316
|
+
border: 1px solid var(--block-border-color);
|
|
317
|
+
border-radius: var(--radius-sm);
|
|
318
|
+
margin: var(--spacing-xxl) 0 var(--spacing-xxl) 0;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
.image-container {
|
|
322
|
+
display: flex;
|
|
323
|
+
height: 100%;
|
|
324
|
+
flex-direction: column;
|
|
325
|
+
justify-content: center;
|
|
326
|
+
align-items: center;
|
|
327
|
+
max-height: 100%;
|
|
328
|
+
}
|
|
329
|
+
</style>
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
|
+
import type { Writable } from "svelte/store";
|
|
3
|
+
import type { Spring } from "svelte/motion";
|
|
4
|
+
import { type PixiApp } from "./utils/pixi";
|
|
5
|
+
import { type CommandManager } from "./utils/commands";
|
|
6
|
+
export declare const EDITOR_KEY: unique symbol;
|
|
7
|
+
export type context_type = "bg" | "layers" | "crop" | "draw" | "erase";
|
|
8
|
+
import { type tool } from "./tools";
|
|
9
|
+
export interface EditorContext {
|
|
10
|
+
pixi: Writable<PixiApp | null>;
|
|
11
|
+
current_layer: Writable<LayerScene | null>;
|
|
12
|
+
dimensions: Writable<[number, number]>;
|
|
13
|
+
editor_box: Writable<{
|
|
14
|
+
parent_width: number;
|
|
15
|
+
parent_height: number;
|
|
16
|
+
parent_left: number;
|
|
17
|
+
parent_top: number;
|
|
18
|
+
parent_right: number;
|
|
19
|
+
parent_bottom: number;
|
|
20
|
+
child_width: number;
|
|
21
|
+
child_height: number;
|
|
22
|
+
child_left: number;
|
|
23
|
+
child_top: number;
|
|
24
|
+
child_right: number;
|
|
25
|
+
child_bottom: number;
|
|
26
|
+
}>;
|
|
27
|
+
active_tool: Writable<tool>;
|
|
28
|
+
toolbar_box: Writable<DOMRect | Record<string, never>>;
|
|
29
|
+
crop: Writable<[number, number, number, number]>;
|
|
30
|
+
position_spring: Spring<{
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
}>;
|
|
34
|
+
command_manager: CommandManager;
|
|
35
|
+
current_history: CommandManager["current_history"];
|
|
36
|
+
register_context: (type: context_type, { reset_fn, init_fn }: {
|
|
37
|
+
reset_fn?: () => void;
|
|
38
|
+
init_fn?: (dimensions?: [number, number]) => void;
|
|
39
|
+
}) => void;
|
|
40
|
+
reset: (clear_image: boolean, dimensions: [number, number]) => void;
|
|
41
|
+
}
|
|
42
|
+
import { type LayerScene } from "./layers/utils";
|
|
43
|
+
import { type ImageBlobs } from "./utils/pixi";
|
|
44
|
+
declare const __propDef: {
|
|
45
|
+
props: {
|
|
46
|
+
antialias?: boolean | undefined;
|
|
47
|
+
crop_size: [number, number] | undefined;
|
|
48
|
+
changeable?: boolean | undefined;
|
|
49
|
+
history: boolean;
|
|
50
|
+
bg?: boolean | undefined;
|
|
51
|
+
sources: ("clipboard" | "webcam" | "upload")[];
|
|
52
|
+
crop_constraint?: boolean | undefined;
|
|
53
|
+
canvas_size: [number, number] | undefined;
|
|
54
|
+
parent_height: number;
|
|
55
|
+
height?: number | undefined;
|
|
56
|
+
get_blobs?: (() => Promise<ImageBlobs>) | undefined;
|
|
57
|
+
handle_remove?: (() => void) | undefined;
|
|
58
|
+
set_tool?: ((tool: tool) => void) | undefined;
|
|
59
|
+
};
|
|
60
|
+
events: {
|
|
61
|
+
clear?: CustomEvent<undefined> | undefined;
|
|
62
|
+
save: CustomEvent<void>;
|
|
63
|
+
change: CustomEvent<void>;
|
|
64
|
+
} & {
|
|
65
|
+
[evt: string]: CustomEvent<any>;
|
|
66
|
+
};
|
|
67
|
+
slots: {
|
|
68
|
+
default: {};
|
|
69
|
+
};
|
|
70
|
+
};
|
|
71
|
+
export type ImageEditorProps = typeof __propDef.props;
|
|
72
|
+
export type ImageEditorEvents = typeof __propDef.events;
|
|
73
|
+
export type ImageEditorSlots = typeof __propDef.slots;
|
|
74
|
+
export default class ImageEditor extends SvelteComponent<ImageEditorProps, ImageEditorEvents, ImageEditorSlots> {
|
|
75
|
+
get get_blobs(): () => Promise<ImageBlobs>;
|
|
76
|
+
get handle_remove(): () => void;
|
|
77
|
+
get set_tool(): (tool: tool) => void;
|
|
78
|
+
}
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
<script context="module"></script>
|
|
2
|
+
|
|
3
|
+
<script>import { createEventDispatcher } from "svelte";
|
|
4
|
+
import {} from "@gradio/utils";
|
|
5
|
+
import { prepare_files } from "@gradio/client";
|
|
6
|
+
import ImageEditor from "./ImageEditor.svelte";
|
|
7
|
+
import Layers from "./layers/Layers.svelte";
|
|
8
|
+
import {} from "./tools/Brush.svelte";
|
|
9
|
+
import {} from "./tools/Brush.svelte";
|
|
10
|
+
import { Tools, Crop, Brush, Sources } from "./tools";
|
|
11
|
+
import { BlockLabel } from "@gradio/atoms";
|
|
12
|
+
import { Image as ImageIcon } from "@gradio/icons";
|
|
13
|
+
import { inject } from "./utils/parse_placeholder";
|
|
14
|
+
export let brush;
|
|
15
|
+
export let eraser;
|
|
16
|
+
export let sources;
|
|
17
|
+
export let crop_size = null;
|
|
18
|
+
export let i18n;
|
|
19
|
+
export let root;
|
|
20
|
+
export let label = void 0;
|
|
21
|
+
export let show_label;
|
|
22
|
+
export let changeable = false;
|
|
23
|
+
export let value = {
|
|
24
|
+
background: null,
|
|
25
|
+
layers: [],
|
|
26
|
+
composite: null
|
|
27
|
+
};
|
|
28
|
+
export let transforms = ["crop"];
|
|
29
|
+
export let layers;
|
|
30
|
+
export let accept_blobs;
|
|
31
|
+
export let status = "complete";
|
|
32
|
+
export let canvas_size;
|
|
33
|
+
export let realtime;
|
|
34
|
+
export let upload;
|
|
35
|
+
export let stream_handler;
|
|
36
|
+
export let dragging;
|
|
37
|
+
export let placeholder = void 0;
|
|
38
|
+
export let height = 400;
|
|
39
|
+
const dispatch = createEventDispatcher();
|
|
40
|
+
let editor;
|
|
41
|
+
function is_not_null(o) {
|
|
42
|
+
return !!o;
|
|
43
|
+
}
|
|
44
|
+
function is_file_data(o) {
|
|
45
|
+
return !!o;
|
|
46
|
+
}
|
|
47
|
+
$:
|
|
48
|
+
if (bg)
|
|
49
|
+
dispatch("upload");
|
|
50
|
+
export async function get_data() {
|
|
51
|
+
const blobs = await editor.get_blobs();
|
|
52
|
+
const bg2 = blobs.background ? upload(
|
|
53
|
+
await prepare_files([new File([blobs.background], "background.png")]),
|
|
54
|
+
root
|
|
55
|
+
) : Promise.resolve(null);
|
|
56
|
+
const layers2 = blobs.layers.filter(is_not_null).map(
|
|
57
|
+
async (blob, i) => upload(await prepare_files([new File([blob], `layer_${i}.png`)]), root)
|
|
58
|
+
);
|
|
59
|
+
const composite = blobs.composite ? upload(
|
|
60
|
+
await prepare_files([new File([blobs.composite], "composite.png")]),
|
|
61
|
+
root
|
|
62
|
+
) : Promise.resolve(null);
|
|
63
|
+
const [background, composite_, ...layers_] = await Promise.all([
|
|
64
|
+
bg2,
|
|
65
|
+
composite,
|
|
66
|
+
...layers2
|
|
67
|
+
]);
|
|
68
|
+
return {
|
|
69
|
+
background: Array.isArray(background) ? background[0] : background,
|
|
70
|
+
layers: layers_.flatMap((layer) => Array.isArray(layer) ? layer : [layer]).filter(is_file_data),
|
|
71
|
+
composite: Array.isArray(composite_) ? composite_[0] : composite_
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function handle_value(value2) {
|
|
75
|
+
if (!editor)
|
|
76
|
+
return;
|
|
77
|
+
if (value2 == null) {
|
|
78
|
+
editor.handle_remove();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
$:
|
|
82
|
+
handle_value(value);
|
|
83
|
+
$:
|
|
84
|
+
crop_constraint = crop_size;
|
|
85
|
+
let bg = false;
|
|
86
|
+
let history = false;
|
|
87
|
+
export let image_id = null;
|
|
88
|
+
$:
|
|
89
|
+
editor && editor.set_tool && (sources && sources.length ? editor.set_tool("bg") : editor.set_tool("draw"));
|
|
90
|
+
function nextframe() {
|
|
91
|
+
return new Promise((resolve) => setTimeout(() => resolve(), 30));
|
|
92
|
+
}
|
|
93
|
+
let uploading = false;
|
|
94
|
+
let pending = false;
|
|
95
|
+
async function handle_change(e) {
|
|
96
|
+
if (!realtime)
|
|
97
|
+
return;
|
|
98
|
+
if (uploading) {
|
|
99
|
+
pending = true;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
uploading = true;
|
|
103
|
+
await nextframe();
|
|
104
|
+
const blobs = await editor.get_blobs();
|
|
105
|
+
const images = [];
|
|
106
|
+
let id = Math.random().toString(36).substring(2);
|
|
107
|
+
if (blobs.background)
|
|
108
|
+
images.push([
|
|
109
|
+
id,
|
|
110
|
+
"background",
|
|
111
|
+
new File([blobs.background], "background.png"),
|
|
112
|
+
null
|
|
113
|
+
]);
|
|
114
|
+
if (blobs.composite)
|
|
115
|
+
images.push([
|
|
116
|
+
id,
|
|
117
|
+
"composite",
|
|
118
|
+
new File([blobs.composite], "composite.png"),
|
|
119
|
+
null
|
|
120
|
+
]);
|
|
121
|
+
blobs.layers.forEach((layer, i) => {
|
|
122
|
+
if (layer)
|
|
123
|
+
images.push([
|
|
124
|
+
id,
|
|
125
|
+
`layer`,
|
|
126
|
+
new File([layer], `layer_${i}.png`),
|
|
127
|
+
i
|
|
128
|
+
]);
|
|
129
|
+
});
|
|
130
|
+
await Promise.all(
|
|
131
|
+
images.map(async ([image_id2, type, data, index]) => {
|
|
132
|
+
return accept_blobs({
|
|
133
|
+
binary: true,
|
|
134
|
+
data: { file: data, id: image_id2, type, index }
|
|
135
|
+
});
|
|
136
|
+
})
|
|
137
|
+
);
|
|
138
|
+
image_id = id;
|
|
139
|
+
dispatch("change");
|
|
140
|
+
await nextframe();
|
|
141
|
+
uploading = false;
|
|
142
|
+
if (pending) {
|
|
143
|
+
pending = false;
|
|
144
|
+
uploading = false;
|
|
145
|
+
handle_change(e);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
let active_mode = null;
|
|
149
|
+
let editor_height = height - 100;
|
|
150
|
+
$:
|
|
151
|
+
[heading, paragraph] = placeholder ? inject(placeholder) : [false, false];
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<BlockLabel
|
|
155
|
+
{show_label}
|
|
156
|
+
Icon={ImageIcon}
|
|
157
|
+
label={label || i18n("image.image")}
|
|
158
|
+
/>
|
|
159
|
+
<ImageEditor
|
|
160
|
+
{canvas_size}
|
|
161
|
+
crop_size={Array.isArray(crop_size) ? crop_size : undefined}
|
|
162
|
+
bind:this={editor}
|
|
163
|
+
bind:height={editor_height}
|
|
164
|
+
parent_height={height}
|
|
165
|
+
{changeable}
|
|
166
|
+
on:save
|
|
167
|
+
on:change={handle_change}
|
|
168
|
+
on:clear={() => dispatch("clear")}
|
|
169
|
+
bind:history
|
|
170
|
+
bind:bg
|
|
171
|
+
{sources}
|
|
172
|
+
crop_constraint={!!crop_constraint}
|
|
173
|
+
>
|
|
174
|
+
<Tools {i18n}>
|
|
175
|
+
<Layers layer_files={value?.layers || null} enable_layers={layers} />
|
|
176
|
+
|
|
177
|
+
<Sources
|
|
178
|
+
bind:dragging
|
|
179
|
+
{i18n}
|
|
180
|
+
{root}
|
|
181
|
+
{sources}
|
|
182
|
+
{upload}
|
|
183
|
+
{stream_handler}
|
|
184
|
+
bind:bg
|
|
185
|
+
bind:active_mode
|
|
186
|
+
background_file={value?.background || value?.composite || null}
|
|
187
|
+
></Sources>
|
|
188
|
+
|
|
189
|
+
{#if transforms.includes("crop")}
|
|
190
|
+
<Crop {crop_constraint} />
|
|
191
|
+
{/if}
|
|
192
|
+
{#if brush}
|
|
193
|
+
<Brush
|
|
194
|
+
color_mode={brush.color_mode}
|
|
195
|
+
default_color={brush.default_color}
|
|
196
|
+
default_size={brush.default_size}
|
|
197
|
+
colors={brush.colors}
|
|
198
|
+
mode="draw"
|
|
199
|
+
/>
|
|
200
|
+
{/if}
|
|
201
|
+
|
|
202
|
+
{#if brush && eraser}
|
|
203
|
+
<Brush default_size={eraser.default_size} mode="erase" />
|
|
204
|
+
{/if}
|
|
205
|
+
</Tools>
|
|
206
|
+
|
|
207
|
+
{#if !bg && !history && active_mode !== "webcam" && status !== "error"}
|
|
208
|
+
<div class="empty wrap" style:height={`${editor_height}px`}>
|
|
209
|
+
{#if sources && sources.length}
|
|
210
|
+
{#if heading || paragraph}
|
|
211
|
+
{#if heading}
|
|
212
|
+
<h2>{heading}</h2>
|
|
213
|
+
{/if}
|
|
214
|
+
{#if paragraph}
|
|
215
|
+
<p>{paragraph}</p>
|
|
216
|
+
{/if}
|
|
217
|
+
{:else}
|
|
218
|
+
<div>Upload an image</div>
|
|
219
|
+
{/if}
|
|
220
|
+
{/if}
|
|
221
|
+
|
|
222
|
+
{#if sources && sources.length && brush && !placeholder}
|
|
223
|
+
<div class="or">or</div>
|
|
224
|
+
{/if}
|
|
225
|
+
{#if brush && !placeholder}
|
|
226
|
+
<div>select the draw tool to start</div>
|
|
227
|
+
{/if}
|
|
228
|
+
</div>
|
|
229
|
+
{/if}
|
|
230
|
+
</ImageEditor>
|
|
231
|
+
|
|
232
|
+
<style>
|
|
233
|
+
h2 {
|
|
234
|
+
font-size: var(--text-xl);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
p,
|
|
238
|
+
h2 {
|
|
239
|
+
white-space: pre-line;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
.empty {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-direction: column;
|
|
245
|
+
justify-content: center;
|
|
246
|
+
align-items: center;
|
|
247
|
+
position: absolute;
|
|
248
|
+
height: 100%;
|
|
249
|
+
width: 100%;
|
|
250
|
+
left: 0;
|
|
251
|
+
right: 0;
|
|
252
|
+
margin: auto;
|
|
253
|
+
z-index: var(--layer-top);
|
|
254
|
+
text-align: center;
|
|
255
|
+
color: var(--body-text-color);
|
|
256
|
+
top: var(--size-8);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
.wrap {
|
|
260
|
+
display: flex;
|
|
261
|
+
flex-direction: column;
|
|
262
|
+
justify-content: center;
|
|
263
|
+
align-items: center;
|
|
264
|
+
color: var(--block-label-text-color);
|
|
265
|
+
line-height: var(--line-md);
|
|
266
|
+
font-size: var(--text-md);
|
|
267
|
+
pointer-events: none;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.or {
|
|
271
|
+
color: var(--body-text-color-subdued);
|
|
272
|
+
}
|
|
273
|
+
</style>
|