@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.
Files changed (130) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/Example.svelte +1 -1
  3. package/IMAGE_EDITOR_OVERVIEW.md +224 -0
  4. package/ImageEditor.stories.svelte +4 -4
  5. package/Index.svelte +35 -46
  6. package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +56 -103
  7. package/dist/Example.svelte.d.ts +1 -1
  8. package/dist/Index.svelte +29 -33
  9. package/dist/Index.svelte.d.ts +17 -16
  10. package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +47 -88
  11. package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +13 -15
  12. package/dist/shared/Anchor.svelte +311 -0
  13. package/dist/shared/Anchor.svelte.d.ts +18 -0
  14. package/dist/shared/Controls.svelte +162 -23
  15. package/dist/shared/Controls.svelte.d.ts +7 -2
  16. package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
  17. package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
  18. package/dist/shared/ImageEditor.svelte +546 -289
  19. package/dist/shared/ImageEditor.svelte.d.ts +51 -56
  20. package/dist/shared/Layers.svelte +233 -0
  21. package/dist/shared/Layers.svelte.d.ts +35 -0
  22. package/dist/shared/Resize.svelte +218 -0
  23. package/dist/shared/Resize.svelte.d.ts +29 -0
  24. package/dist/shared/SecondaryToolbar.svelte +54 -0
  25. package/dist/shared/SecondaryToolbar.svelte.d.ts +35 -0
  26. package/dist/shared/Toolbar.svelte +337 -0
  27. package/dist/shared/Toolbar.svelte.d.ts +42 -0
  28. package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
  29. package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
  30. package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
  31. package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
  32. package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
  33. package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
  34. package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
  35. package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
  36. package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
  37. package/dist/shared/brush/brush-cursor.d.ts +101 -0
  38. package/dist/shared/brush/brush-cursor.js +327 -0
  39. package/dist/shared/brush/brush-textures.d.ts +71 -0
  40. package/dist/shared/brush/brush-textures.js +404 -0
  41. package/dist/shared/brush/brush-utils.d.ts +32 -0
  42. package/dist/shared/brush/brush-utils.js +59 -0
  43. package/dist/shared/brush/brush.d.ts +152 -0
  44. package/dist/shared/brush/brush.js +396 -0
  45. package/dist/shared/brush/types.d.ts +37 -0
  46. package/dist/shared/brush/types.js +1 -0
  47. package/dist/shared/core/editor.d.ts +252 -0
  48. package/dist/shared/core/editor.js +1221 -0
  49. package/dist/shared/crop/crop.d.ts +201 -0
  50. package/dist/shared/crop/crop.js +727 -0
  51. package/dist/shared/image/image.d.ts +89 -0
  52. package/dist/shared/image/image.js +185 -0
  53. package/dist/shared/resize/resize.d.ts +270 -0
  54. package/dist/shared/resize/resize.js +1413 -0
  55. package/dist/shared/types.d.ts +12 -0
  56. package/dist/shared/utils/pixi.d.ts +16 -57
  57. package/dist/shared/utils/pixi.js +15 -113
  58. package/dist/shared/zoom/zoom.d.ts +136 -0
  59. package/dist/shared/zoom/zoom.js +548 -0
  60. package/package.json +11 -10
  61. package/shared/Anchor.svelte +384 -0
  62. package/shared/Controls.svelte +194 -27
  63. package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
  64. package/shared/ImageEditor.svelte +682 -349
  65. package/shared/Layers.svelte +249 -0
  66. package/shared/Resize.svelte +239 -0
  67. package/shared/SecondaryToolbar.svelte +61 -0
  68. package/shared/Toolbar.svelte +386 -0
  69. package/shared/brush/BRUSH_TOOL.md +134 -0
  70. package/shared/brush/BrushOptions.svelte +200 -0
  71. package/shared/{tools → brush}/BrushSize.svelte +2 -30
  72. package/shared/{tools → brush}/ColorField.svelte +3 -1
  73. package/shared/{tools → brush}/ColorPicker.svelte +86 -9
  74. package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
  75. package/shared/brush/brush-cursor.ts +408 -0
  76. package/shared/brush/brush-textures.ts +508 -0
  77. package/shared/brush/brush-utils.ts +76 -0
  78. package/shared/brush/brush.ts +506 -0
  79. package/shared/brush/types.ts +41 -0
  80. package/shared/core/EDITOR.md +182 -0
  81. package/shared/core/editor.ts +1730 -0
  82. package/shared/crop/CROP.md +139 -0
  83. package/shared/crop/crop.ts +1027 -0
  84. package/shared/image/IMAGE.md +135 -0
  85. package/shared/image/image.ts +300 -0
  86. package/shared/resize/resize.ts +1894 -0
  87. package/shared/types.ts +14 -0
  88. package/shared/utils/pixi.ts +19 -246
  89. package/shared/zoom/zoom.ts +729 -0
  90. package/dist/shared/layers/Layers.svelte +0 -231
  91. package/dist/shared/layers/Layers.svelte.d.ts +0 -18
  92. package/dist/shared/layers/utils.d.ts +0 -73
  93. package/dist/shared/layers/utils.js +0 -156
  94. package/dist/shared/tools/Brush.svelte +0 -215
  95. package/dist/shared/tools/Brush.svelte.d.ts +0 -29
  96. package/dist/shared/tools/Crop.svelte +0 -155
  97. package/dist/shared/tools/Crop.svelte.d.ts +0 -16
  98. package/dist/shared/tools/Cropper.svelte +0 -271
  99. package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
  100. package/dist/shared/tools/Handle.svelte +0 -260
  101. package/dist/shared/tools/Handle.svelte.d.ts +0 -29
  102. package/dist/shared/tools/Sources.svelte +0 -257
  103. package/dist/shared/tools/Sources.svelte.d.ts +0 -34
  104. package/dist/shared/tools/Tools.svelte +0 -102
  105. package/dist/shared/tools/Tools.svelte.d.ts +0 -48
  106. package/dist/shared/tools/brush.d.ts +0 -62
  107. package/dist/shared/tools/brush.js +0 -133
  108. package/dist/shared/tools/crop.d.ts +0 -15
  109. package/dist/shared/tools/crop.js +0 -154
  110. package/dist/shared/tools/index.d.ts +0 -5
  111. package/dist/shared/tools/index.js +0 -5
  112. package/dist/shared/tools/sources.d.ts +0 -46
  113. package/dist/shared/tools/sources.js +0 -121
  114. package/dist/shared/tools/types.d.ts +0 -1
  115. package/shared/layers/Layers.svelte +0 -250
  116. package/shared/layers/utils.ts +0 -294
  117. package/shared/tools/Brush.svelte +0 -280
  118. package/shared/tools/BrushOptions.svelte +0 -180
  119. package/shared/tools/Crop.svelte +0 -189
  120. package/shared/tools/Cropper.svelte +0 -346
  121. package/shared/tools/Handle.svelte +0 -289
  122. package/shared/tools/Sources.svelte +0 -283
  123. package/shared/tools/Tools.svelte +0 -162
  124. package/shared/tools/brush.ts +0 -247
  125. package/shared/tools/crop.ts +0 -213
  126. package/shared/tools/index.ts +0 -5
  127. package/shared/tools/sources.ts +0 -195
  128. package/shared/tools/types.ts +0 -1
  129. /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
  130. /package/dist/shared/{tools/types.js → types.js} +0 -0
