@gradio/imageeditor 0.12.14 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/Example.svelte +1 -1
- package/IMAGE_EDITOR_OVERVIEW.md +224 -0
- package/ImageEditor.stories.svelte +4 -4
- package/Index.svelte +35 -46
- package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +56 -103
- package/dist/Example.svelte.d.ts +1 -1
- package/dist/Index.svelte +29 -33
- package/dist/Index.svelte.d.ts +17 -16
- package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +47 -88
- package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +13 -15
- package/dist/shared/Anchor.svelte +311 -0
- package/dist/shared/Anchor.svelte.d.ts +18 -0
- package/dist/shared/Controls.svelte +162 -23
- package/dist/shared/Controls.svelte.d.ts +7 -2
- package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
- package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
- package/dist/shared/ImageEditor.svelte +546 -289
- package/dist/shared/ImageEditor.svelte.d.ts +51 -56
- package/dist/shared/Layers.svelte +233 -0
- package/dist/shared/Layers.svelte.d.ts +35 -0
- package/dist/shared/Resize.svelte +218 -0
- package/dist/shared/Resize.svelte.d.ts +29 -0
- package/dist/shared/SecondaryToolbar.svelte +54 -0
- package/dist/shared/SecondaryToolbar.svelte.d.ts +35 -0
- package/dist/shared/Toolbar.svelte +337 -0
- package/dist/shared/Toolbar.svelte.d.ts +42 -0
- package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
- package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
- package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
- package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
- package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
- package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
- package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
- package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
- package/dist/shared/brush/brush-cursor.d.ts +101 -0
- package/dist/shared/brush/brush-cursor.js +327 -0
- package/dist/shared/brush/brush-textures.d.ts +71 -0
- package/dist/shared/brush/brush-textures.js +404 -0
- package/dist/shared/brush/brush-utils.d.ts +32 -0
- package/dist/shared/brush/brush-utils.js +59 -0
- package/dist/shared/brush/brush.d.ts +152 -0
- package/dist/shared/brush/brush.js +396 -0
- package/dist/shared/brush/types.d.ts +37 -0
- package/dist/shared/brush/types.js +1 -0
- package/dist/shared/core/editor.d.ts +252 -0
- package/dist/shared/core/editor.js +1221 -0
- package/dist/shared/crop/crop.d.ts +201 -0
- package/dist/shared/crop/crop.js +727 -0
- package/dist/shared/image/image.d.ts +89 -0
- package/dist/shared/image/image.js +185 -0
- package/dist/shared/resize/resize.d.ts +270 -0
- package/dist/shared/resize/resize.js +1413 -0
- package/dist/shared/types.d.ts +12 -0
- package/dist/shared/utils/pixi.d.ts +16 -57
- package/dist/shared/utils/pixi.js +15 -113
- package/dist/shared/zoom/zoom.d.ts +136 -0
- package/dist/shared/zoom/zoom.js +548 -0
- package/package.json +10 -9
- package/shared/Anchor.svelte +384 -0
- package/shared/Controls.svelte +194 -27
- package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
- package/shared/ImageEditor.svelte +682 -349
- package/shared/Layers.svelte +249 -0
- package/shared/Resize.svelte +239 -0
- package/shared/SecondaryToolbar.svelte +61 -0
- package/shared/Toolbar.svelte +386 -0
- package/shared/brush/BRUSH_TOOL.md +134 -0
- package/shared/brush/BrushOptions.svelte +200 -0
- package/shared/{tools → brush}/BrushSize.svelte +2 -30
- package/shared/{tools → brush}/ColorField.svelte +3 -1
- package/shared/{tools → brush}/ColorPicker.svelte +86 -9
- package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
- package/shared/brush/brush-cursor.ts +408 -0
- package/shared/brush/brush-textures.ts +508 -0
- package/shared/brush/brush-utils.ts +76 -0
- package/shared/brush/brush.ts +506 -0
- package/shared/brush/types.ts +41 -0
- package/shared/core/EDITOR.md +182 -0
- package/shared/core/editor.ts +1730 -0
- package/shared/crop/CROP.md +139 -0
- package/shared/crop/crop.ts +1027 -0
- package/shared/image/IMAGE.md +135 -0
- package/shared/image/image.ts +300 -0
- package/shared/resize/resize.ts +1894 -0
- package/shared/types.ts +14 -0
- package/shared/utils/pixi.ts +19 -246
- package/shared/zoom/zoom.ts +729 -0
- package/dist/shared/layers/Layers.svelte +0 -231
- package/dist/shared/layers/Layers.svelte.d.ts +0 -18
- package/dist/shared/layers/utils.d.ts +0 -73
- package/dist/shared/layers/utils.js +0 -156
- package/dist/shared/tools/Brush.svelte +0 -215
- package/dist/shared/tools/Brush.svelte.d.ts +0 -29
- package/dist/shared/tools/Crop.svelte +0 -155
- package/dist/shared/tools/Crop.svelte.d.ts +0 -16
- package/dist/shared/tools/Cropper.svelte +0 -271
- package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
- package/dist/shared/tools/Handle.svelte +0 -260
- package/dist/shared/tools/Handle.svelte.d.ts +0 -29
- package/dist/shared/tools/Sources.svelte +0 -257
- package/dist/shared/tools/Sources.svelte.d.ts +0 -34
- package/dist/shared/tools/Tools.svelte +0 -102
- package/dist/shared/tools/Tools.svelte.d.ts +0 -48
- package/dist/shared/tools/brush.d.ts +0 -62
- package/dist/shared/tools/brush.js +0 -133
- package/dist/shared/tools/crop.d.ts +0 -15
- package/dist/shared/tools/crop.js +0 -154
- package/dist/shared/tools/index.d.ts +0 -5
- package/dist/shared/tools/index.js +0 -5
- package/dist/shared/tools/sources.d.ts +0 -46
- package/dist/shared/tools/sources.js +0 -121
- package/dist/shared/tools/types.d.ts +0 -1
- package/shared/layers/Layers.svelte +0 -250
- package/shared/layers/utils.ts +0 -294
- package/shared/tools/Brush.svelte +0 -280
- package/shared/tools/BrushOptions.svelte +0 -180
- package/shared/tools/Crop.svelte +0 -189
- package/shared/tools/Cropper.svelte +0 -346
- package/shared/tools/Handle.svelte +0 -289
- package/shared/tools/Sources.svelte +0 -283
- package/shared/tools/Tools.svelte +0 -162
- package/shared/tools/brush.ts +0 -247
- package/shared/tools/crop.ts +0 -213
- package/shared/tools/index.ts +0 -5
- package/shared/tools/sources.ts +0 -195
- package/shared/tools/types.ts +0 -1
- /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
- /package/dist/shared/{tools/types.js → types.js} +0 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import { Container, RenderTexture, Sprite, Graphics, Application } from "pixi.js";
|
|
2
|
+
import { safe_destroy, safe_remove_child } from "./brush-utils";
|
|
3
|
+
import {} from "../core/editor";
|
|
4
|
+
/**
|
|
5
|
+
* Class to handle texture operations for the brush tool.
|
|
6
|
+
* Simplified to work directly with layer textures.
|
|
7
|
+
*/
|
|
8
|
+
export class BrushTextures {
|
|
9
|
+
image_editor_context;
|
|
10
|
+
app;
|
|
11
|
+
// Only keep what we need
|
|
12
|
+
stroke_texture = null;
|
|
13
|
+
erase_texture = null;
|
|
14
|
+
display_container = null;
|
|
15
|
+
stroke_container = null;
|
|
16
|
+
stroke_graphics = null;
|
|
17
|
+
preview_sprite = null;
|
|
18
|
+
erase_graphics = null;
|
|
19
|
+
dimensions;
|
|
20
|
+
// Track if a new stroke is starting
|
|
21
|
+
is_new_stroke = true;
|
|
22
|
+
// Current scale factor
|
|
23
|
+
scale = 1;
|
|
24
|
+
// Track the current drawing opacity
|
|
25
|
+
current_opacity = 1.0;
|
|
26
|
+
// Track current drawing mode
|
|
27
|
+
current_mode = "draw";
|
|
28
|
+
// Store last drawing parameters for eraser recreation
|
|
29
|
+
last_from_x = null;
|
|
30
|
+
last_from_y = null;
|
|
31
|
+
last_to_x = null;
|
|
32
|
+
last_to_y = null;
|
|
33
|
+
last_size = null;
|
|
34
|
+
constructor(image_editor_context, app) {
|
|
35
|
+
this.image_editor_context = image_editor_context;
|
|
36
|
+
this.app = app;
|
|
37
|
+
this.dimensions = {
|
|
38
|
+
width: this.image_editor_context.image_container.width,
|
|
39
|
+
height: this.image_editor_context.image_container.height
|
|
40
|
+
};
|
|
41
|
+
// Subscribe to scale changes
|
|
42
|
+
this.image_editor_context.scale.subscribe((scale) => {
|
|
43
|
+
this.scale = scale;
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Initializes textures needed for the brush tool.
|
|
48
|
+
*/
|
|
49
|
+
initialize_textures() {
|
|
50
|
+
// Clean up any existing resources
|
|
51
|
+
this.cleanup_textures();
|
|
52
|
+
// Get dimensions from the image container
|
|
53
|
+
const local_bounds = this.image_editor_context.image_container.getLocalBounds();
|
|
54
|
+
this.dimensions = {
|
|
55
|
+
width: local_bounds.width,
|
|
56
|
+
height: local_bounds.height
|
|
57
|
+
};
|
|
58
|
+
// Create a texture for the current stroke preview
|
|
59
|
+
this.stroke_texture = RenderTexture.create({
|
|
60
|
+
width: this.dimensions.width,
|
|
61
|
+
height: this.dimensions.height,
|
|
62
|
+
resolution: window.devicePixelRatio || 1
|
|
63
|
+
});
|
|
64
|
+
// Create a texture for erasing
|
|
65
|
+
this.erase_texture = RenderTexture.create({
|
|
66
|
+
width: this.dimensions.width,
|
|
67
|
+
height: this.dimensions.height,
|
|
68
|
+
resolution: window.devicePixelRatio || 1
|
|
69
|
+
});
|
|
70
|
+
// Create a container to display preview
|
|
71
|
+
this.display_container = new Container();
|
|
72
|
+
// Get the active layer and add the display container to it
|
|
73
|
+
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
74
|
+
if (active_layer) {
|
|
75
|
+
active_layer.addChild(this.display_container);
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
this.image_editor_context.image_container.addChild(this.display_container);
|
|
79
|
+
}
|
|
80
|
+
// Create container for the stroke graphics
|
|
81
|
+
this.stroke_container = new Container();
|
|
82
|
+
this.stroke_graphics = new Graphics();
|
|
83
|
+
this.stroke_container.addChild(this.stroke_graphics);
|
|
84
|
+
// Create graphics for eraser mask
|
|
85
|
+
this.erase_graphics = new Graphics();
|
|
86
|
+
// Create a sprite for the preview of the current stroke
|
|
87
|
+
this.preview_sprite = new Sprite(this.stroke_texture);
|
|
88
|
+
// Start with the preview sprite invisible
|
|
89
|
+
this.preview_sprite.alpha = 0;
|
|
90
|
+
this.display_container.addChild(this.preview_sprite);
|
|
91
|
+
// Clear the stroke texture
|
|
92
|
+
const clear_container = new Container();
|
|
93
|
+
this.app.renderer.render(clear_container, {
|
|
94
|
+
renderTexture: this.stroke_texture
|
|
95
|
+
});
|
|
96
|
+
this.app.renderer.render(clear_container, {
|
|
97
|
+
renderTexture: this.erase_texture
|
|
98
|
+
});
|
|
99
|
+
clear_container.destroy();
|
|
100
|
+
// Reset drawing state
|
|
101
|
+
this.is_new_stroke = true;
|
|
102
|
+
this.current_opacity = 1.0;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Reinitializes textures when needed (e.g., after resizing).
|
|
106
|
+
*/
|
|
107
|
+
reinitialize() {
|
|
108
|
+
if (this.image_editor_context.image_container.width !==
|
|
109
|
+
this.dimensions.width ||
|
|
110
|
+
this.image_editor_context.image_container.height !==
|
|
111
|
+
this.dimensions.height) {
|
|
112
|
+
this.initialize_textures();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Cleans up texture resources.
|
|
117
|
+
*/
|
|
118
|
+
cleanup_textures() {
|
|
119
|
+
// Clean up stroke texture
|
|
120
|
+
if (this.stroke_texture) {
|
|
121
|
+
this.stroke_texture.destroy();
|
|
122
|
+
this.stroke_texture = null;
|
|
123
|
+
}
|
|
124
|
+
// Clean up erase texture
|
|
125
|
+
if (this.erase_texture) {
|
|
126
|
+
this.erase_texture.destroy();
|
|
127
|
+
this.erase_texture = null;
|
|
128
|
+
}
|
|
129
|
+
// Clean up display container
|
|
130
|
+
if (this.display_container) {
|
|
131
|
+
if (this.display_container.parent) {
|
|
132
|
+
this.display_container.parent.removeChild(this.display_container);
|
|
133
|
+
}
|
|
134
|
+
this.display_container.destroy({ children: true });
|
|
135
|
+
this.display_container = null;
|
|
136
|
+
}
|
|
137
|
+
// Nullify other references
|
|
138
|
+
this.stroke_container = null;
|
|
139
|
+
this.stroke_graphics = null;
|
|
140
|
+
this.preview_sprite = null;
|
|
141
|
+
this.erase_graphics = null;
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Preserve the canvas state when starting a new stroke.
|
|
145
|
+
*/
|
|
146
|
+
preserve_canvas_state() {
|
|
147
|
+
// Reset for a new stroke
|
|
148
|
+
this.is_new_stroke = true;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Resets the eraser mask.
|
|
152
|
+
*/
|
|
153
|
+
reset_eraser_mask() {
|
|
154
|
+
if (!this.erase_graphics || !this.erase_texture)
|
|
155
|
+
return;
|
|
156
|
+
// Clear and reset the eraser graphics
|
|
157
|
+
this.erase_graphics.clear();
|
|
158
|
+
this.erase_graphics.beginFill(0xffffff, 1);
|
|
159
|
+
this.erase_graphics.drawRect(0, 0, this.dimensions.width, this.dimensions.height);
|
|
160
|
+
this.erase_graphics.endFill();
|
|
161
|
+
// Render the eraser graphics to the erase texture
|
|
162
|
+
this.app.renderer.render(this.erase_graphics, {
|
|
163
|
+
renderTexture: this.erase_texture
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Commits the current stroke to the active layer.
|
|
168
|
+
*/
|
|
169
|
+
commit_stroke() {
|
|
170
|
+
if (!this.stroke_texture || !this.preview_sprite || !this.stroke_graphics)
|
|
171
|
+
return;
|
|
172
|
+
// Hide the preview sprite
|
|
173
|
+
this.preview_sprite.visible = false;
|
|
174
|
+
// Get the active layer's texture
|
|
175
|
+
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
176
|
+
if (!active_layer)
|
|
177
|
+
return;
|
|
178
|
+
// Get all layers to find the ID of the active layer
|
|
179
|
+
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
180
|
+
const layer = layers.find((l) => l.container === active_layer);
|
|
181
|
+
if (!layer)
|
|
182
|
+
return;
|
|
183
|
+
// Get the active layer's draw texture
|
|
184
|
+
const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
185
|
+
if (!layer_textures)
|
|
186
|
+
return;
|
|
187
|
+
// Save the current opacity for use in committing
|
|
188
|
+
const currentOpacity = this.current_opacity;
|
|
189
|
+
// Handle drawing or erasing
|
|
190
|
+
if (this.current_mode === "draw") {
|
|
191
|
+
// Drawing mode - simple composition
|
|
192
|
+
const temp_container = new Container();
|
|
193
|
+
// Add current layer texture as base
|
|
194
|
+
const base_sprite = new Sprite(layer_textures.draw);
|
|
195
|
+
temp_container.addChild(base_sprite);
|
|
196
|
+
// Add the current stroke with proper opacity
|
|
197
|
+
const stroke_sprite = new Sprite(this.stroke_texture);
|
|
198
|
+
stroke_sprite.alpha = currentOpacity;
|
|
199
|
+
temp_container.addChild(stroke_sprite);
|
|
200
|
+
// Render the composited result directly to the layer texture
|
|
201
|
+
this.app.renderer.render(temp_container, {
|
|
202
|
+
renderTexture: layer_textures.draw
|
|
203
|
+
});
|
|
204
|
+
// Clean up
|
|
205
|
+
temp_container.destroy({ children: true });
|
|
206
|
+
}
|
|
207
|
+
else {
|
|
208
|
+
// Erasing mode - use proper masking with setMask
|
|
209
|
+
if (!this.stroke_texture)
|
|
210
|
+
return;
|
|
211
|
+
// Create a container for the erase operation
|
|
212
|
+
const erase_container = new Container();
|
|
213
|
+
// Add the current layer content
|
|
214
|
+
const content_sprite = new Sprite(layer_textures.draw);
|
|
215
|
+
erase_container.addChild(content_sprite);
|
|
216
|
+
// Create the mask sprite from our stroke texture
|
|
217
|
+
const mask_sprite = new Sprite(this.stroke_texture);
|
|
218
|
+
// Apply the mask to the container using setMask with inverse: true
|
|
219
|
+
// This will only show content where the mask is NOT (inverse: true)
|
|
220
|
+
erase_container.setMask({ mask: mask_sprite, inverse: true });
|
|
221
|
+
// Render the masked content back to layer texture
|
|
222
|
+
this.app.renderer.render(erase_container, {
|
|
223
|
+
renderTexture: layer_textures.draw
|
|
224
|
+
});
|
|
225
|
+
// Clean up
|
|
226
|
+
erase_container.destroy({ children: true });
|
|
227
|
+
}
|
|
228
|
+
// Clear the stroke graphics and texture for the next stroke
|
|
229
|
+
if (this.stroke_graphics) {
|
|
230
|
+
this.stroke_graphics.clear();
|
|
231
|
+
}
|
|
232
|
+
const clear_container = new Container();
|
|
233
|
+
this.app.renderer.render(clear_container, {
|
|
234
|
+
renderTexture: this.stroke_texture
|
|
235
|
+
});
|
|
236
|
+
clear_container.destroy();
|
|
237
|
+
// Reset state for the next stroke
|
|
238
|
+
this.is_new_stroke = true;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Calculates the distance between two points.
|
|
242
|
+
*/
|
|
243
|
+
calculateDistance(x1, y1, x2, y2) {
|
|
244
|
+
return Math.sqrt(Math.pow(x2 - x1, 2) + Math.pow(y2 - y1, 2));
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Draws a segment for the current stroke using a simple "stamp" approach.
|
|
248
|
+
*/
|
|
249
|
+
draw_segment(from_x, from_y, to_x, to_y, size, color, opacity, mode) {
|
|
250
|
+
if (!this.stroke_graphics ||
|
|
251
|
+
!this.stroke_texture ||
|
|
252
|
+
!this.stroke_container ||
|
|
253
|
+
!this.preview_sprite)
|
|
254
|
+
return;
|
|
255
|
+
// Store the current mode and opacity
|
|
256
|
+
this.current_mode = mode;
|
|
257
|
+
this.current_opacity =
|
|
258
|
+
mode === "draw" ? Math.min(Math.max(opacity, 0), 1) : 0.5;
|
|
259
|
+
// Store parameters for potential eraser recreation
|
|
260
|
+
this.last_from_x = from_x;
|
|
261
|
+
this.last_from_y = from_y;
|
|
262
|
+
this.last_to_x = to_x;
|
|
263
|
+
this.last_to_y = to_y;
|
|
264
|
+
this.last_size = size;
|
|
265
|
+
// Apply scale to brush size for consistent rendering
|
|
266
|
+
const scaled_size = size;
|
|
267
|
+
// For new strokes, clear the graphics and texture completely
|
|
268
|
+
if (this.is_new_stroke) {
|
|
269
|
+
// Clean up for a new stroke
|
|
270
|
+
this.stroke_graphics.clear();
|
|
271
|
+
// Clear the stroke texture
|
|
272
|
+
const clear_container = new Container();
|
|
273
|
+
this.app.renderer.render(clear_container, {
|
|
274
|
+
renderTexture: this.stroke_texture
|
|
275
|
+
});
|
|
276
|
+
clear_container.destroy();
|
|
277
|
+
this.is_new_stroke = false;
|
|
278
|
+
}
|
|
279
|
+
// Set fill color and opacity for drawing
|
|
280
|
+
if (mode === "draw") {
|
|
281
|
+
// For drawing, use the specified color, but always full opacity in the graphics
|
|
282
|
+
// We need to convert the color string to a number
|
|
283
|
+
let colorValue = 0xffffff; // Default to white
|
|
284
|
+
try {
|
|
285
|
+
if (color.startsWith("#")) {
|
|
286
|
+
colorValue = parseInt(color.replace("#", "0x"), 16);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (e) {
|
|
290
|
+
// Default to white if there's an error
|
|
291
|
+
colorValue = 0xffffff;
|
|
292
|
+
}
|
|
293
|
+
this.stroke_graphics.setFillStyle({
|
|
294
|
+
color: colorValue,
|
|
295
|
+
alpha: 1.0
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// For erasing, always use white
|
|
300
|
+
this.stroke_graphics.setFillStyle({
|
|
301
|
+
color: 0xffffff,
|
|
302
|
+
alpha: 1.0
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
// Calculate the distance between the points
|
|
306
|
+
const distance = this.calculateDistance(from_x, from_y, to_x, to_y);
|
|
307
|
+
// If the points are the same, just draw a single circle
|
|
308
|
+
if (distance < 0.1) {
|
|
309
|
+
// Draw a circle at the point - use scaled_size as RADIUS
|
|
310
|
+
this.stroke_graphics.circle(from_x, from_y, scaled_size).fill();
|
|
311
|
+
}
|
|
312
|
+
else {
|
|
313
|
+
// Calculate how many circles to draw along the path
|
|
314
|
+
// Adaptive spacing that balances quality and performance
|
|
315
|
+
const spacing = Math.max(scaled_size / 3, 2);
|
|
316
|
+
const steps = Math.max(Math.ceil(distance / spacing), 2);
|
|
317
|
+
// Draw a circle at each point along the path
|
|
318
|
+
for (let i = 0; i < steps; i++) {
|
|
319
|
+
const t = i / (steps - 1);
|
|
320
|
+
const x = from_x + (to_x - from_x) * t;
|
|
321
|
+
const y = from_y + (to_y - from_y) * t;
|
|
322
|
+
// Create a circle at this point - use scaled_size as RADIUS
|
|
323
|
+
this.stroke_graphics.circle(x, y, scaled_size).fill();
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
// End the fill after all circles are drawn
|
|
327
|
+
this.stroke_graphics.endFill();
|
|
328
|
+
// Render the stroke to the texture
|
|
329
|
+
this.app.renderer.render(this.stroke_container, {
|
|
330
|
+
renderTexture: this.stroke_texture
|
|
331
|
+
});
|
|
332
|
+
// Update the preview based on mode
|
|
333
|
+
if (mode === "draw") {
|
|
334
|
+
// For drawing, show the stroke directly with proper opacity
|
|
335
|
+
this.preview_sprite.texture = this.stroke_texture;
|
|
336
|
+
this.preview_sprite.alpha = this.current_opacity;
|
|
337
|
+
// Set tint to white (no tint) instead of trying to tint the texture
|
|
338
|
+
this.preview_sprite.tint = 0xffffff;
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
// For erasing, create a preview that shows what will be erased
|
|
342
|
+
// Get the active layer's texture for preview
|
|
343
|
+
const active_layer = this.image_editor_context.layer_manager.get_active_layer();
|
|
344
|
+
if (!active_layer)
|
|
345
|
+
return;
|
|
346
|
+
const layers = this.image_editor_context.layer_manager.get_layers();
|
|
347
|
+
const layer = layers.find((l) => l.container === active_layer);
|
|
348
|
+
if (!layer)
|
|
349
|
+
return;
|
|
350
|
+
const layer_textures = this.image_editor_context.layer_manager.get_layer_textures(layer.id);
|
|
351
|
+
if (!layer_textures)
|
|
352
|
+
return;
|
|
353
|
+
// Create a temporary texture for the preview
|
|
354
|
+
const preview_texture = RenderTexture.create({
|
|
355
|
+
width: this.dimensions.width,
|
|
356
|
+
height: this.dimensions.height,
|
|
357
|
+
resolution: window.devicePixelRatio || 1
|
|
358
|
+
});
|
|
359
|
+
// First, create a copy of the current layer content
|
|
360
|
+
const base_container = new Container();
|
|
361
|
+
const content_sprite = new Sprite(layer_textures.draw);
|
|
362
|
+
base_container.addChild(content_sprite);
|
|
363
|
+
this.app.renderer.render(base_container, {
|
|
364
|
+
renderTexture: preview_texture
|
|
365
|
+
});
|
|
366
|
+
base_container.destroy({ children: true });
|
|
367
|
+
// Create a container for the eraser preview with red overlay
|
|
368
|
+
const preview_container = new Container();
|
|
369
|
+
// Add our current stroke as the content
|
|
370
|
+
const mask_sprite = new Sprite(this.stroke_texture);
|
|
371
|
+
// Add a red overlay to show what's being erased
|
|
372
|
+
const overlay = new Graphics();
|
|
373
|
+
overlay.setFillStyle({ color: 0xffffff, alpha: 0.5 });
|
|
374
|
+
overlay.rect(0, 0, this.dimensions.width, this.dimensions.height).fill();
|
|
375
|
+
// Use setMask with inverse: false to only show overlay where the stroke is
|
|
376
|
+
// We want to show the red overlay ONLY where the stroke will erase
|
|
377
|
+
overlay.setMask({ mask: mask_sprite, inverse: false });
|
|
378
|
+
preview_container.addChild(overlay);
|
|
379
|
+
// Render the overlay on top of the base content
|
|
380
|
+
this.app.renderer.render(preview_container, {
|
|
381
|
+
renderTexture: preview_texture
|
|
382
|
+
});
|
|
383
|
+
// Update the preview sprite with our preview texture
|
|
384
|
+
this.preview_sprite.texture = preview_texture;
|
|
385
|
+
this.preview_sprite.alpha = 1.0;
|
|
386
|
+
// Clean up
|
|
387
|
+
preview_container.destroy({ children: true });
|
|
388
|
+
preview_texture.destroy();
|
|
389
|
+
}
|
|
390
|
+
this.preview_sprite.visible = true;
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Gets current dimensions of the texture.
|
|
394
|
+
*/
|
|
395
|
+
get_dimensions() {
|
|
396
|
+
return this.dimensions;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Cleanup all resources.
|
|
400
|
+
*/
|
|
401
|
+
cleanup() {
|
|
402
|
+
this.cleanup_textures();
|
|
403
|
+
}
|
|
404
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { Container, Sprite, Graphics } from "pixi.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sets the cursor style for a single PIXI object.
|
|
4
|
+
* @param child - The PIXI object to set the cursor for.
|
|
5
|
+
* @param cursor - The cursor style to set.
|
|
6
|
+
*/
|
|
7
|
+
export declare function set_cursor(child: Container | Sprite | Graphics, cursor: "unset" | "none"): void;
|
|
8
|
+
/**
|
|
9
|
+
* Recursively sets the cursor style for PIXI objects.
|
|
10
|
+
* @param children - The PIXI objects to set the cursor for.
|
|
11
|
+
* @param cursor - The cursor style to set.
|
|
12
|
+
*/
|
|
13
|
+
export declare function recurse_set_cursor(children: (Container | Sprite | Graphics)[], cursor: "unset" | "none"): void;
|
|
14
|
+
/**
|
|
15
|
+
* Safely removes a child from its parent.
|
|
16
|
+
* @param child - The child to remove.
|
|
17
|
+
*/
|
|
18
|
+
export declare function safe_remove_child(child: Container | Sprite | Graphics | null): void;
|
|
19
|
+
/**
|
|
20
|
+
* Safely destroys a PIXI object and nullifies it.
|
|
21
|
+
* @param obj - The object to destroy.
|
|
22
|
+
* @returns null to assist with nullifying the reference.
|
|
23
|
+
*/
|
|
24
|
+
export declare function safe_destroy<T extends {
|
|
25
|
+
destroy: (options?: any) => void;
|
|
26
|
+
}>(obj: T | null): null;
|
|
27
|
+
/**
|
|
28
|
+
* Clears a timeout safely and nullifies the reference.
|
|
29
|
+
* @param timeout - The timeout to clear.
|
|
30
|
+
* @returns null to assist with nullifying the reference.
|
|
31
|
+
*/
|
|
32
|
+
export declare function clear_timeout(timeout: number | null): null;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { Container, Sprite, Graphics } from "pixi.js";
|
|
2
|
+
/**
|
|
3
|
+
* Sets the cursor style for a single PIXI object.
|
|
4
|
+
* @param child - The PIXI object to set the cursor for.
|
|
5
|
+
* @param cursor - The cursor style to set.
|
|
6
|
+
*/
|
|
7
|
+
export function set_cursor(child, cursor) {
|
|
8
|
+
if (child instanceof Container) {
|
|
9
|
+
child.cursor = cursor;
|
|
10
|
+
}
|
|
11
|
+
else if ("cursor" in child) {
|
|
12
|
+
child.cursor = cursor;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Recursively sets the cursor style for PIXI objects.
|
|
17
|
+
* @param children - The PIXI objects to set the cursor for.
|
|
18
|
+
* @param cursor - The cursor style to set.
|
|
19
|
+
*/
|
|
20
|
+
export function recurse_set_cursor(children, cursor) {
|
|
21
|
+
for (const child of children) {
|
|
22
|
+
set_cursor(child, cursor);
|
|
23
|
+
if (child instanceof Container && child.children.length > 0) {
|
|
24
|
+
recurse_set_cursor(child.children, cursor);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Safely removes a child from its parent.
|
|
30
|
+
* @param child - The child to remove.
|
|
31
|
+
*/
|
|
32
|
+
export function safe_remove_child(child) {
|
|
33
|
+
if (child && child.parent) {
|
|
34
|
+
child.parent.removeChild(child);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Safely destroys a PIXI object and nullifies it.
|
|
39
|
+
* @param obj - The object to destroy.
|
|
40
|
+
* @returns null to assist with nullifying the reference.
|
|
41
|
+
*/
|
|
42
|
+
export function safe_destroy(obj) {
|
|
43
|
+
if (obj) {
|
|
44
|
+
safe_remove_child(obj);
|
|
45
|
+
obj.destroy({ children: true });
|
|
46
|
+
}
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Clears a timeout safely and nullifies the reference.
|
|
51
|
+
* @param timeout - The timeout to clear.
|
|
52
|
+
* @returns null to assist with nullifying the reference.
|
|
53
|
+
*/
|
|
54
|
+
export function clear_timeout(timeout) {
|
|
55
|
+
if (timeout !== null) {
|
|
56
|
+
window.clearTimeout(timeout);
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { type ImageEditorContext, type Tool } from "../core/editor";
|
|
2
|
+
import { type Tool as ToolbarTool, type Subtool } from "../Toolbar.svelte";
|
|
3
|
+
import type { ColorInput } from "tinycolor2";
|
|
4
|
+
/**
|
|
5
|
+
* BrushTool class implements the Tool interface for drawing and erasing.
|
|
6
|
+
* @class
|
|
7
|
+
* @implements {Tool}
|
|
8
|
+
*/
|
|
9
|
+
export declare class BrushTool implements Tool {
|
|
10
|
+
/**
|
|
11
|
+
* The name of the tool.
|
|
12
|
+
* @type {string}
|
|
13
|
+
*/
|
|
14
|
+
name: "brush";
|
|
15
|
+
/**
|
|
16
|
+
* The context of the image editor.
|
|
17
|
+
* @private
|
|
18
|
+
*/
|
|
19
|
+
private image_editor_context;
|
|
20
|
+
/**
|
|
21
|
+
* The current tool selected in the toolbar.
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
private current_tool;
|
|
25
|
+
/**
|
|
26
|
+
* The current subtool selected in the toolbar.
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
private current_subtool;
|
|
30
|
+
/**
|
|
31
|
+
* The state of the brush tool.
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
34
|
+
private state;
|
|
35
|
+
/**
|
|
36
|
+
* Size settings for brush and eraser
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
private brush_size;
|
|
40
|
+
private eraser_size;
|
|
41
|
+
/**
|
|
42
|
+
* Drawing state
|
|
43
|
+
* @private
|
|
44
|
+
*/
|
|
45
|
+
private is_drawing;
|
|
46
|
+
private last_x;
|
|
47
|
+
private last_y;
|
|
48
|
+
private scale;
|
|
49
|
+
/**
|
|
50
|
+
* Event listeners
|
|
51
|
+
* @private
|
|
52
|
+
*/
|
|
53
|
+
private _bound_pointer_down;
|
|
54
|
+
private _bound_pointer_move;
|
|
55
|
+
private _bound_pointer_up;
|
|
56
|
+
private event_callbacks;
|
|
57
|
+
/**
|
|
58
|
+
* Utility modules
|
|
59
|
+
* @private
|
|
60
|
+
*/
|
|
61
|
+
private brush_cursor;
|
|
62
|
+
private brush_textures;
|
|
63
|
+
/**
|
|
64
|
+
* Sets up the brush tool with the given context, tool, and subtool.
|
|
65
|
+
* @param {ImageEditorContext} context - The image editor context.
|
|
66
|
+
* @param {ToolbarTool} tool - The tool from the toolbar.
|
|
67
|
+
* @param {Subtool} subtool - The subtool from the toolbar.
|
|
68
|
+
* @returns {Promise<void>}
|
|
69
|
+
*/
|
|
70
|
+
setup(context: ImageEditorContext, tool: ToolbarTool, subtool: Subtool): Promise<void>;
|
|
71
|
+
/**
|
|
72
|
+
* Handles cursor styles for the brush tool.
|
|
73
|
+
* @param {ToolbarTool} tool - The current tool.
|
|
74
|
+
* @private
|
|
75
|
+
*/
|
|
76
|
+
private handle_cursors;
|
|
77
|
+
/**
|
|
78
|
+
* Sets the current tool and subtool.
|
|
79
|
+
* @param {ToolbarTool} tool - The current tool.
|
|
80
|
+
* @param {Subtool} subtool - The current subtool.
|
|
81
|
+
*/
|
|
82
|
+
set_tool(tool: ToolbarTool, subtool: Subtool): void;
|
|
83
|
+
/**
|
|
84
|
+
* Commits any pending changes to the canvas.
|
|
85
|
+
* @private
|
|
86
|
+
*/
|
|
87
|
+
private commit_pending_changes;
|
|
88
|
+
/**
|
|
89
|
+
* Called when the user presses the mouse button.
|
|
90
|
+
* @param {FederatedPointerEvent} event - The pointer event.
|
|
91
|
+
* @private
|
|
92
|
+
*/
|
|
93
|
+
private on_pointer_down;
|
|
94
|
+
/**
|
|
95
|
+
* Called when the user moves the mouse while drawing.
|
|
96
|
+
* @param {FederatedPointerEvent} event - The pointer event.
|
|
97
|
+
* @private
|
|
98
|
+
*/
|
|
99
|
+
private on_pointer_move;
|
|
100
|
+
/**
|
|
101
|
+
* Called when the user releases the mouse button.
|
|
102
|
+
* @private
|
|
103
|
+
*/
|
|
104
|
+
private on_pointer_up;
|
|
105
|
+
/**
|
|
106
|
+
* Sets up event listeners for drawing.
|
|
107
|
+
* @private
|
|
108
|
+
*/
|
|
109
|
+
private setup_event_listeners;
|
|
110
|
+
/**
|
|
111
|
+
* Cleans up event listeners.
|
|
112
|
+
* @private
|
|
113
|
+
*/
|
|
114
|
+
private cleanup_event_listeners;
|
|
115
|
+
/**
|
|
116
|
+
* Sets the brush size.
|
|
117
|
+
* @param {number} size - The new brush size.
|
|
118
|
+
*/
|
|
119
|
+
set_brush_size(size: number): void;
|
|
120
|
+
/**
|
|
121
|
+
* Sets the brush color.
|
|
122
|
+
* @param {string|ColorInput} color - The new brush color.
|
|
123
|
+
*/
|
|
124
|
+
set_brush_color(color: string | ColorInput): void;
|
|
125
|
+
/**
|
|
126
|
+
* Sets the brush opacity.
|
|
127
|
+
* @param {number} opacity - The new brush opacity.
|
|
128
|
+
*/
|
|
129
|
+
set_brush_opacity(opacity: number): void;
|
|
130
|
+
/**
|
|
131
|
+
* Sets the eraser size.
|
|
132
|
+
* @param {number} size - The new eraser size.
|
|
133
|
+
*/
|
|
134
|
+
set_eraser_size(size: number): void;
|
|
135
|
+
/**
|
|
136
|
+
* Gets the current size based on mode (brush or eraser).
|
|
137
|
+
* @returns {number} The current size.
|
|
138
|
+
*/
|
|
139
|
+
get_current_size(): number;
|
|
140
|
+
/**
|
|
141
|
+
* Shows or hides the brush preview.
|
|
142
|
+
* @param {boolean} show - Whether to show the preview.
|
|
143
|
+
*/
|
|
144
|
+
preview_brush(show: boolean): void;
|
|
145
|
+
/**
|
|
146
|
+
* Cleans up resources used by the brush tool.
|
|
147
|
+
*/
|
|
148
|
+
cleanup(): void;
|
|
149
|
+
on<T extends string>(event: T, callback: () => void): void;
|
|
150
|
+
off<T extends string>(event: T, callback: () => void): void;
|
|
151
|
+
private notify;
|
|
152
|
+
}
|