@gradio/imageeditor 0.14.5 → 0.15.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 +23 -0
- package/InteractiveImageEditor.svelte +4 -7
- package/dist/InteractiveImageEditor.svelte +4 -3
- package/dist/InteractiveImageEditor.svelte.d.ts +1 -1
- package/dist/shared/Controls.svelte +32 -1
- package/dist/shared/Controls.svelte.d.ts +2 -0
- package/dist/shared/ImageEditor.svelte +23 -1
- package/dist/shared/ImageEditor.svelte.d.ts +2 -1
- package/dist/shared/brush/brush-cursor.js +0 -42
- package/dist/shared/brush/brush-textures.d.ts +19 -2
- package/dist/shared/brush/brush-textures.js +120 -82
- package/dist/shared/brush/brush.js +0 -52
- package/dist/shared/{utils → core}/commands.d.ts +19 -58
- package/dist/shared/core/commands.js +70 -0
- package/dist/shared/core/editor.d.ts +4 -91
- package/dist/shared/core/editor.js +18 -708
- package/dist/shared/core/layers.d.ts +143 -0
- package/dist/shared/core/layers.js +774 -0
- package/dist/shared/image/image.d.ts +14 -4
- package/dist/shared/image/image.js +91 -18
- package/dist/shared/resize/resize.d.ts +31 -0
- package/dist/shared/resize/resize.js +127 -290
- package/dist/shared/zoom/zoom.js +0 -51
- package/package.json +7 -7
- package/shared/Controls.svelte +33 -1
- package/shared/ImageEditor.svelte +25 -9
- package/shared/Toolbar.svelte +1 -4
- package/shared/brush/ColorSwatch.svelte +0 -3
- package/shared/brush/brush-cursor.ts +0 -42
- package/shared/brush/brush-textures.ts +166 -83
- package/shared/brush/brush.ts +0 -53
- package/shared/core/commands.ts +118 -0
- package/shared/core/editor.ts +33 -957
- package/shared/core/layers.ts +1068 -0
- package/shared/image/image.ts +139 -21
- package/shared/resize/resize.ts +160 -299
- package/shared/zoom/zoom.ts +0 -51
- package/dist/shared/utils/commands.js +0 -73
- package/shared/utils/commands.ts +0 -174
package/shared/brush/brush.ts
CHANGED
|
@@ -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
|
+
}
|