@bensitu/image-editor 2.1.0 → 2.2.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 (113) hide show
  1. package/README.md +235 -81
  2. package/dist/cjs/index.cjs +2969 -747
  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 +232 -3
  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 +104 -24
  25. package/dist/esm/core/state-serializer.js.map +1 -1
  26. package/dist/esm/crop/crop-controller.js +2 -0
  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 +123 -135
  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 +18 -49
  36. package/dist/esm/image/image-loader.js.map +1 -1
  37. package/dist/esm/image/transform-controller.js.map +1 -1
  38. package/dist/esm/image-editor.js +1063 -60
  39. package/dist/esm/image-editor.js.map +1 -1
  40. package/dist/esm/index.js +1 -1
  41. package/dist/esm/index.js.map +1 -1
  42. package/dist/esm/mask/mask-factory.js +39 -14
  43. package/dist/esm/mask/mask-factory.js.map +1 -1
  44. package/dist/esm/mask/mask-label-manager.js +2 -0
  45. package/dist/esm/mask/mask-label-manager.js.map +1 -1
  46. package/dist/esm/mask/mask-list.js.map +1 -1
  47. package/dist/esm/mask/mask-style.js.map +1 -1
  48. package/dist/esm/mosaic/mosaic-controller.js +24 -28
  49. package/dist/esm/mosaic/mosaic-controller.js.map +1 -1
  50. package/dist/esm/utils/image-element-loader.js +55 -0
  51. package/dist/esm/utils/image-element-loader.js.map +1 -0
  52. package/dist/esm/utils/pointer.js +28 -0
  53. package/dist/esm/utils/pointer.js.map +1 -0
  54. package/dist/types/annotation/annotation-lock.d.ts +12 -0
  55. package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
  56. package/dist/types/annotation/annotation-manager.d.ts +33 -0
  57. package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
  58. package/dist/types/annotation/annotation-style.d.ts +13 -0
  59. package/dist/types/annotation/annotation-style.d.ts.map +1 -0
  60. package/dist/types/annotation/draw-controller.d.ts +43 -0
  61. package/dist/types/annotation/draw-controller.d.ts.map +1 -0
  62. package/dist/types/annotation/text-controller.d.ts +47 -0
  63. package/dist/types/annotation/text-controller.d.ts.map +1 -0
  64. package/dist/types/core/default-options.d.ts +14 -2
  65. package/dist/types/core/default-options.d.ts.map +1 -1
  66. package/dist/types/core/editor-object-kind.d.ts +29 -0
  67. package/dist/types/core/editor-object-kind.d.ts.map +1 -0
  68. package/dist/types/core/errors.d.ts +11 -1
  69. package/dist/types/core/errors.d.ts.map +1 -1
  70. package/dist/types/core/layer-order.d.ts +21 -0
  71. package/dist/types/core/layer-order.d.ts.map +1 -0
  72. package/dist/types/core/public-types.d.ts +222 -24
  73. package/dist/types/core/public-types.d.ts.map +1 -1
  74. package/dist/types/core/state-serializer.d.ts +30 -5
  75. package/dist/types/core/state-serializer.d.ts.map +1 -1
  76. package/dist/types/crop/crop-controller.d.ts +6 -7
  77. package/dist/types/crop/crop-controller.d.ts.map +1 -1
  78. package/dist/types/export/export-format.d.ts +5 -33
  79. package/dist/types/export/export-format.d.ts.map +1 -1
  80. package/dist/types/export/export-service.d.ts +24 -15
  81. package/dist/types/export/export-service.d.ts.map +1 -1
  82. package/dist/types/export/overlay-merge-service.d.ts +38 -0
  83. package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
  84. package/dist/types/history/history-manager.d.ts +11 -14
  85. package/dist/types/history/history-manager.d.ts.map +1 -1
  86. package/dist/types/image/image-loader.d.ts +22 -17
  87. package/dist/types/image/image-loader.d.ts.map +1 -1
  88. package/dist/types/image/image-resampler.d.ts +1 -1
  89. package/dist/types/image/transform-controller.d.ts +5 -7
  90. package/dist/types/image/transform-controller.d.ts.map +1 -1
  91. package/dist/types/image-editor.d.ts +75 -7
  92. package/dist/types/image-editor.d.ts.map +1 -1
  93. package/dist/types/index.d.cts +3 -3
  94. package/dist/types/index.d.cts.map +1 -1
  95. package/dist/types/index.d.ts +3 -3
  96. package/dist/types/index.d.ts.map +1 -1
  97. package/dist/types/mask/mask-factory.d.ts.map +1 -1
  98. package/dist/types/mask/mask-label-manager.d.ts +10 -9
  99. package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
  100. package/dist/types/mask/mask-list.d.ts +11 -12
  101. package/dist/types/mask/mask-list.d.ts.map +1 -1
  102. package/dist/types/mask/mask-style.d.ts +19 -20
  103. package/dist/types/mask/mask-style.d.ts.map +1 -1
  104. package/dist/types/mosaic/mosaic-controller.d.ts +3 -3
  105. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -1
  106. package/dist/types/ui/visibility-state.d.ts +2 -2
  107. package/dist/types/utils/image-element-loader.d.ts +19 -0
  108. package/dist/types/utils/image-element-loader.d.ts.map +1 -0
  109. package/dist/types/utils/pointer.d.ts +16 -0
  110. package/dist/types/utils/pointer.d.ts.map +1 -0
  111. package/dist/umd/image-editor.umd.js +1 -1
  112. package/dist/umd/image-editor.umd.js.map +1 -1
  113. 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, 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
 
