@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
|
@@ -1,672 +1,12 @@
|
|
|
1
|
-
import { Application, Container, Graphics, Sprite,
|
|
1
|
+
import { Application, Container, Graphics, Sprite, Assets } from "pixi.js";
|
|
2
2
|
import { DropShadowFilter as BlurFilter } from "pixi-filters/drop-shadow";
|
|
3
3
|
import { ImageTool } from "../image/image";
|
|
4
|
-
import { AddImageCommand } from "../image/image";
|
|
5
4
|
import { ZoomTool } from "../zoom/zoom";
|
|
6
5
|
import { spring } from "svelte/motion";
|
|
7
|
-
import { writable
|
|
8
|
-
import { Rectangle } from "pixi.js";
|
|
6
|
+
import { writable } from "svelte/store";
|
|
9
7
|
import {} from "../types";
|
|
10
|
-
import {
|
|
11
|
-
|
|
12
|
-
undo_stack = [];
|
|
13
|
-
redo_stack = [];
|
|
14
|
-
async execute(command) {
|
|
15
|
-
await command.execute();
|
|
16
|
-
this.undo_stack.push(command);
|
|
17
|
-
this.redo_stack = []; // Clear redo stack when new command is executed
|
|
18
|
-
}
|
|
19
|
-
async undo() {
|
|
20
|
-
const command = this.undo_stack.pop();
|
|
21
|
-
if (command) {
|
|
22
|
-
await command.undo();
|
|
23
|
-
this.redo_stack.push(command);
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
async redo() {
|
|
27
|
-
const command = this.redo_stack.pop();
|
|
28
|
-
if (command) {
|
|
29
|
-
await command.execute();
|
|
30
|
-
this.undo_stack.push(command);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
export class LayerManager {
|
|
35
|
-
layers = [];
|
|
36
|
-
active_layer = null;
|
|
37
|
-
active_layer_id = null;
|
|
38
|
-
draw_textures = new Map();
|
|
39
|
-
layer_store = writable({
|
|
40
|
-
active_layer: "",
|
|
41
|
-
layers: []
|
|
42
|
-
});
|
|
43
|
-
background_layer = null;
|
|
44
|
-
image_container;
|
|
45
|
-
app;
|
|
46
|
-
fixed_canvas;
|
|
47
|
-
dark;
|
|
48
|
-
border_region;
|
|
49
|
-
layer_options;
|
|
50
|
-
constructor(image_container, app, fixed_canvas, dark, border_region, layer_options) {
|
|
51
|
-
this.image_container = image_container;
|
|
52
|
-
this.app = app;
|
|
53
|
-
this.fixed_canvas = fixed_canvas;
|
|
54
|
-
this.dark = dark;
|
|
55
|
-
this.border_region = border_region;
|
|
56
|
-
this.layer_options = layer_options;
|
|
57
|
-
}
|
|
58
|
-
toggle_layer_visibility(id) {
|
|
59
|
-
const layer = this.layers.find((l) => l.id === id);
|
|
60
|
-
if (layer) {
|
|
61
|
-
layer.container.visible = !layer.container.visible;
|
|
62
|
-
layer.visible = layer.container.visible;
|
|
63
|
-
this.layer_store.update((state) => ({
|
|
64
|
-
active_layer: state.active_layer,
|
|
65
|
-
layers: this.layers
|
|
66
|
-
}));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
create_background_layer(width, height) {
|
|
70
|
-
if (this.background_layer) {
|
|
71
|
-
this.background_layer.destroy();
|
|
72
|
-
}
|
|
73
|
-
// Create new background layer
|
|
74
|
-
const layer = new Container();
|
|
75
|
-
this.background_layer = layer;
|
|
76
|
-
// Create a render texture for the background with transparency
|
|
77
|
-
const bg_texture = RenderTexture.create({
|
|
78
|
-
width,
|
|
79
|
-
height,
|
|
80
|
-
resolution: window.devicePixelRatio,
|
|
81
|
-
antialias: true,
|
|
82
|
-
scaleMode: SCALE_MODES.NEAREST
|
|
83
|
-
});
|
|
84
|
-
// Create sprite to display the texture with transparency
|
|
85
|
-
const bg_sprite = new Sprite(bg_texture);
|
|
86
|
-
layer.addChild(bg_sprite);
|
|
87
|
-
// Clear the texture with transparency
|
|
88
|
-
const clear_graphics = new Graphics();
|
|
89
|
-
clear_graphics.clear();
|
|
90
|
-
clear_graphics
|
|
91
|
-
.rect(0, 0, width, height)
|
|
92
|
-
.fill({ color: this.dark ? 0x333333 : 0xffffff, alpha: 1 });
|
|
93
|
-
// Render with transparency
|
|
94
|
-
this.app.renderer.render({
|
|
95
|
-
container: clear_graphics,
|
|
96
|
-
target: bg_texture,
|
|
97
|
-
clear: true
|
|
98
|
-
});
|
|
99
|
-
this.image_container.addChild(layer);
|
|
100
|
-
layer.zIndex = -1;
|
|
101
|
-
this.update_layer_order();
|
|
102
|
-
return layer;
|
|
103
|
-
}
|
|
104
|
-
set_layer_options(layer_options, width, height) {
|
|
105
|
-
this.layer_options = layer_options;
|
|
106
|
-
this.reset_layers(width, height);
|
|
107
|
-
}
|
|
108
|
-
/**
|
|
109
|
-
* Creates a background layer from an image URL
|
|
110
|
-
* @param url The URL of the image to use as background
|
|
111
|
-
* @param width The width of the layer (if fixed_canvas is true)
|
|
112
|
-
* @param height The height of the layer (if fixed_canvas is true)
|
|
113
|
-
* @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
|
|
114
|
-
* @returns A Promise that resolves to the background Container when the image is loaded
|
|
115
|
-
*/
|
|
116
|
-
async create_background_layer_from_url(url, width, height) {
|
|
117
|
-
const layer = this.create_background_layer(width || this.image_container.width, height || this.image_container.height);
|
|
118
|
-
try {
|
|
119
|
-
const texture = await Texture.from(url);
|
|
120
|
-
const sprite = new Sprite(texture);
|
|
121
|
-
const imageWidth = sprite.texture.width;
|
|
122
|
-
const imageHeight = sprite.texture.height;
|
|
123
|
-
// Calculate dimensions based on whether canvas size is fixed
|
|
124
|
-
const containerWidth = width || this.image_container.width;
|
|
125
|
-
const containerHeight = height || this.image_container.height;
|
|
126
|
-
if (this.fixed_canvas) {
|
|
127
|
-
// If fixed canvas, fit the image within the specified dimensions minus border region
|
|
128
|
-
// Calculate aspect-preserving dimensions
|
|
129
|
-
const effectiveContainerWidth = Math.max(containerWidth - this.border_region * 2, 10);
|
|
130
|
-
const effectiveContainerHeight = Math.max(containerHeight - this.border_region * 2, 10);
|
|
131
|
-
const imageAspectRatio = imageWidth / imageHeight;
|
|
132
|
-
const containerAspectRatio = effectiveContainerWidth / effectiveContainerHeight;
|
|
133
|
-
let finalWidth, finalHeight;
|
|
134
|
-
let posX = this.border_region, // Start with border offset
|
|
135
|
-
posY = this.border_region;
|
|
136
|
-
if (imageWidth <= effectiveContainerWidth &&
|
|
137
|
-
imageHeight <= effectiveContainerHeight) {
|
|
138
|
-
// Image fits within container without scaling
|
|
139
|
-
finalWidth = imageWidth;
|
|
140
|
-
finalHeight = imageHeight;
|
|
141
|
-
}
|
|
142
|
-
else {
|
|
143
|
-
if (imageAspectRatio > containerAspectRatio) {
|
|
144
|
-
// Width is the limiting factor
|
|
145
|
-
finalWidth = effectiveContainerWidth;
|
|
146
|
-
finalHeight = effectiveContainerWidth / imageAspectRatio;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
// Height is the limiting factor
|
|
150
|
-
finalHeight = effectiveContainerHeight;
|
|
151
|
-
finalWidth = effectiveContainerHeight * imageAspectRatio;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
// Center image within the effective container area (which already accounts for border)
|
|
155
|
-
posX += Math.round((effectiveContainerWidth - finalWidth) / 2);
|
|
156
|
-
posY += Math.round((effectiveContainerHeight - finalHeight) / 2);
|
|
157
|
-
sprite.width = finalWidth;
|
|
158
|
-
sprite.height = finalHeight;
|
|
159
|
-
sprite.position.set(posX, posY);
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
// If not fixed canvas, use the natural dimensions of the image plus border
|
|
163
|
-
// Position starts at the border offset
|
|
164
|
-
sprite.position.set(this.border_region, this.border_region);
|
|
165
|
-
// We need to re-create the background layer with the actual image dimensions plus border
|
|
166
|
-
if (this.background_layer) {
|
|
167
|
-
this.background_layer.destroy();
|
|
168
|
-
}
|
|
169
|
-
// Create new layer with image dimensions plus border on all sides
|
|
170
|
-
const totalWidth = imageWidth + this.border_region * 2;
|
|
171
|
-
const totalHeight = imageHeight + this.border_region * 2;
|
|
172
|
-
const newLayer = this.create_background_layer(totalWidth, totalHeight);
|
|
173
|
-
sprite.width = imageWidth;
|
|
174
|
-
sprite.height = imageHeight;
|
|
175
|
-
newLayer.addChild(sprite);
|
|
176
|
-
return newLayer;
|
|
177
|
-
}
|
|
178
|
-
layer.addChild(sprite);
|
|
179
|
-
return layer;
|
|
180
|
-
}
|
|
181
|
-
catch (error) {
|
|
182
|
-
console.error("Error loading image from URL:", error);
|
|
183
|
-
return layer;
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
create_layer({ width, height, layer_name, user_created, layer_id = undefined, make_active = false }) {
|
|
187
|
-
const layer = new Container();
|
|
188
|
-
const _layer_id = layer_id || Math.random().toString(36).substring(2, 15);
|
|
189
|
-
const _layer_name = layer_name || `Layer ${this.layers.length + 1}`;
|
|
190
|
-
this.layers.push({
|
|
191
|
-
name: _layer_name,
|
|
192
|
-
id: _layer_id,
|
|
193
|
-
container: layer,
|
|
194
|
-
user_created,
|
|
195
|
-
visible: true
|
|
196
|
-
});
|
|
197
|
-
this.image_container.addChild(layer);
|
|
198
|
-
// Create a single render texture for drawing with transparency
|
|
199
|
-
const draw_texture = RenderTexture.create({
|
|
200
|
-
width,
|
|
201
|
-
height,
|
|
202
|
-
resolution: window.devicePixelRatio,
|
|
203
|
-
antialias: true,
|
|
204
|
-
scaleMode: SCALE_MODES.NEAREST
|
|
205
|
-
});
|
|
206
|
-
const canvas_sprite = new Sprite(draw_texture);
|
|
207
|
-
layer.addChild(canvas_sprite);
|
|
208
|
-
const clear_graphics = new Graphics();
|
|
209
|
-
clear_graphics.clear();
|
|
210
|
-
clear_graphics.beginFill(0, 0);
|
|
211
|
-
clear_graphics.drawRect(0, 0, width, height);
|
|
212
|
-
clear_graphics.endFill();
|
|
213
|
-
this.app.renderer.render({
|
|
214
|
-
container: clear_graphics,
|
|
215
|
-
target: draw_texture,
|
|
216
|
-
clear: true
|
|
217
|
-
});
|
|
218
|
-
// Store texture for drawing
|
|
219
|
-
this.draw_textures.set(layer, draw_texture);
|
|
220
|
-
this.update_layer_order();
|
|
221
|
-
if (make_active) {
|
|
222
|
-
this.set_active_layer(_layer_id);
|
|
223
|
-
}
|
|
224
|
-
this.layer_store.set({
|
|
225
|
-
active_layer: this.active_layer_id || "",
|
|
226
|
-
layers: this.layers
|
|
227
|
-
});
|
|
228
|
-
return layer;
|
|
229
|
-
}
|
|
230
|
-
/**
|
|
231
|
-
* Creates a new layer with an image loaded from a URL
|
|
232
|
-
* @param url The URL of the image to load
|
|
233
|
-
* @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
|
|
234
|
-
* @returns A Promise that resolves to the layer ID
|
|
235
|
-
*/
|
|
236
|
-
async add_layer_from_url(url) {
|
|
237
|
-
const { width, height } = this.image_container.getLocalBounds();
|
|
238
|
-
const layer = this.create_layer({
|
|
239
|
-
width,
|
|
240
|
-
height,
|
|
241
|
-
layer_name: "Layer 1",
|
|
242
|
-
user_created: true
|
|
243
|
-
});
|
|
244
|
-
const layerIndex = this.layers.findIndex((l) => l.container === layer);
|
|
245
|
-
if (layerIndex === -1) {
|
|
246
|
-
console.error("Could not find newly created layer");
|
|
247
|
-
return "";
|
|
248
|
-
}
|
|
249
|
-
const layerId = this.layers[layerIndex].id;
|
|
250
|
-
try {
|
|
251
|
-
const texture = await Assets.load(url);
|
|
252
|
-
const drawTexture = this.draw_textures.get(layer);
|
|
253
|
-
if (!drawTexture) {
|
|
254
|
-
console.error("No draw texture found for layer");
|
|
255
|
-
return layerId;
|
|
256
|
-
}
|
|
257
|
-
const sprite = new Sprite(texture);
|
|
258
|
-
const imageWidth = sprite.width;
|
|
259
|
-
const imageHeight = sprite.height;
|
|
260
|
-
let posX = this.border_region;
|
|
261
|
-
let posY = this.border_region;
|
|
262
|
-
// Calculate effective dimensions (accounting for border)
|
|
263
|
-
const effectiveWidth = this.fixed_canvas
|
|
264
|
-
? width - this.border_region * 2
|
|
265
|
-
: width;
|
|
266
|
-
const effectiveHeight = this.fixed_canvas
|
|
267
|
-
? height - this.border_region * 2
|
|
268
|
-
: height;
|
|
269
|
-
// If the image is smaller than the effective layer area, center it within that area
|
|
270
|
-
if (imageWidth < effectiveWidth || imageHeight < effectiveHeight) {
|
|
271
|
-
posX = Math.floor((effectiveWidth - imageWidth) / 2);
|
|
272
|
-
posY = Math.floor((effectiveHeight - imageHeight) / 2);
|
|
273
|
-
}
|
|
274
|
-
sprite.position.set(posX, posY);
|
|
275
|
-
// If the image is larger than the effective layer area, scale it down to fit
|
|
276
|
-
if (imageWidth > effectiveWidth || imageHeight > effectiveHeight) {
|
|
277
|
-
const imageAspectRatio = imageWidth / imageHeight;
|
|
278
|
-
const areaAspectRatio = effectiveWidth / effectiveHeight;
|
|
279
|
-
let finalWidth, finalHeight;
|
|
280
|
-
if (imageAspectRatio > areaAspectRatio) {
|
|
281
|
-
// Width is the limiting factor
|
|
282
|
-
finalWidth = effectiveWidth;
|
|
283
|
-
finalHeight = effectiveWidth / imageAspectRatio;
|
|
284
|
-
}
|
|
285
|
-
else {
|
|
286
|
-
// Height is the limiting factor
|
|
287
|
-
finalHeight = effectiveHeight;
|
|
288
|
-
finalWidth = effectiveHeight * imageAspectRatio;
|
|
289
|
-
}
|
|
290
|
-
sprite.width = finalWidth;
|
|
291
|
-
sprite.height = finalHeight;
|
|
292
|
-
// Recalculate position to center within effective area
|
|
293
|
-
posX =
|
|
294
|
-
this.border_region + Math.floor((effectiveWidth - finalWidth) / 2);
|
|
295
|
-
posY =
|
|
296
|
-
this.border_region + Math.floor((effectiveHeight - finalHeight) / 2);
|
|
297
|
-
sprite.position.set(posX, posY);
|
|
298
|
-
}
|
|
299
|
-
this.app.renderer.render(sprite, { renderTexture: drawTexture });
|
|
300
|
-
this.set_active_layer(layerId);
|
|
301
|
-
return layerId;
|
|
302
|
-
}
|
|
303
|
-
catch (error) {
|
|
304
|
-
console.error("Error loading image from URL:", error);
|
|
305
|
-
return layerId;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
get_active_layer() {
|
|
309
|
-
return this.active_layer;
|
|
310
|
-
}
|
|
311
|
-
set_active_layer(id) {
|
|
312
|
-
if (this.layers.some((l) => l.id === id)) {
|
|
313
|
-
this.active_layer =
|
|
314
|
-
this.layers.find((l) => l.id === id)?.container ||
|
|
315
|
-
this.layers[0]?.container ||
|
|
316
|
-
null;
|
|
317
|
-
this.active_layer_id = id;
|
|
318
|
-
this.layer_store.set({
|
|
319
|
-
active_layer: id,
|
|
320
|
-
layers: this.layers
|
|
321
|
-
});
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
get_layers() {
|
|
325
|
-
return this.layers;
|
|
326
|
-
}
|
|
327
|
-
get_layer_textures(id) {
|
|
328
|
-
const layer = this.layers.find((l) => l.id === id);
|
|
329
|
-
if (layer) {
|
|
330
|
-
const draw = this.draw_textures.get(layer.container);
|
|
331
|
-
if (draw) {
|
|
332
|
-
return { draw };
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return null;
|
|
336
|
-
}
|
|
337
|
-
delete_layer(id) {
|
|
338
|
-
const index = this.layers.findIndex((l) => l.id === id);
|
|
339
|
-
if (index > -1) {
|
|
340
|
-
// Clean up texture
|
|
341
|
-
const draw_texture = this.draw_textures.get(this.layers[index].container);
|
|
342
|
-
if (draw_texture) {
|
|
343
|
-
draw_texture.destroy();
|
|
344
|
-
this.draw_textures.delete(this.layers[index].container);
|
|
345
|
-
}
|
|
346
|
-
this.layers[index].container.destroy();
|
|
347
|
-
if (this.active_layer === this.layers[index].container) {
|
|
348
|
-
const new_active_layer = this.layers[Math.max(0, index - 1)] || null;
|
|
349
|
-
this.active_layer = new_active_layer?.container || null;
|
|
350
|
-
this.active_layer_id = new_active_layer?.id || null;
|
|
351
|
-
}
|
|
352
|
-
this.layers = this.layers.filter((l) => l.id !== id);
|
|
353
|
-
this.layer_store.update((_layers) => ({
|
|
354
|
-
active_layer: _layers.active_layer === id
|
|
355
|
-
? this.layers[this.layers.length - 1]?.id
|
|
356
|
-
: _layers.active_layer,
|
|
357
|
-
layers: this.layers
|
|
358
|
-
}));
|
|
359
|
-
this.update_layer_order();
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
update_layer_order() {
|
|
363
|
-
// Ensure background layer always has lowest z-index
|
|
364
|
-
if (this.background_layer) {
|
|
365
|
-
this.background_layer.zIndex = -1;
|
|
366
|
-
}
|
|
367
|
-
// Update other layers starting from 0
|
|
368
|
-
this.layers.forEach((layer, index) => {
|
|
369
|
-
layer.container.zIndex = index;
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
move_layer(id, direction) {
|
|
373
|
-
const index = this.layers.findIndex((l) => l.id === id);
|
|
374
|
-
if (index > -1) {
|
|
375
|
-
const new_index = direction === "up" ? index - 1 : index + 1;
|
|
376
|
-
this.layers = this.layers.map((l, i) => {
|
|
377
|
-
if (i === index) {
|
|
378
|
-
return this.layers[new_index];
|
|
379
|
-
}
|
|
380
|
-
if (i === new_index) {
|
|
381
|
-
return this.layers[index];
|
|
382
|
-
}
|
|
383
|
-
return l;
|
|
384
|
-
});
|
|
385
|
-
this.update_layer_order();
|
|
386
|
-
this.layer_store.update((_layers) => ({
|
|
387
|
-
active_layer: id,
|
|
388
|
-
layers: this.layers
|
|
389
|
-
}));
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
/**
|
|
393
|
-
* Resizes all layers to a new width and height
|
|
394
|
-
* @param newWidth The new width of the layers
|
|
395
|
-
* @param newHeight The new height of the layers
|
|
396
|
-
* @param scale If true, scales the layer content to fit the new dimensions. If false, keeps the content size unchanged.
|
|
397
|
-
* @param anchor The anchor point to position the content relative to the new size
|
|
398
|
-
*/
|
|
399
|
-
resize_all_layers(newWidth, newHeight, scale, anchor,
|
|
400
|
-
// Add old canvas dimensions for anchor calculations when not scaling
|
|
401
|
-
oldCanvasWidth, oldCanvasHeight) {
|
|
402
|
-
// Create a map of the old layers by ID for reference
|
|
403
|
-
const oldLayersById = new Map(this.layers.map((l) => [l.id, l]));
|
|
404
|
-
const oldBackgroundLayer = this.background_layer;
|
|
405
|
-
// Calculate offset for non-scaling resize based on anchor and dimension change
|
|
406
|
-
const calculateOffset = () => {
|
|
407
|
-
let offsetX = 0;
|
|
408
|
-
let offsetY = 0;
|
|
409
|
-
const deltaWidth = newWidth - oldCanvasWidth;
|
|
410
|
-
const deltaHeight = newHeight - oldCanvasHeight;
|
|
411
|
-
// Determine X offset based on anchor point
|
|
412
|
-
if (anchor.includes("left")) {
|
|
413
|
-
offsetX = 0;
|
|
414
|
-
}
|
|
415
|
-
else if (anchor.includes("right")) {
|
|
416
|
-
offsetX = deltaWidth;
|
|
417
|
-
}
|
|
418
|
-
else {
|
|
419
|
-
// center
|
|
420
|
-
offsetX = Math.floor(deltaWidth / 2);
|
|
421
|
-
}
|
|
422
|
-
// Determine Y offset based on anchor point
|
|
423
|
-
if (anchor.includes("top")) {
|
|
424
|
-
offsetY = 0;
|
|
425
|
-
}
|
|
426
|
-
else if (anchor.includes("bottom")) {
|
|
427
|
-
offsetY = deltaHeight;
|
|
428
|
-
}
|
|
429
|
-
else {
|
|
430
|
-
// center
|
|
431
|
-
offsetY = Math.floor(deltaHeight / 2);
|
|
432
|
-
}
|
|
433
|
-
return { offsetX, offsetY };
|
|
434
|
-
};
|
|
435
|
-
// First, handle the background layer using the helper method
|
|
436
|
-
// Note: create_background_layer called inside the helper might destroy the old one
|
|
437
|
-
this.background_layer = this._resize_background_layer(oldBackgroundLayer, // Pass the potentially null old layer
|
|
438
|
-
newWidth, newHeight, scale, calculateOffset);
|
|
439
|
-
// Now process regular layers
|
|
440
|
-
const processedLayers = [];
|
|
441
|
-
// Store old layer data before clearing the main arrays
|
|
442
|
-
const oldLayerData = this.layers.map((oldLayer) => ({
|
|
443
|
-
id: oldLayer.id,
|
|
444
|
-
name: oldLayer.name,
|
|
445
|
-
user_created: oldLayer.user_created,
|
|
446
|
-
texture: this.draw_textures.get(oldLayer.container),
|
|
447
|
-
container: oldLayer.container
|
|
448
|
-
}));
|
|
449
|
-
// Clear the current layers array and textures map. New layers will be added by the helper.
|
|
450
|
-
this.layers = [];
|
|
451
|
-
this.draw_textures.clear(); // Ensure map is cleared before helpers add to it
|
|
452
|
-
// Rebuild layers in the original order using the helper method
|
|
453
|
-
for (const oldData of oldLayerData) {
|
|
454
|
-
// The helper _resize_single_layer handles creating the new layer,
|
|
455
|
-
// drawing content, cleanup, and adding the new layer to this.layers.
|
|
456
|
-
// It returns null if resizing failed for that layer.
|
|
457
|
-
const newLayer = this._resize_single_layer(oldData, newWidth, newHeight, scale, calculateOffset);
|
|
458
|
-
if (newLayer) {
|
|
459
|
-
processedLayers.push(newLayer);
|
|
460
|
-
}
|
|
461
|
-
}
|
|
462
|
-
// Ensure the active layer still exists, otherwise select the top one
|
|
463
|
-
const currentActiveId = get(this.layer_store).active_layer;
|
|
464
|
-
const activeLayerExists = processedLayers.some((l) => l.id === currentActiveId);
|
|
465
|
-
if (!activeLayerExists && processedLayers.length > 0) {
|
|
466
|
-
this.set_active_layer(processedLayers[0].id); // Set to top layer (index 0 is bottom visually)
|
|
467
|
-
}
|
|
468
|
-
else if (processedLayers.length === 0) {
|
|
469
|
-
this.layer_store.update((s) => ({ ...s, active_layer: "" })); // No layers left
|
|
470
|
-
}
|
|
471
|
-
else {
|
|
472
|
-
// If the active layer still exists, just trigger an update to ensure consistency
|
|
473
|
-
this.layer_store.update((s) => ({ ...s }));
|
|
474
|
-
}
|
|
475
|
-
// Update Z-indices
|
|
476
|
-
this.update_layer_order();
|
|
477
|
-
// Final cleanup attempt - textures might need explicit destroy calls
|
|
478
|
-
// Using setTimeout allows the current execution stack to clear
|
|
479
|
-
setTimeout(() => {
|
|
480
|
-
// Attempt to force GC or resource cleanup if needed, though Pixi often handles this
|
|
481
|
-
Assets.cache.reset(); // Be careful with this, might clear too much
|
|
482
|
-
this.app.renderer.textureGC.run(); // Force texture garbage collection
|
|
483
|
-
}, 100);
|
|
484
|
-
}
|
|
485
|
-
/**
|
|
486
|
-
* Helper method to resize the background layer.
|
|
487
|
-
*/
|
|
488
|
-
_resize_background_layer(oldBackgroundLayer, newWidth, newHeight, scale, calculateOffset) {
|
|
489
|
-
if (!oldBackgroundLayer) {
|
|
490
|
-
// If no old background layer existed, create a new one
|
|
491
|
-
return this.create_background_layer(newWidth, newHeight);
|
|
492
|
-
}
|
|
493
|
-
// Identify the actual image sprite within the background container
|
|
494
|
-
let backgroundImage = oldBackgroundLayer.children.find((child) => child instanceof Sprite &&
|
|
495
|
-
child.texture !== oldBackgroundLayer.children[0]?.texture);
|
|
496
|
-
// Create the new background layer first (this also destroys the old one implicitly if it exists)
|
|
497
|
-
const newBackgroundLayer = this.create_background_layer(newWidth, newHeight);
|
|
498
|
-
if (backgroundImage) {
|
|
499
|
-
const newBgImage = new Sprite(backgroundImage.texture);
|
|
500
|
-
newBgImage.width = backgroundImage.width; // Preserve original size unless scaling
|
|
501
|
-
newBgImage.height = backgroundImage.height;
|
|
502
|
-
if (scale) {
|
|
503
|
-
newBgImage.width = newWidth;
|
|
504
|
-
newBgImage.height = newHeight;
|
|
505
|
-
newBgImage.position.set(0, 0);
|
|
506
|
-
}
|
|
507
|
-
else {
|
|
508
|
-
const { offsetX, offsetY } = calculateOffset();
|
|
509
|
-
newBgImage.position.set(backgroundImage.x + offsetX, backgroundImage.y + offsetY);
|
|
510
|
-
}
|
|
511
|
-
newBackgroundLayer.addChild(newBgImage);
|
|
512
|
-
}
|
|
513
|
-
// If no background image, the new layer created above is already blank and correctly sized.
|
|
514
|
-
return newBackgroundLayer;
|
|
515
|
-
}
|
|
516
|
-
/**
|
|
517
|
-
* Helper method to resize a single regular layer.
|
|
518
|
-
*/
|
|
519
|
-
_resize_single_layer(oldData, newWidth, newHeight, scale, calculateOffset) {
|
|
520
|
-
if (!oldData.texture) {
|
|
521
|
-
console.warn(`No texture found for layer ${oldData.id}, skipping cleanup.`);
|
|
522
|
-
// Destroy the old container if it exists and is not destroyed
|
|
523
|
-
if (oldData.container && !oldData.container.destroyed) {
|
|
524
|
-
if (this.image_container.children.includes(oldData.container)) {
|
|
525
|
-
this.image_container.removeChild(oldData.container);
|
|
526
|
-
}
|
|
527
|
-
oldData.container.destroy({ children: true });
|
|
528
|
-
}
|
|
529
|
-
return null;
|
|
530
|
-
}
|
|
531
|
-
// Create a new layer container and associated render texture
|
|
532
|
-
const newContainer = this.create_layer({
|
|
533
|
-
// This adds the layer to this.layers and draw_textures
|
|
534
|
-
width: newWidth,
|
|
535
|
-
height: newHeight,
|
|
536
|
-
layer_name: oldData.name,
|
|
537
|
-
user_created: oldData.user_created
|
|
538
|
-
});
|
|
539
|
-
// Retrieve the newly created layer data (it's the last one added by create_layer)
|
|
540
|
-
const newLayer = this.layers[this.layers.length - 1];
|
|
541
|
-
newLayer.id = oldData.id; // Restore the original ID
|
|
542
|
-
// Get the render texture for the new layer
|
|
543
|
-
const newTexture = this.draw_textures.get(newContainer);
|
|
544
|
-
if (!newTexture) {
|
|
545
|
-
console.error(`Failed to get texture for newly created layer ${newLayer.id}. Cleaning up.`);
|
|
546
|
-
// Clean up the partially created new layer resources
|
|
547
|
-
if (newContainer && !newContainer.destroyed) {
|
|
548
|
-
if (this.image_container.children.includes(newContainer)) {
|
|
549
|
-
this.image_container.removeChild(newContainer);
|
|
550
|
-
}
|
|
551
|
-
newContainer.destroy({ children: true });
|
|
552
|
-
const idx = this.layers.findIndex((l) => l.container === newContainer);
|
|
553
|
-
if (idx > -1)
|
|
554
|
-
this.layers.splice(idx, 1);
|
|
555
|
-
this.draw_textures.delete(newContainer);
|
|
556
|
-
}
|
|
557
|
-
// Clean up old resources
|
|
558
|
-
if (oldData.texture && !oldData.texture.destroyed)
|
|
559
|
-
oldData.texture.destroy(true);
|
|
560
|
-
if (oldData.container && !oldData.container.destroyed)
|
|
561
|
-
oldData.container.destroy({ children: true });
|
|
562
|
-
return null;
|
|
563
|
-
}
|
|
564
|
-
// Explicitly clear the new render texture (create_layer might already do this, but explicit is safer)
|
|
565
|
-
this.app.renderer.clear({ target: newTexture, clearColor: [0, 0, 0, 0] });
|
|
566
|
-
// Create a sprite with the old texture content
|
|
567
|
-
const sprite = new Sprite(oldData.texture);
|
|
568
|
-
if (scale) {
|
|
569
|
-
sprite.width = newWidth;
|
|
570
|
-
sprite.height = newHeight;
|
|
571
|
-
sprite.position.set(0, 0);
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
574
|
-
const { offsetX, offsetY } = calculateOffset();
|
|
575
|
-
sprite.position.set(offsetX, offsetY);
|
|
576
|
-
}
|
|
577
|
-
// Render the sprite onto the new texture
|
|
578
|
-
this.app.renderer.render(sprite, { renderTexture: newTexture });
|
|
579
|
-
// Clean up temporary sprite and old resources
|
|
580
|
-
sprite.destroy();
|
|
581
|
-
if (oldData.texture && !oldData.texture.destroyed) {
|
|
582
|
-
oldData.texture.destroy(true);
|
|
583
|
-
}
|
|
584
|
-
if (oldData.container && !oldData.container.destroyed) {
|
|
585
|
-
if (this.image_container.children.includes(oldData.container)) {
|
|
586
|
-
this.image_container.removeChild(oldData.container);
|
|
587
|
-
}
|
|
588
|
-
oldData.container.destroy({ children: true });
|
|
589
|
-
}
|
|
590
|
-
// Return the data for the successfully resized layer
|
|
591
|
-
return newLayer;
|
|
592
|
-
}
|
|
593
|
-
async get_blobs(width, height) {
|
|
594
|
-
const blobs = {
|
|
595
|
-
background: await get_canvas_blob(this.app.renderer, this.background_layer, {
|
|
596
|
-
width,
|
|
597
|
-
height,
|
|
598
|
-
x: 0,
|
|
599
|
-
y: 0
|
|
600
|
-
}),
|
|
601
|
-
layers: await Promise.all(this.layers.map(async (layer) => {
|
|
602
|
-
const blob = await get_canvas_blob(this.app.renderer, layer.container, {
|
|
603
|
-
width,
|
|
604
|
-
height,
|
|
605
|
-
x: 0,
|
|
606
|
-
y: 0
|
|
607
|
-
});
|
|
608
|
-
if (blob) {
|
|
609
|
-
return blob;
|
|
610
|
-
}
|
|
611
|
-
return null;
|
|
612
|
-
})),
|
|
613
|
-
composite: await get_canvas_blob(this.app.renderer, this.image_container)
|
|
614
|
-
};
|
|
615
|
-
return blobs;
|
|
616
|
-
}
|
|
617
|
-
reset_layers(width, height, persist = false) {
|
|
618
|
-
const _layers_to_recreate = persist
|
|
619
|
-
? this.layers.map((layer) => [layer.name, layer.id])
|
|
620
|
-
: this.layer_options.layers.map((layer) => [layer, undefined]);
|
|
621
|
-
this.layers.forEach((layer) => {
|
|
622
|
-
this.delete_layer(layer.id);
|
|
623
|
-
});
|
|
624
|
-
for (const [layer_name, layer_id] of _layers_to_recreate) {
|
|
625
|
-
this.create_layer({
|
|
626
|
-
width,
|
|
627
|
-
height,
|
|
628
|
-
layer_name: layer_name,
|
|
629
|
-
user_created: this.layer_options.layers.find((l) => l === layer_name)
|
|
630
|
-
? false
|
|
631
|
-
: true,
|
|
632
|
-
layer_id: layer_id
|
|
633
|
-
});
|
|
634
|
-
}
|
|
635
|
-
if (!persist) {
|
|
636
|
-
this.active_layer = this.layers[0].container;
|
|
637
|
-
this.active_layer_id = this.layers[0].id;
|
|
638
|
-
}
|
|
639
|
-
else {
|
|
640
|
-
// the id is the same but the layer is new, we need to find the new layer
|
|
641
|
-
this.active_layer =
|
|
642
|
-
this.layers.find((l) => l.id === this.active_layer_id)?.container ||
|
|
643
|
-
this.layers[0].container;
|
|
644
|
-
}
|
|
645
|
-
this.layer_store.update((state) => ({
|
|
646
|
-
active_layer: this.active_layer_id || this.layers[0].id,
|
|
647
|
-
layers: this.layers
|
|
648
|
-
}));
|
|
649
|
-
}
|
|
650
|
-
init_layers(width, height) {
|
|
651
|
-
for (const layer of this.layers) {
|
|
652
|
-
this.delete_layer(layer.id);
|
|
653
|
-
}
|
|
654
|
-
for (const layer of this.layer_options.layers) {
|
|
655
|
-
this.create_layer({
|
|
656
|
-
width,
|
|
657
|
-
height,
|
|
658
|
-
layer_name: layer,
|
|
659
|
-
user_created: false
|
|
660
|
-
});
|
|
661
|
-
}
|
|
662
|
-
this.active_layer = this.layers[0].container;
|
|
663
|
-
this.active_layer_id = this.layers[0].id;
|
|
664
|
-
this.layer_store.update((_layers) => ({
|
|
665
|
-
active_layer: this.layers[0].id,
|
|
666
|
-
layers: this.layers
|
|
667
|
-
}));
|
|
668
|
-
}
|
|
669
|
-
}
|
|
8
|
+
import { CommandManager } from "./commands";
|
|
9
|
+
import { LayerManager, AddLayerCommand, RemoveLayerCommand, ReorderLayerCommand } from "./layers";
|
|
670
10
|
const core_tools = ["image", "zoom"];
|
|
671
11
|
const core_tool_map = {
|
|
672
12
|
image: () => new ImageTool(),
|
|
@@ -685,7 +25,6 @@ class EditorState {
|
|
|
685
25
|
if (!(editor instanceof ImageEditor)) {
|
|
686
26
|
throw new Error("EditorState must be created by ImageEditor");
|
|
687
27
|
}
|
|
688
|
-
// Store private data
|
|
689
28
|
this.scale = 1.0;
|
|
690
29
|
this.position = { x: 0, y: 0 };
|
|
691
30
|
this.subscribers = new Set();
|
|
@@ -706,7 +45,6 @@ class EditorState {
|
|
|
706
45
|
subscribe: this.subscribe.bind(this)
|
|
707
46
|
});
|
|
708
47
|
}
|
|
709
|
-
// Internal methods - only accessible to EditorState class
|
|
710
48
|
_set_position(x, y) {
|
|
711
49
|
const oldPosition = { ...this.position };
|
|
712
50
|
this.position = { x, y };
|
|
@@ -727,7 +65,6 @@ class EditorState {
|
|
|
727
65
|
});
|
|
728
66
|
});
|
|
729
67
|
}
|
|
730
|
-
// Public method but only affects notifications
|
|
731
68
|
subscribe(callback) {
|
|
732
69
|
this.subscribers.add(callback);
|
|
733
70
|
return () => this.subscribers.delete(callback);
|
|
@@ -858,13 +195,10 @@ export class ImageEditor {
|
|
|
858
195
|
antialias: true,
|
|
859
196
|
powerPreference: "high-performance"
|
|
860
197
|
});
|
|
861
|
-
// Set canvas properties
|
|
862
198
|
const canvas = this.app.canvas;
|
|
863
199
|
canvas.style.background = "transparent";
|
|
864
200
|
await this.setup_containers();
|
|
865
|
-
//create background layer
|
|
866
201
|
this.layer_manager.create_background_layer(this.width, this.height);
|
|
867
|
-
// Create initial layer
|
|
868
202
|
this.layer_manager.init_layers(this.width, this.height);
|
|
869
203
|
for (const tool of this.tools.values()) {
|
|
870
204
|
await tool.setup(this.context, this.current_tool, this.current_subtool);
|
|
@@ -876,7 +210,6 @@ export class ImageEditor {
|
|
|
876
210
|
});
|
|
877
211
|
}
|
|
878
212
|
this.target_element.appendChild(canvas);
|
|
879
|
-
// Ensure parent element is transparent
|
|
880
213
|
this.target_element.style.background = "transparent";
|
|
881
214
|
this.dimensions.subscribe((dimensions) => {
|
|
882
215
|
this.dimensions_value = dimensions;
|
|
@@ -894,10 +227,8 @@ export class ImageEditor {
|
|
|
894
227
|
this.image_container.position.set(this.position_value.x, this.position_value.y);
|
|
895
228
|
const effective_width = this.dimensions_value.width * this.scale_value;
|
|
896
229
|
const effective_height = this.dimensions_value.height * this.scale_value;
|
|
897
|
-
// Convert image_container's position (in stage space) to outline_container local coordinates.
|
|
898
230
|
const local_x = Math.round(this.image_container.position.x - this.outline_container.position.x);
|
|
899
231
|
const local_y = Math.round(this.image_container.position.y - this.outline_container.position.y);
|
|
900
|
-
// Make sure overlay container follows the same position as outline container
|
|
901
232
|
this.overlay_container.position.set(this.outline_container.position.x, this.outline_container.position.y);
|
|
902
233
|
this.outline_graphics.clear();
|
|
903
234
|
this.outline_graphics
|
|
@@ -906,16 +237,13 @@ export class ImageEditor {
|
|
|
906
237
|
color: this.dark ? 0x333333 : 0xffffff,
|
|
907
238
|
alpha: 1
|
|
908
239
|
});
|
|
909
|
-
// Draw border region indicator if border_region > 0
|
|
910
240
|
if (this.border_region > 0) {
|
|
911
241
|
const scaled_border = this.border_region * this.scale_value;
|
|
912
242
|
const border_x = local_x + scaled_border - 1;
|
|
913
243
|
const border_y = local_y + scaled_border - 1;
|
|
914
244
|
const border_width = effective_width - scaled_border * 2 + 1;
|
|
915
245
|
const border_height = effective_height - scaled_border * 2 + 1;
|
|
916
|
-
// Clear previous overlay graphics
|
|
917
246
|
this.overlay_graphics.clear();
|
|
918
|
-
// Draw border rectangle using pixelLine for crisp 1px outline
|
|
919
247
|
this.overlay_graphics
|
|
920
248
|
.rect(border_x, border_y, border_width, border_height)
|
|
921
249
|
.stroke({
|
|
@@ -924,36 +252,28 @@ export class ImageEditor {
|
|
|
924
252
|
alpha: 0,
|
|
925
253
|
pixelLine: true
|
|
926
254
|
});
|
|
927
|
-
// Create dashed line effect by drawing small rects along the border
|
|
928
255
|
const dashLength = 5;
|
|
929
256
|
const gapLength = 5;
|
|
930
257
|
const totalLength = dashLength + gapLength;
|
|
931
258
|
const lineColor = 0x999999;
|
|
932
|
-
// Draw dashed horizontal lines (top and bottom)
|
|
933
259
|
for (let x = border_x; x < border_x + border_width; x += totalLength) {
|
|
934
|
-
// Top dash
|
|
935
260
|
this.overlay_graphics
|
|
936
261
|
.rect(x, border_y, Math.min(dashLength, border_x + border_width - x), 1)
|
|
937
262
|
.fill({ color: lineColor, alpha: 0.7 });
|
|
938
|
-
// Bottom dash
|
|
939
263
|
this.overlay_graphics
|
|
940
264
|
.rect(x, border_y + border_height, Math.min(dashLength, border_x + border_width - x), 1)
|
|
941
265
|
.fill({ color: lineColor, alpha: 0.7 });
|
|
942
266
|
}
|
|
943
|
-
// Draw dashed vertical lines (left and right)
|
|
944
267
|
for (let y = border_y; y < border_y + border_height; y += totalLength) {
|
|
945
|
-
// Left dash
|
|
946
268
|
this.overlay_graphics
|
|
947
269
|
.rect(border_x, y, 1, Math.min(dashLength, border_y + border_height - y))
|
|
948
270
|
.fill({ color: lineColor, alpha: 0.7 });
|
|
949
|
-
// Right dash
|
|
950
271
|
this.overlay_graphics
|
|
951
272
|
.rect(border_x + border_width, y, 1, Math.min(dashLength, border_y + border_height - y))
|
|
952
273
|
.fill({ color: lineColor, alpha: 0.7 });
|
|
953
274
|
}
|
|
954
275
|
}
|
|
955
276
|
else {
|
|
956
|
-
// Clear overlay graphics if no border region
|
|
957
277
|
this.overlay_graphics.clear();
|
|
958
278
|
}
|
|
959
279
|
});
|
|
@@ -1020,7 +340,6 @@ export class ImageEditor {
|
|
|
1020
340
|
if (this.app.renderer) {
|
|
1021
341
|
this.app.renderer.resize(width, height);
|
|
1022
342
|
}
|
|
1023
|
-
// Instead, update the container position to stay centered
|
|
1024
343
|
const app_center_x = width / 2;
|
|
1025
344
|
const app_center_y = height / 2;
|
|
1026
345
|
if (this.image_container) {
|
|
@@ -1036,6 +355,10 @@ export class ImageEditor {
|
|
|
1036
355
|
zoom.cleanup();
|
|
1037
356
|
zoom.setup(this.context, this.current_tool, this.current_subtool);
|
|
1038
357
|
}
|
|
358
|
+
const brush = this.tools.get("brush");
|
|
359
|
+
if (brush) {
|
|
360
|
+
this.set_tool("draw");
|
|
361
|
+
}
|
|
1039
362
|
}
|
|
1040
363
|
async set_image_properties(properties) {
|
|
1041
364
|
let hard = typeof properties.animate !== "undefined" ? !properties.animate : true;
|
|
@@ -1084,6 +407,7 @@ export class ImageEditor {
|
|
|
1084
407
|
* @param url The URL of the image to add
|
|
1085
408
|
*/
|
|
1086
409
|
async add_image_from_url(url) {
|
|
410
|
+
this.command_manager.reset();
|
|
1087
411
|
const image_tool = this.tools.get("image");
|
|
1088
412
|
const texture = await Assets.load(url);
|
|
1089
413
|
await image_tool.add_image({
|
|
@@ -1091,13 +415,10 @@ export class ImageEditor {
|
|
|
1091
415
|
fixed_canvas: this.fixed_canvas,
|
|
1092
416
|
border_region: this.border_region
|
|
1093
417
|
});
|
|
1094
|
-
// Update resize tool if present
|
|
1095
418
|
const resize_tool = this.tools.get("resize");
|
|
1096
419
|
if (resize_tool && typeof resize_tool.set_border_region === "function") {
|
|
1097
420
|
resize_tool.set_border_region(this.border_region);
|
|
1098
421
|
}
|
|
1099
|
-
this.notify("change");
|
|
1100
|
-
this.notify("input");
|
|
1101
422
|
}
|
|
1102
423
|
set_tool(tool) {
|
|
1103
424
|
this.current_tool = tool;
|
|
@@ -1116,10 +437,8 @@ export class ImageEditor {
|
|
|
1116
437
|
}
|
|
1117
438
|
async reset_canvas() {
|
|
1118
439
|
this.layer_manager.reset_layers(this.width, this.height);
|
|
1119
|
-
// Clear background image
|
|
1120
440
|
this.background_image = undefined;
|
|
1121
441
|
this.background_image_present.set(false);
|
|
1122
|
-
// Reset position, scale and dimensions to default values
|
|
1123
442
|
await this.set_image_properties({
|
|
1124
443
|
width: this.width,
|
|
1125
444
|
height: this.height,
|
|
@@ -1127,11 +446,8 @@ export class ImageEditor {
|
|
|
1127
446
|
position: { x: 0, y: 0 },
|
|
1128
447
|
animate: false
|
|
1129
448
|
});
|
|
1130
|
-
// Reset command stacks
|
|
1131
449
|
this.command_manager = new CommandManager();
|
|
1132
|
-
//create background layer
|
|
1133
450
|
this.layer_manager.create_background_layer(this.width, this.height);
|
|
1134
|
-
// Reset tools
|
|
1135
451
|
for (const tool of this.tools.values()) {
|
|
1136
452
|
tool.cleanup();
|
|
1137
453
|
tool.setup(this.context, this.current_tool, this.current_subtool);
|
|
@@ -1145,14 +461,13 @@ export class ImageEditor {
|
|
|
1145
461
|
this.notify("change");
|
|
1146
462
|
}
|
|
1147
463
|
add_layer() {
|
|
1148
|
-
this.
|
|
464
|
+
const add_layer_command = new AddLayerCommand(this.context, {
|
|
1149
465
|
width: this.width,
|
|
1150
466
|
height: this.height,
|
|
1151
|
-
layer_name: undefined,
|
|
1152
467
|
user_created: true,
|
|
1153
|
-
layer_id: undefined,
|
|
1154
468
|
make_active: true
|
|
1155
469
|
});
|
|
470
|
+
this.command_manager.execute(add_layer_command);
|
|
1156
471
|
this.notify("change");
|
|
1157
472
|
}
|
|
1158
473
|
/**
|
|
@@ -1161,6 +476,7 @@ export class ImageEditor {
|
|
|
1161
476
|
* @returns A Promise that resolves when all layers are added
|
|
1162
477
|
*/
|
|
1163
478
|
async add_layers_from_url(layer_urls) {
|
|
479
|
+
this.command_manager.reset();
|
|
1164
480
|
const _layers = this.layer_manager.get_layers();
|
|
1165
481
|
_layers.forEach((l) => this.layer_manager.delete_layer(l.id));
|
|
1166
482
|
if (layer_urls === undefined || layer_urls.length === 0) {
|
|
@@ -1176,7 +492,6 @@ export class ImageEditor {
|
|
|
1176
492
|
await this.layer_manager.add_layer_from_url(url);
|
|
1177
493
|
}
|
|
1178
494
|
this.layer_manager.set_active_layer(_layers[0].id);
|
|
1179
|
-
// Update resize tool if present with border region
|
|
1180
495
|
const resize_tool = this.tools.get("resize");
|
|
1181
496
|
if (resize_tool && typeof resize_tool.set_border_region === "function") {
|
|
1182
497
|
resize_tool.set_border_region(this.border_region);
|
|
@@ -1189,28 +504,26 @@ export class ImageEditor {
|
|
|
1189
504
|
this.notify("change");
|
|
1190
505
|
}
|
|
1191
506
|
move_layer(id, direction) {
|
|
1192
|
-
this.
|
|
507
|
+
const reorder_layer_command = new ReorderLayerCommand(this.context, id, direction);
|
|
508
|
+
this.command_manager.execute(reorder_layer_command);
|
|
1193
509
|
this.notify("change");
|
|
1194
510
|
}
|
|
1195
511
|
delete_layer(id) {
|
|
1196
|
-
this.
|
|
512
|
+
const remove_layer_command = new RemoveLayerCommand(this.context, id);
|
|
513
|
+
this.command_manager.execute(remove_layer_command);
|
|
1197
514
|
this.notify("change");
|
|
1198
515
|
}
|
|
1199
516
|
modify_canvas_size(width, height, anchor, scale) {
|
|
1200
|
-
// Store old dimensions before updating
|
|
1201
517
|
const oldWidth = this.width;
|
|
1202
518
|
const oldHeight = this.height;
|
|
1203
|
-
// Resize layers first, passing old dimensions for anchor calculation
|
|
1204
519
|
this.layer_manager.resize_all_layers(width, height, scale, anchor, oldWidth, oldHeight);
|
|
1205
|
-
// Update the editor's main dimension tracking *after* resizing layers
|
|
1206
520
|
this.width = width;
|
|
1207
521
|
this.height = height;
|
|
1208
|
-
// Update the dimensions store and reset view scale/position
|
|
1209
522
|
this.set_image_properties({
|
|
1210
523
|
width,
|
|
1211
524
|
height,
|
|
1212
|
-
scale: 1,
|
|
1213
|
-
position: { x: 0, y: 0 },
|
|
525
|
+
scale: 1,
|
|
526
|
+
position: { x: 0, y: 0 },
|
|
1214
527
|
animate: false
|
|
1215
528
|
});
|
|
1216
529
|
this.notify("change");
|
|
@@ -1236,9 +549,6 @@ export class ImageEditor {
|
|
|
1236
549
|
destroy() {
|
|
1237
550
|
if (!this.app)
|
|
1238
551
|
return;
|
|
1239
|
-
// for (const tool of this.tools) {
|
|
1240
|
-
// tool[1]?.cleanup();
|
|
1241
|
-
// }
|
|
1242
552
|
this.app?.destroy();
|
|
1243
553
|
}
|
|
1244
554
|
resize(width, height) {
|