@bensitu/image-editor 2.1.0 → 2.3.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 (118) hide show
  1. package/README.md +305 -95
  2. package/dist/cjs/index.cjs +3460 -843
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/esm/annotation/annotation-lock.js +7 -0
  5. package/dist/esm/annotation/annotation-lock.js.map +1 -0
  6. package/dist/esm/annotation/annotation-manager.js +217 -0
  7. package/dist/esm/annotation/annotation-manager.js.map +1 -0
  8. package/dist/esm/annotation/annotation-style.js +50 -0
  9. package/dist/esm/annotation/annotation-style.js.map +1 -0
  10. package/dist/esm/annotation/draw-controller.js +114 -0
  11. package/dist/esm/annotation/draw-controller.js.map +1 -0
  12. package/dist/esm/annotation/text-controller.js +234 -0
  13. package/dist/esm/annotation/text-controller.js.map +1 -0
  14. package/dist/esm/core/default-options.js +240 -9
  15. package/dist/esm/core/default-options.js.map +1 -1
  16. package/dist/esm/core/editor-object-kind.js +37 -0
  17. package/dist/esm/core/editor-object-kind.js.map +1 -0
  18. package/dist/esm/core/errors.js +19 -0
  19. package/dist/esm/core/errors.js.map +1 -1
  20. package/dist/esm/core/layer-order.js +100 -0
  21. package/dist/esm/core/layer-order.js.map +1 -0
  22. package/dist/esm/core/public-types.js +34 -1
  23. package/dist/esm/core/public-types.js.map +1 -1
  24. package/dist/esm/core/state-serializer.js +112 -24
  25. package/dist/esm/core/state-serializer.js.map +1 -1
  26. package/dist/esm/crop/crop-controller.js +220 -10
  27. package/dist/esm/crop/crop-controller.js.map +1 -1
  28. package/dist/esm/export/export-format.js.map +1 -1
  29. package/dist/esm/export/export-service.js +157 -168
  30. package/dist/esm/export/export-service.js.map +1 -1
  31. package/dist/esm/export/overlay-merge-service.js +75 -0
  32. package/dist/esm/export/overlay-merge-service.js.map +1 -0
  33. package/dist/esm/history/history-manager.js +2 -2
  34. package/dist/esm/history/history-manager.js.map +1 -1
  35. package/dist/esm/image/image-loader.js +20 -51
  36. package/dist/esm/image/image-loader.js.map +1 -1
  37. package/dist/esm/image/transform-controller.js +42 -0
  38. package/dist/esm/image/transform-controller.js.map +1 -1
  39. package/dist/esm/image-editor.js +1200 -72
  40. package/dist/esm/image-editor.js.map +1 -1
  41. package/dist/esm/index.js +1 -1
  42. package/dist/esm/index.js.map +1 -1
  43. package/dist/esm/mask/mask-factory.js +39 -14
  44. package/dist/esm/mask/mask-factory.js.map +1 -1
  45. package/dist/esm/mask/mask-label-manager.js +2 -0
  46. package/dist/esm/mask/mask-label-manager.js.map +1 -1
  47. package/dist/esm/mask/mask-list.js.map +1 -1
  48. package/dist/esm/mask/mask-style.js.map +1 -1
  49. package/dist/esm/mosaic/mosaic-controller.js +24 -28
  50. package/dist/esm/mosaic/mosaic-controller.js.map +1 -1
  51. package/dist/esm/utils/file.js +10 -0
  52. package/dist/esm/utils/file.js.map +1 -1
  53. package/dist/esm/utils/image-element-loader.js +55 -0
  54. package/dist/esm/utils/image-element-loader.js.map +1 -0
  55. package/dist/esm/utils/pointer.js +28 -0
  56. package/dist/esm/utils/pointer.js.map +1 -0
  57. package/dist/types/annotation/annotation-lock.d.ts +12 -0
  58. package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
  59. package/dist/types/annotation/annotation-manager.d.ts +33 -0
  60. package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
  61. package/dist/types/annotation/annotation-style.d.ts +13 -0
  62. package/dist/types/annotation/annotation-style.d.ts.map +1 -0
  63. package/dist/types/annotation/draw-controller.d.ts +43 -0
  64. package/dist/types/annotation/draw-controller.d.ts.map +1 -0
  65. package/dist/types/annotation/text-controller.d.ts +47 -0
  66. package/dist/types/annotation/text-controller.d.ts.map +1 -0
  67. package/dist/types/core/default-options.d.ts +14 -2
  68. package/dist/types/core/default-options.d.ts.map +1 -1
  69. package/dist/types/core/editor-object-kind.d.ts +29 -0
  70. package/dist/types/core/editor-object-kind.d.ts.map +1 -0
  71. package/dist/types/core/errors.d.ts +11 -1
  72. package/dist/types/core/errors.d.ts.map +1 -1
  73. package/dist/types/core/layer-order.d.ts +21 -0
  74. package/dist/types/core/layer-order.d.ts.map +1 -0
  75. package/dist/types/core/public-types.d.ts +272 -56
  76. package/dist/types/core/public-types.d.ts.map +1 -1
  77. package/dist/types/core/state-serializer.d.ts +34 -5
  78. package/dist/types/core/state-serializer.d.ts.map +1 -1
  79. package/dist/types/crop/crop-controller.d.ts +18 -14
  80. package/dist/types/crop/crop-controller.d.ts.map +1 -1
  81. package/dist/types/export/export-format.d.ts +9 -40
  82. package/dist/types/export/export-format.d.ts.map +1 -1
  83. package/dist/types/export/export-service.d.ts +45 -41
  84. package/dist/types/export/export-service.d.ts.map +1 -1
  85. package/dist/types/export/overlay-merge-service.d.ts +38 -0
  86. package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
  87. package/dist/types/history/history-manager.d.ts +11 -14
  88. package/dist/types/history/history-manager.d.ts.map +1 -1
  89. package/dist/types/image/image-loader.d.ts +27 -22
  90. package/dist/types/image/image-loader.d.ts.map +1 -1
  91. package/dist/types/image/image-resampler.d.ts +1 -1
  92. package/dist/types/image/transform-controller.d.ts +19 -14
  93. package/dist/types/image/transform-controller.d.ts.map +1 -1
  94. package/dist/types/image-editor.d.ts +93 -15
  95. package/dist/types/image-editor.d.ts.map +1 -1
  96. package/dist/types/index.d.cts +3 -3
  97. package/dist/types/index.d.cts.map +1 -1
  98. package/dist/types/index.d.ts +3 -3
  99. package/dist/types/index.d.ts.map +1 -1
  100. package/dist/types/mask/mask-factory.d.ts.map +1 -1
  101. package/dist/types/mask/mask-label-manager.d.ts +10 -9
  102. package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
  103. package/dist/types/mask/mask-list.d.ts +11 -12
  104. package/dist/types/mask/mask-list.d.ts.map +1 -1
  105. package/dist/types/mask/mask-style.d.ts +19 -20
  106. package/dist/types/mask/mask-style.d.ts.map +1 -1
  107. package/dist/types/mosaic/mosaic-controller.d.ts +3 -3
  108. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -1
  109. package/dist/types/ui/visibility-state.d.ts +2 -2
  110. package/dist/types/utils/file.d.ts +13 -0
  111. package/dist/types/utils/file.d.ts.map +1 -1
  112. package/dist/types/utils/image-element-loader.d.ts +19 -0
  113. package/dist/types/utils/image-element-loader.d.ts.map +1 -0
  114. package/dist/types/utils/pointer.d.ts +16 -0
  115. package/dist/types/utils/pointer.d.ts.map +1 -0
  116. package/dist/umd/image-editor.umd.js +1 -1
  117. package/dist/umd/image-editor.umd.js.map +1 -1
  118. package/package.json +1 -1