@@ -127,6 +133,26 @@ resolve to `undefined`.
127
133
  <button id="createMaskButton">Add Mask</button>
128
134
  <button id="removeSelectedMaskButton">Remove Mask</button>
129
135
  <button id="removeAllMasksButton">Remove All Masks</button>
136
+ <ul id="maskList"></ul>
137
+
138
+ <button id="enterTextModeButton">Text</button>
139
+ <button id="exitTextModeButton">Exit Text</button>
140
+ <input id="textColorInput" type="color" value="#ff0000" />
141
+ <input id="textFontSizeInput" type="number" min="8" max="160" value="32" />
142
+
143
+ <button id="enterDrawModeButton">Draw</button>
144
+ <button id="exitDrawModeButton">Exit Draw</button>
145
+ <input id="drawColorInput" type="color" value="#ff0000" />
146
+ <input id="drawBrushSizeInput" type="range" min="1" max="80" value="8" />
147
+
148
+ <button id="removeSelectedAnnotationButton">Remove Annotation</button>
149
+ <button id="removeAllAnnotationsButton">Remove All Annotations</button>
150
+ <button id="deleteSelectedObjectButton">Delete Selected</button>
151
+ <button id="bringSelectedObjectForwardButton">Forward</button>
152
+ <button id="sendSelectedObjectBackwardButton">Backward</button>
153
+ <button id="bringSelectedObjectToFrontButton">Front</button>
154
+ <button id="sendSelectedObjectToBackButton">Back</button>
155
+ <ul id="annotationList"></ul>
130
156
 
131
157
  <button id="enterCropModeButton">Crop</button>
132
158
  <button id="applyCropButton">Apply Crop</button>
@@ -143,14 +169,14 @@ resolve to `undefined`.
143
169
  <input id="mosaicBlockSizeInput" type="range" min="2" max="40" step="1" value="8" />
144
170
  </label>
145
171
 
146
- <button id="mergeMasksButton">Merge</button>
172
+ <button id="mergeMasksButton">Merge Masks</button>
173
+ <button id="mergeAnnotationsButton">Merge Annotations</button>
147
174
  <button id="downloadImageButton">Download</button>
148
175
  <button id="undoButton">Undo</button>
149
176
  <button id="redoButton">Redo</button>
150
177
  <button id="resetImageTransformButton">Reset</button>
151
178
 
152
179
  <input id="imageInput" type="file" accept="image/*" />
