@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.
@@ -2,22 +2,22 @@ import { Container, RenderTexture, Sprite, Graphics, Application, Texture } from
2
2
  import {} from "../core/editor";
3
3
  import {} from "../core/commands";
4
4
  export class BrushCommand {
5
+ stroke_data;
5
6
  context;
6
- layer_id;
7
- original_texture;
8
- final_texture;
7
+ original_texture = null;
9
8
  name;
10
- constructor(context, layer_id, original_texture, final_texture) {
11
- this.context = context;
9
+ constructor(context, stroke_data, original_texture) {
12
10
  this.name = "Draw";
13
- this.layer_id = layer_id;
14
- this.original_texture = this.createTextureFrom(original_texture);
15
- this.final_texture = this.createTextureFrom(final_texture);
11
+ this.stroke_data = stroke_data;
12
+ this.context = context;
13
+ if (original_texture) {
14
+ this.original_texture = this.create_texture_from(original_texture);
15
+ }
16
16
  }
17
17
  /**
18
18
  * Creates a new texture with the same content as the source texture
19
19
  */
20
- createTextureFrom(source) {
20
+ create_texture_from(source) {
21
21
  const texture = RenderTexture.create({
22
22
  width: source.width,
23
23
  height: source.height,
@@ -32,27 +32,184 @@ export class BrushCommand {
32
32
  container.destroy({ children: true });
33
33
  return texture;
34
34
  }
35
- async execute() {
36
- const layer_textures = this.context.layer_manager.get_layer_textures(this.layer_id);
35
+ /**
36
+ * Recreates the stroke by rendering all segments from the stored parameters
37
+ * Preserves the exact order of draw/erase operations
38
+ */
39
+ render_stroke_from_data(stroke_data, target_texture) {
40
+ const draw_segments = stroke_data.segments.filter((s) => s.mode === "draw");
41
+ const erase_segments = stroke_data.segments.filter((s) => s.mode === "erase");
42
+ if (draw_segments.length > 0) {
43
+ const graphics = new Graphics();
44
+ const container = new Container();
45
+ container.addChild(graphics);
46
+ let alpha = 1;
47
+ for (const segment of draw_segments) {
48
+ if (segment.opacity < alpha) {
49
+ alpha = segment.opacity;
50
+ }
51
+ let colorValue = 0xffffff;
52
+ if (segment.color.startsWith("#")) {
53
+ colorValue = parseInt(segment.color.replace("#", "0x"), 16);
54
+ }
55
+ graphics.setFillStyle({
56
+ color: colorValue,
57
+ alpha: 1
58
+ });
59
+ this.render_segment_to_graphics(graphics, segment);
60
+ }
61
+ // we need a sprite in order to set the alpha and for that we need a texture
62
+ // i'm not entirely sure why other approaches didn't work
63
+ const alpha_sprite_texture = RenderTexture.create({
64
+ width: target_texture.width,
65
+ height: target_texture.height,
66
+ resolution: window.devicePixelRatio || 1
67
+ });
68
+ const alpha_sprite = new Sprite(alpha_sprite_texture);
69
+ this.context.app.renderer.render({
70
+ container: container,
71
+ target: alpha_sprite_texture
72
+ });
73
+ alpha_sprite.alpha = alpha;
74
+ this.context.app.renderer.render({
75
+ container: alpha_sprite,
76
+ target: target_texture,
77
+ clear: false
78
+ });
79
+ container.destroy({ children: true });
80
+ alpha_sprite.destroy();
81
+ alpha_sprite_texture.destroy();
82
+ }
83
+ if (erase_segments.length > 0) {
84
+ // create a temp texture to work with
85
+ const temp_content_texture = RenderTexture.create({
86
+ width: target_texture.width,
87
+ height: target_texture.height,
88
+ resolution: window.devicePixelRatio || 1
89
+ });
90
+ const copy_sprite = new Sprite(target_texture);
91
+ const copy_container = new Container();
92
+ copy_container.addChild(copy_sprite);
93
+ this.context.app.renderer.render({
94
+ container: copy_container,
95
+ target: temp_content_texture,
96
+ clear: true
97
+ });
98
+ // create a graphics object to draw the erase segments
99
+ const erase_graphics = new Graphics();
100
+ const erase_container = new Container();
101
+ erase_container.addChild(erase_graphics);
102
+ erase_graphics.setFillStyle({
103
+ color: 0xffffff,
104
+ alpha: 1.0
105
+ });
106
+ for (const segment of erase_segments) {
107
+ this.render_segment_to_graphics(erase_graphics, segment);
108
+ }
109
+ // create a separate texture to hold the mask
110
+ const mask_texture = RenderTexture.create({
111
+ width: target_texture.width,
112
+ height: target_texture.height,
113
+ resolution: window.devicePixelRatio || 1
114
+ });
115
+ this.context.app.renderer.render({
116
+ container: erase_container,
117
+ target: mask_texture,
118
+ clear: true
119
+ });
120
+ const content_sprite = new Sprite(temp_content_texture);
121
+ const mask_sprite = new Sprite(mask_texture);
122
+ // only now do we create a sprite from the original texture and add the mask
123
+ const masked_container = new Container();
124
+ masked_container.addChild(content_sprite);
125
+ masked_container.setMask({ mask: mask_sprite, inverse: true }); // inverse mask = erase
126
+ // now we can render the masked content back to the target texture
127
+ this.context.app.renderer.render({
128
+ container: masked_container,
129
+ target: target_texture,
130
+ clear: true
131
+ });
132
+ copy_container.destroy({ children: true });
133
+ erase_container.destroy({ children: true });
134
+ masked_container.destroy({ children: true });
135
+ temp_content_texture.destroy();
136
+ mask_texture.destroy();
137
+ }
138
+ }
139
+ /**
140
+ * Renders a segment to a graphics object (extracted from renderSegment)
141
+ */
142
+ render_segment_to_graphics(graphics, segment) {
143
+ const distance = Math.sqrt(Math.pow(segment.to_x - segment.from_x, 2) +
144
+ Math.pow(segment.to_y - segment.from_y, 2));
145
+ if (distance < 0.1) {
146
+ graphics.circle(segment.from_x, segment.from_y, segment.size).fill();
147
+ }
148
+ else {
149
+ const spacing = Math.max(segment.size / 3, 2);
150
+ const steps = Math.max(Math.ceil(distance / spacing), 2);
151
+ for (let i = 0; i < steps; i++) {
152
+ const t = i / (steps - 1);
153
+ const x = segment.from_x + (segment.to_x - segment.from_x) * t;
154
+ const y = segment.from_y + (segment.to_y - segment.from_y) * t;
155
+ graphics.circle(x, y, segment.size).fill();
156
+ }
157
+ }
158
+ }
159
+ async execute(context) {
160
+ if (context) {
161
+ this.context = context;
162
+ }
163
+ let layer_textures = this.context.layer_manager.get_layer_textures(this.stroke_data.layer_id);
164
+ if (!layer_textures) {
165
+ const all_layers = this.context.layer_manager.get_layers();
166
+ const top_layer = all_layers[all_layers.length - 1];
167
+ layer_textures = this.context.layer_manager.get_layer_textures(top_layer.id);
168
+ }
37
169
  if (!layer_textures)
38
170
  return;
39
- const temp_sprite = new Sprite(this.final_texture);
171
+ // create a temporary texture to render the stroke
172
+ const temp_texture = RenderTexture.create({
173
+ width: layer_textures.draw.width,
174
+ height: layer_textures.draw.height,
175
+ resolution: window.devicePixelRatio || 1
176
+ });
177
+ // copy current layer content to temp texture
178
+ const current_sprite = new Sprite(layer_textures.draw);
40
179
  const temp_container = new Container();
41
- temp_container.addChild(temp_sprite);
42
- this.context.app.renderer.render(temp_container, {
43
- renderTexture: layer_textures.draw
180
+ temp_container.addChild(current_sprite);
181
+ this.context.app.renderer.render({
182
+ container: temp_container,
183
+ target: temp_texture,
184
+ clear: true
44
185
  });
45
186
  temp_container.destroy({ children: true });
187
+ this.render_stroke_from_data(this.stroke_data, temp_texture);
188
+ // copy final result back to layer texture
189
+ const final_sprite = new Sprite(temp_texture);
190
+ const final_container = new Container();
191
+ final_container.addChild(final_sprite);
192
+ this.context.app.renderer.render({
193
+ container: final_container,
194
+ target: layer_textures.draw,
195
+ clear: true
196
+ });
197
+ final_container.destroy({ children: true });
198
+ temp_texture.destroy();
46
199
  }
47
200
  async undo() {
48
- const layer_textures = this.context.layer_manager.get_layer_textures(this.layer_id);
201
+ if (!this.original_texture)
202
+ return;
203
+ const layer_textures = this.context.layer_manager.get_layer_textures(this.stroke_data.layer_id);
49
204
  if (!layer_textures)
50
205
  return;
51
206
  const temp_sprite = new Sprite(this.original_texture);
52
207
  const temp_container = new Container();
53
208
  temp_container.addChild(temp_sprite);
54
- this.context.app.renderer.render(temp_container, {
55
- renderTexture: layer_textures.draw
209
+ this.context.app.renderer.render({
210
+ container: temp_container,
211
+ target: layer_textures.draw,
212
+ clear: true
56
213
  });
57
214
  temp_container.destroy({ children: true });
58
215
  }
@@ -62,8 +219,6 @@ export class BrushCommand {
62
219
  * Simplified to work directly with layer textures.
63
220
  */
64
221
  export class BrushTextures {
65
- image_editor_context;
66
- app;
67
222
  stroke_texture = null;
68
223
  erase_texture = null;
69
224
  display_container = null;
@@ -72,11 +227,13 @@ export class BrushTextures {
72
227
  preview_sprite = null;
73
228
  erase_graphics = null;
74
229
  dimensions;
230
+ image_editor_context;
231
+ app;
75
232
  is_new_stroke = true;
76
233
  current_opacity = 1.0;
77
- current_mode = "draw";
78
234
  original_layer_texture = null;
79
235
  active_layer_id = null;
236
+ current_stroke_segments = [];
80
237
  constructor(image_editor_context, app) {
81
238
  this.image_editor_context = image_editor_context;
82
239
  this.app = app;
@@ -106,13 +263,7 @@ export class BrushTextures {
106
263
  resolution: window.devicePixelRatio || 1
107
264
  });
108
265
  this.display_container = new Container();
109
- const active_layer = this.image_editor_context.layer_manager.get_active_layer();
110
- if (active_layer) {
111
- active_layer.addChild(this.display_container);
112
- }
113
- else {
114
- this.image_editor_context.image_container.addChild(this.display_container);
115
- }
266
+ this.image_editor_context.image_container.addChild(this.display_container);
116
267
  this.stroke_container = new Container();
117
268
  this.stroke_graphics = new Graphics();
118
269
  this.stroke_container.addChild(this.stroke_graphics);
@@ -210,8 +361,10 @@ export class BrushTextures {
210
361
  if (!this.erase_graphics || !this.erase_texture)
211
362
  return;
212
363
  this.erase_graphics.clear();
213
- this.erase_graphics.beginFill(0xffffff, 1);
214
- this.erase_graphics.drawRect(0, 0, this.dimensions.width, this.dimensions.height);
364
+ this.erase_graphics.setFillStyle({ color: 0xffffff, alpha: 1.0 });
365
+ this.erase_graphics
366
+ .rect(0, 0, this.dimensions.width, this.dimensions.height)
367
+ .fill();
215
368
  this.erase_graphics.endFill();
216
369
  this.app.renderer.render(this.erase_graphics, {
217
370
  renderTexture: this.erase_texture
@@ -240,45 +393,11 @@ export class BrushTextures {
240
393
  const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
241
394
  if (!layer_textures)
242
395
  return;
243
- const currentOpacity = this.current_opacity;
244
- if (this.current_mode === "draw") {
245
- const temp_container = new Container();
246
- const base_sprite = new Sprite(layer_textures.draw);
247
- temp_container.addChild(base_sprite);
248
- const stroke_sprite = new Sprite(this.stroke_texture);
249
- stroke_sprite.alpha = currentOpacity;
250
- temp_container.addChild(stroke_sprite);
251
- this.app.renderer.render(temp_container, {
252
- renderTexture: layer_textures.draw
253
- });
254
- temp_container.destroy({ children: true });
255
- }
256
- else {
257
- if (!this.stroke_texture)
258
- return;
259
- const erase_container = new Container();
260
- const content_sprite = new Sprite(layer_textures.draw);
261
- erase_container.addChild(content_sprite);
262
- const mask_sprite = new Sprite(this.stroke_texture);
263
- erase_container.setMask({ mask: mask_sprite, inverse: true });
264
- this.app.renderer.render(erase_container, {
265
- renderTexture: layer_textures.draw
266
- });
267
- erase_container.destroy({ children: true });
268
- }
269
- const final_texture = RenderTexture.create({
270
- width: this.dimensions.width,
271
- height: this.dimensions.height,
272
- resolution: window.devicePixelRatio || 1
273
- });
274
- const final_container = new Container();
275
- const final_sprite = new Sprite(layer_textures.draw);
276
- final_container.addChild(final_sprite);
277
- this.app.renderer.render(final_container, {
278
- renderTexture: final_texture
279
- });
280
- final_container.destroy({ children: true });
281
- const brush_command = new BrushCommand(this.image_editor_context, this.active_layer_id, this.original_layer_texture, final_texture);
396
+ const stroke_data = {
397
+ segments: [...this.current_stroke_segments],
398
+ layer_id: this.active_layer_id
399
+ };
400
+ const brush_command = new BrushCommand(this.image_editor_context, stroke_data, this.original_layer_texture);
282
401
  if (this.stroke_graphics) {
283
402
  this.stroke_graphics.clear();
284
403
  }
@@ -288,15 +407,15 @@ export class BrushTextures {
288
407
  });
289
408
  clear_container.destroy();
290
409
  this.is_new_stroke = true;
291
- final_texture.destroy();
292
410
  this.original_layer_texture = null;
293
411
  this.active_layer_id = null;
294
- this.image_editor_context.command_manager.execute(brush_command);
412
+ this.current_stroke_segments = [];
413
+ this.image_editor_context.command_manager.execute(brush_command, this.image_editor_context);
295
414
  }
296
415
  /**
297
416
  * Calculates the distance between two points.
298
417
  */
299
- calculateDistance(x1, y1, x2, y2) {
418
+ calculate_distance(x1, y1, x2, y2) {
300
419
  return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
301
420
  }
302
421
  /**
@@ -311,12 +430,12 @@ export class BrushTextures {
311
430
  if (this.is_new_stroke && !this.original_layer_texture) {
312
431
  this.preserve_canvas_state();
313
432
  }
314
- this.current_mode = mode;
315
433
  this.current_opacity =
316
434
  mode === "draw" ? Math.min(Math.max(opacity, 0), 1) : 0.5;
317
435
  const scaled_size = size;
318
436
  if (this.is_new_stroke) {
319
437
  this.stroke_graphics.clear();
438
+ this.current_stroke_segments = [];
320
439
  const clear_container = new Container();
321
440
  this.app.renderer.render(clear_container, {
322
441
  renderTexture: this.stroke_texture
@@ -324,6 +443,16 @@ export class BrushTextures {
324
443
  clear_container.destroy();
325
444
  this.is_new_stroke = false;
326
445
  }
446
+ this.current_stroke_segments.push({
447
+ from_x,
448
+ from_y,
449
+ to_x,
450
+ to_y,
451
+ size: scaled_size,
452
+ color,
453
+ opacity: this.current_opacity,
454
+ mode
455
+ });
327
456
  if (mode === "draw") {
328
457
  let colorValue = 0xffffff;
329
458
  try {
@@ -345,7 +474,7 @@ export class BrushTextures {
345
474
  alpha: 1.0
346
475
  });
347
476
  }
348
- const distance = this.calculateDistance(from_x, from_y, to_x, to_y);
477
+ const distance = this.calculate_distance(from_x, from_y, to_x, to_y);
349
478
  if (distance < 0.1) {
350
479
  this.stroke_graphics.circle(from_x, from_y, scaled_size).fill();
351
480
  }
@@ -414,6 +543,14 @@ export class BrushTextures {
414
543
  get_dimensions() {
415
544
  return this.dimensions;
416
545
  }
546
+ /**
547
+ * Checks if textures are properly initialized.
548
+ */
549
+ get textures_initialized() {
550
+ return !!(this.stroke_texture &&
551
+ this.display_container &&
552
+ this.preview_sprite);
553
+ }
417
554
  /**
418
555
  * Cleanup all resources.
419
556
  */
@@ -121,10 +121,14 @@ export class BrushTool {
121
121
  this.commit_pending_changes();
122
122
  }
123
123
  if (this.brush_cursor) {
124
- this.brush_cursor.set_active(tool === "draw" || tool === "erase");
124
+ const should_be_active = tool === "draw" || tool === "erase";
125
+ this.brush_cursor.set_active(should_be_active);
125
126
  }
126
127
  const new_mode = tool === "erase" ? "erase" : "draw";
127
- if (tool === "erase" || tool === "draw") {
128
+ const mode_changed = this.state.mode !== new_mode;
129
+ const needs_brush_tool = tool === "erase" || tool === "draw";
130
+ const textures_initialized = this.brush_textures?.textures_initialized ?? false;
131
+ if (needs_brush_tool && (mode_changed || !textures_initialized)) {
128
132
  this.brush_textures?.initialize_textures();
129
133
  }
130
134
  if (this.state.mode !== new_mode) {
@@ -160,8 +164,9 @@ export class BrushTool {
160
164
  (this.current_tool !== "erase" && this.current_tool !== "draw")) {
161
165
  return;
162
166
  }
163
- if (this.brush_cursor && !this.brush_cursor.is_over_image())
167
+ if (this.brush_cursor && !this.brush_cursor.is_over_image()) {
164
168
  return;
169
+ }
165
170
  if (this.brush_textures) {
166
171
  this.brush_textures.preserve_canvas_state();
167
172
  }
@@ -1,4 +1,4 @@
1
- import { type Writable } from "svelte/store";
1
+ import type { ImageEditorContext } from "./editor";
2
2
  /**
3
3
  * Base command interface that is added to the command_managers history
4
4
  */
@@ -26,7 +26,7 @@ export interface Command {
26
26
  * Called by the command manager to execute the command, can act as a no-op if the work has already been done
27
27
  * This function must be able to recreate the command if the command is undone and redone (`stop`/`start`/`continue` will not be called again)
28
28
  */
29
- execute(): any | Promise<any>;
29
+ execute(context?: ImageEditorContext): any | Promise<any>;
30
30
  /**
31
31
  * Called by the command manager to undo the command
32
32
  * This function must be able to undo the work done by the execute function
@@ -51,11 +51,12 @@ export declare class CommandNode {
51
51
  */
52
52
  export declare class CommandManager {
53
53
  history: CommandNode;
54
- current_history: Writable<CommandNode>;
54
+ current_history: import("svelte/store").Writable<CommandNode>;
55
55
  undo(): void;
56
- redo(): void;
57
- execute(command: Command): void;
58
- replay(full_history: CommandNode): void;
56
+ redo(context?: ImageEditorContext): void;
57
+ execute(command: Command, context: ImageEditorContext): Promise<void>;
58
+ wait_for_next_frame(): Promise<void>;
59
+ replay(full_history: CommandNode, context: ImageEditorContext): Promise<void>;
59
60
  contains(command_name: string): boolean;
60
61
  reset(): void;
61
62
  }
@@ -14,6 +14,7 @@ export class CommandNode {
14
14
  this.previous = null;
15
15
  }
16
16
  push(command) {
17
+ this.next = null;
17
18
  const node = new CommandNode(command);
18
19
  node.previous = this;
19
20
  this.next = node;
@@ -30,29 +31,39 @@ export class CommandManager {
30
31
  if (this.history.previous) {
31
32
  this.history.command?.undo();
32
33
  this.history = this.history.previous;
33
- this.current_history.set(this.history);
34
+ this.current_history.update(() => this.history);
34
35
  }
35
36
  }
36
- redo() {
37
+ redo(context) {
37
38
  if (this.history.next) {
38
39
  this.history = this.history.next;
39
- this.history.command?.execute();
40
+ this.history.command?.execute(context);
41
+ this.current_history.update(() => this.history);
40
42
  }
41
- this.current_history.set(this.history);
42
43
  }
43
- execute(command) {
44
- command.execute();
44
+ async execute(command, context) {
45
+ await command.execute(context);
45
46
  this.history.push(command);
46
47
  this.history = this.history.next;
47
- this.current_history.set(this.history);
48
+ this.current_history.update(() => this.history);
49
+ }
50
+ async wait_for_next_frame() {
51
+ return new Promise((resolve) => {
52
+ requestAnimationFrame(() => {
53
+ resolve();
54
+ });
55
+ });
48
56
  }
49
- replay(full_history) {
50
- setTimeout(() => {
51
- while (full_history.next) {
52
- this.execute(full_history.next.command);
53
- full_history = full_history.next;
54
- }
55
- }, 1000);
57
+ async replay(full_history, context) {
58
+ while (full_history.previous) {
59
+ full_history = full_history.previous;
60
+ }
61
+ while (full_history.next) {
62
+ await full_history.next.command.execute(context);
63
+ full_history = full_history.next;
64
+ }
65
+ this.history = full_history;
66
+ this.current_history.update(() => this.history);
56
67
  }
57
68
  contains(command_name) {
58
69
  let current = this.history;
@@ -65,6 +76,6 @@ export class CommandManager {
65
76
  }
66
77
  reset() {
67
78
  this.history = new CommandNode();
68
- this.current_history.set(this.history);
79
+ this.current_history.update(() => this.history);
69
80
  }
70
81
  }
@@ -106,7 +106,7 @@ export declare class ImageEditor {
106
106
  private pad_bottom;
107
107
  private theme_mode;
108
108
  constructor(options: ImageEditorOptions);
109
- private get context();
109
+ get context(): ImageEditorContext;
110
110
  init(): Promise<void>;
111
111
  private setup_containers;
112
112
  resize_canvas(width: number, height: number): void;
@@ -185,6 +185,9 @@ export class ImageEditor {
185
185
  });
186
186
  }).observe(this.target_element);
187
187
  this.app = new Application();
188
+ if (!this.dark) {
189
+ globalThis.__PIXI_APP__ = this.app;
190
+ }
188
191
  await this.app.init({
189
192
  width: container_box.width,
190
193
  height: container_box.height,
@@ -197,7 +200,7 @@ export class ImageEditor {
197
200
  });
198
201
  const canvas = this.app.canvas;
199
202
  canvas.style.background = "transparent";
200
- await this.setup_containers();
203
+ this.setup_containers();
201
204
  this.layer_manager.create_background_layer(this.width, this.height);
202
205
  this.layer_manager.init_layers(this.width, this.height);
203
206
  for (const tool of this.tools.values()) {
@@ -279,7 +282,7 @@ export class ImageEditor {
279
282
  });
280
283
  this.ready_resolve();
281
284
  }
282
- async setup_containers() {
285
+ setup_containers() {
283
286
  this.image_container = new Container({
284
287
  eventMode: "static",
285
288
  sortableChildren: true
@@ -384,14 +387,14 @@ export class ImageEditor {
384
387
  }
385
388
  }
386
389
  async execute_command(command) {
387
- await this.command_manager.execute(command);
390
+ await this.command_manager.execute(command, this.context);
388
391
  }
389
392
  undo() {
390
393
  this.command_manager.undo();
391
394
  this.notify("change");
392
395
  }
393
396
  redo() {
394
- this.command_manager.redo();
397
+ this.command_manager.redo(this.context);
395
398
  this.notify("change");
396
399
  }
397
400
  async add_image({ image, resize = true }) {
@@ -407,7 +410,6 @@ export class ImageEditor {
407
410
  * @param url The URL of the image to add
408
411
  */
409
412
  async add_image_from_url(url) {
410
- this.command_manager.reset();
411
413
  const image_tool = this.tools.get("image");
412
414
  const texture = await Assets.load(url);
413
415
  await image_tool.add_image({
@@ -439,6 +441,7 @@ export class ImageEditor {
439
441
  this.layer_manager.reset_layers(this.width, this.height);
440
442
  this.background_image = undefined;
441
443
  this.background_image_present.set(false);
444
+ this.command_manager.reset();
442
445
  await this.set_image_properties({
443
446
  width: this.width,
444
447
  height: this.height,
@@ -446,7 +449,6 @@ export class ImageEditor {
446
449
  position: { x: 0, y: 0 },
447
450
  animate: false
448
451
  });
449
- this.command_manager = new CommandManager();
450
452
  this.layer_manager.create_background_layer(this.width, this.height);
451
453
  for (const tool of this.tools.values()) {
452
454
  tool.cleanup();
@@ -467,7 +469,7 @@ export class ImageEditor {
467
469
  user_created: true,
468
470
  make_active: true
469
471
  });
470
- this.command_manager.execute(add_layer_command);
472
+ this.execute_command(add_layer_command);
471
473
  this.notify("change");
472
474
  }
473
475
  /**
@@ -488,10 +490,16 @@ export class ImageEditor {
488
490
  });
489
491
  return;
490
492
  }
493
+ const created_layer_ids = [];
491
494
  for await (const url of layer_urls) {
492
- await this.layer_manager.add_layer_from_url(url);
495
+ const layer_id = await this.layer_manager.add_layer_from_url(url);
496
+ if (layer_id) {
497
+ created_layer_ids.push(layer_id);
498
+ }
499
+ }
500
+ if (created_layer_ids.length > 0) {
501
+ this.layer_manager.set_active_layer(created_layer_ids[0]);
493
502
  }
494
- this.layer_manager.set_active_layer(_layers[0].id);
495
503
  const resize_tool = this.tools.get("resize");
496
504
  if (resize_tool && typeof resize_tool.set_border_region === "function") {
497
505
  resize_tool.set_border_region(this.border_region);
@@ -505,12 +513,12 @@ export class ImageEditor {
505
513
  }
506
514
  move_layer(id, direction) {
507
515
  const reorder_layer_command = new ReorderLayerCommand(this.context, id, direction);
508
- this.command_manager.execute(reorder_layer_command);
516
+ this.execute_command(reorder_layer_command);
509
517
  this.notify("change");
510
518
  }
511
519
  delete_layer(id) {
512
520
  const remove_layer_command = new RemoveLayerCommand(this.context, id);
513
- this.command_manager.execute(remove_layer_command);
521
+ this.execute_command(remove_layer_command);
514
522
  this.notify("change");
515
523
  }
516
524
  modify_canvas_size(width, height, anchor, scale) {
@@ -102,7 +102,7 @@ export declare class AddLayerCommand implements Command {
102
102
  layer_id?: string;
103
103
  make_active?: boolean;
104
104
  });
105
- execute(): Promise<void>;
105
+ execute(context?: ImageEditorContext): Promise<void>;
106
106
  undo(): Promise<void>;
107
107
  }
108
108
  /**
@@ -119,7 +119,7 @@ export declare class RemoveLayerCommand implements Command {
119
119
  * Create a direct copy of the layer's texture
120
120
  */
121
121
  private captureTextureData;
122
- execute(): Promise<void>;
122
+ execute(context?: ImageEditorContext): Promise<void>;
123
123
  undo(): Promise<void>;
124
124
  /**
125
125
  * Clean up resources when the command is no longer needed
@@ -138,6 +138,6 @@ export declare class ReorderLayerCommand implements Command {
138
138
  private direction;
139
139
  name: string;
140
140
  constructor(context: ImageEditorContext, layer_id: string, direction: "up" | "down");
141
- execute(): Promise<void>;
141
+ execute(context?: ImageEditorContext): Promise<void>;
142
142
  undo(): Promise<void>;
143
143
  }