@gradio/imageeditor 0.14.4 → 0.15.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/InteractiveImageEditor.svelte +4 -7
  3. package/dist/InteractiveImageEditor.svelte +4 -3
  4. package/dist/InteractiveImageEditor.svelte.d.ts +1 -1
  5. package/dist/shared/Controls.svelte +32 -1
  6. package/dist/shared/Controls.svelte.d.ts +2 -0
  7. package/dist/shared/ImageEditor.svelte +23 -1
  8. package/dist/shared/ImageEditor.svelte.d.ts +2 -1
  9. package/dist/shared/brush/brush-cursor.js +0 -42
  10. package/dist/shared/brush/brush-textures.d.ts +19 -2
  11. package/dist/shared/brush/brush-textures.js +120 -82
  12. package/dist/shared/brush/brush.js +0 -52
  13. package/dist/shared/{utils → core}/commands.d.ts +19 -58
  14. package/dist/shared/core/commands.js +70 -0
  15. package/dist/shared/core/editor.d.ts +4 -91
  16. package/dist/shared/core/editor.js +18 -708
  17. package/dist/shared/core/layers.d.ts +143 -0
  18. package/dist/shared/core/layers.js +774 -0
  19. package/dist/shared/image/image.d.ts +14 -4
  20. package/dist/shared/image/image.js +91 -18
  21. package/dist/shared/resize/resize.d.ts +31 -0
  22. package/dist/shared/resize/resize.js +127 -290
  23. package/dist/shared/zoom/zoom.js +0 -51
  24. package/package.json +7 -7
  25. package/shared/Controls.svelte +33 -1
  26. package/shared/ImageEditor.svelte +25 -9
  27. package/shared/Toolbar.svelte +1 -4
  28. package/shared/brush/ColorSwatch.svelte +0 -3
  29. package/shared/brush/brush-cursor.ts +0 -42
  30. package/shared/brush/brush-textures.ts +166 -83
  31. package/shared/brush/brush.ts +0 -53
  32. package/shared/core/commands.ts +118 -0
  33. package/shared/core/editor.ts +33 -957
  34. package/shared/core/layers.ts +1068 -0
  35. package/shared/image/image.ts +139 -21
  36. package/shared/resize/resize.ts +160 -299
  37. package/shared/zoom/zoom.ts +0 -51
  38. package/dist/shared/utils/commands.js +0 -73
  39. package/shared/utils/commands.ts +0 -174
