@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,5 +1,62 @@
|
|
|
1
|
-
import { Container, RenderTexture, Sprite, Graphics, Application } from "pixi.js";
|
|
1
|
+
import { Container, RenderTexture, Sprite, Graphics, Application, Texture } from "pixi.js";
|
|
2
2
|
import {} from "../core/editor";
|
|
3
|
+
import {} from "../core/commands";
|
|
4
|
+
export class BrushCommand {
|
|
5
|
+
context;
|
|
6
|
+
layer_id;
|
|
7
|
+
original_texture;
|
|
8
|
+
final_texture;
|
|
9
|
+
name;
|
|
10
|
+
constructor(context, layer_id, original_texture, final_texture) {
|
|
11
|
+
this.context = context;
|
|
12
|
+
this.name = "Draw";
|
|
13
|
+
this.layer_id = layer_id;
|
|
14
|
+
this.original_texture = this.createTextureFrom(original_texture);
|
|
15
|
+
this.final_texture = this.createTextureFrom(final_texture);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Creates a new texture with the same content as the source texture
|
|
19
|
+
*/
|
|
20
|
+
createTextureFrom(source) {
|
|
21
|
+
const texture = RenderTexture.create({
|
|
22
|
+
width: source.width,
|
|
23
|
+
height: source.height,
|
|
24
|
+
resolution: window.devicePixelRatio || 1
|
|
25
|
+
});
|
|
26
|
+
const sprite = new Sprite(source);
|
|
27
|
+
const container = new Container();
|
|
28
|
+
container.addChild(sprite);
|
|
29
|
+
this.context.app.renderer.render(container, {
|
|
30
|
+
renderTexture: texture
|
|
31
|
+
});
|
|
32
|
+
container.destroy({ children: true });
|
|
33
|
+
return texture;
|
|
34
|
+
}
|
|
35
|
+
async execute() {
|
|
36
|
+
const layer_textures = this.context.layer_manager.get_layer_textures(this.layer_id);
|
|
37
|
+
if (!layer_textures)
|
|
38
|
+
return;
|
|
39
|
+
const temp_sprite = new Sprite(this.final_texture);
|
|
40
|
+
const temp_container = new Container();
|
|
41
|
+
temp_container.addChild(temp_sprite);
|
|
42
|
+
this.context.app.renderer.render(temp_container, {
|
|
43
|
+
renderTexture: layer_textures.draw
|
|
44
|
+
});
|
|
45
|
+
temp_container.destroy({ children: true });
|
|
46
|
+
}
|
|
47
|
+
async undo() {
|
|
48
|
+
const layer_textures = this.context.layer_manager.get_layer_textures(this.layer_id);
|
|
49
|
+
if (!layer_textures)
|
|
50
|
+
return;
|
|
51
|
+
const temp_sprite = new Sprite(this.original_texture);
|
|
52
|
+
const temp_container = new Container();
|
|
53
|
+
temp_container.addChild(temp_sprite);
|
|
54
|
+
this.context.app.renderer.render(temp_container, {
|
|
55
|
+
renderTexture: layer_textures.draw
|
|
56
|
+
});
|
|
57
|
+
temp_container.destroy({ children: true });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
3
60
|
/**
|
|
4
61
|
* Class to handle texture operations for the brush tool.
|
|
5
62
|
* Simplified to work directly with layer textures.
|
|
@@ -7,7 +64,6 @@ import {} from "../core/editor";
|
|
|
7
64
|
export class BrushTextures {
|
|
8
65
|
image_editor_context;
|
|
9
66
|
app;
|
|
10
|
-
// Only keep what we need
|
|
11
67
|
stroke_texture = null;
|
|
12
68
|
erase_texture = null;
|
|
13
69
|
display_container = null;
|
|
@@ -16,12 +72,11 @@ export class BrushTextures {
|
|
|
16
72
|
preview_sprite = null;
|
|
17
73
|
erase_graphics = null;
|
|
18
74
|
dimensions;
|
|
19
|
-
// Track if a new stroke is starting
|
|
20
75
|
is_new_stroke = true;
|
|
21
|
-
// Track the current drawing opacity
|
|
22
76
|
current_opacity = 1.0;
|
|
23
|
-
// Track current drawing mode
|
|
24
77
|
current_mode = "draw";
|
|
78
|
+
original_layer_texture = null;
|
|
79
|
+
active_layer_id = null;
|
|
25
80
|
constructor(image_editor_context, app) {
|
|
26
81
|
this.image_editor_context = image_editor_context;
|
|
27
82
|
this.app = app;
|
|
@@ -34,29 +89,23 @@ export class BrushTextures {
|
|
|
34
89
|
* Initializes textures needed for the brush tool.
|
|
35
90
|
*/
|
|
36
91
|
initialize_textures() {
|
|
37
|
-
// Clean up any existing resources
|
|
38
92
|
this.cleanup_textures();
|
|
39
|
-
// Get dimensions from the image container
|
|
40
93
|
const local_bounds = this.image_editor_context.image_container.getLocalBounds();
|
|
41
94
|
this.dimensions = {
|
|
42
95
|
width: local_bounds.width,
|
|
43
96
|
height: local_bounds.height
|
|
44
97
|
};
|
|
45
|
-
// Create a texture for the current stroke preview
|
|
46
98
|
this.stroke_texture = RenderTexture.create({
|
|
47
99
|
width: this.dimensions.width,
|
|
48
100
|
height: this.dimensions.height,
|
|
49
101
|
resolution: window.devicePixelRatio || 1
|
|
50
102
|
});
|
|
51
|
-
// Create a texture for erasing
|
|
52
103
|
this.erase_texture = RenderTexture.create({
|
|
53
104
|
width: this.dimensions.width,
|
|
54
105
|
height: this.dimensions.height,
|
|
55
106
|
resolution: window.devicePixelRatio || 1
|
|
56
107
|
});
|
|
57
|
-
// Create a container to display preview
|
|
58
108
|
this.display_container = new Container();
|
|
59
|
-
// Get the active layer and add the display container to it
|
|
60
109
|
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
61
110
|
if (active_layer) {
|
|
62
111
|
active_layer.addChild(this.display_container);
|
|
@@ -64,18 +113,13 @@ export class BrushTextures {
|
|
|
64
113
|
else {
|
|
65
114
|
this.image_editor_context.image_container.addChild(this.display_container);
|
|
66
115
|
}
|
|
67
|
-
// Create container for the stroke graphics
|
|
68
116
|
this.stroke_container = new Container();
|
|
69
117
|
this.stroke_graphics = new Graphics();
|
|
70
118
|
this.stroke_container.addChild(this.stroke_graphics);
|
|
71
|
-
// Create graphics for eraser mask
|
|
72
119
|
this.erase_graphics = new Graphics();
|
|
73
|
-
// Create a sprite for the preview of the current stroke
|
|
74
120
|
this.preview_sprite = new Sprite(this.stroke_texture);
|
|
75
|
-
// Start with the preview sprite invisible
|
|
76
121
|
this.preview_sprite.alpha = 0;
|
|
77
122
|
this.display_container.addChild(this.preview_sprite);
|
|
78
|
-
// Clear the stroke texture
|
|
79
123
|
const clear_container = new Container();
|
|
80
124
|
this.app.renderer.render(clear_container, {
|
|
81
125
|
renderTexture: this.stroke_texture
|
|
@@ -84,7 +128,6 @@ export class BrushTextures {
|
|
|
84
128
|
renderTexture: this.erase_texture
|
|
85
129
|
});
|
|
86
130
|
clear_container.destroy();
|
|
87
|
-
// Reset drawing state
|
|
88
131
|
this.is_new_stroke = true;
|
|
89
132
|
this.current_opacity = 1.0;
|
|
90
133
|
}
|
|
@@ -103,17 +146,14 @@ export class BrushTextures {
|
|
|
103
146
|
* Cleans up texture resources.
|
|
104
147
|
*/
|
|
105
148
|
cleanup_textures() {
|
|
106
|
-
// Clean up stroke texture
|
|
107
149
|
if (this.stroke_texture) {
|
|
108
150
|
this.stroke_texture.destroy();
|
|
109
151
|
this.stroke_texture = null;
|
|
110
152
|
}
|
|
111
|
-
// Clean up erase texture
|
|
112
153
|
if (this.erase_texture) {
|
|
113
154
|
this.erase_texture.destroy();
|
|
114
155
|
this.erase_texture = null;
|
|
115
156
|
}
|
|
116
|
-
// Clean up display container
|
|
117
157
|
if (this.display_container) {
|
|
118
158
|
if (this.display_container.parent) {
|
|
119
159
|
this.display_container.parent.removeChild(this.display_container);
|
|
@@ -121,17 +161,46 @@ export class BrushTextures {
|
|
|
121
161
|
this.display_container.destroy({ children: true });
|
|
122
162
|
this.display_container = null;
|
|
123
163
|
}
|
|
124
|
-
|
|
164
|
+
if (this.original_layer_texture) {
|
|
165
|
+
this.original_layer_texture.destroy();
|
|
166
|
+
this.original_layer_texture = null;
|
|
167
|
+
}
|
|
125
168
|
this.stroke_container = null;
|
|
126
169
|
this.stroke_graphics = null;
|
|
127
170
|
this.preview_sprite = null;
|
|
128
171
|
this.erase_graphics = null;
|
|
172
|
+
this.active_layer_id = null;
|
|
129
173
|
}
|
|
130
174
|
/**
|
|
131
175
|
* Preserve the canvas state when starting a new stroke.
|
|
132
176
|
*/
|
|
133
177
|
preserve_canvas_state() {
|
|
134
|
-
|
|
178
|
+
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
179
|
+
if (!active_layer)
|
|
180
|
+
return;
|
|
181
|
+
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
182
|
+
const layer = layers.find((l) => l.container === active_layer);
|
|
183
|
+
if (!layer)
|
|
184
|
+
return;
|
|
185
|
+
this.active_layer_id = layer.id;
|
|
186
|
+
const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
187
|
+
if (!layer_textures)
|
|
188
|
+
return;
|
|
189
|
+
if (this.original_layer_texture) {
|
|
190
|
+
this.original_layer_texture.destroy();
|
|
191
|
+
}
|
|
192
|
+
this.original_layer_texture = RenderTexture.create({
|
|
193
|
+
width: this.dimensions.width,
|
|
194
|
+
height: this.dimensions.height,
|
|
195
|
+
resolution: window.devicePixelRatio || 1
|
|
196
|
+
});
|
|
197
|
+
const temp_sprite = new Sprite(layer_textures.draw);
|
|
198
|
+
const temp_container = new Container();
|
|
199
|
+
temp_container.addChild(temp_sprite);
|
|
200
|
+
this.app.renderer.render(temp_container, {
|
|
201
|
+
renderTexture: this.original_layer_texture
|
|
202
|
+
});
|
|
203
|
+
temp_container.destroy({ children: true });
|
|
135
204
|
this.is_new_stroke = true;
|
|
136
205
|
}
|
|
137
206
|
/**
|
|
@@ -140,79 +209,76 @@ export class BrushTextures {
|
|
|
140
209
|
reset_eraser_mask() {
|
|
141
210
|
if (!this.erase_graphics || !this.erase_texture)
|
|
142
211
|
return;
|
|
143
|
-
// Clear and reset the eraser graphics
|
|
144
212
|
this.erase_graphics.clear();
|
|
145
213
|
this.erase_graphics.beginFill(0xffffff, 1);
|
|
146
214
|
this.erase_graphics.drawRect(0, 0, this.dimensions.width, this.dimensions.height);
|
|
147
215
|
this.erase_graphics.endFill();
|
|
148
|
-
// Render the eraser graphics to the erase texture
|
|
149
216
|
this.app.renderer.render(this.erase_graphics, {
|
|
150
217
|
renderTexture: this.erase_texture
|
|
151
218
|
});
|
|
152
219
|
}
|
|
153
220
|
/**
|
|
154
|
-
* Commits the current stroke to the active layer.
|
|
221
|
+
* Commits the current stroke to the active layer and returns a command for undo/redo.
|
|
155
222
|
*/
|
|
156
223
|
commit_stroke() {
|
|
157
|
-
if (!this.stroke_texture ||
|
|
224
|
+
if (!this.stroke_texture ||
|
|
225
|
+
!this.preview_sprite ||
|
|
226
|
+
!this.stroke_graphics ||
|
|
227
|
+
!this.original_layer_texture ||
|
|
228
|
+
!this.active_layer_id)
|
|
158
229
|
return;
|
|
159
|
-
// Hide the preview sprite
|
|
160
230
|
this.preview_sprite.visible = false;
|
|
161
|
-
// Get the active layer's texture
|
|
162
231
|
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
163
232
|
if (!active_layer)
|
|
164
233
|
return;
|
|
165
|
-
// Get all layers to find the ID of the active layer
|
|
166
234
|
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
167
235
|
const layer = layers.find((l) => l.container === active_layer);
|
|
168
236
|
if (!layer)
|
|
169
237
|
return;
|
|
170
|
-
|
|
238
|
+
if (layer.id !== this.active_layer_id)
|
|
239
|
+
return;
|
|
171
240
|
const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
172
241
|
if (!layer_textures)
|
|
173
242
|
return;
|
|
174
|
-
// Save the current opacity for use in committing
|
|
175
243
|
const currentOpacity = this.current_opacity;
|
|
176
|
-
// Handle drawing or erasing
|
|
177
244
|
if (this.current_mode === "draw") {
|
|
178
|
-
// Drawing mode - simple composition
|
|
179
245
|
const temp_container = new Container();
|
|
180
|
-
// Add current layer texture as base
|
|
181
246
|
const base_sprite = new Sprite(layer_textures.draw);
|
|
182
247
|
temp_container.addChild(base_sprite);
|
|
183
|
-
// Add the current stroke with proper opacity
|
|
184
248
|
const stroke_sprite = new Sprite(this.stroke_texture);
|
|
185
249
|
stroke_sprite.alpha = currentOpacity;
|
|
186
250
|
temp_container.addChild(stroke_sprite);
|
|
187
|
-
// Render the composited result directly to the layer texture
|
|
188
251
|
this.app.renderer.render(temp_container, {
|
|
189
252
|
renderTexture: layer_textures.draw
|
|
190
253
|
});
|
|
191
|
-
// Clean up
|
|
192
254
|
temp_container.destroy({ children: true });
|
|
193
255
|
}
|
|
194
256
|
else {
|
|
195
|
-
// Erasing mode - use proper masking with setMask
|
|
196
257
|
if (!this.stroke_texture)
|
|
197
258
|
return;
|
|
198
|
-
// Create a container for the erase operation
|
|
199
259
|
const erase_container = new Container();
|
|
200
|
-
// Add the current layer content
|
|
201
260
|
const content_sprite = new Sprite(layer_textures.draw);
|
|
202
261
|
erase_container.addChild(content_sprite);
|
|
203
|
-
// Create the mask sprite from our stroke texture
|
|
204
262
|
const mask_sprite = new Sprite(this.stroke_texture);
|
|
205
|
-
// Apply the mask to the container using setMask with inverse: true
|
|
206
|
-
// This will only show content where the mask is NOT (inverse: true)
|
|
207
263
|
erase_container.setMask({ mask: mask_sprite, inverse: true });
|
|
208
|
-
// Render the masked content back to layer texture
|
|
209
264
|
this.app.renderer.render(erase_container, {
|
|
210
265
|
renderTexture: layer_textures.draw
|
|
211
266
|
});
|
|
212
|
-
// Clean up
|
|
213
267
|
erase_container.destroy({ children: true });
|
|
214
268
|
}
|
|
215
|
-
|
|
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);
|
|
216
282
|
if (this.stroke_graphics) {
|
|
217
283
|
this.stroke_graphics.clear();
|
|
218
284
|
}
|
|
@@ -221,8 +287,11 @@ export class BrushTextures {
|
|
|
221
287
|
renderTexture: this.stroke_texture
|
|
222
288
|
});
|
|
223
289
|
clear_container.destroy();
|
|
224
|
-
// Reset state for the next stroke
|
|
225
290
|
this.is_new_stroke = true;
|
|
291
|
+
final_texture.destroy();
|
|
292
|
+
this.original_layer_texture = null;
|
|
293
|
+
this.active_layer_id = null;
|
|
294
|
+
this.image_editor_context.command_manager.execute(brush_command);
|
|
226
295
|
}
|
|
227
296
|
/**
|
|
228
297
|
* Calculates the distance between two points.
|
|
@@ -239,17 +308,15 @@ export class BrushTextures {
|
|
|
239
308
|
!this.stroke_container ||
|
|
240
309
|
!this.preview_sprite)
|
|
241
310
|
return;
|
|
242
|
-
|
|
311
|
+
if (this.is_new_stroke && !this.original_layer_texture) {
|
|
312
|
+
this.preserve_canvas_state();
|
|
313
|
+
}
|
|
243
314
|
this.current_mode = mode;
|
|
244
315
|
this.current_opacity =
|
|
245
316
|
mode === "draw" ? Math.min(Math.max(opacity, 0), 1) : 0.5;
|
|
246
|
-
// Apply scale to brush size for consistent rendering
|
|
247
317
|
const scaled_size = size;
|
|
248
|
-
// For new strokes, clear the graphics and texture completely
|
|
249
318
|
if (this.is_new_stroke) {
|
|
250
|
-
// Clean up for a new stroke
|
|
251
319
|
this.stroke_graphics.clear();
|
|
252
|
-
// Clear the stroke texture
|
|
253
320
|
const clear_container = new Container();
|
|
254
321
|
this.app.renderer.render(clear_container, {
|
|
255
322
|
renderTexture: this.stroke_texture
|
|
@@ -257,18 +324,14 @@ export class BrushTextures {
|
|
|
257
324
|
clear_container.destroy();
|
|
258
325
|
this.is_new_stroke = false;
|
|
259
326
|
}
|
|
260
|
-
// Set fill color and opacity for drawing
|
|
261
327
|
if (mode === "draw") {
|
|
262
|
-
|
|
263
|
-
// We need to convert the color string to a number
|
|
264
|
-
let colorValue = 0xffffff; // Default to white
|
|
328
|
+
let colorValue = 0xffffff;
|
|
265
329
|
try {
|
|
266
330
|
if (color.startsWith("#")) {
|
|
267
331
|
colorValue = parseInt(color.replace("#", "0x"), 16);
|
|
268
332
|
}
|
|
269
333
|
}
|
|
270
334
|
catch (e) {
|
|
271
|
-
// Default to white if there's an error
|
|
272
335
|
colorValue = 0xffffff;
|
|
273
336
|
}
|
|
274
337
|
this.stroke_graphics.setFillStyle({
|
|
@@ -277,50 +340,35 @@ export class BrushTextures {
|
|
|
277
340
|
});
|
|
278
341
|
}
|
|
279
342
|
else {
|
|
280
|
-
// For erasing, always use white
|
|
281
343
|
this.stroke_graphics.setFillStyle({
|
|
282
344
|
color: 0xffffff,
|
|
283
345
|
alpha: 1.0
|
|
284
346
|
});
|
|
285
347
|
}
|
|
286
|
-
// Calculate the distance between the points
|
|
287
348
|
const distance = this.calculateDistance(from_x, from_y, to_x, to_y);
|
|
288
|
-
// If the points are the same, just draw a single circle
|
|
289
349
|
if (distance < 0.1) {
|
|
290
|
-
// Draw a circle at the point - use scaled_size as RADIUS
|
|
291
350
|
this.stroke_graphics.circle(from_x, from_y, scaled_size).fill();
|
|
292
351
|
}
|
|
293
352
|
else {
|
|
294
|
-
// Calculate how many circles to draw along the path
|
|
295
|
-
// Adaptive spacing that balances quality and performance
|
|
296
353
|
const spacing = Math.max(scaled_size / 3, 2);
|
|
297
354
|
const steps = Math.max(Math.ceil(distance / spacing), 2);
|
|
298
|
-
// Draw a circle at each point along the path
|
|
299
355
|
for (let i = 0; i < steps; i++) {
|
|
300
356
|
const t = i / (steps - 1);
|
|
301
357
|
const x = from_x + (to_x - from_x) * t;
|
|
302
358
|
const y = from_y + (to_y - from_y) * t;
|
|
303
|
-
// Create a circle at this point - use scaled_size as RADIUS
|
|
304
359
|
this.stroke_graphics.circle(x, y, scaled_size).fill();
|
|
305
360
|
}
|
|
306
361
|
}
|
|
307
|
-
// End the fill after all circles are drawn
|
|
308
362
|
this.stroke_graphics.endFill();
|
|
309
|
-
// Render the stroke to the texture
|
|
310
363
|
this.app.renderer.render(this.stroke_container, {
|
|
311
364
|
renderTexture: this.stroke_texture
|
|
312
365
|
});
|
|
313
|
-
// Update the preview based on mode
|
|
314
366
|
if (mode === "draw") {
|
|
315
|
-
// For drawing, show the stroke directly with proper opacity
|
|
316
367
|
this.preview_sprite.texture = this.stroke_texture;
|
|
317
368
|
this.preview_sprite.alpha = this.current_opacity;
|
|
318
|
-
// Set tint to white (no tint) instead of trying to tint the texture
|
|
319
369
|
this.preview_sprite.tint = 0xffffff;
|
|
320
370
|
}
|
|
321
371
|
else {
|
|
322
|
-
// For erasing, create a preview that shows what will be erased
|
|
323
|
-
// Get the active layer's texture for preview
|
|
324
372
|
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
325
373
|
if (!active_layer)
|
|
326
374
|
return;
|
|
@@ -331,13 +379,11 @@ export class BrushTextures {
|
|
|
331
379
|
const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
332
380
|
if (!layer_textures)
|
|
333
381
|
return;
|
|
334
|
-
// Create a temporary texture for the preview
|
|
335
382
|
const preview_texture = RenderTexture.create({
|
|
336
383
|
width: this.dimensions.width,
|
|
337
384
|
height: this.dimensions.height,
|
|
338
385
|
resolution: window.devicePixelRatio || 1
|
|
339
386
|
});
|
|
340
|
-
// First, create a copy of the current layer content
|
|
341
387
|
const base_container = new Container();
|
|
342
388
|
const content_sprite = new Sprite(layer_textures.draw);
|
|
343
389
|
base_container.addChild(content_sprite);
|
|
@@ -345,26 +391,18 @@ export class BrushTextures {
|
|
|
345
391
|
renderTexture: preview_texture
|
|
346
392
|
});
|
|
347
393
|
base_container.destroy({ children: true });
|
|
348
|
-
// Create a container for the eraser preview with red overlay
|
|
349
394
|
const preview_container = new Container();
|
|
350
|
-
// Add our current stroke as the content
|
|
351
395
|
const mask_sprite = new Sprite(this.stroke_texture);
|
|
352
|
-
// Add a red overlay to show what's being erased
|
|
353
396
|
const overlay = new Graphics();
|
|
354
397
|
overlay.setFillStyle({ color: 0xffffff, alpha: 0.5 });
|
|
355
398
|
overlay.rect(0, 0, this.dimensions.width, this.dimensions.height).fill();
|
|
356
|
-
// Use setMask with inverse: false to only show overlay where the stroke is
|
|
357
|
-
// We want to show the red overlay ONLY where the stroke will erase
|
|
358
399
|
overlay.setMask({ mask: mask_sprite, inverse: false });
|
|
359
400
|
preview_container.addChild(overlay);
|
|
360
|
-
// Render the overlay on top of the base content
|
|
361
401
|
this.app.renderer.render(preview_container, {
|
|
362
402
|
renderTexture: preview_texture
|
|
363
403
|
});
|
|
364
|
-
// Update the preview sprite with our preview texture
|
|
365
404
|
this.preview_sprite.texture = preview_texture;
|
|
366
405
|
this.preview_sprite.alpha = 1.0;
|
|
367
|
-
// Clean up
|
|
368
406
|
preview_container.destroy({ children: true });
|
|
369
407
|
preview_texture.destroy();
|
|
370
408
|
}
|
|
@@ -78,36 +78,27 @@ export class BrushTool {
|
|
|
78
78
|
* @returns {Promise<void>}
|
|
79
79
|
*/
|
|
80
80
|
async setup(context, tool, subtool) {
|
|
81
|
-
// Store context and tool information
|
|
82
81
|
this.image_editor_context = context;
|
|
83
82
|
this.current_tool = tool;
|
|
84
83
|
this.current_subtool = subtool;
|
|
85
|
-
// Set mode based on tool
|
|
86
84
|
this.state.mode = tool === "erase" ? "erase" : "draw";
|
|
87
|
-
// Set size based on mode
|
|
88
85
|
if (this.state.mode === "draw") {
|
|
89
86
|
this.state.brush_size = this.brush_size;
|
|
90
87
|
}
|
|
91
88
|
else {
|
|
92
89
|
this.state.brush_size = this.eraser_size;
|
|
93
90
|
}
|
|
94
|
-
// Subscribe to scale changes
|
|
95
91
|
context.scale.subscribe((scale) => {
|
|
96
92
|
this.scale = scale;
|
|
97
93
|
if (this.brush_cursor) {
|
|
98
94
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
99
95
|
}
|
|
100
96
|
});
|
|
101
|
-
// Initialize brush cursor
|
|
102
97
|
this.brush_cursor = new BrushCursor(this.image_editor_context, this.state, this.scale);
|
|
103
|
-
// Set initial active state
|
|
104
98
|
this.brush_cursor.set_active(tool === "draw" || tool === "erase");
|
|
105
|
-
// Initialize brush textures
|
|
106
99
|
this.brush_textures = new BrushTextures(this.image_editor_context, context.app);
|
|
107
100
|
this.brush_textures.initialize_textures();
|
|
108
|
-
// Set up event listeners
|
|
109
101
|
this.setup_event_listeners();
|
|
110
|
-
// Set cursor style for image container
|
|
111
102
|
this.handle_cursors(tool);
|
|
112
103
|
}
|
|
113
104
|
/**
|
|
@@ -116,7 +107,6 @@ export class BrushTool {
|
|
|
116
107
|
* @private
|
|
117
108
|
*/
|
|
118
109
|
handle_cursors(tool) {
|
|
119
|
-
// Set cursor style to 'none' for all children of the image container
|
|
120
110
|
recurse_set_cursor(this.image_editor_context.image_container.children, "none");
|
|
121
111
|
}
|
|
122
112
|
/**
|
|
@@ -125,32 +115,26 @@ export class BrushTool {
|
|
|
125
115
|
* @param {Subtool} subtool - The current subtool.
|
|
126
116
|
*/
|
|
127
117
|
set_tool(tool, subtool) {
|
|
128
|
-
// Update current tool and subtool
|
|
129
118
|
this.current_tool = tool;
|
|
130
119
|
this.current_subtool = subtool;
|
|
131
120
|
if (this.current_tool !== "erase" && this.current_tool !== "draw") {
|
|
132
121
|
this.commit_pending_changes();
|
|
133
122
|
}
|
|
134
|
-
// Commit any pending changes
|
|
135
|
-
// Set brush cursor active state
|
|
136
123
|
if (this.brush_cursor) {
|
|
137
124
|
this.brush_cursor.set_active(tool === "draw" || tool === "erase");
|
|
138
125
|
}
|
|
139
|
-
// Update mode based on tool
|
|
140
126
|
const new_mode = tool === "erase" ? "erase" : "draw";
|
|
141
127
|
if (tool === "erase" || tool === "draw") {
|
|
142
128
|
this.brush_textures?.initialize_textures();
|
|
143
129
|
}
|
|
144
130
|
if (this.state.mode !== new_mode) {
|
|
145
131
|
this.state.mode = new_mode;
|
|
146
|
-
// Update brush size based on mode
|
|
147
132
|
if (this.state.mode === "draw") {
|
|
148
133
|
this.state.brush_size = this.brush_size;
|
|
149
134
|
}
|
|
150
135
|
else {
|
|
151
136
|
this.state.brush_size = this.eraser_size;
|
|
152
137
|
}
|
|
153
|
-
// Update cursor
|
|
154
138
|
if (this.brush_cursor) {
|
|
155
139
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
156
140
|
}
|
|
@@ -178,17 +162,13 @@ export class BrushTool {
|
|
|
178
162
|
}
|
|
179
163
|
if (this.brush_cursor && !this.brush_cursor.is_over_image())
|
|
180
164
|
return;
|
|
181
|
-
// Preserve the current canvas state
|
|
182
165
|
if (this.brush_textures) {
|
|
183
166
|
this.brush_textures.preserve_canvas_state();
|
|
184
167
|
}
|
|
185
|
-
// Get the local position
|
|
186
168
|
const local_pos = this.image_editor_context.image_container.toLocal(event.global);
|
|
187
|
-
// Start drawing
|
|
188
169
|
this.is_drawing = true;
|
|
189
170
|
this.last_x = local_pos.x;
|
|
190
171
|
this.last_y = local_pos.y;
|
|
191
|
-
// Draw a single point
|
|
192
172
|
if (this.brush_textures) {
|
|
193
173
|
this.brush_textures.draw_segment(local_pos.x, local_pos.y, local_pos.x, local_pos.y, this.state.brush_size, this.state.color, this.state.opacity, this.state.mode);
|
|
194
174
|
}
|
|
@@ -199,20 +179,15 @@ export class BrushTool {
|
|
|
199
179
|
* @private
|
|
200
180
|
*/
|
|
201
181
|
on_pointer_move(event) {
|
|
202
|
-
// Update cursor position
|
|
203
182
|
if (this.brush_cursor) {
|
|
204
183
|
this.brush_cursor.update_cursor_position(event);
|
|
205
184
|
}
|
|
206
|
-
// If not drawing, exit
|
|
207
185
|
if (!this.is_drawing)
|
|
208
186
|
return;
|
|
209
|
-
// Get the local position
|
|
210
187
|
const local_pos = this.image_editor_context.image_container.toLocal(event.global);
|
|
211
|
-
// Draw line from last position to current position
|
|
212
188
|
if (this.brush_textures) {
|
|
213
189
|
this.brush_textures.draw_segment(this.last_x, this.last_y, local_pos.x, local_pos.y, this.state.brush_size, this.state.color, this.state.opacity, this.state.mode);
|
|
214
190
|
}
|
|
215
|
-
// Update last position
|
|
216
191
|
this.last_x = local_pos.x;
|
|
217
192
|
this.last_y = local_pos.y;
|
|
218
193
|
}
|
|
@@ -221,12 +196,9 @@ export class BrushTool {
|
|
|
221
196
|
* @private
|
|
222
197
|
*/
|
|
223
198
|
on_pointer_up() {
|
|
224
|
-
// If not drawing, exit
|
|
225
199
|
if (!this.is_drawing)
|
|
226
200
|
return;
|
|
227
|
-
// Stop drawing
|
|
228
201
|
this.is_drawing = false;
|
|
229
|
-
// Commit the stroke
|
|
230
202
|
if (this.brush_textures) {
|
|
231
203
|
this.brush_textures.commit_stroke();
|
|
232
204
|
}
|
|
@@ -237,25 +209,19 @@ export class BrushTool {
|
|
|
237
209
|
* @private
|
|
238
210
|
*/
|
|
239
211
|
setup_event_listeners() {
|
|
240
|
-
// Remove existing listeners if they exist
|
|
241
212
|
this.cleanup_event_listeners();
|
|
242
|
-
// Create bound event handlers
|
|
243
213
|
this._bound_pointer_down = this.on_pointer_down.bind(this);
|
|
244
214
|
this._bound_pointer_move = this.on_pointer_move.bind(this);
|
|
245
215
|
this._bound_pointer_up = this.on_pointer_up.bind(this);
|
|
246
|
-
// Set interactive mode for image container
|
|
247
216
|
const image_container = this.image_editor_context.image_container;
|
|
248
217
|
image_container.eventMode = "static";
|
|
249
218
|
image_container.interactiveChildren = true;
|
|
250
|
-
// Setup for stage
|
|
251
219
|
const stage = this.image_editor_context.app.stage;
|
|
252
220
|
stage.eventMode = "static";
|
|
253
|
-
// Add pointer events to the stage for global tracking
|
|
254
221
|
stage.on("pointerdown", this._bound_pointer_down);
|
|
255
222
|
stage.on("pointermove", this._bound_pointer_move);
|
|
256
223
|
stage.on("pointerup", this._bound_pointer_up);
|
|
257
224
|
stage.on("pointerupoutside", this._bound_pointer_up);
|
|
258
|
-
// Set up cursor event listeners
|
|
259
225
|
if (this.brush_cursor) {
|
|
260
226
|
this.brush_cursor.setup_event_listeners();
|
|
261
227
|
}
|
|
@@ -266,7 +232,6 @@ export class BrushTool {
|
|
|
266
232
|
*/
|
|
267
233
|
cleanup_event_listeners() {
|
|
268
234
|
const stage = this.image_editor_context.app.stage;
|
|
269
|
-
// Remove event listeners if they exist
|
|
270
235
|
if (this._bound_pointer_down) {
|
|
271
236
|
stage.off("pointerdown", this._bound_pointer_down);
|
|
272
237
|
this._bound_pointer_down = null;
|
|
@@ -280,7 +245,6 @@ export class BrushTool {
|
|
|
280
245
|
stage.off("pointerupoutside", this._bound_pointer_up);
|
|
281
246
|
this._bound_pointer_up = null;
|
|
282
247
|
}
|
|
283
|
-
// Clean up cursor event listeners
|
|
284
248
|
if (this.brush_cursor) {
|
|
285
249
|
this.brush_cursor.cleanup_event_listeners();
|
|
286
250
|
}
|
|
@@ -290,12 +254,9 @@ export class BrushTool {
|
|
|
290
254
|
* @param {number} size - The new brush size.
|
|
291
255
|
*/
|
|
292
256
|
set_brush_size(size) {
|
|
293
|
-
// Set brush size
|
|
294
257
|
this.brush_size = size;
|
|
295
|
-
// Update state if in draw mode
|
|
296
258
|
if (this.state.mode === "draw") {
|
|
297
259
|
this.state.brush_size = size;
|
|
298
|
-
// Update cursor
|
|
299
260
|
if (this.brush_cursor) {
|
|
300
261
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
301
262
|
}
|
|
@@ -306,11 +267,8 @@ export class BrushTool {
|
|
|
306
267
|
* @param {string|ColorInput} color - The new brush color.
|
|
307
268
|
*/
|
|
308
269
|
set_brush_color(color) {
|
|
309
|
-
// Convert to hex string
|
|
310
270
|
const color_string = tinycolor(color).toHexString();
|
|
311
|
-
// Set color
|
|
312
271
|
this.state.color = color_string;
|
|
313
|
-
// Update cursor
|
|
314
272
|
if (this.brush_cursor) {
|
|
315
273
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
316
274
|
}
|
|
@@ -320,11 +278,8 @@ export class BrushTool {
|
|
|
320
278
|
* @param {number} opacity - The new brush opacity.
|
|
321
279
|
*/
|
|
322
280
|
set_brush_opacity(opacity) {
|
|
323
|
-
// Clamp opacity between 0 and 1
|
|
324
281
|
const clamped_opacity = Math.max(0, Math.min(1, opacity));
|
|
325
|
-
// Set opacity
|
|
326
282
|
this.state.opacity = clamped_opacity;
|
|
327
|
-
// Update cursor
|
|
328
283
|
if (this.brush_cursor) {
|
|
329
284
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
330
285
|
}
|
|
@@ -334,12 +289,9 @@ export class BrushTool {
|
|
|
334
289
|
* @param {number} size - The new eraser size.
|
|
335
290
|
*/
|
|
336
291
|
set_eraser_size(size) {
|
|
337
|
-
// Store the new eraser size
|
|
338
292
|
this.eraser_size = size;
|
|
339
|
-
// Update state if in erase mode
|
|
340
293
|
if (this.state.mode === "erase") {
|
|
341
294
|
this.state.brush_size = size;
|
|
342
|
-
// Update cursor
|
|
343
295
|
if (this.brush_cursor) {
|
|
344
296
|
this.brush_cursor.update_state(this.state, this.scale);
|
|
345
297
|
}
|
|
@@ -365,16 +317,12 @@ export class BrushTool {
|
|
|
365
317
|
* Cleans up resources used by the brush tool.
|
|
366
318
|
*/
|
|
367
319
|
cleanup() {
|
|
368
|
-
// Commit any pending changes
|
|
369
320
|
this.commit_pending_changes();
|
|
370
|
-
// Clean up event listeners
|
|
371
321
|
this.cleanup_event_listeners();
|
|
372
|
-
// Clean up brush textures
|
|
373
322
|
if (this.brush_textures) {
|
|
374
323
|
this.brush_textures.cleanup();
|
|
375
324
|
this.brush_textures = null;
|
|
376
325
|
}
|
|
377
|
-
// Clean up brush cursor
|
|
378
326
|
if (this.brush_cursor) {
|
|
379
327
|
this.brush_cursor.cleanup();
|
|
380
328
|
this.brush_cursor = null;
|