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