package/README.md CHANGED
@@ -6,8 +6,9 @@
6
6
 
7
7
  A lightweight, TypeScript-first canvas image editor built on top of
8
8
  [Fabric.js](https://fabricjs.com/) v7. `ImageEditor` wraps a Fabric canvas
9
- with image loading, scale and rotation, mask creation, crop, Mosaic mode, history
10
- (undo/redo), and base64/file export exposed as a single canonical class
9
+ with image loading, scale and rotation, mask creation, Text and Draw
10
+ annotations, crop, Mosaic mode, base-image flips, history (undo/redo), layer operations, and
11
+ base64/file export — exposed as a single canonical class
11
12
  with a stable public surface.
12
13
 
13
14
  ## Demo
@@ -30,8 +31,13 @@ with a stable public surface.
30
31
  - Crop session with mask preservation toggle and atomic apply/cancel
31
32
  - Mosaic mode with circular brush preview, runtime brush/block controls, and
32
33
  one undo step per successful pixelation click
34
+ - Unified editor-owned object model for base images, masks, annotations, and
35
+ session overlays
36
+ - Text annotations, Draw mode, annotation update/delete APIs, and layer
37
+ operations
33
38
  - Base64 and `File` exports with PNG/JPEG/WebP support, configurable
34
- multiplier, and mask compositing
39
+ multiplier, independent mask/annotation rendering toggles, and state-mutating
40
+ mask/annotation merge APIs
35
41
 
36
42
  ## Requirements
37
43
 
@@ -123,12 +129,44 @@ resolve to `undefined`.
123
129
  <input id="rotateLeftDegreesInput" type="number" value="90" />
124
130
  <button id="rotateRightButton">Rotate Right</button>
125
131
  <input id="rotateRightDegreesInput" type="number" value="90" />
132
+ <button id="flipHorizontalButton">Flip Horizontal</button>
133
+ <button id="flipVerticalButton">Flip Vertical</button>
126
134
 
127
135
  <button id="createMaskButton">Add Mask</button>
128
136
  <button id="removeSelectedMaskButton">Remove Mask</button>
129
137
  <button id="removeAllMasksButton">Remove All Masks</button>
138
+ <ul id="maskList"></ul>
139
+
140
+ <button id="enterTextModeButton">Text</button>
141
+ <button id="exitTextModeButton">Exit Text</button>
142
+ <input id="textColorInput" type="color" value="#ff0000" />
143
+ <input id="textFontSizeInput" type="number" min="8" max="160" value="32" />
144
+
145
+ <button id="enterDrawModeButton">Draw</button>
146
+ <button id="exitDrawModeButton">Exit Draw</button>
147
+ <input id="drawColorInput" type="color" value="#ff0000" />
148
+ <input id="drawBrushSizeInput" type="range" min="1" max="80" value="8" />
149
+
150
+ <button id="removeSelectedAnnotationButton">Remove Annotation</button>
151
+ <button id="removeAllAnnotationsButton">Remove All Annotations</button>
152
+ <button id="deleteSelectedObjectButton">Delete Selected</button>
153
+ <button id="bringSelectedObjectForwardButton">Forward</button>
154
+ <button id="sendSelectedObjectBackwardButton">Backward</button>
155
+ <button id="bringSelectedObjectToFrontButton">Front</button>
156
+ <button id="sendSelectedObjectToBackButton">Back</button>
157
+ <ul id="annotationList"></ul>
130
158
 
131
159
  <button id="enterCropModeButton">Crop</button>
160
+ <select id="cropAspectRatioSelect">
161
+ <option value="free">Free</option>
162
+ <option value="1:1">1:1</option>
163
+ <option value="3:4">3:4</option>
164
+ <option value="4:3">4:3</option>
165
+ <option value="3:2">3:2</option>
166
+ <option value="2:3">2:3</option>
167
+ <option value="9:16">9:16</option>
168
+ <option value="16:9">16:9</option>
169
+ </select>
132
170
  <button id="applyCropButton">Apply Crop</button>
133
171
  <button id="cancelCropButton">Cancel Crop</button>
134
172
 
@@ -143,14 +181,14 @@ resolve to `undefined`.
143
181
  <input id="mosaicBlockSizeInput" type="range" min="2" max="40" step="1" value="8" />
144
182
  </label>
145
183
 
146
- <button id="mergeMasksButton">Merge</button>
184
+ <button id="mergeMasksButton">Merge Masks</button>
185
+ <button id="mergeAnnotationsButton">Merge Annotations</button>
147
186
  <button id="downloadImageButton">Download</button>
148
187
  <button id="undoButton">Undo</button>
149
188
  <button id="redoButton">Redo</button>
150
189
  <button id="resetImageTransformButton">Reset</button>
151
190
 
152
191
  <input id="imageInput" type="file" accept="image/*" />
153
- <ul id="maskList"></ul>
154
192
  ```
155
193
 
156
194
  ### TypeScript / ESM
@@ -168,6 +206,14 @@ const editor = new ImageEditor(fabric, {
168
206
  brushSize: 48,
169
207
  blockSize: 8,
170
208
  },
209
+ defaultTextConfig: {
210
+ fill: '#ff0000',
211
+ fontSize: 32,
212
+ },
213
+ defaultDrawConfig: {
214
+ color: '#ff0000',
215
+ brushSize: 8,
216
+ },
171
217
  } satisfies ImageEditorOptions);
172
218
 
173
219
  editor.init({
@@ -178,23 +224,43 @@ editor.init({
178
224
  rotateLeftDegreesInput: 'rotateLeftDegreesInput',
179
225
  rotateRightButton: 'rotateRightButton',
180
226
  rotateRightDegreesInput: 'rotateRightDegreesInput',
227
+ flipHorizontalButton: 'flipHorizontalButton',
228
+ flipVerticalButton: 'flipVerticalButton',
181
229
  createMaskButton: 'createMaskButton',
182
230
  removeSelectedMaskButton: 'removeSelectedMaskButton',
183
231
  removeAllMasksButton: 'removeAllMasksButton',
232
+ maskList: 'maskList',
184
233
  enterCropModeButton: 'enterCropModeButton',
234
+ cropAspectRatioSelect: 'cropAspectRatioSelect',
185
235
  applyCropButton: 'applyCropButton',
186
236
  cancelCropButton: 'cancelCropButton',
187
237
  enterMosaicModeButton: 'enterMosaicModeButton',
188
238
  exitMosaicModeButton: 'exitMosaicModeButton',
189
239
  mosaicBrushSizeInput: 'mosaicBrushSizeInput',
190
240
  mosaicBlockSizeInput: 'mosaicBlockSizeInput',
241
+ enterTextModeButton: 'enterTextModeButton',
242
+ exitTextModeButton: 'exitTextModeButton',
243
+ textColorInput: 'textColorInput',
244
+ textFontSizeInput: 'textFontSizeInput',
245
+ enterDrawModeButton: 'enterDrawModeButton',
246
+ exitDrawModeButton: 'exitDrawModeButton',
247
+ drawColorInput: 'drawColorInput',
248
+ drawBrushSizeInput: 'drawBrushSizeInput',
249
+ removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
250
+ removeAllAnnotationsButton: 'removeAllAnnotationsButton',
251
+ deleteSelectedObjectButton: 'deleteSelectedObjectButton',
252
+ mergeAnnotationsButton: 'mergeAnnotationsButton',
253
+ bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
254
+ sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
255
+ bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
256
+ sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
257
+ annotationList: 'annotationList',
191
258
  mergeMasksButton: 'mergeMasksButton',
192
259
  downloadImageButton: 'downloadImageButton',
193
260
  undoButton: 'undoButton',
194
261
  redoButton: 'redoButton',
195
262
  resetImageTransformButton: 'resetImageTransformButton',
196
263
  imageInput: 'imageInput',
197
- maskList: 'maskList',
198
264
  });
199
265
 
200
266
  // Load an image programmatically (base64 data URL).
@@ -203,6 +269,9 @@ await editor.loadImage('data:image/jpeg;base64,...');
203
269
  // Add a rectangular mask, then export the result as base64.
204
270
  const mask: MaskConfig = { shape: 'rect', width: 120, height: 80, left: '25%', top: '25%' };
205
271
  editor.createMask(mask);
272
+ editor.createTextAnnotation({ text: 'Label', left: 120, top: 80 });
273
+ editor.enterDrawMode();
274
+ editor.setDrawConfig({ color: '#00aaff', brushSize: 10 });
206
275
 
207
276
  const dataUrl = await editor.exportImageBase64({ fileType: 'png' });
208
277
  ```
@@ -217,17 +286,33 @@ const editor = new ImageEditor(fabric, { canvasWidth: 800, canvasHeight: 600 });
217
286
  ```
218
287
 
219
288
  In v2, `require('@bensitu/image-editor')` returns a namespace object with
220
- `ImageEditor`, `default`, and `isMaskObject`; it does not return the
221
- constructor directly.
289
+ `ImageEditor`, `default`, and the editor object guards
290
+ (`isBaseImageObject`, `isMaskObject`, `isAnnotationObject`,
291
+ `isTextAnnotationObject`, `isDrawAnnotationObject`, `isSessionObject`, and
292
+ `isEditableOverlayObject`); it does not return the constructor directly.
222
293
 
223
294
  ## Public API
224
295
 
225
296
  `ImageEditor` is the only public class. The package barrel re-exports it as
226
- both the default export and a named export, alongside `isMaskObject` and the
227
- documented public types. Internal helpers (animation queue, command, history
297
+ both the default export and a named export, alongside the editor object guards
298
+ and the documented public types. Internal helpers (animation queue, command, history
228
299
  manager, controllers, services, managers, utility modules) are intentionally
229
300
  not exported and may change without notice.
230
301
 
302
+ ### Object model
303
+
304
+ Every editor-owned Fabric object carries strict `editorObjectKind` metadata:
305
+
306
+ | Kind | Meaning |
307
+ | ------------ | ------------------------------------------------------------------------ |
308
+ | `baseImage` | The committed image at the bottom of the stack. |
309
+ | `mask` | Editable mask overlay with required `maskId`, `maskUid`, and `maskName`. |
310
+ | `annotation` | Editable Text or Draw overlay. Masks are not annotations. |
311
+ | `session` | Internal crop labels, mask labels, Mosaic previews, and tool previews. |
312
+
313
+ Session objects are never persisted, exported, or user-deletable. Strict type
314
+ guards reject legacy mask-like objects that do not carry `editorObjectKind`.
315
+
231
316
  ### Constructor
232
317
 
233
318
  ```ts
@@ -244,12 +329,12 @@ new ImageEditor(options?: ImageEditorOptions) // UMD: reads globalThis.fabric
244
329
 
245
330
  ### Image loading
246
331
 
247
- | Method | Description |
248
- | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
249
- | `loadImage(base64, options?)` | Load an image from a `data:image/...` URL. Returns `Promise<void>`. Transactional: any failure restores the prior canvas, scroll, overflow, and snapshot state. |
250
- | `isImageLoaded()` | Returns `true` if a valid image is currently loaded on the canvas. |
251
- | `isBusy()` | Returns `true` while the editor is loading, animating, in crop mode, or in Mosaic mode. |
252
- | `setLayoutMode(mode)` | Select the layout strategy for future image loads. `mode` is `'fit'`, `'cover'`, or `'expand'`. |
332
+ | Method | Description |
333
+ | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
334
+ | `loadImage(base64, options?)` | Load a supported raster image data URL (`png`, `jpeg`, `webp`, `gif`, or `bmp`). Returns `Promise<void>`. Transactional: any failure restores the prior canvas, scroll, overflow, and snapshot state. |
335
+ | `isImageLoaded()` | Returns `true` if a valid image is currently loaded on the canvas. |
336
+ | `isBusy()` | Returns `true` while the editor is loading, animating, or in Crop, Mosaic, Text, or Draw mode. |
337
+ | `setLayoutMode(mode)` | Select the layout strategy for future image loads. `mode` is `'fit'`, `'cover'`, or `'expand'`. |
253
338
 
254
339
  `LoadImageOptions` currently includes `preserveScroll?: boolean` for
255
340
  preserving the container's scroll position across both successful loads and
@@ -280,11 +365,18 @@ the JPEG, PNG, or WebP export options.
280
365
 
281
366
  ### Transforms
282
367
 
283
- | Method | Description |
284
- | ----------------------- | --------------------------------------------------------------------------------------------------- |
285
- | `scaleImage(factor)` | Scale to `factor` (clamped to `[minScale, maxScale]`). Non-finite values are no-ops. Animated. |
286
- | `rotateImage(degrees)` | Rotate to `degrees`. Non-finite values resolve without changing canvas state. Animated. |
287
- | `resetImageTransform()` | Animate to scale 1 and rotation 0. Records exactly one history entry covering the entire transform. |
368
+ | Method | Description |
369
+ | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
370
+ | `scaleImage(factor)` | Scale to `factor` (clamped to `[minScale, maxScale]`). Non-finite values are no-ops. Animated. |
371
+ | `rotateImage(degrees)` | Rotate to `degrees`. Non-finite values resolve without changing canvas state. Animated. |
372
+ | `flipHorizontal()` | Toggle horizontal flip on the base image only. Masks, annotations, and session overlays are not mirrored. Returns `Promise<void>`. |
373
+ | `flipVertical()` | Toggle vertical flip on the base image only. Masks, annotations, and session overlays are not mirrored. Returns `Promise<void>`. |
374
+ | `resetImageTransform()` | Animate to scale 1, rotation 0, and an unflipped state. Records exactly one history entry covering the entire transform. |
375
+
376
+ ```ts
377
+ await editor.flipHorizontal();
378
+ await editor.flipVertical();
379
+ ```
288
380
 
289
381
  ### Masks
290
382
 
@@ -297,6 +389,8 @@ the JPEG, PNG, or WebP export options.
297
389
  `MaskConfig` supports rect, circle, ellipse, polygon, and a custom
298
390
  `fabricGenerator`. Falsy values in `styles` (`0`, `false`, `null`, `''`,
299
391
  `NaN`) are applied verbatim.
392
+ Every mask is marked as `editorObjectKind: 'mask'` and includes required
393
+ `maskId`, `maskUid`, and `maskName` metadata.
300
394
 
301
395
  Use `defaultMaskConfig` to define constructor-level defaults for masks created
302
396
  through either `createMask()` or the built-in `createMaskButton`. Per-call
@@ -321,11 +415,29 @@ editor.createMask({ color: 'rgba(0, 128, 255, 0.35)' }); // Per-call override.
321
415
 
322
416
  ### Crop
323
417
 
324
- | Method | Description |
325
- | ----------------- | ------------------------------------------------------------------------------------ |
326
- | `enterCropMode()` | Add an interactive crop rectangle on top of the image. |
327
- | `applyCrop()` | Apply the current crop region. Atomic: failure rolls back to the pre-crop snapshot. |
328
- | `cancelCrop()` | Cancel crop mode and restore the prior canvas state without pushing a history entry. |
418
+ | Method | Description |
419
+ | --------------------------- | ------------------------------------------------------------------------------------ |
420
+ | `enterCropMode(options?)` | Add an interactive crop rectangle on top of the image. |
421
+ | `setCropAspectRatio(ratio)` | Update the active crop rectangle ratio while crop mode is open. |
422
+ | `applyCrop()` | Apply the current crop region. Atomic: failure rolls back to the pre-crop snapshot. |
423
+ | `cancelCrop()` | Cancel crop mode and restore the prior canvas state without pushing a history entry. |
424
+
425
+ `enterCropMode({ aspectRatio })` locks the crop rectangle to a preset or custom
426
+ ratio. Supported preset strings are `'free'`, `'1:1'`, `'3:4'`, `'4:3'`,
427
+ `'3:2'`, `'2:3'`, `'16:9'`, and `'9:16'`. Custom ratios use
428
+ `{ width, height }`. Per-call options override `crop.aspectRatio` from the
429
+ constructor.
430
+
431
+ When `cropAspectRatioSelect` is bound through `init(idMap)`, the built-in Crop
432
+ button uses the select's current value and changing the select while crop mode
433
+ is open calls `setCropAspectRatio()` to resize the active crop rectangle.
434
+
435
+ ```ts
436
+ editor.enterCropMode({ aspectRatio: '1:1' });
437
+ editor.enterCropMode({ aspectRatio: '16:9' });
438
+ editor.setCropAspectRatio('4:3');
439
+ editor.enterCropMode({ aspectRatio: { width: 2, height: 1 } });
440
+ ```
329
441
 
330
442
  ### Mosaic mode
331
443
 
@@ -374,35 +486,108 @@ Mosaic edits replace base image pixels rather than adding Fabric overlay
374
486
  objects, exported images include the Mosaic naturally while the preview circle
375
487
  is never exported or saved in history.
376
488
 
489
+ ### Text and Draw annotations
490
+
491
+ Tool modes are mutually exclusive: Crop, Mosaic, Text, and Draw cannot be
492
+ active at the same time. `getEditorState()` reports `activeToolMode` plus
493
+ `isCropMode`, `isMosaicMode`, `isTextMode`, and `isDrawMode`.
494
+
495
+ | Method | Description |
496
+ | ------------------------------------ | -------------------------------------------------------------------------- |
497
+ | `getAnnotations()` | Return current annotation objects in canvas order. Masks are not included. |
498
+ | `enterTextMode()` / `exitTextMode()` | Click empty canvas space to create editable text annotations. |
499
+ | `createTextAnnotation(config?)` | Create a text annotation directly and return it. |
500
+ | `getTextConfig()` | Return a defensive copy of the current Text config. |
501
+ | `setTextConfig(config)` | Patch current Text config without history. |
502
+ | `resetTextConfig()` | Restore Text config from constructor defaults. |
503
+ | `setTextColor(color)` | Convenience setter for text fill color. |
504
+ | `setTextFontSize(size)` | Convenience setter for text font size. |
505
+ | `enterDrawMode()` / `exitDrawMode()` | Use Fabric free drawing; each stroke becomes a Draw annotation. |
506
+ | `getDrawConfig()` | Return a defensive copy of the current Draw config. |
507
+ | `setDrawConfig(config)` | Patch current Draw config without history. |
508
+ | `resetDrawConfig()` | Restore Draw config from constructor defaults. |
509
+ | `setDrawColor(color)` | Convenience setter for brush color. |
510
+ | `setDrawBrushSize(size)` | Convenience setter for brush size. |
511
+ | `updateAnnotation(id, config)` | Update an annotation by id. |
512
+ | `updateSelectedAnnotation(config)` | Update selected annotation objects. |
513
+ | `removeSelectedAnnotation()` | Remove selected unlocked annotations. |
514
+ | `removeAllAnnotations(options?)` | Remove annotations only. Masks are preserved. |
515
+ | `deleteSelectedObject()` | Convenience deletion for selected masks and unlocked annotations. |
516
+
517
+ ```ts
518
+ editor.enterTextMode();
519
+ editor.setTextConfig({ fill: '#ff0000', fontSize: 32 });
520
+ editor.updateSelectedAnnotation({ fill: '#00aaff' });
521
+
522
+ editor.enterDrawMode();
523
+ editor.setDrawConfig({ color: '#00aaff', brushSize: 10 });
524
+ ```
525
+
526
+ Annotations carry `annotationHidden` and `annotationLocked` metadata. Hidden
527
+ annotations are not rendered during export. Locked annotations are not
528
+ selectable/editable and are skipped by selected-annotation update/delete
529
+ operations unless an API explicitly opts into forced removal.
530
+
531
+ ### Layer operations
532
+
533
+ Editable overlays include masks and annotations. Layer operations keep the
534
+ base image below overlays and session objects above overlays.
535
+
536
+ | Method | Description |
537
+ | ------------------------------ | ------------------------------------------------- |
538
+ | `bringSelectedObjectForward()` | Move selected editable overlays one step up. |
539
+ | `sendSelectedObjectBackward()` | Move selected editable overlays one step down. |
540
+ | `bringSelectedObjectToFront()` | Move selected editable overlays to overlay front. |
541
+ | `sendSelectedObjectToBack()` | Move selected editable overlays to overlay back. |
542
+
377
543
  ### Merge and export
378
544
 
379
545
  | Method | Description |
380
546
  | ----------------------------- | ---------------------------------------------------------------------------------------------- |
381
547
  | `mergeMasks()` | Bake masks into the base image atomically. Returns `Promise<void>`. |
548
+ | `mergeAnnotations()` | Bake annotations into the base image atomically. Returns `Promise<void>`. |
382
549
  | `exportImageBase64(options?)` | Returns `Promise<string>` (data URL). Resolves to `''` with a warning when no image is loaded. |
383
550
  | `exportImageFile(options?)` | Returns `Promise<File>`. Rejects when no image is loaded. |
384
- | `downloadImage(fileName?)` | Triggers a browser download. No-op when no image is loaded. |
551
+ | `downloadImage(options?)` | Returns `Promise<void>` and triggers a browser download. No-op when no image is loaded. |
385
552
 
386
- `Base64ExportOptions` and `ImageFileExportOptions` separate mask compositing
387
- from export region selection:
553
+ All export APIs use the same `ImageExportOptions` shape:
388
554
 
389
- | Option | Default | Description |
390
- | ------------ | --------- | --------------------------------------------------------------------------- |
391
- | `mergeMask` | `true` | Flatten masks into exported pixels. Mask labels are never exported. |
392
- | `exportArea` | `'image'` | `'image'` clips to the image bounding box; `'canvas'` exports the canvas. |
393
- | `fileType` | `'jpeg'` | `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, or matching full MIME strings. |
394
- | `format` | `'jpeg'` | Alias for `fileType` on `exportImageBase64`; `fileType` wins when both set. |
395
- | `quality` | `0.92` | Lossy quality clamped to `[0, 1]`; ignored for PNG. |
396
- | `multiplier` | `1` | Output resolution multiplier. |
397
- | `fileName` | option | `ImageFileExportOptions` only. Defaults to `defaultDownloadFileName`. |
555
+ | Option | Default | Description |
556
+ | ------------------ | --------- | --------------------------------------------------------------------------------- |
557
+ | `mergeMasks` | `true` | Render masks into exported pixels. Mask labels are never exported. |
558
+ | `mergeAnnotations` | `true` | Render non-hidden annotations into exported pixels. |
559
+ | `exportArea` | `'image'` | `'image'` clips to the image bounding box; `'canvas'` exports the canvas. |
560
+ | `fileType` | `'jpeg'` | `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, or matching full MIME strings. |
561
+ | `format` | `'jpeg'` | Alias for `fileType` on all export APIs; `fileType` wins when both set. |
562
+ | `quality` | `0.92` | Lossy quality clamped to `[0, 1]`; ignored for PNG. |
563
+ | `multiplier` | `1` | Output resolution multiplier. |
564
+ | `fileName` | option | `exportImageFile()` and `downloadImage()`. Defaults to `defaultDownloadFileName`. |
398
565
 
399
566
  ```ts
400
- await editor.exportImageBase64({ exportArea: 'image', mergeMask: true });
401
- await editor.exportImageBase64({ exportArea: 'image', mergeMask: false });
402
- await editor.exportImageBase64({ exportArea: 'canvas', mergeMask: true });
403
- await editor.exportImageBase64({ exportArea: 'canvas', mergeMask: false });
567
+ await editor.exportImageBase64({ mergeMasks: true, mergeAnnotations: true });
568
+ await editor.exportImageBase64({ mergeMasks: false, mergeAnnotations: true });
569
+ await editor.exportImageBase64({ mergeMasks: true, mergeAnnotations: false });
570
+ await editor.exportImageBase64({ mergeMasks: false, mergeAnnotations: false });
571
+
572
+ const dataUrl = await editor.exportImageBase64({ fileType: 'png', exportArea: 'image' });
573
+ const file = await editor.exportImageFile({
574
+ fileType: 'webp',
575
+ quality: 0.85,
576
+ fileName: 'edited',
577
+ });
578
+ await editor.downloadImage({
579
+ fileType: 'png',
580
+ fileName: 'edited',
581
+ mergeMasks: false,
582
+ mergeAnnotations: false,
583
+ });
404
584
  ```
405
585
 
586
+ `mergeMasks` and `mergeAnnotations` in export options affect the rendered output
587
+ only. They do not mutate editor state, remove objects, or push history entries.
588
+ State-mutating merge APIs are `mergeMasks()` and `mergeAnnotations()`.
589
+ `mergeMasks()` preserves annotations; `mergeAnnotations()` preserves masks.
590
+
406
591
  ### State and history
407
592
 
408
593
  | Method | Description |
@@ -418,53 +603,59 @@ Pass an `ImageEditorOptions` object as the second constructor argument
418
603
  (or as the only argument when using the UMD global form). Unknown keys are
419
604
  ignored; nested `label` and `crop` objects are deep-merged with the defaults.
420
605
 
421
- | Option | Default | Description |
422
- | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
423
- | `canvasWidth` | `800` | Initial and hidden-container fallback canvas width in pixels. |
424
- | `canvasHeight` | `600` | Initial and hidden-container fallback canvas height in pixels. |
425
- | `backgroundColor` | `'transparent'` | Fabric canvas background color. |
426
- | `animationDuration` | `300` | Duration of scale and rotate animations (ms). |
427
- | `minScale` | `0.1` | Minimum scale factor. |
428
- | `maxScale` | `5.0` | Maximum scale factor. |
429
- | `scaleStep` | `0.05` | Scale delta per zoom step. |
430
- | `rotationStep` | `90` | Rotation step in degrees. |
431
- | `defaultLayoutMode` | `'expand'` | Initial layout mode for image loads until changed by `setLayoutMode()`. Use `'fit'`, `'cover'`, or `'expand'`. Invalid runtime values fall back to `'expand'`. |
432
- | `downsampleOnLoad` | `true` | Downsample large images on load. |
433
- | `downsampleMaxWidth` | `4000` | Max width before downsampling kicks in. |
434
- | `downsampleMaxHeight` | `3000` | Max height before downsampling kicks in. |
435
- | `downsampleQuality` | `0.92` | Lossy quality used when downsampling and exporting. |
436
- | `preserveSourceFormat` | `true` | Preserve PNG/WebP MIME through downsampling unless `downsampleMimeType` is set. |
437
- | `downsampleMimeType` | `null` | Explicit downsample MIME type. Overrides `preserveSourceFormat`. |
438
- | `imageLoadTimeoutMs` | `30000` | Maximum duration for both decode and Fabric image creation during `loadImage`. |
439
- | `exportMultiplier` | `1` | Output resolution multiplier. |
440
- | `maxExportPixels` | `50000000` | Maximum output pixel count after applying the export multiplier. Invalid values fall back to this default. |
441
- | `maxHistorySize` | `50` | Maximum undo-history entries. Snapshots may include full image data URLs, so large images can duplicate memory across history entries. Lower this for memory-constrained pages. |
442
- | `exportAreaByDefault` | `'image'` | Default export region for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
443
- | `mergeMaskByDefault` | `true` | Default mask compositing behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
444
- | `defaultMaskWidth` | `50` | Default mask width. |
445
- | `defaultMaskHeight` | `80` | Default mask height. |
446
- | `defaultMaskConfig` | `{}` | Defaults applied by `createMask()` after `defaultMaskWidth` / `defaultMaskHeight` and before per-call config. Supports `MaskConfig` fields except `onCreate` and `fabricGenerator`. |
447
- | `defaultMosaicConfig` | see source | Defaults used to initialize the current Mosaic tool config. Supports `brushSize`, `blockSize`, preview circle styling, `outputFileType`, and `outputQuality`. Runtime Mosaic setters update the current config only. |
448
- | `maskRotatable` | `false` | Allow masks to be rotated by the user. |
449
- | `maskLabelOnSelect` | `true` | Show a label above a selected mask. |
450
- | `maskLabelOffset` | `3` | Pixel offset of the label from the mask's top-left corner. |
451
- | `maskName` | `'mask'` | Prefix used for auto-generated mask names. |
452
- | `groupSelection` | `false` | Allow Fabric multi-object group selection on the canvas. |
453
- | `showPlaceholder` | `true` | Show a placeholder element while no image is loaded. |
454
- | `initialImageBase64` | `null` | Base64 data URL auto-loaded after construction. |
455
- | `defaultDownloadFileName` | `'edited_image.jpg'` | Default file name used by `downloadImage()`. |
456
- | `onImageLoadStart` | `null` | Called before a valid image load begins. |
457
- | `onImageLoaded` | `null` | Called as `(info, context)` once after a successful `loadImage`. Extra arguments are ignored by existing zero-argument JavaScript handlers. |
458
- | `onImageCleared` | `null` | Called when a committed image is replaced or cleared. |
459
- | `onImageChanged` | `null` | Called with a safe editor state snapshot after visible editor state changes. |
460
- | `onBusyChange` | `null` | Called only when the public busy state changes. |
461
- | `onEditorDisposed` | `null` | Called once when `dispose()` performs teardown. |
462
- | `onMasksChanged` | `null` | Called with a shallow copy of current mask objects after the mask collection changes. |
463
- | `onSelectionChange` | `null` | Called with selected mask payload after mask selection changes. |
464
- | `onError` | `null` | Called as `(error, message)` when the editor reports an error. |
465
- | `onWarning` | `null` | Called as `(error, message)` when the editor reports a recoverable warning. |
466
- | `label` | see source | `LabelConfig` for selected-mask labels (`getText`, `textOptions`, `create`). |
467
- | `crop` | see source | `CropConfig` (`minWidth`, `minHeight`, `padding`, `hideMasksDuringCrop`, `preserveMasksAfterCrop`, `allowRotationOfCropRect`, `exportFileType`, `exportQuality`). `applyCrop()` preserves the current image format by default (`'source'`) and falls back to PNG when unknown. |
606
+ | Option | Default | Description |
607
+ | --------------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
608
+ | `canvasWidth` | `800` | Initial and hidden-container fallback canvas width in pixels. |
609
+ | `canvasHeight` | `600` | Initial and hidden-container fallback canvas height in pixels. |
610
+ | `backgroundColor` | `'transparent'` | Fabric canvas background color. |
611
+ | `animationDuration` | `300` | Duration of scale and rotate animations (ms). |
612
+ | `minScale` | `0.1` | Minimum scale factor. |
613
+ | `maxScale` | `5.0` | Maximum scale factor. |
614
+ | `scaleStep` | `0.05` | Scale delta per zoom step. |
615
+ | `rotationStep` | `90` | Rotation step in degrees. |
616
+ | `defaultLayoutMode` | `'expand'` | Initial layout mode for image loads until changed by `setLayoutMode()`. Use `'fit'`, `'cover'`, or `'expand'`. Invalid runtime values fall back to `'expand'`. |
617
+ | `downsampleOnLoad` | `true` | Downsample large images on load. |
618
+ | `downsampleMaxWidth` | `4000` | Max width before downsampling kicks in. |
619
+ | `downsampleMaxHeight` | `3000` | Max height before downsampling kicks in. |
620
+ | `downsampleQuality` | `0.92` | Lossy quality used when downsampling and exporting. |
621
+ | `preserveSourceFormat` | `true` | Preserve PNG/WebP MIME through downsampling unless `downsampleMimeType` is set. |
622
+ | `downsampleMimeType` | `null` | Explicit downsample MIME type. Overrides `preserveSourceFormat`. |
623
+ | `imageLoadTimeoutMs` | `30000` | Maximum duration for both decode and Fabric image creation during `loadImage`. |
624
+ | `exportMultiplier` | `1` | Output resolution multiplier. |
625
+ | `maxExportPixels` | `50000000` | Maximum output pixel count after applying the export multiplier. Invalid values fall back to this default. |
626
+ | `maxHistorySize` | `50` | Maximum undo-history entries. Snapshots may include full image data URLs, so large images can duplicate memory across history entries. Lower this for memory-constrained pages. |
627
+ | `exportAreaByDefault` | `'image'` | Default export region for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
628
+ | `mergeMasksByDefault` | `true` | Default mask rendering behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
629
+ | `mergeAnnotationsByDefault` | `true` | Default annotation rendering behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
630
+ | `defaultMaskWidth` | `50` | Default mask width. |
631
+ | `defaultMaskHeight` | `80` | Default mask height. |
632
+ | `defaultMaskConfig` | `{}` | Defaults applied by `createMask()` after `defaultMaskWidth` / `defaultMaskHeight` and before per-call config. Supports `MaskConfig` fields except `onCreate` and `fabricGenerator`. |
633
+ | `defaultMosaicConfig` | see source | Defaults used to initialize the current Mosaic tool config. Supports `brushSize`, `blockSize`, preview circle styling, `outputFileType`, and `outputQuality`. Runtime Mosaic setters update the current config only. |
634
+ | `defaultTextConfig` | see source | Defaults used to initialize the current Text annotation config. Runtime Text setters update the current config only. |
635
+ | `defaultDrawConfig` | see source | Defaults used to initialize the current Draw mode config. Runtime Draw setters update the current config only. |
636
+ | `maskRotatable` | `false` | Allow masks to be rotated by the user. |
637
+ | `maskLabelOnSelect` | `true` | Show a label above a selected mask. |
638
+ | `maskLabelOffset` | `3` | Pixel offset of the label from the mask's top-left corner. |
639
+ | `maskName` | `'mask'` | Prefix used for auto-generated mask names. |
640
+ | `textAnnotationName` | `'text'` | Prefix used for auto-generated text annotation names. |
641
+ | `drawAnnotationName` | `'draw'` | Prefix used for auto-generated draw annotation names. |
642
+ | `groupSelection` | `false` | Allow Fabric multi-object group selection on the canvas. |
643
+ | `showPlaceholder` | `true` | Show a placeholder element while no image is loaded. |
644
+ | `initialImageBase64` | `null` | Base64 data URL auto-loaded after construction. |
645
+ | `defaultDownloadFileName` | `'edited_image'` | Default filename base used by `exportImageFile()` and `downloadImage()`. The resolved export format supplies or corrects the extension. |
646
+ | `onImageLoadStart` | `null` | Called before a valid image load begins. |
647
+ | `onImageLoaded` | `null` | Called as `(info, context)` once after a successful `loadImage`. Extra arguments are ignored by existing zero-argument JavaScript handlers. |
648
+ | `onImageCleared` | `null` | Called when a committed image is replaced or cleared. |
649
+ | `onImageChanged` | `null` | Called with a safe editor state snapshot after visible editor state changes. |
650
+ | `onBusyChange` | `null` | Called only when the public busy state changes. |
651
+ | `onEditorDisposed` | `null` | Called once when `dispose()` performs teardown. |
652
+ | `onMasksChanged` | `null` | Called with a shallow copy of current mask objects after the mask collection changes. |
653
+ | `onAnnotationsChanged` | `null` | Called with a shallow copy of current annotation objects after the annotation collection changes. |
654
+ | `onSelectionChange` | `null` | Called with selected object payload after selection changes. |
655
+ | `onError` | `null` | Called as `(error, message)` when the editor reports an error. |
656
+ | `onWarning` | `null` | Called as `(error, message)` when the editor reports a recoverable warning. |
657
+ | `label` | see source | `LabelConfig` for selected-mask labels (`getText`, `textOptions`, `create`). |
658
+ | `crop` | see source | `CropConfig` (`minWidth`, `minHeight`, `padding`, `aspectRatio`, `hideMasksDuringCrop`, `preserveMasksAfterCrop`, `allowRotationOfCropRect`, `exportFileType`, `exportQuality`). `applyCrop()` preserves the current image format by default (`'source'`) and falls back to PNG when unknown. |
468
659
 
469
660
  `crop.exportFileType` defaults to `'source'`. Supported explicit values are
470
661
  `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, and full image MIME strings. PNG is
@@ -477,14 +668,31 @@ preserve the current image MIME type when known and fall back to PNG when the
477
668
  source format cannot be determined. JPEG/WebP commits use
478
669
  `defaultMosaicConfig.outputQuality` when finite, otherwise `downsampleQuality`.
479
670
 
671
+ ## Breaking changes in v2
672
+
673
+ - `Base64ExportOptions`, `ImageFileExportOptions`, and `DownloadImageOptions`
674
+ were replaced by `ImageExportOptions`.
675
+ - `downloadImage(fileName: string)` was removed. Use
676
+ `downloadImage({ fileName })`.
677
+ - `downloadImage()` now returns `Promise<void>`.
678
+ - `defaultDownloadFileName` now defaults to `'edited_image'`; export format
679
+ resolution supplies or corrects the final extension.
680
+ - All editor-owned Fabric objects now require `editorObjectKind`.
681
+ - `isMaskObject()` is strict and rejects legacy objects with only `maskId`.
682
+ - `MaskObject.maskUid` is required.
683
+ - Serialized states without `editorObjectKind` are not migrated.
684
+ - Export option mergeMask was removed; use `mergeMasks`.
685
+ - Constructor option mergeMaskByDefault was removed; use `mergeMasksByDefault`.
686
+
480
687
  ## Example workflow
481
688
 
482
689
  1. Construct `ImageEditor` with options and call `init(idMap)` to wire it up.
483
690
  2. Load an image via `loadImage(base64)` or the bound file input.
484
- 3. Adjust with `scaleImage`, `rotateImage`, `resetImageTransform`, the crop
485
- session, or Mosaic mode.
486
- 4. Add `createMask` calls and inspect via the `maskList` element.
487
- 5. Use `mergeMasks` to bake masks into the image, then
691
+ 3. Adjust with `scaleImage`, `rotateImage`, `flipHorizontal`, `flipVertical`,
692
+ `resetImageTransform`, Crop mode, Mosaic mode, Text mode, or Draw mode.
693
+ 4. Add `createMask` and annotation calls, then inspect via `maskList` and
694
+ `annotationList`.
695
+ 5. Use `mergeMasks()` or `mergeAnnotations()` to bake overlays into the image, then
488
696
  `exportImageBase64`, `exportImageFile`, or `downloadImage` to produce
489
697
  the final output.
490
698
  6. Call `dispose()` when the editor is unmounted.
@@ -560,8 +768,10 @@ import type {
560
768
  ExportArea,
561
769
  CropExportFileType,
562
770
  MosaicOutputFileType,
563
- Base64ExportOptions,
564
- ImageFileExportOptions,
771
+ ImageExportOptions,
772
+ CropAspectRatioPreset,
773
+ CropAspectRatio,
774
+ CropModeOptions,
565
775
  ImageInfo,
566
776
  ImageEditorState,
567
777
  ImageEditorSelection,