@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,135 @@
1
+ # Image Tool Documentation
2
+
3
+ ## Overview
4
+
5
+ The Image Tool is a component of the image editor that handles adding and managing background images on the canvas. It allows users to upload, paste, or capture images from a webcam and set them as the background of the editing canvas. The tool manages image sizing, positioning, and integration with the layer system.
6
+
7
+ ## File Structure
8
+
9
+ - `js/imageeditor/shared/image/image.ts` - Main implementation of the image tool
10
+ - `js/imageeditor/shared/image/Sources.svelte` - UI component for image source options
11
+ - `js/imageeditor/shared/core/editor.ts` - Defines the `Tool` interface and `ImageEditorContext`
12
+ - `js/imageeditor/shared/Toolbar.svelte` - Defines the tool types and subtool types
13
+
14
+ ## Implementation Details
15
+
16
+ ### Class: `ImageTool`
17
+
18
+ The `ImageTool` class implements the `Tool` interface defined in `editor.ts`. It provides the following functionality:
19
+
20
+ - Adding images to the canvas
21
+ - Managing image dimensions and positioning
22
+ - Integrating with the layer system
23
+
24
+ ### Key Components
25
+
26
+ #### `ImageTool` Class
27
+
28
+ The main class that implements the `Tool` interface with methods:
29
+ - `setup(context, tool, subtool)` - Initializes the tool with the editor context
30
+ - `cleanup()` - Cleans up resources
31
+ - `add_image(image, fixed_canvas)` - Adds an image to the canvas
32
+ - `set_tool(tool, subtool)` - Updates the current tool and subtool
33
+
34
+ #### `AddImageCommand` Class
35
+
36
+ Implements the command pattern for adding images, allowing for undo/redo functionality:
37
+ - `start()` - Initializes the image sprite and calculates dimensions
38
+ - `execute()` - Adds the image to the canvas and updates the editor state
39
+ - `undo()` - Removes the image from the canvas
40
+
41
+ #### Helper Functions
42
+
43
+ - `fit_image_to_canvas(image_width, image_height, canvas_width, canvas_height)` - Calculates dimensions to fit an image within the canvas while maintaining aspect ratio
44
+ - `add_bg_color(container, renderer, color, width, height, resize)` - Adds a solid color background to the canvas
45
+
46
+ ### Image Processing Flow
47
+
48
+ 1. **Image Acquisition**: The image is acquired as a Blob or File from one of the sources (upload, clipboard, webcam)
49
+ 2. **Image Processing**:
50
+ - The image is converted to a bitmap and then to a PIXI.js Texture
51
+ - The dimensions are calculated based on whether fixed_canvas is true or false
52
+ - If fixed_canvas is true, the image is scaled to fit the canvas while maintaining aspect ratio
53
+ - If fixed_canvas is false, the canvas is resized to match the image dimensions
54
+
55
+ 3. **Canvas Integration**:
56
+ - The editor's image properties are updated with the new dimensions
57
+ - Existing layers are preserved and scaled to match the new dimensions
58
+ - A new background layer is created with the image sprite
59
+ - The image is centered in the viewport
60
+
61
+ 4. **Layer Management**:
62
+ - The image is added as a sprite to a background layer
63
+ - Existing layers are preserved and scaled to match the new dimensions
64
+ - If no layers exist, an initial drawing layer is created
65
+
66
+ ## Command Pattern Implementation
67
+
68
+ The image tool uses the command pattern to implement undo/redo functionality:
69
+
70
+ 1. **Command Creation**: When adding an image, an `AddImageCommand` is created
71
+ 2. **Command Execution**: The command's `execute()` method is called to add the image
72
+ 3. **Command Registration**: The command is registered with the editor's command manager
73
+ 4. **Undo Support**: The command's `undo()` method can be called to remove the image
74
+
75
+ ## Integration with Editor
76
+
77
+ The image tool integrates with the editor through the `ImageEditorContext` interface, which provides:
78
+
79
+ - `app` - The PIXI.js Application instance
80
+ - `layer_manager` - Manages the layers in the editor
81
+ - `set_image_properties` - Updates the image dimensions and position
82
+ - `set_background_image` - Sets the background image sprite
83
+ - `execute_command` - Registers a command with the command manager
84
+
85
+ ## Usage Flow
86
+
87
+ 1. The user selects an image source (upload, clipboard, webcam)
88
+ 2. The image is acquired as a Blob or File
89
+ 3. The `add_image` method is called with the image and a flag indicating whether to maintain the canvas size
90
+ 4. An `AddImageCommand` is created and executed
91
+ 5. The image is added to the canvas as a background layer
92
+ 6. The editor's state is updated with the new dimensions and position
93
+
94
+ ## Implementation Notes
95
+
96
+ ### Image Scaling
97
+
98
+ The tool provides two modes for handling image dimensions:
99
+
100
+ 1. **Fixed Canvas Mode** (fixed_canvas = true):
101
+ - The image is scaled to fit within the canvas dimensions
102
+ - The aspect ratio is maintained
103
+ - The canvas size remains unchanged
104
+
105
+ 2. **Flexible Canvas Mode** (fixed_canvas = false):
106
+ - The canvas is resized to match the image dimensions
107
+ - No scaling is applied to the image
108
+ - Existing layers are scaled to match the new dimensions
109
+
110
+ ### Layer Preservation
111
+
112
+ When adding a new background image:
113
+
114
+ 1. Existing layers are preserved
115
+ 2. Layer textures are captured before modification
116
+ 3. New layers are created with the new dimensions
117
+ 4. Content from old layers is scaled and centered on the new layers
118
+ 5. If no layers exist, an initial drawing layer is created
119
+
120
+ ## Maintenance Notes
121
+
122
+ When modifying the image tool, consider:
123
+
124
+ 1. **Command Pattern**: Ensure that all modifications to the canvas state are implemented as commands for proper undo/redo support
125
+ 2. **Layer Management**: Be careful with layer creation and destruction to avoid memory leaks
126
+ 3. **Image Scaling**: Ensure that aspect ratios are maintained when scaling images
127
+ 4. **Performance**: Large images may need to be downsampled for performance
128
+ 5. **Memory Management**: Properly destroy textures and sprites when they are no longer needed
129
+
130
+ ## Related Components
131
+
132
+ - **Toolbar**: Controls tool selection
133
+ - **ImageEditor**: Provides the context and manages the overall editor state
134
+ - **LayerManager**: Manages image layers
135
+ - **Sources.svelte**: Provides UI for selecting image sources
@@ -0,0 +1,300 @@
1
+ import {
2
+ Container,
3
+ Graphics,
4
+ Color,
5
+ type ColorSource,
6
+ RenderTexture,
7
+ Sprite,
8
+ type Renderer,
9
+ Texture
10
+ } from "pixi.js";
11
+ import { type ImageEditorContext, type Tool } from "../core/editor";
12
+ import { type Tool as ToolbarTool, type Subtool } from "../Toolbar.svelte";
13
+ import { type Command } from "../utils/commands";
14
+ import { make_graphics } from "../utils/pixi";
15
+ import { get } from "svelte/store";
16
+
17
+ /**
18
+ * Handles adding images to the editor
19
+ * Only a single image can be added at a time
20
+ * Adding an image removes the current image is one exists
21
+ */
22
+ export class ImageTool implements Tool {
23
+ name = "image";
24
+ context!: ImageEditorContext;
25
+ current_tool!: ToolbarTool;
26
+ current_subtool!: Subtool;
27
+ async setup(
28
+ context: ImageEditorContext,
29
+ tool: ToolbarTool,
30
+ subtool: Subtool
31
+ ): Promise<void> {
32
+ this.context = context;
33
+ this.current_tool = tool;
34
+ this.current_subtool = subtool;
35
+ }
36
+
37
+ cleanup(): void {}
38
+
39
+ async add_image({
40
+ image,
41
+ fixed_canvas,
42
+ border_region = 0
43
+ }: {
44
+ image: Blob | File | Texture;
45
+ fixed_canvas: boolean;
46
+ border_region?: number;
47
+ }): Promise<void> {
48
+ const image_command = new AddImageCommand(
49
+ this.context,
50
+ image,
51
+ fixed_canvas,
52
+ border_region
53
+ );
54
+
55
+ await image_command.start();
56
+ this.context.execute_command(image_command);
57
+ }
58
+
59
+ set_tool(tool: ToolbarTool, subtool: Subtool): void {
60
+ this.current_tool = tool;
61
+ this.current_subtool = subtool;
62
+ }
63
+ }
64
+
65
+ interface BgImageCommand extends Command {
66
+ /**
67
+ * Initial setup for the bg command
68
+ * @returns
69
+ */
70
+ start: () => Promise<[number, number]>;
71
+ }
72
+
73
+ export class AddImageCommand implements BgImageCommand {
74
+ sprite: Sprite;
75
+ fixed_canvas: boolean;
76
+ context: ImageEditorContext;
77
+ background: Blob | File | Texture;
78
+ current_canvas_size: { width: number; height: number };
79
+ current_scale: number;
80
+ current_position: { x: number; y: number };
81
+ border_region: number;
82
+
83
+ scaled_width!: number;
84
+ scaled_height!: number;
85
+
86
+ constructor(
87
+ context: ImageEditorContext,
88
+ background: Blob | File | Texture,
89
+ fixed_canvas: boolean,
90
+ border_region = 0
91
+ ) {
92
+ this.context = context;
93
+ this.background = background;
94
+ this.fixed_canvas = fixed_canvas;
95
+ this.border_region = border_region;
96
+ this.current_canvas_size = get(this.context.dimensions);
97
+ this.current_scale = get(this.context.scale);
98
+ this.current_position = get(this.context.position);
99
+ this.sprite = new Sprite();
100
+ }
101
+
102
+ async start(): Promise<[number, number]> {
103
+ // Create the sprite from the blob
104
+ let image_texture: Texture;
105
+ if (this.background instanceof Texture) {
106
+ image_texture = this.background;
107
+ } else {
108
+ const img = await createImageBitmap(this.background);
109
+ image_texture = Texture.from(img);
110
+ }
111
+
112
+ this.sprite = new Sprite(image_texture);
113
+
114
+ return this.handle_image();
115
+ }
116
+
117
+ private handle_image(): [number, number] {
118
+ // Handle fixed canvas differently when border region is present
119
+ if (this.fixed_canvas) {
120
+ // Calculate the effective canvas size accounting for border region
121
+ const effectiveCanvasWidth = Math.max(
122
+ this.current_canvas_size.width - this.border_region * 2,
123
+ 10
124
+ );
125
+ const effectiveCanvasHeight = Math.max(
126
+ this.current_canvas_size.height - this.border_region * 2,
127
+ 10
128
+ );
129
+
130
+ // Fit the image to the canvas while maintaining aspect ratio
131
+
132
+ const { width, height, x, y } = fit_image_to_canvas(
133
+ this.sprite.width,
134
+ this.sprite.height,
135
+ effectiveCanvasWidth,
136
+ effectiveCanvasHeight
137
+ );
138
+
139
+ this.sprite.width = width;
140
+ this.sprite.height = height;
141
+
142
+ // Center the image in the canvas
143
+ this.sprite.x = x + this.border_region;
144
+ this.sprite.y = y + this.border_region;
145
+ } else {
146
+ // For non-fixed canvas, use natural dimensions plus border
147
+ const width = this.sprite.width;
148
+ const height = this.sprite.height;
149
+
150
+ // Position at the border's offset from origin
151
+ this.sprite.x = this.border_region;
152
+ this.sprite.y = this.border_region;
153
+
154
+ return [width + this.border_region * 2, height + this.border_region * 2];
155
+ }
156
+
157
+ return [this.current_canvas_size.width, this.current_canvas_size.height];
158
+ }
159
+
160
+ async execute(): Promise<void> {
161
+ // First ensure we have run start() to create the sprite and get dimensions
162
+ const [width, height] = await this.start();
163
+
164
+ // Update image properties with the original dimensions and center in viewport
165
+ this.context.set_image_properties({
166
+ scale: 1, // Start at 1:1 scale
167
+ position: {
168
+ x: this.context.app.screen.width / 2,
169
+ y: this.context.app.screen.height / 2
170
+ },
171
+ width: this.fixed_canvas ? this.current_canvas_size.width : width,
172
+ height: this.fixed_canvas ? this.current_canvas_size.height : height
173
+ });
174
+
175
+ // Create new background layer and add the sprite
176
+ const background_layer = this.context.layer_manager.create_background_layer(
177
+ this.fixed_canvas ? this.current_canvas_size.width : width,
178
+ this.fixed_canvas ? this.current_canvas_size.height : height
179
+ );
180
+ this.sprite.zIndex = 0;
181
+ background_layer.addChild(this.sprite);
182
+
183
+ // Resize and preserve content of existing layers
184
+ this.context.layer_manager.reset_layers(
185
+ this.fixed_canvas ? this.current_canvas_size.width : width,
186
+ this.fixed_canvas ? this.current_canvas_size.height : height,
187
+ true
188
+ );
189
+
190
+ this.context.set_background_image(this.sprite);
191
+
192
+ this.context.reset();
193
+ }
194
+
195
+ async undo(): Promise<void> {
196
+ if (this.sprite) {
197
+ this.sprite.destroy();
198
+ }
199
+ // The background layer will be automatically cleaned up when a new image is added
200
+ }
201
+ }
202
+
203
+ /**
204
+ * Command that sets a background
205
+ */
206
+ interface BgColorCommand extends Command {
207
+ /**
208
+ * Initial setup for the bg command
209
+ * @returns
210
+ */
211
+ start: () => [number, number];
212
+ }
213
+
214
+ /**
215
+ * Adds a background color to the canvas.
216
+ * @param container The container to add the image to.
217
+ * @param renderer The renderer to use for the image.
218
+ * @param color The background color to add.
219
+ * @param width The width of the background.
220
+ * @param height The height of the background.
221
+ * @param resize The function to resize the canvas.
222
+ * @returns A command that can be used to undo the action.
223
+ */
224
+ export function add_bg_color(
225
+ container: Container,
226
+ renderer: Renderer,
227
+ color: ColorSource,
228
+ width: number,
229
+ height: number,
230
+ resize: (width: number, height: number) => void
231
+ ): BgColorCommand {
232
+ let sprite: Sprite;
233
+ return {
234
+ start() {
235
+ container.removeChildren();
236
+ const graphics = make_graphics(1);
237
+ const texture = RenderTexture.create({
238
+ width,
239
+ height
240
+ });
241
+
242
+ renderer.render(graphics, { renderTexture: texture });
243
+ sprite = new Sprite(texture);
244
+ return [sprite.width, sprite.height];
245
+ },
246
+ async execute() {
247
+ resize(sprite.width, sprite.height);
248
+ sprite.zIndex = 1;
249
+ container.addChild(sprite);
250
+ },
251
+ undo() {
252
+ container.removeChildren();
253
+ }
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Calculates new dimensions and position for an image to fit within a canvas while maintaining aspect ratio
259
+ * @param image_width Original width of the image
260
+ * @param image_height Original height of the image
261
+ * @param canvas_width Width of the canvas
262
+ * @param canvas_height Height of the canvas
263
+ * @returns Object containing new dimensions and position
264
+ */
265
+ export function fit_image_to_canvas(
266
+ image_width: number,
267
+ image_height: number,
268
+ canvas_width: number,
269
+ canvas_height: number
270
+ ): {
271
+ width: number;
272
+ height: number;
273
+ x: number;
274
+ y: number;
275
+ } {
276
+ const image_aspect_ratio = image_width / image_height;
277
+ const canvas_aspect_ratio = canvas_width / canvas_height;
278
+
279
+ let new_width: number;
280
+ let new_height: number;
281
+
282
+ if (image_aspect_ratio > canvas_aspect_ratio) {
283
+ new_width = canvas_width;
284
+ new_height = canvas_width / image_aspect_ratio;
285
+ } else {
286
+ new_height = canvas_height;
287
+ new_width = canvas_height * image_aspect_ratio;
288
+ }
289
+
290
+ // center image
291
+ const x = Math.round((canvas_width - new_width) / 2);
292
+ const y = Math.round((canvas_height - new_height) / 2);
293
+
294
+ return {
295
+ width: Math.round(new_width),
296
+ height: Math.round(new_height),
297
+ x,
298
+ y
299
+ };
300
+ }