@gradio/imageeditor 0.12.13 → 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 +23 -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 +11 -10
- 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,396 @@
|
|
|
1
|
+
import { Container } from "pixi.js";
|
|
2
|
+
import {} from "../core/editor";
|
|
3
|
+
import {} from "../Toolbar.svelte";
|
|
4
|
+
import tinycolor from "tinycolor2";
|
|
5
|
+
import { BrushCursor } from "./brush-cursor";
|
|
6
|
+
import { BrushTextures } from "./brush-textures";
|
|
7
|
+
import { recurse_set_cursor } from "./brush-utils";
|
|
8
|
+
import {} from "./types";
|
|
9
|
+
/**
|
|
10
|
+
* BrushTool class implements the Tool interface for drawing and erasing.
|
|
11
|
+
* @class
|
|
12
|
+
* @implements {Tool}
|
|
13
|
+
*/
|
|
14
|
+
export class BrushTool {
|
|
15
|
+
/**
|
|
16
|
+
* The name of the tool.
|
|
17
|
+
* @type {string}
|
|
18
|
+
*/
|
|
19
|
+
name = "brush";
|
|
20
|
+
/**
|
|
21
|
+
* The context of the image editor.
|
|
22
|
+
* @private
|
|
23
|
+
*/
|
|
24
|
+
image_editor_context;
|
|
25
|
+
/**
|
|
26
|
+
* The current tool selected in the toolbar.
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
current_tool;
|
|
30
|
+
/**
|
|
31
|
+
* The current subtool selected in the toolbar.
|
|
32
|
+
* @private
|
|
33
|
+
*/
|
|
34
|
+
current_subtool;
|
|
35
|
+
/**
|
|
36
|
+
* The state of the brush tool.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
state = {
|
|
40
|
+
opacity: 1,
|
|
41
|
+
brush_size: 10,
|
|
42
|
+
color: "#000000",
|
|
43
|
+
mode: "draw"
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Size settings for brush and eraser
|
|
47
|
+
* @private
|
|
48
|
+
*/
|
|
49
|
+
brush_size = 10;
|
|
50
|
+
eraser_size = 20;
|
|
51
|
+
/**
|
|
52
|
+
* Drawing state
|
|
53
|
+
* @private
|
|
54
|
+
*/
|
|
55
|
+
is_drawing = false;
|
|
56
|
+
last_x = 0;
|
|
57
|
+
last_y = 0;
|
|
58
|
+
scale = 1;
|
|
59
|
+
/**
|
|
60
|
+
* Event listeners
|
|
61
|
+
* @private
|
|
62
|
+
*/
|
|
63
|
+
_bound_pointer_down = null;
|
|
64
|
+
_bound_pointer_move = null;
|
|
65
|
+
_bound_pointer_up = null;
|
|
66
|
+
event_callbacks = new Map();
|
|
67
|
+
/**
|
|
68
|
+
* Utility modules
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
brush_cursor = null;
|
|
72
|
+
brush_textures = null;
|
|
73
|
+
/**
|
|
74
|
+
* Sets up the brush tool with the given context, tool, and subtool.
|
|
75
|
+
* @param {ImageEditorContext} context - The image editor context.
|
|
76
|
+
* @param {ToolbarTool} tool - The tool from the toolbar.
|
|
77
|
+
* @param {Subtool} subtool - The subtool from the toolbar.
|
|
78
|
+
* @returns {Promise<void>}
|
|
79
|
+
*/
|
|
80
|
+
async setup(context, tool, subtool) {
|
|
81
|
+
// Store context and tool information
|
|
82
|
+
this.image_editor_context = context;
|
|
83
|
+
this.current_tool = tool;
|
|
84
|
+
this.current_subtool = subtool;
|
|
85
|
+
// Set mode based on tool
|
|
86
|
+
this.state.mode = tool === "erase" ? "erase" : "draw";
|
|
87
|
+
// Set size based on mode
|
|
88
|
+
if (this.state.mode === "draw") {
|
|
89
|
+
this.state.brush_size = this.brush_size;
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
this.state.brush_size = this.eraser_size;
|
|
93
|
+
}
|
|
94
|
+
// Subscribe to scale changes
|
|
95
|
+
context.scale.subscribe((scale) => {
|
|
96
|
+
this.scale = scale;
|
|
97
|
+
if (this.brush_cursor) {
|
|
98
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
// Initialize brush cursor
|
|
102
|
+
this.brush_cursor = new BrushCursor(this.image_editor_context, this.state, this.scale);
|
|
103
|
+
// Set initial active state
|
|
104
|
+
this.brush_cursor.set_active(tool === "draw" || tool === "erase");
|
|
105
|
+
// Initialize brush textures
|
|
106
|
+
this.brush_textures = new BrushTextures(this.image_editor_context, context.app);
|
|
107
|
+
this.brush_textures.initialize_textures();
|
|
108
|
+
// Set up event listeners
|
|
109
|
+
this.setup_event_listeners();
|
|
110
|
+
// Set cursor style for image container
|
|
111
|
+
this.handle_cursors(tool);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Handles cursor styles for the brush tool.
|
|
115
|
+
* @param {ToolbarTool} tool - The current tool.
|
|
116
|
+
* @private
|
|
117
|
+
*/
|
|
118
|
+
handle_cursors(tool) {
|
|
119
|
+
// Set cursor style to 'none' for all children of the image container
|
|
120
|
+
recurse_set_cursor(this.image_editor_context.image_container.children, "none");
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Sets the current tool and subtool.
|
|
124
|
+
* @param {ToolbarTool} tool - The current tool.
|
|
125
|
+
* @param {Subtool} subtool - The current subtool.
|
|
126
|
+
*/
|
|
127
|
+
set_tool(tool, subtool) {
|
|
128
|
+
// Update current tool and subtool
|
|
129
|
+
this.current_tool = tool;
|
|
130
|
+
this.current_subtool = subtool;
|
|
131
|
+
if (this.current_tool !== "erase" && this.current_tool !== "draw") {
|
|
132
|
+
this.commit_pending_changes();
|
|
133
|
+
}
|
|
134
|
+
// Commit any pending changes
|
|
135
|
+
// Set brush cursor active state
|
|
136
|
+
if (this.brush_cursor) {
|
|
137
|
+
this.brush_cursor.set_active(tool === "draw" || tool === "erase");
|
|
138
|
+
}
|
|
139
|
+
// Update mode based on tool
|
|
140
|
+
const new_mode = tool === "erase" ? "erase" : "draw";
|
|
141
|
+
if (tool === "erase" || tool === "draw") {
|
|
142
|
+
this.brush_textures?.initialize_textures();
|
|
143
|
+
}
|
|
144
|
+
if (this.state.mode !== new_mode) {
|
|
145
|
+
this.state.mode = new_mode;
|
|
146
|
+
// Update brush size based on mode
|
|
147
|
+
if (this.state.mode === "draw") {
|
|
148
|
+
this.state.brush_size = this.brush_size;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
this.state.brush_size = this.eraser_size;
|
|
152
|
+
}
|
|
153
|
+
// Update cursor
|
|
154
|
+
if (this.brush_cursor) {
|
|
155
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Commits any pending changes to the canvas.
|
|
161
|
+
* @private
|
|
162
|
+
*/
|
|
163
|
+
commit_pending_changes() {
|
|
164
|
+
if (this.is_drawing) {
|
|
165
|
+
this.on_pointer_up();
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Called when the user presses the mouse button.
|
|
170
|
+
* @param {FederatedPointerEvent} event - The pointer event.
|
|
171
|
+
* @private
|
|
172
|
+
*/
|
|
173
|
+
on_pointer_down(event) {
|
|
174
|
+
// Don't draw if not over image
|
|
175
|
+
if (this.current_tool !== "erase" && this.current_tool !== "draw") {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
if (this.brush_cursor && !this.brush_cursor.is_over_image())
|
|
179
|
+
return;
|
|
180
|
+
// Preserve the current canvas state
|
|
181
|
+
if (this.brush_textures) {
|
|
182
|
+
this.brush_textures.preserve_canvas_state();
|
|
183
|
+
}
|
|
184
|
+
// Get the local position
|
|
185
|
+
const local_pos = this.image_editor_context.image_container.toLocal(event.global);
|
|
186
|
+
// Start drawing
|
|
187
|
+
this.is_drawing = true;
|
|
188
|
+
this.last_x = local_pos.x;
|
|
189
|
+
this.last_y = local_pos.y;
|
|
190
|
+
// Draw a single point
|
|
191
|
+
if (this.brush_textures) {
|
|
192
|
+
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);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Called when the user moves the mouse while drawing.
|
|
197
|
+
* @param {FederatedPointerEvent} event - The pointer event.
|
|
198
|
+
* @private
|
|
199
|
+
*/
|
|
200
|
+
on_pointer_move(event) {
|
|
201
|
+
// Update cursor position
|
|
202
|
+
if (this.brush_cursor) {
|
|
203
|
+
this.brush_cursor.update_cursor_position(event);
|
|
204
|
+
}
|
|
205
|
+
// If not drawing, exit
|
|
206
|
+
if (!this.is_drawing)
|
|
207
|
+
return;
|
|
208
|
+
// Get the local position
|
|
209
|
+
const local_pos = this.image_editor_context.image_container.toLocal(event.global);
|
|
210
|
+
// Draw line from last position to current position
|
|
211
|
+
if (this.brush_textures) {
|
|
212
|
+
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);
|
|
213
|
+
}
|
|
214
|
+
// Update last position
|
|
215
|
+
this.last_x = local_pos.x;
|
|
216
|
+
this.last_y = local_pos.y;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Called when the user releases the mouse button.
|
|
220
|
+
* @private
|
|
221
|
+
*/
|
|
222
|
+
on_pointer_up() {
|
|
223
|
+
// If not drawing, exit
|
|
224
|
+
if (!this.is_drawing)
|
|
225
|
+
return;
|
|
226
|
+
// Stop drawing
|
|
227
|
+
this.is_drawing = false;
|
|
228
|
+
// Commit the stroke
|
|
229
|
+
if (this.brush_textures) {
|
|
230
|
+
this.brush_textures.commit_stroke();
|
|
231
|
+
}
|
|
232
|
+
this.notify("change");
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Sets up event listeners for drawing.
|
|
236
|
+
* @private
|
|
237
|
+
*/
|
|
238
|
+
setup_event_listeners() {
|
|
239
|
+
// Remove existing listeners if they exist
|
|
240
|
+
this.cleanup_event_listeners();
|
|
241
|
+
// Create bound event handlers
|
|
242
|
+
this._bound_pointer_down = this.on_pointer_down.bind(this);
|
|
243
|
+
this._bound_pointer_move = this.on_pointer_move.bind(this);
|
|
244
|
+
this._bound_pointer_up = this.on_pointer_up.bind(this);
|
|
245
|
+
// Set interactive mode for image container
|
|
246
|
+
const image_container = this.image_editor_context.image_container;
|
|
247
|
+
image_container.eventMode = "static";
|
|
248
|
+
image_container.interactiveChildren = true;
|
|
249
|
+
// Setup for stage
|
|
250
|
+
const stage = this.image_editor_context.app.stage;
|
|
251
|
+
stage.eventMode = "static";
|
|
252
|
+
// Add pointer events to the stage for global tracking
|
|
253
|
+
stage.on("pointerdown", this._bound_pointer_down);
|
|
254
|
+
stage.on("pointermove", this._bound_pointer_move);
|
|
255
|
+
stage.on("pointerup", this._bound_pointer_up);
|
|
256
|
+
stage.on("pointerupoutside", this._bound_pointer_up);
|
|
257
|
+
// Set up cursor event listeners
|
|
258
|
+
if (this.brush_cursor) {
|
|
259
|
+
this.brush_cursor.setup_event_listeners();
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Cleans up event listeners.
|
|
264
|
+
* @private
|
|
265
|
+
*/
|
|
266
|
+
cleanup_event_listeners() {
|
|
267
|
+
const stage = this.image_editor_context.app.stage;
|
|
268
|
+
// Remove event listeners if they exist
|
|
269
|
+
if (this._bound_pointer_down) {
|
|
270
|
+
stage.off("pointerdown", this._bound_pointer_down);
|
|
271
|
+
this._bound_pointer_down = null;
|
|
272
|
+
}
|
|
273
|
+
if (this._bound_pointer_move) {
|
|
274
|
+
stage.off("pointermove", this._bound_pointer_move);
|
|
275
|
+
this._bound_pointer_move = null;
|
|
276
|
+
}
|
|
277
|
+
if (this._bound_pointer_up) {
|
|
278
|
+
stage.off("pointerup", this._bound_pointer_up);
|
|
279
|
+
stage.off("pointerupoutside", this._bound_pointer_up);
|
|
280
|
+
this._bound_pointer_up = null;
|
|
281
|
+
}
|
|
282
|
+
// Clean up cursor event listeners
|
|
283
|
+
if (this.brush_cursor) {
|
|
284
|
+
this.brush_cursor.cleanup_event_listeners();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
/**
|
|
288
|
+
* Sets the brush size.
|
|
289
|
+
* @param {number} size - The new brush size.
|
|
290
|
+
*/
|
|
291
|
+
set_brush_size(size) {
|
|
292
|
+
// Set brush size
|
|
293
|
+
this.brush_size = size;
|
|
294
|
+
// Update state if in draw mode
|
|
295
|
+
if (this.state.mode === "draw") {
|
|
296
|
+
this.state.brush_size = size;
|
|
297
|
+
// Update cursor
|
|
298
|
+
if (this.brush_cursor) {
|
|
299
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Sets the brush color.
|
|
305
|
+
* @param {string|ColorInput} color - The new brush color.
|
|
306
|
+
*/
|
|
307
|
+
set_brush_color(color) {
|
|
308
|
+
// Convert to hex string
|
|
309
|
+
const color_string = tinycolor(color).toHexString();
|
|
310
|
+
// Set color
|
|
311
|
+
this.state.color = color_string;
|
|
312
|
+
// Update cursor
|
|
313
|
+
if (this.brush_cursor) {
|
|
314
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* Sets the brush opacity.
|
|
319
|
+
* @param {number} opacity - The new brush opacity.
|
|
320
|
+
*/
|
|
321
|
+
set_brush_opacity(opacity) {
|
|
322
|
+
// Clamp opacity between 0 and 1
|
|
323
|
+
const clamped_opacity = Math.max(0, Math.min(1, opacity));
|
|
324
|
+
// Set opacity
|
|
325
|
+
this.state.opacity = clamped_opacity;
|
|
326
|
+
// Update cursor
|
|
327
|
+
if (this.brush_cursor) {
|
|
328
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Sets the eraser size.
|
|
333
|
+
* @param {number} size - The new eraser size.
|
|
334
|
+
*/
|
|
335
|
+
set_eraser_size(size) {
|
|
336
|
+
// Store the new eraser size
|
|
337
|
+
this.eraser_size = size;
|
|
338
|
+
// Update state if in erase mode
|
|
339
|
+
if (this.state.mode === "erase") {
|
|
340
|
+
this.state.brush_size = size;
|
|
341
|
+
// Update cursor
|
|
342
|
+
if (this.brush_cursor) {
|
|
343
|
+
this.brush_cursor.update_state(this.state, this.scale);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Gets the current size based on mode (brush or eraser).
|
|
349
|
+
* @returns {number} The current size.
|
|
350
|
+
*/
|
|
351
|
+
get_current_size() {
|
|
352
|
+
return this.state.mode === "draw" ? this.brush_size : this.eraser_size;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Shows or hides the brush preview.
|
|
356
|
+
* @param {boolean} show - Whether to show the preview.
|
|
357
|
+
*/
|
|
358
|
+
preview_brush(show) {
|
|
359
|
+
if (this.brush_cursor) {
|
|
360
|
+
this.brush_cursor.preview_brush(show);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Cleans up resources used by the brush tool.
|
|
365
|
+
*/
|
|
366
|
+
cleanup() {
|
|
367
|
+
// Commit any pending changes
|
|
368
|
+
this.commit_pending_changes();
|
|
369
|
+
// Clean up event listeners
|
|
370
|
+
this.cleanup_event_listeners();
|
|
371
|
+
// Clean up brush textures
|
|
372
|
+
if (this.brush_textures) {
|
|
373
|
+
this.brush_textures.cleanup();
|
|
374
|
+
this.brush_textures = null;
|
|
375
|
+
}
|
|
376
|
+
// Clean up brush cursor
|
|
377
|
+
if (this.brush_cursor) {
|
|
378
|
+
this.brush_cursor.cleanup();
|
|
379
|
+
this.brush_cursor = null;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
on(event, callback) {
|
|
383
|
+
this.event_callbacks.set(event, [
|
|
384
|
+
...(this.event_callbacks.get(event) || []),
|
|
385
|
+
callback
|
|
386
|
+
]);
|
|
387
|
+
}
|
|
388
|
+
off(event, callback) {
|
|
389
|
+
this.event_callbacks.set(event, this.event_callbacks.get(event)?.filter((cb) => cb !== callback) || []);
|
|
390
|
+
}
|
|
391
|
+
notify(event) {
|
|
392
|
+
for (const callback of this.event_callbacks.get(event) || []) {
|
|
393
|
+
callback();
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ColorInput } from "tinycolor2";
|
|
2
|
+
/**
|
|
3
|
+
* Represents the state of the brush tool.
|
|
4
|
+
* @interface BrushState
|
|
5
|
+
* @property {number} opacity - The opacity of the brush.
|
|
6
|
+
* @property {number} brush_size - The size of the brush.
|
|
7
|
+
* @property {string} color - The color of the brush in hex format.
|
|
8
|
+
* @property {"draw"|"erase"} mode - The mode of the brush, either draw or erase.
|
|
9
|
+
*/
|
|
10
|
+
export interface BrushState {
|
|
11
|
+
opacity: number;
|
|
12
|
+
brush_size: number;
|
|
13
|
+
color: string;
|
|
14
|
+
mode: "draw" | "erase";
|
|
15
|
+
}
|
|
16
|
+
export interface Eraser {
|
|
17
|
+
/**
|
|
18
|
+
* The default size of the eraser.
|
|
19
|
+
*/
|
|
20
|
+
default_size: number | "auto";
|
|
21
|
+
}
|
|
22
|
+
export type ColorTuple = [ColorInput, number];
|
|
23
|
+
export interface Brush extends Eraser {
|
|
24
|
+
/**
|
|
25
|
+
* The default color of the brush.
|
|
26
|
+
*/
|
|
27
|
+
default_color: ColorInput;
|
|
28
|
+
/**
|
|
29
|
+
* The colors to show in the color swatch.
|
|
30
|
+
* Can be either simple color strings or [color, opacity] tuples.
|
|
31
|
+
*/
|
|
32
|
+
colors: (ColorInput | ColorTuple)[];
|
|
33
|
+
/**
|
|
34
|
+
* Whether to show _only_ the color swatches specified in `colors`, or to show the color swatches specified in `colors` along with the colorpicker.
|
|
35
|
+
*/
|
|
36
|
+
color_mode: "fixed" | "defaults";
|
|
37
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import {} from "tinycolor2";
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { Application, Container, Sprite, RenderTexture } from "pixi.js";
|
|
2
|
+
import type { Subtool, Tool as ToolbarTool } from "../Toolbar.svelte";
|
|
3
|
+
import type { Readable, Writable } from "svelte/store";
|
|
4
|
+
import { type Spring } from "svelte/motion";
|
|
5
|
+
import { type ImageBlobs, type LayerOptions } from "../types";
|
|
6
|
+
export interface Command {
|
|
7
|
+
execute: () => Promise<void>;
|
|
8
|
+
undo: () => Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface Tool {
|
|
11
|
+
name: string;
|
|
12
|
+
setup(context: ImageEditorContext, tool: ToolbarTool, subtool: Subtool): Promise<void>;
|
|
13
|
+
cleanup(): void;
|
|
14
|
+
set_tool(tool: ToolbarTool, subtool: Subtool): void;
|
|
15
|
+
on?: (event: string, callback: () => void) => void;
|
|
16
|
+
off?: (event: string, callback: () => void) => void;
|
|
17
|
+
}
|
|
18
|
+
export declare class CommandManager {
|
|
19
|
+
private undo_stack;
|
|
20
|
+
private redo_stack;
|
|
21
|
+
execute(command: Command): Promise<void>;
|
|
22
|
+
undo(): Promise<void>;
|
|
23
|
+
redo(): Promise<void>;
|
|
24
|
+
}
|
|
25
|
+
export declare class LayerManager {
|
|
26
|
+
private layers;
|
|
27
|
+
private active_layer;
|
|
28
|
+
private draw_textures;
|
|
29
|
+
layer_store: Writable<{
|
|
30
|
+
active_layer: string;
|
|
31
|
+
layers: {
|
|
32
|
+
name: string;
|
|
33
|
+
id: string;
|
|
34
|
+
user_created: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
}>;
|
|
37
|
+
private background_layer;
|
|
38
|
+
private image_container;
|
|
39
|
+
private app;
|
|
40
|
+
private fixed_canvas;
|
|
41
|
+
private dark;
|
|
42
|
+
private border_region;
|
|
43
|
+
private layer_options;
|
|
44
|
+
constructor(image_container: Container, app: Application, fixed_canvas: boolean, dark: boolean, border_region: number, layer_options: LayerOptions);
|
|
45
|
+
create_background_layer(width: number, height: number): Container;
|
|
46
|
+
set_layer_options(layer_options: LayerOptions, width: number, height: number): void;
|
|
47
|
+
/**
|
|
48
|
+
* Creates a background layer from an image URL
|
|
49
|
+
* @param url The URL of the image to use as background
|
|
50
|
+
* @param width The width of the layer (if fixed_canvas is true)
|
|
51
|
+
* @param height The height of the layer (if fixed_canvas is true)
|
|
52
|
+
* @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
|
|
53
|
+
* @returns A Promise that resolves to the background Container when the image is loaded
|
|
54
|
+
*/
|
|
55
|
+
create_background_layer_from_url(url: string, width?: number, height?: number): Promise<Container>;
|
|
56
|
+
create_layer({ width, height, layer_name, user_created }: {
|
|
57
|
+
width: number;
|
|
58
|
+
height: number;
|
|
59
|
+
layer_name?: string;
|
|
60
|
+
user_created: boolean;
|
|
61
|
+
}): Container;
|
|
62
|
+
/**
|
|
63
|
+
* Creates a new layer with an image loaded from a URL
|
|
64
|
+
* @param url The URL of the image to load
|
|
65
|
+
* @param borderRegion Minimum border region to add around the image for outpainting (in pixels)
|
|
66
|
+
* @returns A Promise that resolves to the layer ID
|
|
67
|
+
*/
|
|
68
|
+
add_layer_from_url(url: string): Promise<string>;
|
|
69
|
+
get_active_layer(): typeof this.active_layer;
|
|
70
|
+
set_active_layer(id: string): void;
|
|
71
|
+
get_layers(): typeof this.layers;
|
|
72
|
+
get_layer_textures(id: string): {
|
|
73
|
+
draw: RenderTexture;
|
|
74
|
+
} | null;
|
|
75
|
+
delete_layer(id: string): void;
|
|
76
|
+
private update_layer_order;
|
|
77
|
+
move_layer(id: string, direction: "up" | "down"): void;
|
|
78
|
+
/**
|
|
79
|
+
* Resizes all layers to a new width and height
|
|
80
|
+
* @param newWidth The new width of the layers
|
|
81
|
+
* @param newHeight The new height of the layers
|
|
82
|
+
* @param scale If true, scales the layer content to fit the new dimensions. If false, keeps the content size unchanged.
|
|
83
|
+
* @param anchor The anchor point to position the content relative to the new size
|
|
84
|
+
*/
|
|
85
|
+
resize_all_layers(newWidth: number, newHeight: number, scale: boolean, anchor: "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right", oldCanvasWidth: number, oldCanvasHeight: number): void;
|
|
86
|
+
/**
|
|
87
|
+
* Helper method to resize the background layer.
|
|
88
|
+
*/
|
|
89
|
+
private _resize_background_layer;
|
|
90
|
+
/**
|
|
91
|
+
* Helper method to resize a single regular layer.
|
|
92
|
+
*/
|
|
93
|
+
private _resize_single_layer;
|
|
94
|
+
get_blobs(width: number, height: number): Promise<ImageBlobs>;
|
|
95
|
+
reset_layers(width: number, height: number, persist?: boolean): void;
|
|
96
|
+
init_layers(width: number, height: number): void;
|
|
97
|
+
}
|
|
98
|
+
declare const core_tools: readonly ["image", "zoom"];
|
|
99
|
+
interface ImageEditorOptions {
|
|
100
|
+
target_element: HTMLElement;
|
|
101
|
+
width: number;
|
|
102
|
+
height: number;
|
|
103
|
+
tools: ((typeof core_tools)[number] | Tool)[];
|
|
104
|
+
fixed_canvas?: boolean;
|
|
105
|
+
dark?: boolean;
|
|
106
|
+
border_region?: number;
|
|
107
|
+
layer_options?: LayerOptions;
|
|
108
|
+
pad_bottom?: number;
|
|
109
|
+
}
|
|
110
|
+
export interface ImageEditorContext {
|
|
111
|
+
app: Application;
|
|
112
|
+
ui_container: Container;
|
|
113
|
+
image_container: Container;
|
|
114
|
+
background_image?: Sprite;
|
|
115
|
+
command_manager: CommandManager;
|
|
116
|
+
layer_manager: LayerManager;
|
|
117
|
+
dimensions: Readable<{
|
|
118
|
+
width: number;
|
|
119
|
+
height: number;
|
|
120
|
+
}>;
|
|
121
|
+
scale: Readable<number>;
|
|
122
|
+
position: Readable<{
|
|
123
|
+
x: number;
|
|
124
|
+
y: number;
|
|
125
|
+
}>;
|
|
126
|
+
set_image_properties: (properties: {
|
|
127
|
+
width?: number;
|
|
128
|
+
height?: number;
|
|
129
|
+
scale?: number;
|
|
130
|
+
position?: {
|
|
131
|
+
x: number;
|
|
132
|
+
y: number;
|
|
133
|
+
};
|
|
134
|
+
animate?: boolean;
|
|
135
|
+
}) => void;
|
|
136
|
+
execute_command: (command: Command) => void;
|
|
137
|
+
resize_canvas: (width: number, height: number) => void;
|
|
138
|
+
reset: () => void;
|
|
139
|
+
set_background_image: (image: Sprite) => void;
|
|
140
|
+
pad_bottom: number;
|
|
141
|
+
}
|
|
142
|
+
export declare class ImageEditor {
|
|
143
|
+
ready: Promise<void>;
|
|
144
|
+
background_image_present: Writable<boolean>;
|
|
145
|
+
min_zoom: Writable<boolean>;
|
|
146
|
+
private app;
|
|
147
|
+
private ui_container;
|
|
148
|
+
private image_container;
|
|
149
|
+
private command_manager;
|
|
150
|
+
private layer_manager;
|
|
151
|
+
private tools;
|
|
152
|
+
private current_tool;
|
|
153
|
+
private current_subtool;
|
|
154
|
+
private target_element;
|
|
155
|
+
private width;
|
|
156
|
+
private height;
|
|
157
|
+
dimensions: Spring<{
|
|
158
|
+
width: number;
|
|
159
|
+
height: number;
|
|
160
|
+
}>;
|
|
161
|
+
scale: Spring<number>;
|
|
162
|
+
private position;
|
|
163
|
+
private state;
|
|
164
|
+
private scale_value;
|
|
165
|
+
private position_value;
|
|
166
|
+
private dimensions_value;
|
|
167
|
+
layers: Writable<{
|
|
168
|
+
active_layer: string;
|
|
169
|
+
layers: {
|
|
170
|
+
name: string;
|
|
171
|
+
id: string;
|
|
172
|
+
user_created: boolean;
|
|
173
|
+
}[];
|
|
174
|
+
}>;
|
|
175
|
+
private outline_container;
|
|
176
|
+
private outline_graphics;
|
|
177
|
+
private background_image?;
|
|
178
|
+
private ready_resolve;
|
|
179
|
+
private event_callbacks;
|
|
180
|
+
private fixed_canvas;
|
|
181
|
+
private dark;
|
|
182
|
+
private border_region;
|
|
183
|
+
private layer_options;
|
|
184
|
+
private overlay_container;
|
|
185
|
+
private overlay_graphics;
|
|
186
|
+
private pad_bottom;
|
|
187
|
+
constructor(options: ImageEditorOptions);
|
|
188
|
+
private get context();
|
|
189
|
+
init(): Promise<void>;
|
|
190
|
+
private setup_containers;
|
|
191
|
+
resize_canvas(width: number, height: number): void;
|
|
192
|
+
reset(): void;
|
|
193
|
+
set_image_properties(properties: {
|
|
194
|
+
width?: number;
|
|
195
|
+
height?: number;
|
|
196
|
+
scale?: number;
|
|
197
|
+
position?: {
|
|
198
|
+
x: number;
|
|
199
|
+
y: number;
|
|
200
|
+
};
|
|
201
|
+
animate?: boolean;
|
|
202
|
+
}): void;
|
|
203
|
+
execute_command(command: Command): void;
|
|
204
|
+
undo(): void;
|
|
205
|
+
redo(): void;
|
|
206
|
+
add_image({ image, resize }: {
|
|
207
|
+
image: Blob | File;
|
|
208
|
+
resize?: boolean;
|
|
209
|
+
}): Promise<void>;
|
|
210
|
+
/**
|
|
211
|
+
* Adds an image from a URL as the background layer
|
|
212
|
+
* @param url The URL of the image to add
|
|
213
|
+
*/
|
|
214
|
+
add_image_from_url(url: string): Promise<void>;
|
|
215
|
+
set_tool(tool: ToolbarTool): void;
|
|
216
|
+
set_subtool(subtool: Subtool): void;
|
|
217
|
+
set_background_image(image: Sprite): void;
|
|
218
|
+
reset_canvas(): void;
|
|
219
|
+
add_layer(): void;
|
|
220
|
+
/**
|
|
221
|
+
* Adds a new layer with an image loaded from a URL
|
|
222
|
+
* @param layer_urls The URLs of the images to load
|
|
223
|
+
* @returns A Promise that resolves when all layers are added
|
|
224
|
+
*/
|
|
225
|
+
add_layers_from_url(layer_urls: string[] | undefined): Promise<void>;
|
|
226
|
+
set_layer(id: string): void;
|
|
227
|
+
move_layer(id: string, direction: "up" | "down"): void;
|
|
228
|
+
delete_layer(id: string): void;
|
|
229
|
+
modify_canvas_size(width: number, height: number, anchor: "top-left" | "top" | "top-right" | "left" | "center" | "right" | "bottom-left" | "bottom" | "bottom-right", scale: boolean): void;
|
|
230
|
+
get_blobs(): Promise<ImageBlobs>;
|
|
231
|
+
on(event: "change" | "input", callback: () => void): void;
|
|
232
|
+
off(event: "change", callback: () => void): void;
|
|
233
|
+
private notify;
|
|
234
|
+
destroy(): void;
|
|
235
|
+
resize(width: number, height: number): void;
|
|
236
|
+
get_crop_bounds(): Promise<{
|
|
237
|
+
image: Blob | null;
|
|
238
|
+
crop_dimensions: {
|
|
239
|
+
width: number;
|
|
240
|
+
height: number;
|
|
241
|
+
};
|
|
242
|
+
image_dimensions: {
|
|
243
|
+
width: number;
|
|
244
|
+
height: number;
|
|
245
|
+
};
|
|
246
|
+
x: number;
|
|
247
|
+
y: number;
|
|
248
|
+
}>;
|
|
249
|
+
get background_image_sprite(): Sprite | undefined;
|
|
250
|
+
set_layer_options(layer_options: LayerOptions): void;
|
|
251
|
+
}
|
|
252
|
+
export {};
|