@gradio/imageeditor 0.15.5 → 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 +14 -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
|
@@ -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
|
-
|
|
7
|
-
original_texture;
|
|
8
|
-
final_texture;
|
|
7
|
+
original_texture = null;
|
|
9
8
|
name;
|
|
10
|
-
constructor(context,
|
|
11
|
-
this.context = context;
|
|
9
|
+
constructor(context, stroke_data, original_texture) {
|
|
12
10
|
this.name = "Draw";
|
|
13
|
-
this.
|
|
14
|
-
this.
|
|
15
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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(
|
|
42
|
-
this.context.app.renderer.render(
|
|
43
|
-
|
|
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
|
-
|
|
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(
|
|
55
|
-
|
|
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
|
-
|
|
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.
|
|
214
|
-
this.erase_graphics
|
|
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
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|