@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
|
@@ -3,16 +3,92 @@ import {
|
|
|
3
3
|
RenderTexture,
|
|
4
4
|
Sprite,
|
|
5
5
|
Graphics,
|
|
6
|
-
Application
|
|
6
|
+
Application,
|
|
7
|
+
Texture
|
|
7
8
|
} from "pixi.js";
|
|
8
9
|
import { type ImageEditorContext } from "../core/editor";
|
|
10
|
+
import { type Command } from "../core/commands";
|
|
11
|
+
export class BrushCommand implements Command {
|
|
12
|
+
private layer_id: string;
|
|
13
|
+
private original_texture: Texture;
|
|
14
|
+
private final_texture: Texture;
|
|
15
|
+
name: string;
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
private context: ImageEditorContext,
|
|
19
|
+
layer_id: string,
|
|
20
|
+
original_texture: Texture,
|
|
21
|
+
final_texture: Texture
|
|
22
|
+
) {
|
|
23
|
+
this.name = "Draw";
|
|
24
|
+
this.layer_id = layer_id;
|
|
25
|
+
|
|
26
|
+
this.original_texture = this.createTextureFrom(original_texture);
|
|
27
|
+
this.final_texture = this.createTextureFrom(final_texture);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Creates a new texture with the same content as the source texture
|
|
32
|
+
*/
|
|
33
|
+
private createTextureFrom(source: Texture): Texture {
|
|
34
|
+
const texture = RenderTexture.create({
|
|
35
|
+
width: source.width,
|
|
36
|
+
height: source.height,
|
|
37
|
+
resolution: window.devicePixelRatio || 1
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const sprite = new Sprite(source);
|
|
41
|
+
const container = new Container();
|
|
42
|
+
container.addChild(sprite);
|
|
43
|
+
|
|
44
|
+
this.context.app.renderer.render(container, {
|
|
45
|
+
renderTexture: texture
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
container.destroy({ children: true });
|
|
49
|
+
return texture;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async execute(): Promise<void> {
|
|
53
|
+
const layer_textures = this.context.layer_manager.get_layer_textures(
|
|
54
|
+
this.layer_id
|
|
55
|
+
);
|
|
56
|
+
if (!layer_textures) return;
|
|
57
|
+
|
|
58
|
+
const temp_sprite = new Sprite(this.final_texture);
|
|
59
|
+
const temp_container = new Container();
|
|
60
|
+
temp_container.addChild(temp_sprite);
|
|
61
|
+
|
|
62
|
+
this.context.app.renderer.render(temp_container, {
|
|
63
|
+
renderTexture: layer_textures.draw
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
temp_container.destroy({ children: true });
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async undo(): Promise<void> {
|
|
70
|
+
const layer_textures = this.context.layer_manager.get_layer_textures(
|
|
71
|
+
this.layer_id
|
|
72
|
+
);
|
|
73
|
+
if (!layer_textures) return;
|
|
74
|
+
|
|
75
|
+
const temp_sprite = new Sprite(this.original_texture);
|
|
76
|
+
const temp_container = new Container();
|
|
77
|
+
temp_container.addChild(temp_sprite);
|
|
78
|
+
|
|
79
|
+
this.context.app.renderer.render(temp_container, {
|
|
80
|
+
renderTexture: layer_textures.draw
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
temp_container.destroy({ children: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
9
86
|
|
|
10
87
|
/**
|
|
11
88
|
* Class to handle texture operations for the brush tool.
|
|
12
89
|
* Simplified to work directly with layer textures.
|
|
13
90
|
*/
|
|
14
91
|
export class BrushTextures {
|
|
15
|
-
// Only keep what we need
|
|
16
92
|
private stroke_texture: RenderTexture | null = null;
|
|
17
93
|
private erase_texture: RenderTexture | null = null;
|
|
18
94
|
private display_container: Container | null = null;
|
|
@@ -22,15 +98,15 @@ export class BrushTextures {
|
|
|
22
98
|
private erase_graphics: Graphics | null = null;
|
|
23
99
|
private dimensions: { width: number; height: number };
|
|
24
100
|
|
|
25
|
-
// Track if a new stroke is starting
|
|
26
101
|
private is_new_stroke = true;
|
|
27
102
|
|
|
28
|
-
// Track the current drawing opacity
|
|
29
103
|
private current_opacity = 1.0;
|
|
30
104
|
|
|
31
|
-
// Track current drawing mode
|
|
32
105
|
private current_mode: "draw" | "erase" = "draw";
|
|
33
106
|
|
|
107
|
+
private original_layer_texture: Texture | null = null;
|
|
108
|
+
private active_layer_id: string | null = null;
|
|
109
|
+
|
|
34
110
|
constructor(
|
|
35
111
|
private image_editor_context: ImageEditorContext,
|
|
36
112
|
private app: Application
|
|
@@ -45,10 +121,8 @@ export class BrushTextures {
|
|
|
45
121
|
* Initializes textures needed for the brush tool.
|
|
46
122
|
*/
|
|
47
123
|
initialize_textures(): void {
|
|
48
|
-
// Clean up any existing resources
|
|
49
124
|
this.cleanup_textures();
|
|
50
125
|
|
|
51
|
-
// Get dimensions from the image container
|
|
52
126
|
const local_bounds =
|
|
53
127
|
this.image_editor_context.image_container.getLocalBounds();
|
|
54
128
|
|
|
@@ -57,24 +131,20 @@ export class BrushTextures {
|
|
|
57
131
|
height: local_bounds.height
|
|
58
132
|
};
|
|
59
133
|
|
|
60
|
-
// Create a texture for the current stroke preview
|
|
61
134
|
this.stroke_texture = RenderTexture.create({
|
|
62
135
|
width: this.dimensions.width,
|
|
63
136
|
height: this.dimensions.height,
|
|
64
137
|
resolution: window.devicePixelRatio || 1
|
|
65
138
|
});
|
|
66
139
|
|
|
67
|
-
// Create a texture for erasing
|
|
68
140
|
this.erase_texture = RenderTexture.create({
|
|
69
141
|
width: this.dimensions.width,
|
|
70
142
|
height: this.dimensions.height,
|
|
71
143
|
resolution: window.devicePixelRatio || 1
|
|
72
144
|
});
|
|
73
145
|
|
|
74
|
-
// Create a container to display preview
|
|
75
146
|
this.display_container = new Container();
|
|
76
147
|
|
|
77
|
-
// Get the active layer and add the display container to it
|
|
78
148
|
const active_layer =
|
|
79
149
|
this.image_editor_context.layer_manager.get_active_layer();
|
|
80
150
|
if (active_layer) {
|
|
@@ -85,21 +155,16 @@ export class BrushTextures {
|
|
|
85
155
|
);
|
|
86
156
|
}
|
|
87
157
|
|
|
88
|
-
// Create container for the stroke graphics
|
|
89
158
|
this.stroke_container = new Container();
|
|
90
159
|
this.stroke_graphics = new Graphics();
|
|
91
160
|
this.stroke_container.addChild(this.stroke_graphics);
|
|
92
161
|
|
|
93
|
-
// Create graphics for eraser mask
|
|
94
162
|
this.erase_graphics = new Graphics();
|
|
95
163
|
|
|
96
|
-
// Create a sprite for the preview of the current stroke
|
|
97
164
|
this.preview_sprite = new Sprite(this.stroke_texture);
|
|
98
|
-
// Start with the preview sprite invisible
|
|
99
165
|
this.preview_sprite.alpha = 0;
|
|
100
166
|
this.display_container.addChild(this.preview_sprite);
|
|
101
167
|
|
|
102
|
-
// Clear the stroke texture
|
|
103
168
|
const clear_container = new Container();
|
|
104
169
|
this.app.renderer.render(clear_container, {
|
|
105
170
|
renderTexture: this.stroke_texture
|
|
@@ -109,7 +174,6 @@ export class BrushTextures {
|
|
|
109
174
|
});
|
|
110
175
|
clear_container.destroy();
|
|
111
176
|
|
|
112
|
-
// Reset drawing state
|
|
113
177
|
this.is_new_stroke = true;
|
|
114
178
|
this.current_opacity = 1.0;
|
|
115
179
|
}
|
|
@@ -132,19 +196,16 @@ export class BrushTextures {
|
|
|
132
196
|
* Cleans up texture resources.
|
|
133
197
|
*/
|
|
134
198
|
cleanup_textures(): void {
|
|
135
|
-
// Clean up stroke texture
|
|
136
199
|
if (this.stroke_texture) {
|
|
137
200
|
this.stroke_texture.destroy();
|
|
138
201
|
this.stroke_texture = null;
|
|
139
202
|
}
|
|
140
203
|
|
|
141
|
-
// Clean up erase texture
|
|
142
204
|
if (this.erase_texture) {
|
|
143
205
|
this.erase_texture.destroy();
|
|
144
206
|
this.erase_texture = null;
|
|
145
207
|
}
|
|
146
208
|
|
|
147
|
-
// Clean up display container
|
|
148
209
|
if (this.display_container) {
|
|
149
210
|
if (this.display_container.parent) {
|
|
150
211
|
this.display_container.parent.removeChild(this.display_container);
|
|
@@ -153,18 +214,56 @@ export class BrushTextures {
|
|
|
153
214
|
this.display_container = null;
|
|
154
215
|
}
|
|
155
216
|
|
|
156
|
-
|
|
217
|
+
if (this.original_layer_texture) {
|
|
218
|
+
this.original_layer_texture.destroy();
|
|
219
|
+
this.original_layer_texture = null;
|
|
220
|
+
}
|
|
221
|
+
|
|
157
222
|
this.stroke_container = null;
|
|
158
223
|
this.stroke_graphics = null;
|
|
159
224
|
this.preview_sprite = null;
|
|
160
225
|
this.erase_graphics = null;
|
|
226
|
+
this.active_layer_id = null;
|
|
161
227
|
}
|
|
162
228
|
|
|
163
229
|
/**
|
|
164
230
|
* Preserve the canvas state when starting a new stroke.
|
|
165
231
|
*/
|
|
166
232
|
preserve_canvas_state(): void {
|
|
167
|
-
|
|
233
|
+
const active_layer =
|
|
234
|
+
this.image_editor_context.layer_manager.get_active_layer();
|
|
235
|
+
if (!active_layer) return;
|
|
236
|
+
|
|
237
|
+
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
238
|
+
const layer = layers.find((l) => l.container === active_layer);
|
|
239
|
+
if (!layer) return;
|
|
240
|
+
|
|
241
|
+
this.active_layer_id = layer.id;
|
|
242
|
+
|
|
243
|
+
const layer_textures =
|
|
244
|
+
this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
245
|
+
if (!layer_textures) return;
|
|
246
|
+
|
|
247
|
+
if (this.original_layer_texture) {
|
|
248
|
+
this.original_layer_texture.destroy();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
this.original_layer_texture = RenderTexture.create({
|
|
252
|
+
width: this.dimensions.width,
|
|
253
|
+
height: this.dimensions.height,
|
|
254
|
+
resolution: window.devicePixelRatio || 1
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const temp_sprite = new Sprite(layer_textures.draw);
|
|
258
|
+
const temp_container = new Container();
|
|
259
|
+
temp_container.addChild(temp_sprite);
|
|
260
|
+
|
|
261
|
+
this.app.renderer.render(temp_container, {
|
|
262
|
+
renderTexture: this.original_layer_texture
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
temp_container.destroy({ children: true });
|
|
266
|
+
|
|
168
267
|
this.is_new_stroke = true;
|
|
169
268
|
}
|
|
170
269
|
|
|
@@ -174,7 +273,6 @@ export class BrushTextures {
|
|
|
174
273
|
reset_eraser_mask(): void {
|
|
175
274
|
if (!this.erase_graphics || !this.erase_texture) return;
|
|
176
275
|
|
|
177
|
-
// Clear and reset the eraser graphics
|
|
178
276
|
this.erase_graphics.clear();
|
|
179
277
|
this.erase_graphics.beginFill(0xffffff, 1);
|
|
180
278
|
this.erase_graphics.drawRect(
|
|
@@ -185,90 +283,100 @@ export class BrushTextures {
|
|
|
185
283
|
);
|
|
186
284
|
this.erase_graphics.endFill();
|
|
187
285
|
|
|
188
|
-
// Render the eraser graphics to the erase texture
|
|
189
286
|
this.app.renderer.render(this.erase_graphics, {
|
|
190
287
|
renderTexture: this.erase_texture
|
|
191
288
|
});
|
|
192
289
|
}
|
|
193
290
|
|
|
194
291
|
/**
|
|
195
|
-
* Commits the current stroke to the active layer.
|
|
292
|
+
* Commits the current stroke to the active layer and returns a command for undo/redo.
|
|
196
293
|
*/
|
|
197
294
|
commit_stroke(): void {
|
|
198
|
-
if (
|
|
295
|
+
if (
|
|
296
|
+
!this.stroke_texture ||
|
|
297
|
+
!this.preview_sprite ||
|
|
298
|
+
!this.stroke_graphics ||
|
|
299
|
+
!this.original_layer_texture ||
|
|
300
|
+
!this.active_layer_id
|
|
301
|
+
)
|
|
199
302
|
return;
|
|
200
303
|
|
|
201
|
-
// Hide the preview sprite
|
|
202
304
|
this.preview_sprite.visible = false;
|
|
203
305
|
|
|
204
|
-
// Get the active layer's texture
|
|
205
306
|
const active_layer =
|
|
206
307
|
this.image_editor_context.layer_manager.get_active_layer();
|
|
207
308
|
|
|
208
309
|
if (!active_layer) return;
|
|
209
310
|
|
|
210
|
-
// Get all layers to find the ID of the active layer
|
|
211
311
|
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
212
312
|
const layer = layers.find((l) => l.container === active_layer);
|
|
213
313
|
if (!layer) return;
|
|
214
314
|
|
|
215
|
-
|
|
315
|
+
if (layer.id !== this.active_layer_id) return;
|
|
316
|
+
|
|
216
317
|
const layer_textures =
|
|
217
318
|
this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
218
319
|
if (!layer_textures) return;
|
|
219
320
|
|
|
220
|
-
// Save the current opacity for use in committing
|
|
221
321
|
const currentOpacity = this.current_opacity;
|
|
222
322
|
|
|
223
|
-
// Handle drawing or erasing
|
|
224
323
|
if (this.current_mode === "draw") {
|
|
225
|
-
// Drawing mode - simple composition
|
|
226
324
|
const temp_container = new Container();
|
|
227
325
|
|
|
228
|
-
// Add current layer texture as base
|
|
229
326
|
const base_sprite = new Sprite(layer_textures.draw);
|
|
230
327
|
temp_container.addChild(base_sprite);
|
|
231
328
|
|
|
232
|
-
// Add the current stroke with proper opacity
|
|
233
329
|
const stroke_sprite = new Sprite(this.stroke_texture);
|
|
234
330
|
stroke_sprite.alpha = currentOpacity;
|
|
235
331
|
temp_container.addChild(stroke_sprite);
|
|
236
332
|
|
|
237
|
-
// Render the composited result directly to the layer texture
|
|
238
333
|
this.app.renderer.render(temp_container, {
|
|
239
334
|
renderTexture: layer_textures.draw
|
|
240
335
|
});
|
|
241
336
|
|
|
242
|
-
// Clean up
|
|
243
337
|
temp_container.destroy({ children: true });
|
|
244
338
|
} else {
|
|
245
|
-
// Erasing mode - use proper masking with setMask
|
|
246
339
|
if (!this.stroke_texture) return;
|
|
247
340
|
|
|
248
|
-
// Create a container for the erase operation
|
|
249
341
|
const erase_container = new Container();
|
|
250
342
|
|
|
251
|
-
// Add the current layer content
|
|
252
343
|
const content_sprite = new Sprite(layer_textures.draw);
|
|
253
344
|
erase_container.addChild(content_sprite);
|
|
254
345
|
|
|
255
|
-
// Create the mask sprite from our stroke texture
|
|
256
346
|
const mask_sprite = new Sprite(this.stroke_texture);
|
|
257
347
|
|
|
258
|
-
// Apply the mask to the container using setMask with inverse: true
|
|
259
|
-
// This will only show content where the mask is NOT (inverse: true)
|
|
260
348
|
erase_container.setMask({ mask: mask_sprite, inverse: true });
|
|
261
349
|
|
|
262
|
-
// Render the masked content back to layer texture
|
|
263
350
|
this.app.renderer.render(erase_container, {
|
|
264
351
|
renderTexture: layer_textures.draw
|
|
265
352
|
});
|
|
266
353
|
|
|
267
|
-
// Clean up
|
|
268
354
|
erase_container.destroy({ children: true });
|
|
269
355
|
}
|
|
270
356
|
|
|
271
|
-
|
|
357
|
+
const final_texture = RenderTexture.create({
|
|
358
|
+
width: this.dimensions.width,
|
|
359
|
+
height: this.dimensions.height,
|
|
360
|
+
resolution: window.devicePixelRatio || 1
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
const final_container = new Container();
|
|
364
|
+
const final_sprite = new Sprite(layer_textures.draw);
|
|
365
|
+
final_container.addChild(final_sprite);
|
|
366
|
+
|
|
367
|
+
this.app.renderer.render(final_container, {
|
|
368
|
+
renderTexture: final_texture
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
final_container.destroy({ children: true });
|
|
372
|
+
|
|
373
|
+
const brush_command = new BrushCommand(
|
|
374
|
+
this.image_editor_context,
|
|
375
|
+
this.active_layer_id,
|
|
376
|
+
this.original_layer_texture,
|
|
377
|
+
final_texture
|
|
378
|
+
);
|
|
379
|
+
|
|
272
380
|
if (this.stroke_graphics) {
|
|
273
381
|
this.stroke_graphics.clear();
|
|
274
382
|
}
|
|
@@ -278,8 +386,14 @@ export class BrushTextures {
|
|
|
278
386
|
});
|
|
279
387
|
clear_container.destroy();
|
|
280
388
|
|
|
281
|
-
// Reset state for the next stroke
|
|
282
389
|
this.is_new_stroke = true;
|
|
390
|
+
|
|
391
|
+
final_texture.destroy();
|
|
392
|
+
|
|
393
|
+
this.original_layer_texture = null;
|
|
394
|
+
this.active_layer_id = null;
|
|
395
|
+
|
|
396
|
+
this.image_editor_context.command_manager.execute(brush_command);
|
|
283
397
|
}
|
|
284
398
|
|
|
285
399
|
/**
|
|
@@ -315,20 +429,19 @@ export class BrushTextures {
|
|
|
315
429
|
)
|
|
316
430
|
return;
|
|
317
431
|
|
|
318
|
-
|
|
432
|
+
if (this.is_new_stroke && !this.original_layer_texture) {
|
|
433
|
+
this.preserve_canvas_state();
|
|
434
|
+
}
|
|
435
|
+
|
|
319
436
|
this.current_mode = mode;
|
|
320
437
|
this.current_opacity =
|
|
321
438
|
mode === "draw" ? Math.min(Math.max(opacity, 0), 1) : 0.5;
|
|
322
439
|
|
|
323
|
-
// Apply scale to brush size for consistent rendering
|
|
324
440
|
const scaled_size = size;
|
|
325
441
|
|
|
326
|
-
// For new strokes, clear the graphics and texture completely
|
|
327
442
|
if (this.is_new_stroke) {
|
|
328
|
-
// Clean up for a new stroke
|
|
329
443
|
this.stroke_graphics.clear();
|
|
330
444
|
|
|
331
|
-
// Clear the stroke texture
|
|
332
445
|
const clear_container = new Container();
|
|
333
446
|
this.app.renderer.render(clear_container, {
|
|
334
447
|
renderTexture: this.stroke_texture
|
|
@@ -338,17 +451,13 @@ export class BrushTextures {
|
|
|
338
451
|
this.is_new_stroke = false;
|
|
339
452
|
}
|
|
340
453
|
|
|
341
|
-
// Set fill color and opacity for drawing
|
|
342
454
|
if (mode === "draw") {
|
|
343
|
-
|
|
344
|
-
// We need to convert the color string to a number
|
|
345
|
-
let colorValue = 0xffffff; // Default to white
|
|
455
|
+
let colorValue = 0xffffff;
|
|
346
456
|
try {
|
|
347
457
|
if (color.startsWith("#")) {
|
|
348
458
|
colorValue = parseInt(color.replace("#", "0x"), 16);
|
|
349
459
|
}
|
|
350
460
|
} catch (e) {
|
|
351
|
-
// Default to white if there's an error
|
|
352
461
|
colorValue = 0xffffff;
|
|
353
462
|
}
|
|
354
463
|
this.stroke_graphics.setFillStyle({
|
|
@@ -356,57 +465,41 @@ export class BrushTextures {
|
|
|
356
465
|
alpha: 1.0
|
|
357
466
|
});
|
|
358
467
|
} else {
|
|
359
|
-
// For erasing, always use white
|
|
360
468
|
this.stroke_graphics.setFillStyle({
|
|
361
469
|
color: 0xffffff,
|
|
362
470
|
alpha: 1.0
|
|
363
471
|
});
|
|
364
472
|
}
|
|
365
473
|
|
|
366
|
-
// Calculate the distance between the points
|
|
367
474
|
const distance = this.calculateDistance(from_x, from_y, to_x, to_y);
|
|
368
475
|
|
|
369
|
-
// If the points are the same, just draw a single circle
|
|
370
476
|
if (distance < 0.1) {
|
|
371
|
-
// Draw a circle at the point - use scaled_size as RADIUS
|
|
372
477
|
this.stroke_graphics.circle(from_x, from_y, scaled_size).fill();
|
|
373
478
|
} else {
|
|
374
|
-
// Calculate how many circles to draw along the path
|
|
375
|
-
// Adaptive spacing that balances quality and performance
|
|
376
479
|
const spacing = Math.max(scaled_size / 3, 2);
|
|
377
480
|
const steps = Math.max(Math.ceil(distance / spacing), 2);
|
|
378
481
|
|
|
379
|
-
// Draw a circle at each point along the path
|
|
380
482
|
for (let i = 0; i < steps; i++) {
|
|
381
483
|
const t = i / (steps - 1);
|
|
382
484
|
const x = from_x + (to_x - from_x) * t;
|
|
383
485
|
const y = from_y + (to_y - from_y) * t;
|
|
384
486
|
|
|
385
|
-
// Create a circle at this point - use scaled_size as RADIUS
|
|
386
487
|
this.stroke_graphics.circle(x, y, scaled_size).fill();
|
|
387
488
|
}
|
|
388
489
|
}
|
|
389
490
|
|
|
390
|
-
// End the fill after all circles are drawn
|
|
391
491
|
this.stroke_graphics.endFill();
|
|
392
492
|
|
|
393
|
-
// Render the stroke to the texture
|
|
394
493
|
this.app.renderer.render(this.stroke_container, {
|
|
395
494
|
renderTexture: this.stroke_texture
|
|
396
495
|
});
|
|
397
496
|
|
|
398
|
-
// Update the preview based on mode
|
|
399
497
|
if (mode === "draw") {
|
|
400
|
-
// For drawing, show the stroke directly with proper opacity
|
|
401
498
|
this.preview_sprite.texture = this.stroke_texture;
|
|
402
499
|
this.preview_sprite.alpha = this.current_opacity;
|
|
403
500
|
|
|
404
|
-
// Set tint to white (no tint) instead of trying to tint the texture
|
|
405
501
|
this.preview_sprite.tint = 0xffffff;
|
|
406
502
|
} else {
|
|
407
|
-
// For erasing, create a preview that shows what will be erased
|
|
408
|
-
|
|
409
|
-
// Get the active layer's texture for preview
|
|
410
503
|
const active_layer =
|
|
411
504
|
this.image_editor_context.layer_manager.get_active_layer();
|
|
412
505
|
if (!active_layer) return;
|
|
@@ -419,14 +512,12 @@ export class BrushTextures {
|
|
|
419
512
|
this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
420
513
|
if (!layer_textures) return;
|
|
421
514
|
|
|
422
|
-
// Create a temporary texture for the preview
|
|
423
515
|
const preview_texture = RenderTexture.create({
|
|
424
516
|
width: this.dimensions.width,
|
|
425
517
|
height: this.dimensions.height,
|
|
426
518
|
resolution: window.devicePixelRatio || 1
|
|
427
519
|
});
|
|
428
520
|
|
|
429
|
-
// First, create a copy of the current layer content
|
|
430
521
|
const base_container = new Container();
|
|
431
522
|
const content_sprite = new Sprite(layer_textures.draw);
|
|
432
523
|
base_container.addChild(content_sprite);
|
|
@@ -435,33 +526,25 @@ export class BrushTextures {
|
|
|
435
526
|
});
|
|
436
527
|
base_container.destroy({ children: true });
|
|
437
528
|
|
|
438
|
-
// Create a container for the eraser preview with red overlay
|
|
439
529
|
const preview_container = new Container();
|
|
440
530
|
|
|
441
|
-
// Add our current stroke as the content
|
|
442
531
|
const mask_sprite = new Sprite(this.stroke_texture);
|
|
443
532
|
|
|
444
|
-
// Add a red overlay to show what's being erased
|
|
445
533
|
const overlay = new Graphics();
|
|
446
534
|
overlay.setFillStyle({ color: 0xffffff, alpha: 0.5 });
|
|
447
535
|
overlay.rect(0, 0, this.dimensions.width, this.dimensions.height).fill();
|
|
448
536
|
|
|
449
|
-
// Use setMask with inverse: false to only show overlay where the stroke is
|
|
450
|
-
// We want to show the red overlay ONLY where the stroke will erase
|
|
451
537
|
overlay.setMask({ mask: mask_sprite, inverse: false });
|
|
452
538
|
|
|
453
539
|
preview_container.addChild(overlay);
|
|
454
540
|
|
|
455
|
-
// Render the overlay on top of the base content
|
|
456
541
|
this.app.renderer.render(preview_container, {
|
|
457
542
|
renderTexture: preview_texture
|
|
458
543
|
});
|
|
459
544
|
|
|
460
|
-
// Update the preview sprite with our preview texture
|
|
461
545
|
this.preview_sprite.texture = preview_texture;
|
|
462
546
|
this.preview_sprite.alpha = 1.0;
|
|
463
547
|
|
|
464
|
-
// Clean up
|
|
465
548
|
preview_container.destroy({ children: true });
|
|
466
549
|
preview_texture.destroy();
|
|
467
550
|
}
|