153
- <ul id="maskList"></ul>
154
180
  ```
155
181
 
156
182
  ### TypeScript / ESM
@@ -168,6 +194,14 @@ const editor = new ImageEditor(fabric, {
168
194
  brushSize: 48,
169
195
  blockSize: 8,
170
196
  },
197
+ defaultTextConfig: {
198
+ fill: '#ff0000',
199
+ fontSize: 32,
200
+ },
201
+ defaultDrawConfig: {
202
+ color: '#ff0000',
203
+ brushSize: 8,
204
+ },
171
205
  } satisfies ImageEditorOptions);
172
206
 
173
207
  editor.init({
@@ -181,6 +215,7 @@ editor.init({
181
215
  createMaskButton: 'createMaskButton',
182
216
  removeSelectedMaskButton: 'removeSelectedMaskButton',
183
217
  removeAllMasksButton: 'removeAllMasksButton',
218
+ maskList: 'maskList',
184
219
  enterCropModeButton: 'enterCropModeButton',
185
220
  applyCropButton: 'applyCropButton',
186
221
  cancelCropButton: 'cancelCropButton',
@@ -188,13 +223,29 @@ editor.init({
188
223
  exitMosaicModeButton: 'exitMosaicModeButton',
189
224
  mosaicBrushSizeInput: 'mosaicBrushSizeInput',
190
225
  mosaicBlockSizeInput: 'mosaicBlockSizeInput',
226
+ enterTextModeButton: 'enterTextModeButton',
227
+ exitTextModeButton: 'exitTextModeButton',
228
+ textColorInput: 'textColorInput',
229
+ textFontSizeInput: 'textFontSizeInput',
230
+ enterDrawModeButton: 'enterDrawModeButton',
231
+ exitDrawModeButton: 'exitDrawModeButton',
232
+ drawColorInput: 'drawColorInput',
233
+ drawBrushSizeInput: 'drawBrushSizeInput',
234
+ removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
235
+ removeAllAnnotationsButton: 'removeAllAnnotationsButton',
236
+ deleteSelectedObjectButton: 'deleteSelectedObjectButton',
237
+ mergeAnnotationsButton: 'mergeAnnotationsButton',
238
+ bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
239
+ sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
240
+ bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
241
+ sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
242
+ annotationList: 'annotationList',
191
243
  mergeMasksButton: 'mergeMasksButton',
192
244
  downloadImageButton: 'downloadImageButton',
193
245
  undoButton: 'undoButton',
194
246
  redoButton: 'redoButton',
195
247
  resetImageTransformButton: 'resetImageTransformButton',
196
248
  imageInput: 'imageInput',
197
- maskList: 'maskList',
198
249
  });
199
250
 
200
251
  // Load an image programmatically (base64 data URL).
@@ -203,6 +254,9 @@ await editor.loadImage('data:image/jpeg;base64,...');
203
254
  // Add a rectangular mask, then export the result as base64.
204
255
  const mask: MaskConfig = { shape: 'rect', width: 120, height: 80, left: '25%', top: '25%' };
205
256
  editor.createMask(mask);
257
+ editor.createTextAnnotation({ text: 'Label', left: 120, top: 80 });
258
+ editor.enterDrawMode();
259
+ editor.setDrawConfig({ color: '#00aaff', brushSize: 10 });
206
260
 
207
261
  const dataUrl = await editor.exportImageBase64({ fileType: 'png' });
208
262
  ```
@@ -216,18 +270,34 @@ const { ImageEditor } = require('@bensitu/image-editor');
216
270
  const editor = new ImageEditor(fabric, { canvasWidth: 800, canvasHeight: 600 });
217
271
  ```
218
272
 
219
- In v2, `require('@bensitu/image-editor')` returns a namespace object with
220
- `ImageEditor`, `default`, and `isMaskObject`; it does not return the
221
- constructor directly.
273
+ In v2.2, `require('@bensitu/image-editor')` returns a namespace object with
274
+ `ImageEditor`, `default`, and the editor object guards
275
+ (`isBaseImageObject`, `isMaskObject`, `isAnnotationObject`,
276
+ `isTextAnnotationObject`, `isDrawAnnotationObject`, `isSessionObject`, and
277
+ `isEditableOverlayObject`); it does not return the constructor directly.
222
278
 
223
279
  ## Public API
224
280
 
225
281
  `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