@@ -0,0 +1,408 @@
1
+ import {
2
+ Container,
3
+ Graphics,
4
+ Sprite,
5
+ type FederatedPointerEvent
6
+ } from "pixi.js";
7
+ import { type ImageEditorContext } from "../core/editor";
8
+ import { type BrushState } from "./types";
9
+ import tinycolor from "tinycolor2";
10
+ import { clear_timeout } from "./brush-utils";
11
+
12
+ /**
13
+ * Class to handle all cursor preview-related functionality for the brush tool.
14
+ */
15
+ export class BrushCursor {
16
+ // Cursor-related properties
17
+ private cursor_graphics: Graphics | null = null;
18
+ private cursor_container: Container | null = null;
19
+ private brush_preview_container: Container | null = null;
20
+ private brush_preview_graphics: Graphics | null = null;
21
+ private is_preview_visible = false;
22
+ private is_cursor_over_image = false;
23
+ private cursor_position_check_timeout: number | null = null;
24
+ private is_brush_or_erase_active = false;
25
+
26
+ // Event handlers
27
+ private _bound_update_cursor:
28
+ | ((event: FederatedPointerEvent) => void)
29
+ | null = null;
30
+ private _bound_check_cursor_over_image:
31
+ | ((event: FederatedPointerEvent) => void)
32
+ | null = null;
33
+
34
+ constructor(
35
+ private image_editor_context: ImageEditorContext,
36
+ private state: BrushState,
37
+ private scale: number
38
+ ) {
39
+ this.initialize_cursor();
40
+ this.initialize_brush_preview();
41
+ }
42
+
43
+ /**
44
+ * Returns whether the cursor is currently over the image.
45
+ */
46
+ is_over_image(): boolean {
47
+ return this.is_cursor_over_image;
48
+ }
49
+
50
+ /**
51
+ * Sets up cursor-related event listeners.
52
+ */
53
+ setup_event_listeners(): void {
54
+ // Clean up any existing listeners
55
+ this.cleanup_event_listeners();
56
+
57
+ // Create bound event handlers
58
+ this._bound_update_cursor = this.update_cursor_position.bind(this);
59
+ this._bound_check_cursor_over_image =
60
+ this.check_cursor_over_image.bind(this);
61
+
62
+ // Add event listeners using Pixi event system
63
+ // Use app.stage for global events like pointermove
64
+ const stage = this.image_editor_context.app.stage;
65
+ stage.on("pointermove", this._bound_update_cursor);
66
+ stage.on("pointermove", this._bound_check_cursor_over_image);
67
+
68
+ // Add event listeners to the image container for enter/leave events
69
+ this.image_editor_context.image_container.on(
70
+ "pointerenter",
71
+ this.on_image_container_pointer_enter.bind(this)
72
+ );
73
+ this.image_editor_context.image_container.on(
74
+ "pointerleave",
75
+ this.on_image_container_pointer_leave.bind(this)
76
+ );
77
+ }
78
+
79
+ /**
80
+ * Removes all cursor-related event listeners.
81
+ */
82
+ cleanup_event_listeners(): void {
83
+ // Use Pixi event system for removal
84
+ const stage = this.image_editor_context.app.stage;
85
+
86
+ // Remove event listeners if they exist
87
+ if (this._bound_update_cursor) {
88
+ stage.off("pointermove", this._bound_update_cursor);
89
+ this._bound_update_cursor = null;
90
+ }
91
+
92
+ if (this._bound_check_cursor_over_image) {
93
+ stage.off("pointermove", this._bound_check_cursor_over_image);
94
+ this._bound_check_cursor_over_image = null;
95
+ }
96
+
97
+ // Remove image container event listeners
98
+ this.image_editor_context.image_container.off("pointerenter");
99
+ this.image_editor_context.image_container.off("pointerleave");
100
+
101
+ // Clear timeout
102
+ this.cursor_position_check_timeout = clear_timeout(
103
+ this.cursor_position_check_timeout
104
+ );
105
+ }
106
+
107
+ /**
108
+ * initializes the cursor for the brush tool.
109
+ */
110
+ initialize_cursor(): void {
111
+ // clean up existing cursor if it exists
112
+ if (this.cursor_container) {
113
+ if (this.cursor_container.parent) {
114
+ this.cursor_container.parent.removeChild(this.cursor_container);
115
+ }
116
+ this.cursor_container.destroy({ children: true });
117
+ this.cursor_container = null;
118
+ }
119
+
120
+ // create new cursor container and add to ui container
121
+ this.cursor_container = new Container();
122
+ this.image_editor_context.ui_container.addChild(this.cursor_container);
123
+
124
+ // create cursor graphics
125
+ this.cursor_graphics = new Graphics();
126
+ this.cursor_container.addChild(this.cursor_graphics);
127
+
128
+ // draw initial cursor
129
+ this.update_cursor_appearance();
130
+ }
131
+
132
+ /**
133
+ * updates the appearance of the cursor.
134
+ */
135
+ update_cursor_appearance(): void {
136
+ if (!this.cursor_graphics) return;
137
+
138
+ // clear previous graphics
139
+ this.cursor_graphics.clear();
140
+
141
+ // set color based on mode
142
+ const cursor_color =
143
+ this.state.mode === "draw"
144
+ ? tinycolor(this.state.color).toString()
145
+ : 0xffffff;
146
+
147
+ // draw circle with current brush size
148
+ this.cursor_graphics
149
+ .circle(0, 0, this.state.brush_size * this.scale)
150
+ .stroke({
151
+ width: 1.5,
152
+ color: cursor_color,
153
+ alpha: 0.8
154
+ });
155
+
156
+ // add a small dot in the center for precision
157
+ this.cursor_graphics.circle(0, 0, 1).fill({
158
+ color: cursor_color,
159
+ alpha: 0.8
160
+ });
161
+ }
162
+
163
+ /**
164
+ * updates the position of the cursor.
165
+ * @param {FederatedPointerEvent} event - The pointer event.
166
+ */
167
+ update_cursor_position(event: FederatedPointerEvent): void {
168
+ if (!this.cursor_container) return;
169
+
170
+ // get position in image coordinates
171
+ const local_pos = this.image_editor_context.image_container.toLocal(
172
+ event.global
173
+ );
174
+
175
+ // convert to ui coordinates
176
+ const ui_pos = this.image_editor_context.ui_container.toLocal(
177
+ this.image_editor_context.image_container.toGlobal(local_pos)
178
+ );
179
+
180
+ // update cursor position
181
+ this.cursor_container.position.set(ui_pos.x, ui_pos.y);
182
+ }
183
+
184
+ /**
185
+ * checks if the cursor is over the image container.
186
+ * @param {FederatedPointerEvent} event - The pointer event.
187
+ */
188
+ check_cursor_over_image(event: FederatedPointerEvent): void {
189
+ // cancel any pending timeout
190
+ if (this.cursor_position_check_timeout !== null) {
191
+ window.clearTimeout(this.cursor_position_check_timeout);
192
+ this.cursor_position_check_timeout = null;
193
+ }
194
+
195
+ // get the image container bounds in global coordinates
196
+ const image_container = this.image_editor_context.image_container;
197
+ const bounds = image_container.getBounds();
198
+
199
+ // check if cursor is over image
200
+ const was_over_image = this.is_cursor_over_image;
201
+ this.is_cursor_over_image =
202
+ event.global.x >= bounds.x &&
203
+ event.global.x <= bounds.x + bounds.width &&
204
+ event.global.y >= bounds.y &&
205
+ event.global.y <= bounds.y + bounds.height;
206
+
207
+ // update visibility if state changed
208
+ if (was_over_image !== this.is_cursor_over_image) {
209
+ this.update_cursor_and_preview_visibility();
210
+ }
211
+ }
212
+
213
+ /**
214
+ * updates the size of the cursor.
215
+ */
216
+ update_cursor_size(): void {
217
+ this.update_cursor_appearance();
218
+ }
219
+
220
+ /**
221
+ * Updates whether brush or erase tool is active
222
+ */
223
+ set_active(is_active: boolean): void {
224
+ this.is_brush_or_erase_active = is_active;
225
+ this.update_cursor_and_preview_visibility();
226
+ }
227
+
228
+ /**
229
+ * updates the visibility of the cursor.
230
+ */
231
+ update_cursor_visibility(): void {
232
+ if (this.cursor_container) {
233
+ this.cursor_container.visible =
234
+ this.is_cursor_over_image && this.is_brush_or_erase_active;
235
+ }
236
+ }
237
+
238
+ /**
239
+ * Shows or hides the brush preview.
240
+ * @param {boolean} show - Whether to show the preview.
241
+ */
242
+ preview_brush(show: boolean): void {
243
+ this.is_preview_visible = show;
244
+
245
+ if (this.brush_preview_container) {
246
+ this.brush_preview_container.visible =
247
+ show && this.is_brush_or_erase_active;
248
+ }
249
+
250
+ if (show) {
251
+ // update preview appearance and position
252
+ this.update_brush_preview();
253
+ this.update_brush_preview_position();
254
+ }
255
+ }
256
+
257
+ /**
258
+ * initializes the brush preview.
259
+ */
260
+ initialize_brush_preview(): void {
261
+ // clean up existing preview if it exists
262
+ if (this.brush_preview_container) {
263
+ if (this.brush_preview_container.parent) {
264
+ this.brush_preview_container.parent.removeChild(
265
+ this.brush_preview_container
266
+ );
267
+ }
268
+ this.brush_preview_container.destroy({ children: true });
269
+ this.brush_preview_container = null;
270
+ }
271
+
272
+ // create new preview container and add to ui container
273
+ this.brush_preview_container = new Container();
274
+ this.image_editor_context.ui_container.addChild(
275
+ this.brush_preview_container
276
+ );
277
+
278
+ // create preview graphics
279
+ this.brush_preview_graphics = new Graphics();
280
+ this.brush_preview_container.addChild(this.brush_preview_graphics);
281
+
282
+ // set initial visibility
283
+ this.brush_preview_container.visible = false;
284
+ }
285
+
286
+ /**
287
+ * updates the brush preview appearance.
288
+ */
289
+ update_brush_preview(): void {
290
+ if (!this.brush_preview_graphics) return;
291
+
292
+ // clear previous graphics
293
+ this.brush_preview_graphics.clear();
294
+
295
+ // draw brush preview based on current settings
296
+ const preview_size = this.state.brush_size * this.scale;
297
+ const preview_color =
298
+ this.state.mode === "draw"
299
+ ? tinycolor(this.state.color).setAlpha(this.state.opacity).toString()
300
+ : 0xffffff;
301
+
302
+ // draw circle with current brush settings
303
+ this.brush_preview_graphics.circle(0, 0, preview_size).fill({
304
+ color: preview_color,
305
+ alpha: this.state.mode === "draw" ? this.state.opacity : 0.3
306
+ });
307
+
308
+ // add outline
309
+ this.brush_preview_graphics.circle(0, 0, preview_size + 1).stroke({
310
+ width: 1,
311
+ color: 0x000000,
312
+ alpha: 0.5
313
+ });
314
+ }
315
+
316
+ /**
317
+ * updates the position of the brush preview to center of canvas.
318
+ */
319
+ update_brush_preview_position(): void {
320
+ if (!this.brush_preview_container) return;
321
+
322
+ // get the center of the image in global coordinates
323
+ const image_container = this.image_editor_context.image_container;
324
+ const center_x = image_container.width / 2;
325
+ const center_y = image_container.height / 2;
326
+
327
+ const global_pos = image_container.toGlobal({ x: center_x, y: center_y });
328
+
329
+ // convert to ui coordinates
330
+ const ui_pos = this.image_editor_context.ui_container.toLocal(global_pos);
331
+
332
+ // update preview position
333
+ this.brush_preview_container.position.set(ui_pos.x, ui_pos.y);
334
+ }
335
+
336
+ /**
337
+ * Called when the pointer enters the image container.
338
+ */
339
+ on_image_container_pointer_enter(): void {
340
+ this.is_cursor_over_image = true;
341
+ this.update_cursor_and_preview_visibility();
342
+ }
343
+
344
+ /**
345
+ * Called when the pointer leaves the image container.
346
+ */
347
+ on_image_container_pointer_leave(): void {
348
+ this.is_cursor_over_image = false;
349
+ this.update_cursor_and_preview_visibility();
350
+ }
351
+
352
+ /**
353
+ * Updates both cursor and preview visibility based on cursor position.
354
+ */
355
+ update_cursor_and_preview_visibility(): void {
356
+ this.update_cursor_visibility();
357
+ if (this.brush_preview_container) {
358
+ this.brush_preview_container.visible =
359
+ this.is_preview_visible && this.is_brush_or_erase_active;
360
+ }
361
+ }
362
+
363
+ /**
364
+ * Cleans up all cursor resources.
365
+ */
366
+ cleanup(): void {
367
+ // Reset active state
368
+ this.is_brush_or_erase_active = false;
369
+
370
+ // clean up cursor
371
+ if (this.cursor_container) {
372
+ if (this.cursor_container.parent) {
373
+ this.cursor_container.parent.removeChild(this.cursor_container);
374
+ }
375
+ this.cursor_container.destroy({ children: true });
376
+ this.cursor_container = null;
377
+ }
378
+
379
+ // clean up preview
380
+ if (this.brush_preview_container) {
381
+ if (this.brush_preview_container.parent) {
382
+ this.brush_preview_container.parent.removeChild(
383
+ this.brush_preview_container
384
+ );
385
+ }
386
+ this.brush_preview_container.destroy({ children: true });
387
+ this.brush_preview_container = null;
388
+ }
389
+
390
+ // clear timeout
391
+ if (this.cursor_position_check_timeout !== null) {
392
+ window.clearTimeout(this.cursor_position_check_timeout);
393
+ this.cursor_position_check_timeout = null;
394
+ }
395
+ }
396
+
397
+ /**
398
+ * Updates the brush state.
399
+ */
400
+ update_state(state: BrushState, scale: number): void {
401
+ this.state = state;
402
+ this.scale = scale;
403
+ this.update_cursor_appearance();
404
+ if (this.is_preview_visible) {
405
+ this.update_brush_preview();
406
+ }
407
+ }
408
+ }