@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.
@@ -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;
@@ -27,7 +27,7 @@ export declare class ResizeCommand implements Command {
27
27
  x: number;
28
28
  y: number;
29
29
  }, reset_ui: () => void);
30
- execute(): Promise<void>;
30
+ execute(context?: ImageEditorContext): Promise<void>;
31
31
  undo(): Promise<void>;
32
32
  }
33
33
  /**
@@ -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.15.4",
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.2",
14
+ "@gradio/atoms": "^0.16.3",
15
+ "@gradio/image": "^0.22.12",
15
16
  "@gradio/icons": "^0.12.0",
16
- "@gradio/image": "^0.22.10",
17
- "@gradio/statustracker": "^0.10.13",
18
- "@gradio/client": "^1.15.3",
19
- "@gradio/upload": "^0.16.8",
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.13.2"
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
- // Set up mutation observer to detect visibility changes
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
- can_undo = editor.command_manager.history.previous !== null;
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); // Get the x-coordinate relative to the box
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; // Scale the x position to a hue value (0-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