282
+ both the default export and a named export, alongside the editor object guards
283
+ and the documented public types. Internal helpers (animation queue, command, history
228
284
  manager, controllers, services, managers, utility modules) are intentionally
229
285
  not exported and may change without notice.
230
286
 
287
+ ### Object model
288
+
289
+ Every editor-owned Fabric object carries strict `editorObjectKind` metadata:
290
+
291
+ | Kind | Meaning |
292
+ | ------------ | ------------------------------------------------------------------------ |
293
+ | `baseImage` | The committed image at the bottom of the stack. |
294
+ | `mask` | Editable mask overlay with required `maskId`, `maskUid`, and `maskName`. |
295
+ | `annotation` | Editable Text or Draw overlay. Masks are not annotations. |
296
+ | `session` | Internal crop labels, mask labels, Mosaic previews, and tool previews. |
297
+
298
+ Session objects are never persisted, exported, or user-deletable. Strict type
299
+ guards reject legacy mask-like objects that do not carry `editorObjectKind`.
300
+
231
301
  ### Constructor
232
302
 
233
303
  ```ts
@@ -248,7 +318,7 @@ new ImageEditor(options?: ImageEditorOptions) // UMD: reads globalThis.fabric
248
318
  | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
249
319
  | `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
320
  | `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. |
321
+ | `isBusy()` | Returns `true` while the editor is loading, animating, or in Crop, Mosaic, Text, or Draw mode. |
252
322
  | `setLayoutMode(mode)` | Select the layout strategy for future image loads. `mode` is `'fit'`, `'cover'`, or `'expand'`. |
253
323
 
254
324
  `LoadImageOptions` currently includes `preserveScroll?: boolean` for
@@ -297,6 +367,8 @@ the JPEG, PNG, or WebP export options.
297
367
  `MaskConfig` supports rect, circle, ellipse, polygon, and a custom
298
368
  `fabricGenerator`. Falsy values in `styles` (`0`, `false`, `null`, `''`,
299
369
  `NaN`) are applied verbatim.
370
+ Every mask is marked as `editorObjectKind: 'mask'` and includes required
371
+ `maskId`, `maskUid`, and `maskName` metadata.
300
372
 
301
373
  Use `defaultMaskConfig` to define constructor-level defaults for masks created
302
374
  through either `createMask()` or the built-in `createMaskButton`. Per-call
@@ -374,35 +446,99 @@ Mosaic edits replace base image pixels rather than adding Fabric overlay
374
446
  objects, exported images include the Mosaic naturally while the preview circle
375
447
  is never exported or saved in history.
376
448
 
449
+ ### Text and Draw annotations
450
+
451
+ Tool modes are mutually exclusive: Crop, Mosaic, Text, and Draw cannot be
452
+ active at the same time. `getEditorState()` reports `activeToolMode` plus
453
+ `isCropMode`, `isMosaicMode`, `isTextMode`, and `isDrawMode`.
454
+
455
+ | Method | Description |
456
+ | ------------------------------------ | -------------------------------------------------------------------------- |
457
+ | `getAnnotations()` | Return current annotation objects in canvas order. Masks are not included. |
458
+ | `enterTextMode()` / `exitTextMode()` | Click empty canvas space to create editable text annotations. |
459
+ | `createTextAnnotation(config?)` | Create a text annotation directly and return it. |
460
+ | `getTextConfig()` | Return a defensive copy of the current Text config. |
461
+ | `setTextConfig(config)` | Patch current Text config without history. |
462
+ | `resetTextConfig()` | Restore Text config from constructor defaults. |
463
+ | `setTextColor(color)` | Convenience setter for text fill color. |
464
+ | `setTextFontSize(size)` | Convenience setter for text font size. |
465
+ | `enterDrawMode()` / `exitDrawMode()` | Use Fabric free drawing; each stroke becomes a Draw annotation. |
466
+ | `getDrawConfig()` | Return a defensive copy of the current Draw config. |
467
+ | `setDrawConfig(config)` | Patch current Draw config without history. |
468
+ | `resetDrawConfig()` | Restore Draw config from constructor defaults. |
469
+ | `setDrawColor(color)` | Convenience setter for brush color. |
470
+ | `setDrawBrushSize(size)` | Convenience setter for brush size. |
471
+ | `updateAnnotation(id, config)` | Update an annotation by id. |
472
+ | `updateSelectedAnnotation(config)` | Update selected annotation objects. |
473
+ | `removeSelectedAnnotation()` | Remove selected unlocked annotations. |
474
+ | `removeAllAnnotations(options?)` | Remove annotations only. Masks are preserved. |
475
+ | `deleteSelectedObject()` | Convenience deletion for selected masks and unlocked annotations. |
476
+
477
+ ```ts
478
+ editor.enterTextMode();
479
+ editor.setTextConfig({ fill: '#ff0000', fontSize: 32 });
480
+ editor.updateSelectedAnnotation({ fill: '#00aaff' });
481
+
482
+ editor.enterDrawMode();
483
+ editor.setDrawConfig({ color: '#00aaff', brushSize: 10 });
484
+ ```
485
+
486
+ Annotations carry `annotationHidden` and `annotationLocked` metadata. Hidden
487
+ annotations are not rendered during export. Locked annotations are not
488
+ selectable/editable and are skipped by selected-annotation update/delete
489
+ operations unless an API explicitly opts into forced removal.
490
+
491
+ ### Layer operations
492
+
493
+ Editable overlays include masks and annotations. Layer operations keep the
494
+ base image below overlays and session objects above overlays.
495
+
496
+ | Method | Description |
497
+ | ------------------------------ | ------------------------------------------------- |
498
+ | `bringSelectedObjectForward()` | Move selected editable overlays one step up. |
499
+ | `sendSelectedObjectBackward()` | Move selected editable overlays one step down. |
500
+ | `bringSelectedObjectToFront()` | Move selected editable overlays to overlay front. |
501
+ | `sendSelectedObjectToBack()` | Move selected editable overlays to overlay back. |
502
+
377
503
  ### Merge and export
378
504
 
379
505
  | Method | Description |
380
506
  | ----------------------------- | ---------------------------------------------------------------------------------------------- |
381
507
  | `mergeMasks()` | Bake masks into the base image atomically. Returns `Promise<void>`. |
508
+ | `mergeAnnotations()` | Bake annotations into the base image atomically. Returns `Promise<void>`. |
382
509
  | `exportImageBase64(options?)` | Returns `Promise<string>` (data URL). Resolves to `''` with a warning when no image is loaded. |
383
510
  | `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. |
