@gradio/imageeditor 0.15.4 → 0.16.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 +22 -0
- package/ImageEditor.stories.svelte +0 -1
- package/Index.svelte +3 -3
- package/InteractiveImageEditor.svelte +1 -1
- package/dist/Index.svelte +1 -2
- package/dist/Index.svelte.d.ts +4 -3
- package/dist/InteractiveImageEditor.svelte +1 -1
- package/dist/InteractiveImageEditor.svelte.d.ts +1 -1
- package/dist/shared/ImageEditor.svelte +62 -10
- package/dist/shared/ImageEditor.svelte.d.ts +1 -3
- package/dist/shared/brush/brush-textures.d.ts +42 -9
- package/dist/shared/brush/brush-textures.js +211 -74
- package/dist/shared/brush/brush.js +8 -3
- package/dist/shared/core/commands.d.ts +7 -6
- package/dist/shared/core/commands.js +26 -15
- package/dist/shared/core/editor.d.ts +1 -1
- package/dist/shared/core/editor.js +19 -11
- package/dist/shared/core/layers.d.ts +3 -3
- package/dist/shared/core/layers.js +22 -5
- package/dist/shared/image/image.d.ts +1 -1
- package/dist/shared/image/image.js +4 -1
- package/dist/shared/resize/resize.d.ts +1 -1
- package/dist/shared/resize/resize.js +4 -1
- package/package.json +9 -9
- package/shared/ImageEditor.svelte +85 -17
- package/shared/brush/BrushOptions.svelte +0 -3
- package/shared/brush/ColorPicker.svelte +2 -2
- package/shared/brush/brush-textures.ts +296 -107
- package/shared/brush/brush.ts +14 -3
- package/shared/core/commands.ts +36 -20
- package/shared/core/editor.ts +22 -13
- package/shared/core/layers.ts +29 -5
- package/shared/image/image.ts +5 -1
- package/shared/resize/resize.ts +5 -1
|
@@ -515,7 +515,12 @@ export class LayerManager {
|
|
|
515
515
|
}
|
|
516
516
|
return null;
|
|
517
517
|
})),
|
|
518
|
-
composite: await get_canvas_blob(this.app.renderer, this.image_container
|
|
518
|
+
composite: await get_canvas_blob(this.app.renderer, this.image_container, {
|
|
519
|
+
width,
|
|
520
|
+
height,
|
|
521
|
+
x: 0,
|
|
522
|
+
y: 0
|
|
523
|
+
})
|
|
519
524
|
};
|
|
520
525
|
return blobs;
|
|
521
526
|
}
|
|
@@ -557,13 +562,16 @@ export class LayerManager {
|
|
|
557
562
|
for (const layer of this.layers) {
|
|
558
563
|
this.delete_layer(layer.id);
|
|
559
564
|
}
|
|
565
|
+
let i = 0;
|
|
560
566
|
for (const layer of this.layer_options.layers) {
|
|
561
567
|
this.create_layer({
|
|
562
568
|
width,
|
|
563
569
|
height,
|
|
564
570
|
layer_name: layer,
|
|
565
|
-
user_created: false
|
|
571
|
+
user_created: false,
|
|
572
|
+
layer_id: `layer-${i}`
|
|
566
573
|
});
|
|
574
|
+
i++;
|
|
567
575
|
}
|
|
568
576
|
this.active_layer = this.layers[0].container;
|
|
569
577
|
this.active_layer_id = this.layers[0].id;
|
|
@@ -602,7 +610,10 @@ export class AddLayerCommand {
|
|
|
602
610
|
const current_active = current_layers.find((l) => l.container === this.context.layer_manager.get_active_layer());
|
|
603
611
|
this.previous_active_layer = current_active?.id || null;
|
|
604
612
|
}
|
|
605
|
-
async execute() {
|
|
613
|
+
async execute(context) {
|
|
614
|
+
if (context) {
|
|
615
|
+
this.context = context;
|
|
616
|
+
}
|
|
606
617
|
this.context.layer_manager.create_layer({
|
|
607
618
|
width: this.width,
|
|
608
619
|
height: this.height,
|
|
@@ -676,7 +687,10 @@ export class RemoveLayerCommand {
|
|
|
676
687
|
this.texture_copy = null;
|
|
677
688
|
}
|
|
678
689
|
}
|
|
679
|
-
async execute() {
|
|
690
|
+
async execute(context) {
|
|
691
|
+
if (context) {
|
|
692
|
+
this.context = context;
|
|
693
|
+
}
|
|
680
694
|
this.context.layer_manager.delete_layer(this.layer_data.id);
|
|
681
695
|
}
|
|
682
696
|
async undo() {
|
|
@@ -755,7 +769,10 @@ export class ReorderLayerCommand {
|
|
|
755
769
|
];
|
|
756
770
|
}
|
|
757
771
|
}
|
|
758
|
-
async execute() {
|
|
772
|
+
async execute(context) {
|
|
773
|
+
if (context) {
|
|
774
|
+
this.context = context;
|
|
775
|
+
}
|
|
759
776
|
this.context.layer_manager.move_layer(this.layer_id, this.direction);
|
|
760
777
|
}
|
|
761
778
|
async undo() {
|
|
@@ -58,7 +58,7 @@ export declare class AddImageCommand implements BgImageCommand {
|
|
|
58
58
|
private clone_texture;
|
|
59
59
|
start(): Promise<void>;
|
|
60
60
|
private handle_image;
|
|
61
|
-
execute(): Promise<void>;
|
|
61
|
+
execute(context?: ImageEditorContext): Promise<void>;
|
|
62
62
|
undo(): Promise<void>;
|
|
63
63
|
}
|
|
64
64
|
/**
|
|
@@ -120,7 +120,10 @@ export class AddImageCommand {
|
|
|
120
120
|
}
|
|
121
121
|
return [this.current_canvas_size.width, this.current_canvas_size.height];
|
|
122
122
|
}
|
|
123
|
-
async execute() {
|
|
123
|
+
async execute(context) {
|
|
124
|
+
if (context) {
|
|
125
|
+
this.context = context;
|
|
126
|
+
}
|
|
124
127
|
await this.start();
|
|
125
128
|
if (this.sprite === null) {
|
|
126
129
|
return;
|
|
@@ -30,7 +30,10 @@ export class ResizeCommand {
|
|
|
30
30
|
this.new_y = new_state.y;
|
|
31
31
|
this.reset_ui = reset_ui;
|
|
32
32
|
}
|
|
33
|
-
async execute() {
|
|
33
|
+
async execute(context) {
|
|
34
|
+
if (context) {
|
|
35
|
+
this.context = context;
|
|
36
|
+
}
|
|
34
37
|
if (!this.context.background_image)
|
|
35
38
|
return;
|
|
36
39
|
this.context.background_image.width = this.new_width;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gradio/imageeditor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Gradio UI packages",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"author": "",
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
"pixi-filters": "^6.1.0",
|
|
12
12
|
"pixi.js": "^8.7.3",
|
|
13
13
|
"tinycolor2": "^1.6.0",
|
|
14
|
-
"@gradio/atoms": "^0.16.
|
|
14
|
+
"@gradio/atoms": "^0.16.3",
|
|
15
|
+
"@gradio/image": "^0.22.12",
|
|
15
16
|
"@gradio/icons": "^0.12.0",
|
|
16
|
-
"@gradio/
|
|
17
|
-
"@gradio/
|
|
18
|
-
"@gradio/client": "^1.15.
|
|
19
|
-
"@gradio/
|
|
20
|
-
"@gradio/utils": "^0.10.2"
|
|
21
|
-
"@gradio/wasm": "^0.18.1"
|
|
17
|
+
"@gradio/statustracker": "^0.10.14",
|
|
18
|
+
"@gradio/upload": "^0.16.10",
|
|
19
|
+
"@gradio/client": "^1.15.5",
|
|
20
|
+
"@gradio/wasm": "^0.18.1",
|
|
21
|
+
"@gradio/utils": "^0.10.2"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
-
"@gradio/preview": "^0.
|
|
24
|
+
"@gradio/preview": "^0.14.0"
|
|
25
25
|
},
|
|
26
26
|
"main_changeset": true,
|
|
27
27
|
"main": "./Index.svelte",
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<script lang="ts">
|
|
9
9
|
import { onMount, createEventDispatcher, tick } from "svelte";
|
|
10
|
+
import { get } from "svelte/store";
|
|
10
11
|
import Toolbar, { type Tool as ToolbarTool } from "./Toolbar.svelte";
|
|
11
12
|
import { CropTool } from "./crop/crop";
|
|
12
13
|
import { ResizeTool } from "./resize/resize";
|
|
@@ -33,14 +34,12 @@
|
|
|
33
34
|
clear?: never;
|
|
34
35
|
save: void;
|
|
35
36
|
change: void;
|
|
36
|
-
history: CommandManager["current_history"];
|
|
37
37
|
upload: void;
|
|
38
38
|
input: void;
|
|
39
39
|
download_error: string;
|
|
40
40
|
}>();
|
|
41
41
|
|
|
42
42
|
export const antialias = true;
|
|
43
|
-
export const full_history: CommandNode | null = null;
|
|
44
43
|
|
|
45
44
|
export let changeable = false;
|
|
46
45
|
export let sources: Source[] = ["upload", "webcam", "clipboard"];
|
|
@@ -63,6 +62,7 @@
|
|
|
63
62
|
export let webcam_options: WebcamOptions;
|
|
64
63
|
export let show_download_button = false;
|
|
65
64
|
export let theme_mode: "dark" | "light";
|
|
65
|
+
export let full_history: CommandNode | null = null;
|
|
66
66
|
|
|
67
67
|
let pixi_target: HTMLDivElement;
|
|
68
68
|
let pixi_target_crop: HTMLDivElement;
|
|
@@ -75,10 +75,42 @@
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
function refresh_tools(): void {
|
|
78
|
+
if (!editor || !ready) return;
|
|
78
79
|
editor.set_tool(current_tool);
|
|
79
80
|
editor.set_subtool(current_subtool);
|
|
80
81
|
}
|
|
81
82
|
|
|
83
|
+
$: if (editor && ready && editor.layers) {
|
|
84
|
+
const current_layers = get(editor.layers);
|
|
85
|
+
|
|
86
|
+
if (current_layers.layers.length > 0 && !current_layers.active_layer) {
|
|
87
|
+
refresh_tools_for_layer_changes(current_layers);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Refreshes tools when layer state changes
|
|
93
|
+
*/
|
|
94
|
+
function refresh_tools_for_layer_changes(current_layers: any): void {
|
|
95
|
+
if (!editor || !ready) return;
|
|
96
|
+
|
|
97
|
+
if (current_layers.layers.length > 0 && !current_layers.active_layer) {
|
|
98
|
+
editor.set_layer(current_layers.layers[0].id);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// reapply current tool to ensure it targets the correct layer
|
|
102
|
+
if (current_tool) {
|
|
103
|
+
editor.set_tool(current_tool);
|
|
104
|
+
if (current_subtool) {
|
|
105
|
+
editor.set_subtool(current_subtool);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (brush && (current_tool === "draw" || current_tool === "erase")) {
|
|
110
|
+
brush.set_tool(current_tool, current_subtool);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
82
114
|
function check_if_should_init(): boolean {
|
|
83
115
|
return layer_options && editor && ready;
|
|
84
116
|
}
|
|
@@ -127,7 +159,6 @@
|
|
|
127
159
|
if (!editor || !source || !check_if_should_init()) return;
|
|
128
160
|
let url: string;
|
|
129
161
|
|
|
130
|
-
// Handle different source types
|
|
131
162
|
if (typeof source === "string") {
|
|
132
163
|
url = source;
|
|
133
164
|
} else if (source?.meta?._type === "gradio.FileData" && source?.url) {
|
|
@@ -161,7 +192,6 @@
|
|
|
161
192
|
): Promise<void> {
|
|
162
193
|
if (!editor || !source.length || !check_if_should_init()) return;
|
|
163
194
|
|
|
164
|
-
// Handle different source types
|
|
165
195
|
if (
|
|
166
196
|
Array.isArray(source) &&
|
|
167
197
|
source.every((item) => item?.meta?._type === "gradio.FileData")
|
|
@@ -203,10 +233,8 @@
|
|
|
203
233
|
current_dimensions.width !== last_dimensions.width ||
|
|
204
234
|
current_dimensions.height !== last_dimensions.height
|
|
205
235
|
) {
|
|
206
|
-
// Use set_zoom with "fit" to reset to appropriate zoom level
|
|
207
236
|
zoom.set_zoom("fit");
|
|
208
237
|
|
|
209
|
-
// Update the last known dimensions
|
|
210
238
|
last_dimensions = {
|
|
211
239
|
width: current_dimensions.width,
|
|
212
240
|
height: current_dimensions.height
|
|
@@ -218,6 +246,7 @@
|
|
|
218
246
|
onMount(() => {
|
|
219
247
|
let intersection_observer: IntersectionObserver;
|
|
220
248
|
let resize_observer: ResizeObserver;
|
|
249
|
+
|
|
221
250
|
init_image_editor().then(() => {
|
|
222
251
|
mounted = true;
|
|
223
252
|
intersection_observer = new IntersectionObserver(() => {
|
|
@@ -230,9 +259,21 @@
|
|
|
230
259
|
|
|
231
260
|
intersection_observer.observe(pixi_target);
|
|
232
261
|
resize_observer.observe(pixi_target);
|
|
262
|
+
|
|
263
|
+
setTimeout(() => {
|
|
264
|
+
if (full_history && editor) {
|
|
265
|
+
editor.command_manager
|
|
266
|
+
.replay(full_history, editor.context)
|
|
267
|
+
.then(() => {
|
|
268
|
+
refresh_tools_after_history();
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}, 0);
|
|
233
272
|
});
|
|
234
273
|
|
|
235
|
-
|
|
274
|
+
if (typeof window !== "undefined") {
|
|
275
|
+
(window as any).editor = editor;
|
|
276
|
+
}
|
|
236
277
|
|
|
237
278
|
return () => {
|
|
238
279
|
if (intersection_observer) {
|
|
@@ -247,6 +288,32 @@
|
|
|
247
288
|
};
|
|
248
289
|
});
|
|
249
290
|
|
|
291
|
+
/**
|
|
292
|
+
* Refreshes tool state after history replay to ensure proper layer targeting
|
|
293
|
+
*/
|
|
294
|
+
function refresh_tools_after_history(): void {
|
|
295
|
+
if (!editor || !ready) return;
|
|
296
|
+
|
|
297
|
+
const current_layers = get(editor.layers);
|
|
298
|
+
|
|
299
|
+
if (current_layers.layers.length > 0 && !current_layers.active_layer) {
|
|
300
|
+
editor.set_layer(current_layers.layers[0].id);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (current_tool) {
|
|
304
|
+
editor.set_tool(current_tool);
|
|
305
|
+
if (current_subtool) {
|
|
306
|
+
editor.set_subtool(current_subtool);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
if (brush && (current_tool === "draw" || current_tool === "erase")) {
|
|
311
|
+
brush.set_tool(current_tool, current_subtool);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
full_history = editor.command_manager.history;
|
|
315
|
+
}
|
|
316
|
+
|
|
250
317
|
let crop: ImageEditor;
|
|
251
318
|
let crop_zoom: ZoomTool;
|
|
252
319
|
export let can_undo = false;
|
|
@@ -291,10 +358,14 @@
|
|
|
291
358
|
});
|
|
292
359
|
|
|
293
360
|
editor.dimensions.subscribe((dimensions) => {
|
|
294
|
-
// Store dimensions for later comparison
|
|
295
361
|
last_dimensions = { ...dimensions };
|
|
296
362
|
});
|
|
297
363
|
|
|
364
|
+
editor.command_manager.current_history.subscribe((history) => {
|
|
365
|
+
can_undo = history.previous !== null;
|
|
366
|
+
can_redo = history.next !== null;
|
|
367
|
+
});
|
|
368
|
+
|
|
298
369
|
await Promise.all([editor.ready, crop.ready]).then(() => {
|
|
299
370
|
handle_tool_change({ tool: "image" });
|
|
300
371
|
ready = true;
|
|
@@ -308,8 +379,7 @@
|
|
|
308
379
|
|
|
309
380
|
editor.on("change", () => {
|
|
310
381
|
dispatch("change");
|
|
311
|
-
|
|
312
|
-
can_redo = editor.command_manager.history.next !== null;
|
|
382
|
+
full_history = editor.command_manager.history;
|
|
313
383
|
});
|
|
314
384
|
|
|
315
385
|
if (background || layers.length > 0) {
|
|
@@ -324,6 +394,8 @@
|
|
|
324
394
|
await add_image_from_url(composite);
|
|
325
395
|
handle_tool_change({ tool: "draw" });
|
|
326
396
|
}
|
|
397
|
+
|
|
398
|
+
refresh_tools();
|
|
327
399
|
}
|
|
328
400
|
|
|
329
401
|
$: if (
|
|
@@ -333,7 +405,7 @@
|
|
|
333
405
|
editor &&
|
|
334
406
|
ready
|
|
335
407
|
) {
|
|
336
|
-
editor.reset_canvas();
|
|
408
|
+
// editor.reset_canvas();
|
|
337
409
|
handle_tool_change({ tool: "image" });
|
|
338
410
|
background_image = false;
|
|
339
411
|
has_drawn = false;
|
|
@@ -352,17 +424,17 @@
|
|
|
352
424
|
): Promise<void> {
|
|
353
425
|
if (files == null) return;
|
|
354
426
|
if (!sources.includes("upload")) return;
|
|
427
|
+
editor.reset_canvas();
|
|
355
428
|
const _file = Array.isArray(files) ? files[0] : files;
|
|
356
429
|
await editor.add_image({ image: _file });
|
|
357
430
|
await crop.add_image({ image: _file });
|
|
358
431
|
crop.reset();
|
|
432
|
+
|
|
359
433
|
background_image = true;
|
|
360
434
|
handle_tool_change({ tool: "draw" });
|
|
361
435
|
dispatch("upload");
|
|
362
436
|
dispatch("input");
|
|
363
437
|
dispatch("change");
|
|
364
|
-
can_undo = editor.command_manager.history.previous !== null;
|
|
365
|
-
can_redo = editor.command_manager.history.next !== null;
|
|
366
438
|
}
|
|
367
439
|
|
|
368
440
|
$: background_image = can_undo && editor.command_manager.contains("AddImage");
|
|
@@ -586,14 +658,10 @@
|
|
|
586
658
|
|
|
587
659
|
function handle_undo(): void {
|
|
588
660
|
editor.undo();
|
|
589
|
-
can_undo = editor.command_manager.history.previous !== null;
|
|
590
|
-
can_redo = editor.command_manager.history.next !== null;
|
|
591
661
|
}
|
|
592
662
|
|
|
593
663
|
function handle_redo(): void {
|
|
594
664
|
editor.redo();
|
|
595
|
-
can_undo = editor.command_manager.history.previous !== null;
|
|
596
|
-
can_redo = editor.command_manager.history.next !== null;
|
|
597
665
|
}
|
|
598
666
|
</script>
|
|
599
667
|
|
|
@@ -47,7 +47,6 @@
|
|
|
47
47
|
if (!color) return;
|
|
48
48
|
selected_color = color;
|
|
49
49
|
|
|
50
|
-
// Update opacity if provided
|
|
51
50
|
if (opacity !== undefined) {
|
|
52
51
|
selected_opacity = opacity;
|
|
53
52
|
}
|
|
@@ -80,11 +79,9 @@
|
|
|
80
79
|
export let preview = false;
|
|
81
80
|
function handle_preview(): void {
|
|
82
81
|
if (!preview) {
|
|
83
|
-
// Show preview immediately when requested
|
|
84
82
|
preview = true;
|
|
85
83
|
debounced_close_preview();
|
|
86
84
|
} else {
|
|
87
|
-
// Only use debounce when hiding the preview
|
|
88
85
|
debounced_close_preview();
|
|
89
86
|
}
|
|
90
87
|
}
|
|
@@ -28,9 +28,9 @@
|
|
|
28
28
|
|
|
29
29
|
function update_hue_from_mouse(x: number): void {
|
|
30
30
|
if (!hue_rect) return;
|
|
31
|
-
const _x = clamp(x - hue_rect.left, 0, hue_rect.width); //
|
|
31
|
+
const _x = clamp(x - hue_rect.left, 0, hue_rect.width); // get the x-coordinate relative to the box
|
|
32
32
|
hue_marker_pos = _x;
|
|
33
|
-
const _hue = (_x / hue_rect.width) * 360; //
|
|
33
|
+
const _hue = (_x / hue_rect.width) * 360; // scale the x position to a hue value (0-360)
|
|
34
34
|
|
|
35
35
|
hue = _hue;
|
|
36
36
|
|