@gradio/imageeditor 0.12.14 → 0.14.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 (131) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/Example.svelte +1 -1
  3. package/IMAGE_EDITOR_OVERVIEW.md +224 -0
  4. package/ImageEditor.stories.svelte +13 -5
  5. package/Index.svelte +46 -45
  6. package/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +65 -103
  7. package/README.md +2 -0
  8. package/dist/Example.svelte.d.ts +1 -1
  9. package/dist/Index.svelte +35 -33
  10. package/dist/Index.svelte.d.ts +25 -16
  11. package/dist/{shared/InteractiveImageEditor.svelte → InteractiveImageEditor.svelte} +54 -88
  12. package/dist/{shared/InteractiveImageEditor.svelte.d.ts → InteractiveImageEditor.svelte.d.ts} +17 -15
  13. package/dist/shared/Anchor.svelte +311 -0
  14. package/dist/shared/Anchor.svelte.d.ts +18 -0
  15. package/dist/shared/Controls.svelte +169 -18
  16. package/dist/shared/Controls.svelte.d.ts +9 -2
  17. package/dist/shared/{tools/IconButton.svelte → IconButton.svelte} +19 -3
  18. package/dist/shared/{tools/IconButton.svelte.d.ts → IconButton.svelte.d.ts} +4 -1
  19. package/dist/shared/ImageEditor.svelte +572 -289
  20. package/dist/shared/ImageEditor.svelte.d.ts +54 -56
  21. package/dist/shared/Layers.svelte +260 -0
  22. package/dist/shared/Layers.svelte.d.ts +37 -0
  23. package/dist/shared/Resize.svelte +218 -0
  24. package/dist/shared/Resize.svelte.d.ts +29 -0
  25. package/dist/shared/SecondaryToolbar.svelte +55 -0
  26. package/dist/shared/SecondaryToolbar.svelte.d.ts +37 -0
  27. package/dist/shared/Toolbar.svelte +337 -0
  28. package/dist/shared/Toolbar.svelte.d.ts +42 -0
  29. package/dist/shared/{tools → brush}/BrushOptions.svelte +68 -64
  30. package/dist/shared/{tools → brush}/BrushOptions.svelte.d.ts +9 -9
  31. package/dist/shared/{tools → brush}/BrushSize.svelte +2 -27
  32. package/dist/shared/{tools → brush}/ColorField.svelte +2 -1
  33. package/dist/shared/{tools → brush}/ColorField.svelte.d.ts +1 -0
  34. package/dist/shared/{tools → brush}/ColorPicker.svelte +81 -9
  35. package/dist/shared/{tools → brush}/ColorPicker.svelte.d.ts +1 -0
  36. package/dist/shared/{tools → brush}/ColorSwatch.svelte +78 -38
  37. package/dist/shared/{tools → brush}/ColorSwatch.svelte.d.ts +6 -2
  38. package/dist/shared/brush/brush-cursor.d.ts +101 -0
  39. package/dist/shared/brush/brush-cursor.js +327 -0
  40. package/dist/shared/brush/brush-textures.d.ts +65 -0
  41. package/dist/shared/brush/brush-textures.js +385 -0
  42. package/dist/shared/brush/brush-utils.d.ts +32 -0
  43. package/dist/shared/brush/brush-utils.js +59 -0
  44. package/dist/shared/brush/brush.d.ts +152 -0
  45. package/dist/shared/brush/brush.js +397 -0
  46. package/dist/shared/brush/types.d.ts +37 -0
  47. package/dist/shared/brush/types.js +1 -0
  48. package/dist/shared/core/editor.d.ts +261 -0
  49. package/dist/shared/core/editor.js +1264 -0
  50. package/dist/shared/crop/crop.d.ts +201 -0
  51. package/dist/shared/crop/crop.js +727 -0
  52. package/dist/shared/image/image.d.ts +89 -0
  53. package/dist/shared/image/image.js +183 -0
  54. package/dist/shared/resize/resize.d.ts +270 -0
  55. package/dist/shared/resize/resize.js +1413 -0
  56. package/dist/shared/types.d.ts +16 -0
  57. package/dist/shared/utils/pixi.d.ts +16 -57
  58. package/dist/shared/utils/pixi.js +15 -113
  59. package/dist/shared/zoom/zoom.d.ts +136 -0
  60. package/dist/shared/zoom/zoom.js +548 -0
  61. package/package.json +8 -7
  62. package/shared/Anchor.svelte +384 -0
  63. package/shared/Controls.svelte +178 -37
  64. package/shared/{tools/IconButton.svelte → IconButton.svelte} +21 -4
  65. package/shared/ImageEditor.svelte +712 -350
  66. package/shared/Layers.svelte +282 -0
  67. package/shared/Resize.svelte +239 -0
  68. package/shared/SecondaryToolbar.svelte +67 -0
  69. package/shared/Toolbar.svelte +386 -0
  70. package/shared/brush/BRUSH_TOOL.md +134 -0
  71. package/shared/brush/BrushOptions.svelte +200 -0
  72. package/shared/{tools → brush}/BrushSize.svelte +2 -30
  73. package/shared/{tools → brush}/ColorField.svelte +3 -1
  74. package/shared/{tools → brush}/ColorPicker.svelte +86 -9
  75. package/shared/{tools → brush}/ColorSwatch.svelte +95 -42
  76. package/shared/brush/brush-cursor.ts +408 -0
  77. package/shared/brush/brush-textures.ts +485 -0
  78. package/shared/brush/brush-utils.ts +76 -0
  79. package/shared/brush/brush.ts +510 -0
  80. package/shared/brush/types.ts +41 -0
  81. package/shared/core/EDITOR.md +182 -0
  82. package/shared/core/editor.ts +1788 -0
  83. package/shared/crop/CROP.md +139 -0
  84. package/shared/crop/crop.ts +1027 -0
  85. package/shared/image/IMAGE.md +135 -0
  86. package/shared/image/image.ts +298 -0
  87. package/shared/resize/resize.ts +1894 -0
  88. package/shared/types.ts +19 -0
  89. package/shared/utils/pixi.ts +19 -246
  90. package/shared/zoom/zoom.ts +729 -0
  91. package/dist/shared/layers/Layers.svelte +0 -231
  92. package/dist/shared/layers/Layers.svelte.d.ts +0 -18
  93. package/dist/shared/layers/utils.d.ts +0 -73
  94. package/dist/shared/layers/utils.js +0 -156
  95. package/dist/shared/tools/Brush.svelte +0 -215
  96. package/dist/shared/tools/Brush.svelte.d.ts +0 -29
  97. package/dist/shared/tools/Crop.svelte +0 -155
  98. package/dist/shared/tools/Crop.svelte.d.ts +0 -16
  99. package/dist/shared/tools/Cropper.svelte +0 -271
  100. package/dist/shared/tools/Cropper.svelte.d.ts +0 -41
  101. package/dist/shared/tools/Handle.svelte +0 -260
  102. package/dist/shared/tools/Handle.svelte.d.ts +0 -29
  103. package/dist/shared/tools/Sources.svelte +0 -257
  104. package/dist/shared/tools/Sources.svelte.d.ts +0 -34
  105. package/dist/shared/tools/Tools.svelte +0 -102
  106. package/dist/shared/tools/Tools.svelte.d.ts +0 -48
  107. package/dist/shared/tools/brush.d.ts +0 -62
  108. package/dist/shared/tools/brush.js +0 -133
  109. package/dist/shared/tools/crop.d.ts +0 -15
  110. package/dist/shared/tools/crop.js +0 -154
  111. package/dist/shared/tools/index.d.ts +0 -5
  112. package/dist/shared/tools/index.js +0 -5
  113. package/dist/shared/tools/sources.d.ts +0 -46
  114. package/dist/shared/tools/sources.js +0 -121
  115. package/dist/shared/tools/types.d.ts +0 -1
  116. package/shared/layers/Layers.svelte +0 -250
  117. package/shared/layers/utils.ts +0 -294
  118. package/shared/tools/Brush.svelte +0 -280
  119. package/shared/tools/BrushOptions.svelte +0 -180
  120. package/shared/tools/Crop.svelte +0 -189
  121. package/shared/tools/Cropper.svelte +0 -346
  122. package/shared/tools/Handle.svelte +0 -289
  123. package/shared/tools/Sources.svelte +0 -283
  124. package/shared/tools/Tools.svelte +0 -162
  125. package/shared/tools/brush.ts +0 -247
  126. package/shared/tools/crop.ts +0 -213
  127. package/shared/tools/index.ts +0 -5
  128. package/shared/tools/sources.ts +0 -195
  129. package/shared/tools/types.ts +0 -1
  130. /package/dist/shared/{tools → brush}/BrushSize.svelte.d.ts +0 -0
  131. /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
+ }