385
-
386
- `Base64ExportOptions` and `ImageFileExportOptions` separate mask compositing
387
- from export region selection:
388
-
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`. |
511
+ | `downloadImage(options?)` | Triggers a browser download. Also accepts a filename string. No-op when no image is loaded. |
512
+
513
+ `Base64ExportOptions` and `ImageFileExportOptions` separate overlay rendering
514
+ from export region selection. `DownloadImageOptions` supports the same overlay
515
+ rendering flags plus `fileName`:
516
+
517
+ | Option | Default | Description |
518
+ | ------------------ | --------- | ------------------------------------------------------------------------------------------- |
519
+ | `mergeMasks` | `true` | Render masks into exported pixels. Mask labels are never exported. |
520
+ | `mergeAnnotations` | `true` | Render non-hidden annotations into exported pixels. |
521
+ | `exportArea` | `'image'` | `'image'` clips to the image bounding box; `'canvas'` exports the canvas. |
522
+ | `fileType` | `'jpeg'` | `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, or matching full MIME strings. |
523
+ | `format` | `'jpeg'` | Alias for `fileType` on `exportImageBase64`; `fileType` wins when both set. |
524
+ | `quality` | `0.92` | Lossy quality clamped to `[0, 1]`; ignored for PNG. |
525
+ | `multiplier` | `1` | Output resolution multiplier. |
526
+ | `fileName` | option | `ImageFileExportOptions` and `DownloadImageOptions`. Defaults to `defaultDownloadFileName`. |
398
527
 
