@gradio/imageeditor 0.12.14 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/CHANGELOG.md +14 -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 +10 -9
  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,548 @@
1
+ import { Point } from "pixi.js";
2
+ import {} from "../core/editor";
3
+ import {} from "../Toolbar.svelte";
4
+ import { get, writable } from "svelte/store";
5
+ /**
6
+ * ZoomTool class for handling zoom and pan functionality in the image editor
7
+ * Implements the Tool interface
8
+ */
9
+ export class ZoomTool {
10
+ name = "zoom";
11
+ min_zoom = writable(true);
12
+ image_editor_context;
13
+ // Constants
14
+ max_zoom = 10;
15
+ border_padding = 30;
16
+ // State
17
+ pad_bottom = 0;
18
+ is_pinching = false;
19
+ is_dragging = false;
20
+ is_pointer_dragging = false;
21
+ last_touch_position = null;
22
+ last_pinch_distance = 0;
23
+ drag_start = new Point();
24
+ current_tool;
25
+ current_subtool;
26
+ local_scale = 1;
27
+ local_dimensions = {
28
+ width: 0,
29
+ height: 0
30
+ };
31
+ local_position = { x: 0, y: 0 };
32
+ /**
33
+ * Prevents default behavior for events
34
+ * @param {FederatedWheelEvent | FederatedPointerEvent} event - The event to prevent default behavior for
35
+ */
36
+ prevent_default(event) {
37
+ event.preventDefault();
38
+ event.stopPropagation();
39
+ }
40
+ /**
41
+ * Sets the current tool and subtool
42
+ * @param {ToolbarTool} tool - The tool to set
43
+ * @param {Subtool} subtool - The subtool to set
44
+ */
45
+ set_tool(tool, subtool) {
46
+ this.current_tool = tool;
47
+ this.current_subtool = subtool;
48
+ }
49
+ /**
50
+ * Sets the zoom level to a specific percentage or fits the image to the screen
51
+ * @param {number | 'fit'} zoom_level - The zoom level to set (0-1) or 'fit' to use min_zoom
52
+ * @returns {void}
53
+ */
54
+ set_zoom(zoom_level) {
55
+ // Calculate the min zoom level
56
+ const fit_zoom = this.calculate_min_zoom(this.local_dimensions.width, this.local_dimensions.height);
57
+ // Determine the target zoom level
58
+ let target_zoom;
59
+ const is_fit_zoom = zoom_level === "fit";
60
+ if (is_fit_zoom) {
61
+ target_zoom = fit_zoom;
62
+ }
63
+ else {
64
+ // Ensure zoom_level is between 0 and 1, then scale it to the range [min_zoom, max_zoom]
65
+ target_zoom = Math.max(0, Math.min(this.max_zoom, zoom_level));
66
+ }
67
+ // Get the canvas dimensions
68
+ const canvas = this.image_editor_context.app.screen;
69
+ const canvas_width = canvas.width;
70
+ const canvas_height = canvas.height;
71
+ // Determine the center point for zooming
72
+ let center_point;
73
+ // Always use the center of the screen
74
+ center_point = {
75
+ x: canvas_width / 2,
76
+ y: canvas_height / 2
77
+ };
78
+ // Apply the zoom using the existing zoom_to_point method
79
+ this.zoom_to_point(target_zoom, center_point, true, is_fit_zoom);
80
+ }
81
+ /**
82
+ * Sets up the zoom tool with the given context and state
83
+ * @param {ImageEditorContext} context - The image editor context
84
+ * @param {ToolbarTool} tool - The current tool
85
+ * @param {Subtool} subtool - The current subtool
86
+ * @returns {Promise<void>}
87
+ */
88
+ async setup(context, tool, subtool) {
89
+ this.image_editor_context = context;
90
+ this.current_tool = tool;
91
+ this.current_subtool = subtool;
92
+ this.pad_bottom = context.pad_bottom;
93
+ // Initialize zoom and position
94
+ const { width, height } = await this.get_container_dimensions();
95
+ const fit_zoom = this.calculate_min_zoom(width, height);
96
+ // Use fit zoom or 1, whichever is smaller
97
+ const min_zoom = Math.min(fit_zoom, 1);
98
+ // Set initial zoom
99
+ this.local_scale = min_zoom;
100
+ // Use simple center positioning
101
+ const canvas = this.image_editor_context.app.screen;
102
+ const center_x = canvas.width / 2;
103
+ const center_y = canvas.height / 2;
104
+ // Calculate initial position to center the scaled image
105
+ const scaled_width = width * min_zoom;
106
+ const scaled_height = height * min_zoom;
107
+ const initial_x = center_x - scaled_width / 2;
108
+ const initial_y = center_y - scaled_height / 2;
109
+ this.local_position = { x: initial_x, y: initial_y };
110
+ // Setup event listeners
111
+ this.setup_event_listeners();
112
+ // Update context with initial values
113
+ this.image_editor_context.set_image_properties({
114
+ scale: min_zoom,
115
+ position: { x: initial_x, y: initial_y }
116
+ });
117
+ this.image_editor_context.dimensions.subscribe((dimensions) => {
118
+ this.local_dimensions = dimensions;
119
+ });
120
+ this.image_editor_context.scale.subscribe((scale) => {
121
+ this.local_scale = scale;
122
+ });
123
+ this.image_editor_context.position.subscribe((position) => {
124
+ this.local_position = position;
125
+ });
126
+ }
127
+ /**
128
+ * Sets up event listeners for zoom and pan functionality
129
+ * @private
130
+ */
131
+ setup_event_listeners() {
132
+ const stage = this.image_editor_context.app.stage;
133
+ const canvas = this.image_editor_context.app.canvas;
134
+ canvas.addEventListener("wheel", this.prevent_default, { passive: false });
135
+ // Configure stage for interaction
136
+ stage.eventMode = "static";
137
+ stage.hitArea = this.image_editor_context.app.screen;
138
+ // Add wheel event with options
139
+ const wheel_listener = this.handle_wheel.bind(this);
140
+ stage.addEventListener("wheel", wheel_listener, { passive: false });
141
+ // Setup touch/pointer events based on device
142
+ if ("ontouchstart" in window) {
143
+ stage.addEventListener("touchstart", this.handle_touch_start.bind(this));
144
+ stage.addEventListener("touchmove", this.handle_touch_move.bind(this));
145
+ stage.addEventListener("touchend", this.handle_touch_end.bind(this));
146
+ }
147
+ else {
148
+ stage.addEventListener("pointerdown", this.handle_pointer_down.bind(this));
149
+ stage.addEventListener("pointermove", this.handle_pointer_move.bind(this));
150
+ stage.addEventListener("pointerup", this.handle_pointer_up.bind(this));
151
+ stage.addEventListener("pointerupoutside", this.handle_pointer_up.bind(this));
152
+ }
153
+ }
154
+ /**
155
+ * Handles wheel events for zooming
156
+ * @param {FederatedWheelEvent} event - The wheel event
157
+ * @private
158
+ */
159
+ handle_wheel(event) {
160
+ const is_trackpad = event.deltaMode === 0 && Math.abs(event.deltaY) < 50;
161
+ const scroll_speed = is_trackpad ? 30 : 10;
162
+ if (event.altKey || event.metaKey) {
163
+ // Handle zooming with our new simplified logic
164
+ const local_point = this.image_editor_context.app.stage.toLocal(event.global);
165
+ const zoom_delta = -event.deltaY * (is_trackpad ? 0.001 : 0.0005);
166
+ const new_scale = this.local_scale * (1 + zoom_delta);
167
+ this.zoom_to_point(new_scale, local_point, true);
168
+ }
169
+ else {
170
+ // Handle panning with bounds
171
+ const delta_x = event.deltaX;
172
+ const delta_y = event.deltaY;
173
+ const raw_position = {
174
+ x: this.local_position.x - (delta_x / 100) * scroll_speed,
175
+ y: this.local_position.y - (delta_y / 100) * scroll_speed
176
+ };
177
+ const canvas = this.image_editor_context.app.screen;
178
+ const scaled_width = this.local_dimensions.width * this.local_scale;
179
+ const scaled_height = this.local_dimensions.height * this.local_scale;
180
+ const available_width = canvas.width - this.border_padding * 2;
181
+ const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
182
+ let final_position = { ...raw_position };
183
+ // If image is smaller than available space, center it
184
+ if (scaled_width <= available_width) {
185
+ final_position.x = (canvas.width - scaled_width) / 2;
186
+ }
187
+ else {
188
+ // Apply 50% rule for horizontal bounds
189
+ const max_offset = canvas.width / 2;
190
+ const left_bound = max_offset;
191
+ const right_bound = canvas.width - max_offset - scaled_width;
192
+ final_position.x = Math.min(Math.max(raw_position.x, right_bound), left_bound);
193
+ }
194
+ if (scaled_height <= available_height) {
195
+ final_position.y =
196
+ (canvas.height - this.pad_bottom - scaled_height) / 2;
197
+ }
198
+ else {
199
+ // Apply 50% rule for vertical bounds, accounting for pad_bottom
200
+ const max_offset = (canvas.height - this.pad_bottom) / 2;
201
+ const top_bound = max_offset;
202
+ const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
203
+ final_position.y = Math.min(Math.max(raw_position.y, bottom_bound), top_bound);
204
+ }
205
+ this.image_editor_context.set_image_properties({
206
+ scale: this.local_scale,
207
+ position: final_position
208
+ });
209
+ }
210
+ }
211
+ /**
212
+ * Cleans up resources and event listeners
213
+ */
214
+ cleanup() {
215
+ const stage = this?.image_editor_context?.app?.stage;
216
+ if (!stage)
217
+ return;
218
+ stage.removeEventListener("wheel", this.handle_wheel.bind(this));
219
+ if ("ontouchstart" in window) {
220
+ stage.removeEventListener("touchstart", this.handle_touch_start.bind(this));
221
+ stage.removeEventListener("touchmove", this.handle_touch_move.bind(this));
222
+ stage.removeEventListener("touchend", this.handle_touch_end.bind(this));
223
+ }
224
+ else {
225
+ stage.removeEventListener("pointerdown", this.handle_pointer_down.bind(this));
226
+ stage.removeEventListener("pointermove", this.handle_pointer_move.bind(this));
227
+ stage.removeEventListener("pointerup", this.handle_pointer_up.bind(this));
228
+ stage.removeEventListener("pointerupoutside", this.handle_pointer_up.bind(this));
229
+ }
230
+ }
231
+ /**
232
+ * Gets the dimensions of the container
233
+ * @returns {Promise<{width: number, height: number}>} The container dimensions
234
+ * @private
235
+ */
236
+ async get_container_dimensions() {
237
+ const bounds = this.image_editor_context.image_container.getLocalBounds();
238
+ return {
239
+ width: bounds.width,
240
+ height: bounds.height
241
+ };
242
+ }
243
+ /**
244
+ * Calculates the minimum zoom level to fit the image in the viewport
245
+ * @param {number} container_width - The width of the container
246
+ * @param {number} container_height - The height of the container
247
+ * @returns {number} The minimum zoom level
248
+ * @private
249
+ */
250
+ calculate_min_zoom(container_width, container_height) {
251
+ const canvas = this.image_editor_context.app.screen;
252
+ const viewport_width = canvas.width;
253
+ const viewport_height = canvas.height;
254
+ if (!container_width ||
255
+ !viewport_width ||
256
+ !container_height ||
257
+ !viewport_height) {
258
+ return 1;
259
+ }
260
+ // Calculate available space accounting for padding
261
+ const available_width = viewport_width - this.border_padding * 2;
262
+ const available_height = viewport_height - this.border_padding * 2 - this.pad_bottom;
263
+ // Calculate zoom ratios for both dimensions
264
+ const width_ratio = available_width / container_width;
265
+ const height_ratio = available_height / container_height;
266
+ // Use the smaller ratio to ensure the image fits in both dimensions
267
+ // Note: we no longer cap at 1, as 'fit' can be above 1
268
+ return Math.min(width_ratio, height_ratio);
269
+ }
270
+ /**
271
+ * Handles touch start events
272
+ * @param {TouchEvent} event - The touch event
273
+ * @private
274
+ */
275
+ handle_touch_start(event) {
276
+ event.preventDefault();
277
+ // Cast to any to access TouchEvent properties
278
+ const touchEvent = event;
279
+ if (touchEvent.touches && touchEvent.touches.length === 2) {
280
+ this.is_pinching = true;
281
+ this.last_pinch_distance = Math.hypot(touchEvent.touches[0].pageX - touchEvent.touches[1].pageX, touchEvent.touches[0].pageY - touchEvent.touches[1].pageY);
282
+ }
283
+ else if (touchEvent.touches && touchEvent.touches.length === 1) {
284
+ this.is_dragging = true;
285
+ const rect = this.image_editor_context.app.view.getBoundingClientRect();
286
+ this.last_touch_position = new Point(touchEvent.touches[0].pageX - rect.left, touchEvent.touches[0].pageY - rect.top);
287
+ }
288
+ }
289
+ /**
290
+ * Handles touch move events
291
+ * @param {TouchEvent} event - The touch event
292
+ * @private
293
+ */
294
+ handle_touch_move(event) {
295
+ event.preventDefault();
296
+ // Cast to any to access TouchEvent properties
297
+ const touchEvent = event;
298
+ if (this.is_pinching &&
299
+ touchEvent.touches &&
300
+ touchEvent.touches.length === 2) {
301
+ const rect = this.image_editor_context.app.view.getBoundingClientRect();
302
+ const current_distance = Math.hypot(touchEvent.touches[0].pageX - touchEvent.touches[1].pageX, touchEvent.touches[0].pageY - touchEvent.touches[1].pageY);
303
+ const pinch_center = {
304
+ x: (touchEvent.touches[0].pageX + touchEvent.touches[1].pageX) / 2 -
305
+ rect.left,
306
+ y: (touchEvent.touches[0].pageY + touchEvent.touches[1].pageY) / 2 -
307
+ rect.top
308
+ };
309
+ const scale_val = current_distance / this.last_pinch_distance;
310
+ this.last_pinch_distance = current_distance;
311
+ // Update zoom based on pinch
312
+ this.zoom_to_point(this.local_scale * scale_val, pinch_center);
313
+ }
314
+ else if (this.is_dragging &&
315
+ touchEvent.touches &&
316
+ touchEvent.touches.length === 1 &&
317
+ this.last_touch_position) {
318
+ const rect = this.image_editor_context.app.view.getBoundingClientRect();
319
+ const current_position = new Point(touchEvent.touches[0].pageX - rect.left, touchEvent.touches[0].pageY - rect.top);
320
+ const dx = current_position.x - this.last_touch_position.x;
321
+ const dy = current_position.y - this.last_touch_position.y;
322
+ // Update position based on drag
323
+ this.image_editor_context.set_image_properties({
324
+ position: {
325
+ x: this.local_position.x + dx,
326
+ y: this.local_position.y + dy
327
+ }
328
+ });
329
+ this.last_touch_position = current_position;
330
+ }
331
+ }
332
+ /**
333
+ * Handles touch end events
334
+ * @param {TouchEvent} event - The touch event
335
+ * @private
336
+ */
337
+ handle_touch_end(event) {
338
+ event.preventDefault();
339
+ // Cast to any to access TouchEvent properties
340
+ const touchEvent = event;
341
+ if (touchEvent.touches && touchEvent.touches.length < 2) {
342
+ this.is_pinching = false;
343
+ if (touchEvent.touches && touchEvent.touches.length === 1) {
344
+ const rect = this.image_editor_context.app.view.getBoundingClientRect();
345
+ this.last_touch_position = new Point(touchEvent.touches[0].pageX - rect.left, touchEvent.touches[0].pageY - rect.top);
346
+ this.is_dragging = true;
347
+ }
348
+ }
349
+ if (touchEvent.touches && touchEvent.touches.length === 0) {
350
+ this.is_dragging = false;
351
+ this.last_touch_position = null;
352
+ this.last_pinch_distance = 0;
353
+ }
354
+ }
355
+ /**
356
+ * Gets the bounded position to keep the image within the viewport
357
+ * @param {Object} position - The position to bound
358
+ * @param {number} position.x - The x coordinate
359
+ * @param {number} position.y - The y coordinate
360
+ * @returns {Object} The bounded position
361
+ * @private
362
+ */
363
+ get_bounded_position(position) {
364
+ const canvas = this.image_editor_context.app.screen;
365
+ const scaled_width = this.local_dimensions.width * this.local_scale;
366
+ const scaled_height = this.local_dimensions.height * this.local_scale;
367
+ // Calculate center position, accounting for pad_bottom
368
+ const center_position = {
369
+ x: (canvas.width - scaled_width) / 2,
370
+ y: (canvas.height - scaled_height - this.pad_bottom) / 2
371
+ };
372
+ if (scaled_width <= canvas.width && scaled_height <= canvas.height) {
373
+ return center_position;
374
+ }
375
+ let x = position.x;
376
+ let y = position.y;
377
+ if (scaled_width <= canvas.width) {
378
+ x = center_position.x;
379
+ }
380
+ else {
381
+ const min_x = canvas.width - scaled_width;
382
+ const max_x = 0;
383
+ x = Math.max(min_x, Math.min(max_x, position.x));
384
+ }
385
+ if (scaled_height <= canvas.height - this.pad_bottom) {
386
+ y = center_position.y;
387
+ }
388
+ else {
389
+ const min_y = canvas.height - scaled_height - this.pad_bottom;
390
+ const max_y = 0;
391
+ y = Math.max(min_y, Math.min(max_y, position.y));
392
+ }
393
+ return { x, y };
394
+ }
395
+ /**
396
+ * Zooms to a specific point with a new zoom level
397
+ * @param {number} new_zoom - The new zoom level
398
+ * @param {Object} point - The point to zoom to
399
+ * @param {number} point.x - The x coordinate of the point
400
+ * @param {number} point.y - The y coordinate of the point
401
+ * @param {boolean} hard - Whether to apply a hard zoom (no animation)
402
+ * @param {boolean} is_fit_zoom - Whether this is a fit zoom operation
403
+ * @private
404
+ */
405
+ zoom_to_point(new_zoom, point, hard, is_fit_zoom) {
406
+ // Get the cursor position relative to the image's top-left corner
407
+ const cursor_relative_to_image = {
408
+ x: (point.x - this.local_position.x) / this.local_scale,
409
+ y: (point.y - this.local_position.y) / this.local_scale
410
+ };
411
+ // Calculate what percentage of the image width/height this represents
412
+ const image_percentages = {
413
+ x: cursor_relative_to_image.x / this.local_dimensions.width,
414
+ y: cursor_relative_to_image.y / this.local_dimensions.height
415
+ };
416
+ // Apply zoom limits
417
+ const fit_zoom = this.calculate_min_zoom(this.local_dimensions.width, this.local_dimensions.height);
418
+ // Use the fit zoom or 1, whichever is smaller as the minimum zoom
419
+ const min_zoom = Math.min(fit_zoom, 1);
420
+ new_zoom = Math.min(Math.max(new_zoom, min_zoom), this.max_zoom);
421
+ // Calculate new dimensions
422
+ const new_scaled_width = this.local_dimensions.width * new_zoom;
423
+ const new_scaled_height = this.local_dimensions.height * new_zoom;
424
+ // Calculate new position maintaining the same relative cursor position
425
+ let new_position = {
426
+ x: point.x - new_scaled_width * image_percentages.x,
427
+ y: point.y - new_scaled_height * image_percentages.y
428
+ };
429
+ // Center the image if we're at minimum zoom or if this is a fit zoom operation
430
+ if (new_zoom === min_zoom || is_fit_zoom) {
431
+ const canvas_width = this.image_editor_context.app.screen.width;
432
+ const canvas_height = this.image_editor_context.app.screen.height;
433
+ // Center the image, accounting for pad_bottom
434
+ new_position = {
435
+ x: (canvas_width - new_scaled_width) / 2,
436
+ y: (canvas_height - this.pad_bottom - new_scaled_height) / 2
437
+ };
438
+ }
439
+ this.image_editor_context.set_image_properties({
440
+ scale: new_zoom,
441
+ position: new_position,
442
+ animate: typeof hard === "boolean" ? !hard : new_zoom === min_zoom
443
+ });
444
+ this.min_zoom.set(new_zoom === min_zoom);
445
+ }
446
+ /**
447
+ * Handles pointer down events
448
+ * @param {FederatedPointerEvent} event - The pointer event
449
+ * @private
450
+ */
451
+ handle_pointer_down(event) {
452
+ // Only allow dragging if the current tool is 'pan'
453
+ if (event.button === 0 && this.current_tool === "pan") {
454
+ this.is_pointer_dragging = true;
455
+ this.drag_start.copyFrom(event.global);
456
+ this.drag_start.x -= this.local_position.x;
457
+ this.drag_start.y -= this.local_position.y;
458
+ }
459
+ }
460
+ /**
461
+ * Handles pointer move events
462
+ * @param {FederatedPointerEvent} event - The pointer event
463
+ * @private
464
+ */
465
+ handle_pointer_move(event) {
466
+ // Only handle move if we're dragging and the tool is 'pan'
467
+ if (this.is_pointer_dragging && this.current_tool === "pan") {
468
+ const raw_position = {
469
+ x: event.global.x - this.drag_start.x,
470
+ y: event.global.y - this.drag_start.y
471
+ };
472
+ const canvas = this.image_editor_context.app.screen;
473
+ const scaled_width = this.local_dimensions.width * this.local_scale;
474
+ const scaled_height = this.local_dimensions.height * this.local_scale;
475
+ const available_width = canvas.width - this.border_padding * 2;
476
+ const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
477
+ let final_position = { ...raw_position };
478
+ // If image is smaller than available space, center it
479
+ if (scaled_width <= available_width) {
480
+ final_position.x = (canvas.width - scaled_width) / 2;
481
+ }
482
+ else {
483
+ // Allow free movement during drag, bounds will be applied on pointer up
484
+ final_position.x = raw_position.x;
485
+ }
486
+ if (scaled_height <= available_height) {
487
+ final_position.y =
488
+ (canvas.height - this.pad_bottom - scaled_height) / 2;
489
+ }
490
+ else {
491
+ // Allow free movement during drag, bounds will be applied on pointer up
492
+ final_position.y = raw_position.y;
493
+ }
494
+ this.image_editor_context.set_image_properties({
495
+ scale: this.local_scale,
496
+ position: final_position
497
+ });
498
+ }
499
+ }
500
+ /**
501
+ * Handles pointer up events
502
+ * @param {FederatedPointerEvent} event - The pointer event
503
+ * @private
504
+ */
505
+ handle_pointer_up(event) {
506
+ // Only handle up if we're dragging and the tool is 'pan'
507
+ if (this.is_pointer_dragging && this.current_tool === "pan") {
508
+ this.is_pointer_dragging = false;
509
+ const raw_position = {
510
+ x: event.global.x - this.drag_start.x,
511
+ y: event.global.y - this.drag_start.y
512
+ };
513
+ const canvas = this.image_editor_context.app.screen;
514
+ const scaled_width = this.local_dimensions.width * this.local_scale;
515
+ const scaled_height = this.local_dimensions.height * this.local_scale;
516
+ const available_width = canvas.width - this.border_padding * 2;
517
+ const available_height = canvas.height - this.border_padding * 2 - this.pad_bottom;
518
+ let final_position = { ...raw_position };
519
+ // If image is smaller than available space, center it
520
+ if (scaled_width <= available_width) {
521
+ final_position.x = (canvas.width - scaled_width) / 2;
522
+ }
523
+ else {
524
+ // Apply 50% rule for horizontal snapping
525
+ const max_offset = canvas.width / 2;
526
+ const left_bound = max_offset;
527
+ const right_bound = canvas.width - max_offset - scaled_width;
528
+ final_position.x = Math.min(Math.max(raw_position.x, right_bound), left_bound);
529
+ }
530
+ if (scaled_height <= available_height) {
531
+ final_position.y =
532
+ (canvas.height - this.pad_bottom - scaled_height) / 2;
533
+ }
534
+ else {
535
+ // Apply 50% rule for vertical snapping, accounting for pad_bottom
536
+ const max_offset = (canvas.height - this.pad_bottom) / 2;
537
+ const top_bound = max_offset;
538
+ const bottom_bound = canvas.height - this.pad_bottom - max_offset - scaled_height;
539
+ final_position.y = Math.min(Math.max(raw_position.y, bottom_bound), top_bound);
540
+ }
541
+ this.image_editor_context.set_image_properties({
542
+ scale: this.local_scale,
543
+ position: final_position,
544
+ animate: true
545
+ });
546
+ }
547
+ }
548
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gradio/imageeditor",
3
- "version": "0.12.14",
3
+ "version": "0.13.0",
4
4
  "description": "Gradio UI packages",
5
5
  "type": "module",
6
6
  "author": "",
@@ -8,16 +8,17 @@
8
8
  "private": false,
9
9
  "dependencies": {
10
10
  "@types/tinycolor2": "^1.4.6",
11
- "pixi.js": "^7.3.2",
11
+ "pixi-filters": "^6.1.0",
12
+ "pixi.js": "^8.7.3",
12
13
  "tinycolor2": "^1.6.0",
13
- "@gradio/atoms": "^0.14.1",
14
- "@gradio/client": "^1.14.1",
15
- "@gradio/icons": "^0.10.0",
16
- "@gradio/statustracker": "^0.10.6",
17
- "@gradio/upload": "^0.15.7",
18
- "@gradio/image": "^0.21.7",
14
+ "@gradio/atoms": "^0.15.0",
15
+ "@gradio/icons": "^0.11.0",
16
+ "@gradio/image": "^0.22.0",
17
+ "@gradio/utils": "^0.10.1",
18
+ "@gradio/statustracker": "^0.10.7",
19
+ "@gradio/upload": "^0.16.0",
19
20
  "@gradio/wasm": "^0.18.1",
20
- "@gradio/utils": "^0.10.1"
21
+ "@gradio/client": "^1.14.1"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@gradio/preview": "^0.13.0"