@@ -94,22 +94,18 @@ export class BrushTool implements Tool {
94
94
  tool: ToolbarTool,
95
95
  subtool: Subtool
96
96
  ): Promise<void> {
97
- // Store context and tool information
98
97
  this.image_editor_context = context;
99
98
  this.current_tool = tool;
100
99
  this.current_subtool = subtool;
101
100
 
102
- // Set mode based on tool
103
101
  this.state.mode = tool === "erase" ? "erase" : "draw";
104
102
 
105
- // Set size based on mode
106
103
  if (this.state.mode === "draw") {
107
104
  this.state.brush_size = this.brush_size;
108
105
  } else {
109
106
  this.state.brush_size = this.eraser_size;
110
107
  }
111
108
 
112
- // Subscribe to scale changes
113
109
  context.scale.subscribe((scale) => {
114
110
  this.scale = scale;
115
111
  if (this.brush_cursor) {
@@ -117,27 +113,22 @@ export class BrushTool implements Tool {
117
113
  }
118
114
  });
119
115
 
120
- // Initialize brush cursor
121
116
  this.brush_cursor = new BrushCursor(
122
117
  this.image_editor_context,
123
118
  this.state,
124
119
  this.scale
125
120
  );
126
121
 
127
- // Set initial active state
128
122
  this.brush_cursor.set_active(tool === "draw" || tool === "erase");
129
123
 
130
- // Initialize brush textures
131
124
  this.brush_textures = new BrushTextures(
132
125
  this.image_editor_context,
133
126
  context.app
134
127
  );
135
128
  this.brush_textures.initialize_textures();
136
129
 
137
- // Set up event listeners
138
130
  this.setup_event_listeners();
139
131
 
140
- // Set cursor style for image container
141
132
  this.handle_cursors(tool);
142
133
  }
143
134
 
@@ -147,7 +138,6 @@ export class BrushTool implements Tool {
147
138
  * @private
148
139
  */
149
140
  private handle_cursors(tool: ToolbarTool): void {
150
- // Set cursor style to 'none' for all children of the image container
151
141
  recurse_set_cursor(
152
142
  this.image_editor_context.image_container.children as Container[],
153
143
  "none"
@@ -160,7 +150,6 @@ export class BrushTool implements Tool {
160
150
  * @param {Subtool} subtool - The current subtool.
161
151
  */
162
152
  set_tool(tool: ToolbarTool, subtool: Subtool): void {
163
- // Update current tool and subtool
164
153
  this.current_tool = tool;
165
154
  this.current_subtool = subtool;
166
155
 
@@ -168,14 +157,10 @@ export class BrushTool implements Tool {
168
157
  this.commit_pending_changes();
169
158
  }
170
159
 
171
- // Commit any pending changes
172
-
173
- // Set brush cursor active state
174
160
  if (this.brush_cursor) {
175
161
  this.brush_cursor.set_active(tool === "draw" || tool === "erase");
176
162
  }
177
163
 
178
- // Update mode based on tool
179
164
  const new_mode = tool === "erase" ? "erase" : "draw";
180
165
  if (tool === "erase" || tool === "draw") {
181
166
  this.brush_textures?.initialize_textures();
@@ -183,14 +168,12 @@ export class BrushTool implements Tool {
183
168
  if (this.state.mode !== new_mode) {
184
169
  this.state.mode = new_mode;
185
170
 
186
- // Update brush size based on mode
187
171
  if (this.state.mode === "draw") {
188
172
  this.state.brush_size = this.brush_size;
189
173
  } else {
190
174
  this.state.brush_size = this.eraser_size;
191
175
  }
192
176
 
193
- // Update cursor
194
177
  if (this.brush_cursor) {
195
178
  this.brush_cursor.update_state(this.state, this.scale);
196
179
  }
@@ -224,22 +207,18 @@ export class BrushTool implements Tool {
224
207
 
225
208
  if (this.brush_cursor && !this.brush_cursor.is_over_image()) return;
226
209
 
227
- // Preserve the current canvas state
228
210
  if (this.brush_textures) {
229
211
  this.brush_textures.preserve_canvas_state();
230
212
  }
231
213
 
232
- // Get the local position
233
214
  const local_pos = this.image_editor_context.image_container.toLocal(
234
215
  event.global
235
216
  );
236
217
 
237
- // Start drawing
238
218
  this.is_drawing = true;
239
219
  this.last_x = local_pos.x;
240
220
  this.last_y = local_pos.y;
241
221
 
242
- // Draw a single point
243
222
  if (this.brush_textures) {
244
223
  this.brush_textures.draw_segment(
245
224
  local_pos.x,
@@ -260,20 +239,16 @@ export class BrushTool implements Tool {
260
239
  * @private
261
240
  */
262
241
  private on_pointer_move(event: FederatedPointerEvent): void {
263
- // Update cursor position
264
242
  if (this.brush_cursor) {
265
243
  this.brush_cursor.update_cursor_position(event);
266
244
  }
267
245
 
268
- // If not drawing, exit
269
246
  if (!this.is_drawing) return;
270
247
 
271
- // Get the local position
272
248
  const local_pos = this.image_editor_context.image_container.toLocal(
273
249
  event.global
274
250
  );
275
251
 
276
- // Draw line from last position to current position
277
252
  if (this.brush_textures) {
278
253
  this.brush_textures.draw_segment(
279
254
  this.last_x,
@@ -287,7 +262,6 @@ export class BrushTool implements Tool {
287
262
  );
288
263
  }
289
264
 
290
- // Update last position
291
265
  this.last_x = local_pos.x;
292
266
  this.last_y = local_pos.y;
293
267
  }
@@ -297,13 +271,10 @@ export class BrushTool implements Tool {
297
271
  * @private
298
272
  */
299
273
  private on_pointer_up(): void {
300
- // If not drawing, exit
301
274
  if (!this.is_drawing) return;
302
275
 
303
- // Stop drawing
304
276
  this.is_drawing = false;
305
277
 
306
- // Commit the stroke
307
278
  if (this.brush_textures) {
308
279
  this.brush_textures.commit_stroke();
309
280
  }
@@ -316,30 +287,24 @@ export class BrushTool implements Tool {
316
287
  * @private
317
288
  */
318
289
  private setup_event_listeners(): void {
319
- // Remove existing listeners if they exist
320
290
  this.cleanup_event_listeners();
321
291
 
322
- // Create bound event handlers
323
292
  this._bound_pointer_down = this.on_pointer_down.bind(this);
324
293
  this._bound_pointer_move = this.on_pointer_move.bind(this);
325
294
  this._bound_pointer_up = this.on_pointer_up.bind(this);
326
295
 
327
- // Set interactive mode for image container
328
296
  const image_container = this.image_editor_context.image_container;
329
297
  image_container.eventMode = "static";
330
298
  image_container.interactiveChildren = true;
331
299
 
332
- // Setup for stage
333
300
  const stage = this.image_editor_context.app.stage;
334
301
  stage.eventMode = "static";
335
302
 
336
- // Add pointer events to the stage for global tracking
337
303
  stage.on("pointerdown", this._bound_pointer_down);
338
304
  stage.on("pointermove", this._bound_pointer_move);
339
305
  stage.on("pointerup", this._bound_pointer_up);
340
306
  stage.on("pointerupoutside", this._bound_pointer_up);
341
307
 
342
- // Set up cursor event listeners
343
308
  if (this.brush_cursor) {
344
309
  this.brush_cursor.setup_event_listeners();
345
310
  }
@@ -352,7 +317,6 @@ export class BrushTool implements Tool {
352
317
  private cleanup_event_listeners(): void {
353
318
  const stage = this.image_editor_context.app.stage;
354
319
 
355
- // Remove event listeners if they exist
356
320
  if (this._bound_pointer_down) {
357
321
  stage.off("pointerdown", this._bound_pointer_down);
358
322
  this._bound_pointer_down = null;
@@ -369,7 +333,6 @@ export class BrushTool implements Tool {
369
333
  this._bound_pointer_up = null;
370
334
  }
371
335
 
372
- // Clean up cursor event listeners
373
336
  if (this.brush_cursor) {
374
337
  this.brush_cursor.cleanup_event_listeners();
375
338
  }
@@ -380,14 +343,11 @@ export class BrushTool implements Tool {
380
343
  * @param {number} size - The new brush size.
381
344
  */
382
345
  set_brush_size(size: number): void {
383
- // Set brush size
384
346
  this.brush_size = size;
385
347
 
386
- // Update state if in draw mode
387
348
  if (this.state.mode === "draw") {
388
349
  this.state.brush_size = size;
389
350
 
390
- // Update cursor
391
351
  if (this.brush_cursor) {
392
352
  this.brush_cursor.update_state(this.state, this.scale);
393
353
  }
@@ -399,13 +359,10 @@ export class BrushTool implements Tool {
399
359
  * @param {string|ColorInput} color - The new brush color.
400
360
  */
401
361
  set_brush_color(color: string | ColorInput): void {
402
- // Convert to hex string
403
362
  const color_string = tinycolor(color).toHexString();
404
363
 
405
- // Set color
406
364
  this.state.color = color_string;
407
365
 
408
- // Update cursor
409
366
  if (this.brush_cursor) {
410
367
  this.brush_cursor.update_state(this.state, this.scale);
411
368
  }
@@ -416,13 +373,10 @@ export class BrushTool implements Tool {
416
373
  * @param {number} opacity - The new brush opacity.
417
374
  */
418
375
  set_brush_opacity(opacity: number): void {
419
- // Clamp opacity between 0 and 1
420
376
  const clamped_opacity = Math.max(0, Math.min(1, opacity));
421
377
 
422
- // Set opacity
423
378
  this.state.opacity = clamped_opacity;
424
379
 
425
- // Update cursor
426
380
  if (this.brush_cursor) {
427
381
  this.brush_cursor.update_state(this.state, this.scale);
428
382
  }
@@ -433,14 +387,11 @@ export class BrushTool implements Tool {
433
387
  * @param {number} size - The new eraser size.
434
388
  */
435
389
  set_eraser_size(size: number): void {
436
- // Store the new eraser size
437
390
  this.eraser_size = size;
438
391
 
439
- // Update state if in erase mode
440
392
  if (this.state.mode === "erase") {
441
393
  this.state.brush_size = size;
442
394
 
443
- // Update cursor
444
395
  if (this.brush_cursor) {
445
396
  this.brush_cursor.update_state(this.state, this.scale);
446
397
  }
@@ -469,19 +420,15 @@ export class BrushTool implements Tool {
469
420
  * Cleans up resources used by the brush tool.
470
421
  */
471
422
  cleanup(): void {
472
- // Commit any pending changes
473
423
  this.commit_pending_changes();
474
424
 
475
- // Clean up event listeners
476
425
  this.cleanup_event_listeners();
477
426
 
478
- // Clean up brush textures
479
427
  if (this.brush_textures) {
480
428
  this.brush_textures.cleanup();
481
429
  this.brush_textures = null;
482
430
  }
483
431
 
484
- // Clean up brush cursor
485
432
  if (this.brush_cursor) {
486
433
  this.brush_cursor.cleanup();
487
434
  this.brush_cursor = null;
@@ -0,0 +1,118 @@
1
+ import { writable, type Writable } from "svelte/store";
2
+
3
+ /**
4
+ * Base command interface that is added to the command_managers history
5
+ */
6
+ export interface Command {
7
+ /**
8
+ * The name of the command
9
+ */
10
+ name: string;
11
+ /**
12
+ * Optionally called when the command is first executed for multi-step commands
13
+ * @param args arguments to pass to the command
14
+ */
15
+ start?: (...args: any) => any | Promise<any>;
16
+ /**
17
+ * Optionally called when the command is continued for multi-step commands
18
+ * @param args arguments to pass to the command
19
+ */
20
+ continue?: (...args: any) => any | Promise<any>;
21
+ /**
22
+ * Optionally called when the command is stopped for multi-step commands
23
+ * @param args arguments to pass to the command
24
+ */
25
+ stop?: (...args: any) => any | Promise<any>;
26
+ /**
27
+ * Called by the command manager to execute the command, can act as a no-op if the work has already been done
28
+ * This function must be able to recreate the command if the command is undone and redone (`stop`/`start`/`continue` will not be called again)
29
+ */
30
+ execute(): any | Promise<any>;
31
+ /**
32
+ * Called by the command manager to undo the command
33
+ * This function must be able to undo the work done by the execute function
34
+ */
35
+ undo(): any | Promise<any>;
36
+ }
37
+
38
+ /**
39
+ * Creates a command node
40
+ * @param command command to add to the node
41
+ * @returns a command node
42
+ */
43
+ export class CommandNode {
44
+ command: Command | null;
45
+ next: CommandNode | null;
46
+ previous: CommandNode | null;
47
+
48
+ constructor(command?: Command) {
49
+ this.command = command || null;
50
+ this.next = null;
51
+ this.previous = null;
52
+ }
53
+
54
+ push(command: Command): void {
55
+ const node = new CommandNode(command);
56
+ node.previous = this;
57
+ this.next = node;
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Creates a command manager
63
+ * @returns a command manager
64
+ */
65
+ export class CommandManager {
66
+ history: CommandNode = new CommandNode();
67
+
68
+ current_history = writable(this.history);
69
+
70
+ undo(): void {
71
+ if (this.history.previous) {
72
+ this.history.command?.undo();
73
+ this.history = this.history.previous;
74
+
75
+ this.current_history.set(this.history);
76
+ }
77
+ }
78
+ redo(): void {
79
+ if (this.history.next) {
80
+ this.history = this.history.next;
81
+ this.history.command?.execute();
82
+ }
83
+
84
+ this.current_history.set(this.history);
85
+ }
86
+
87
+ execute(command: Command): void {
88
+ command.execute();
89
+ this.history.push(command);
90
+ this.history = this.history.next!;
91
+
92
+ this.current_history.set(this.history);
93
+ }
94
+
95
+ replay(full_history: CommandNode): void {
96
+ setTimeout(() => {
97
+ while (full_history.next) {
98
+ this.execute(full_history.next.command!);
99
+ full_history = full_history.next;
100
+ }
101
+ }, 1000);
102
+ }
103
+
104
+ contains(command_name: string): boolean {
105
+ let current: CommandNode | null = this.history;
106
+ while (current) {
107
+ if (current.command?.name === command_name) return true;
108
+ current = current.next;
109
+ }
110
+ return false;
111
+ }
112
+
113
+ reset(): void {
114
+ this.history = new CommandNode();
115
+
116
+ this.current_history.set(this.history);
117
+ }
118
+ }