399
528
  ```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 });
529
+ await editor.exportImageBase64({ mergeMasks: true, mergeAnnotations: true });
530
+ await editor.exportImageBase64({ mergeMasks: false, mergeAnnotations: true });
531
+ await editor.exportImageBase64({ mergeMasks: true, mergeAnnotations: false });
532
+ await editor.exportImageBase64({ mergeMasks: false, mergeAnnotations: false });
533
+ editor.downloadImage({ mergeMasks: false, mergeAnnotations: false });
534
+ editor.downloadImage('edited_image.jpg'); // Backwards-compatible filename shorthand.
404
535
  ```
405
536
 
537
+ `mergeMasks` and `mergeAnnotations` in export options affect the rendered output
538
+ only. They do not mutate editor state, remove objects, or push history entries.
539
+ State-mutating merge APIs are `mergeMasks()` and `mergeAnnotations()`.
540
+ `mergeMasks()` preserves annotations; `mergeAnnotations()` preserves masks.
541
+
406
542
  ### State and history
407
543
 
408
544
  | Method | Description |
@@ -418,53 +554,59 @@ Pass an `ImageEditorOptions` object as the second constructor argument
418
554
  (or as the only argument when using the UMD global form). Unknown keys are
419
555
  ignored; nested `label` and `crop` objects are deep-merged with the defaults.
420
556
 
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. |
557
+ | Option | Default | Description |
558
+ | --------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
559
+ | `canvasWidth` | `800` | Initial and hidden-container fallback canvas width in pixels. |
560
+ | `canvasHeight` | `600` | Initial and hidden-container fallback canvas height in pixels. |
561
+ | `backgroundColor` | `'transparent'` | Fabric canvas background color. |
562
+ | `animationDuration` | `300` | Duration of scale and rotate animations (ms). |
563
+ | `minScale` | `0.1` | Minimum scale factor. |
564
+ | `maxScale` | `5.0` | Maximum scale factor. |
565
+ | `scaleStep` | `0.05` | Scale delta per zoom step. |
566
+ | `rotationStep` | `90` | Rotation step in degrees. |
567
+ | `defaultLayoutMode` | `'expand'` | Initial layout mode for image loads until changed by `setLayoutMode()`. Use `'fit'`, `'cover'`, or `'expand'`. Invalid runtime values fall back to `'expand'`. |
568
+ | `downsampleOnLoad` | `true` | Downsample large images on load. |
569
+ | `downsampleMaxWidth` | `4000` | Max width before downsampling kicks in. |
570
+ | `downsampleMaxHeight` | `3000` | Max height before downsampling kicks in. |
571
+ | `downsampleQuality` | `0.92` | Lossy quality used when downsampling and exporting. |
572
+ | `preserveSourceFormat` | `true` | Preserve PNG/WebP MIME through downsampling unless `downsampleMimeType` is set. |
573
+ | `downsampleMimeType` | `null` | Explicit downsample MIME type. Overrides `preserveSourceFormat`. |
574
+ | `imageLoadTimeoutMs` | `30000` | Maximum duration for both decode and Fabric image creation during `loadImage`. |
575
+ | `exportMultiplier` | `1` | Output resolution multiplier. |
576
+ | `maxExportPixels` | `50000000` | Maximum output pixel count after applying the export multiplier. Invalid values fall back to this default. |
577
+ | `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. |
578
+ | `exportAreaByDefault` | `'image'` | Default export region for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
579
+ | `mergeMasksByDefault` | `true` | Default mask rendering behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
580
+ | `mergeAnnotationsByDefault` | `true` | Default annotation rendering behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
581
+ | `defaultMaskWidth` | `50` | Default mask width. |
582
+ | `defaultMaskHeight` | `80` | Default mask height. |
583
+ | `defaultMaskConfig` | `{}` | Defaults applied by `createMask()` after `defaultMaskWidth` / `defaultMaskHeight` and before per-call config. Supports `MaskConfig` fields except `onCreate` and `fabricGenerator`. |
584
+ | `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. |
585
+ | `defaultTextConfig` | see source | Defaults used to initialize the current Text annotation config. Runtime Text setters update the current config only. |
586
+ | `defaultDrawConfig` | see source | Defaults used to initialize the current Draw mode config. Runtime Draw setters update the current config only. |
587
+ | `maskRotatable` | `false` | Allow masks to be rotated by the user. |
588
+ | `maskLabelOnSelect` | `true` | Show a label above a selected mask. |
589
+ | `maskLabelOffset` | `3` | Pixel offset of the label from the mask's top-left corner. |
590
+ | `maskName` | `'mask'` | Prefix used for auto-generated mask names. |
591
+ | `textAnnotationName` | `'text'` | Prefix used for auto-generated text annotation names. |
592
+ | `drawAnnotationName` | `'draw'` | Prefix used for auto-generated draw annotation names. |
593
+ | `groupSelection` | `false` | Allow Fabric multi-object group selection on the canvas. |
594
+ | `showPlaceholder` | `true` | Show a placeholder element while no image is loaded. |
595
+ | `initialImageBase64` | `null` | Base64 data URL auto-loaded after construction. |
596
+ | `defaultDownloadFileName` | `'edited_image.jpg'` | Default file name used by `downloadImage()`. |
597
+ | `onImageLoadStart` | `null` | Called before a valid image load begins. |
598
+ | `onImageLoaded` | `null` | Called as `(info, context)` once after a successful `loadImage`. Extra arguments are ignored by existing zero-argument JavaScript handlers. |
599
+ | `onImageCleared` | `null` | Called when a committed image is replaced or cleared. |
600
+ | `onImageChanged` | `null` | Called with a safe editor state snapshot after visible editor state changes. |
601
+ | `onBusyChange` | `null` | Called only when the public busy state changes. |
602
+ | `onEditorDisposed` | `null` | Called once when `dispose()` performs teardown. |
603
+ | `onMasksChanged` | `null` | Called with a shallow copy of current mask objects after the mask collection changes. |
604
+ | `onAnnotationsChanged` | `null` | Called with a shallow copy of current annotation objects after the annotation collection changes. |
605
+ | `onSelectionChange` | `null` | Called with selected object payload after selection changes. |
606
+ | `onError` | `null` | Called as `(error, message)` when the editor reports an error. |
607
+ | `onWarning` | `null` | Called as `(error, message)` when the editor reports a recoverable warning. |
608
+ | `label` | see source | `LabelConfig` for selected-mask labels (`getText`, `textOptions`, `create`). |
609
+ | `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. |
468
610
 
469
611
  `crop.exportFileType` defaults to `'source'`. Supported explicit values are
470
612
  `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, and full image MIME strings. PNG is
@@ -477,14 +619,26 @@ preserve the current image MIME type when known and fall back to PNG when the
477
619
  source format cannot be determined. JPEG/WebP commits use
478
620
  `defaultMosaicConfig.outputQuality` when finite, otherwise `downsampleQuality`.
479
621
 
622
+ ## Breaking changes in v2.2.0
623
+
624
+ - All editor-owned Fabric objects now require `editorObjectKind`.
625
+ - `isMaskObject()` is strict and rejects legacy objects with only `maskId`.
626
+ - `MaskObject.maskUid` is required.
627
+ - Serialized states without `editorObjectKind` are not migrated.
628
+ - Export option mergeMask was removed; use `mergeMasks`.
629
+ - Constructor option mergeMaskByDefault was removed; use `mergeMasksByDefault`.
630
+ - `ImageFileExportOptions` keeps `fileType` only; `format` remains limited to
631
+ `exportImageBase64`.
632
+
480
633
  ## Example workflow
481
634
 
482
635
  1. Construct `ImageEditor` with options and call `init(idMap)` to wire it up.
483
636
  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
637
+ 3. Adjust with `scaleImage`, `rotateImage`, `resetImageTransform`, Crop mode,
638
+ Mosaic mode, Text mode, or Draw mode.
639
+ 4. Add `createMask` and annotation calls, then inspect via `maskList` and
640
+ `annotationList`.
641
+ 5. Use `mergeMasks()` or `mergeAnnotations()` to bake overlays into the image, then
488
642
  `exportImageBase64`, `exportImageFile`, or `downloadImage` to produce
489
643
  the final output.
490
644
  6. Call `dispose()` when the editor is unmounted.