@bensitu/image-editor 2.0.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 (147) hide show
  1. package/README.md +346 -90
  2. package/dist/cjs/index.cjs +4883 -1191
  3. package/dist/cjs/index.cjs.map +1 -1
  4. package/dist/esm/animation/animation-queue.js +16 -9
  5. package/dist/esm/animation/animation-queue.js.map +1 -1
  6. package/dist/esm/annotation/annotation-lock.js +7 -0
  7. package/dist/esm/annotation/annotation-lock.js.map +1 -0
  8. package/dist/esm/annotation/annotation-manager.js +217 -0
  9. package/dist/esm/annotation/annotation-manager.js.map +1 -0
  10. package/dist/esm/annotation/annotation-style.js +50 -0
  11. package/dist/esm/annotation/annotation-style.js.map +1 -0
  12. package/dist/esm/annotation/draw-controller.js +114 -0
  13. package/dist/esm/annotation/draw-controller.js.map +1 -0
  14. package/dist/esm/annotation/text-controller.js +234 -0
  15. package/dist/esm/annotation/text-controller.js.map +1 -0
  16. package/dist/esm/core/default-options.js +447 -11
  17. package/dist/esm/core/default-options.js.map +1 -1
  18. package/dist/esm/core/editor-object-kind.js +37 -0
  19. package/dist/esm/core/editor-object-kind.js.map +1 -0
  20. package/dist/esm/core/errors.js +19 -0
  21. package/dist/esm/core/errors.js.map +1 -1
  22. package/dist/esm/core/layer-order.js +100 -0
  23. package/dist/esm/core/layer-order.js.map +1 -0
  24. package/dist/esm/core/operation-guard.js +28 -0
  25. package/dist/esm/core/operation-guard.js.map +1 -1
  26. package/dist/esm/core/public-types.js +34 -1
  27. package/dist/esm/core/public-types.js.map +1 -1
  28. package/dist/esm/core/state-serializer.js +108 -27
  29. package/dist/esm/core/state-serializer.js.map +1 -1
  30. package/dist/esm/crop/crop-controller.js +6 -2
  31. package/dist/esm/crop/crop-controller.js.map +1 -1
  32. package/dist/esm/export/export-format.js.map +1 -1
  33. package/dist/esm/export/export-service.js +140 -141
  34. package/dist/esm/export/export-service.js.map +1 -1
  35. package/dist/esm/export/overlay-merge-service.js +75 -0
  36. package/dist/esm/export/overlay-merge-service.js.map +1 -0
  37. package/dist/esm/fabric/fabric-animation.js +56 -4
  38. package/dist/esm/fabric/fabric-animation.js.map +1 -1
  39. package/dist/esm/history/history-manager.js +2 -2
  40. package/dist/esm/history/history-manager.js.map +1 -1
  41. package/dist/esm/image/image-loader.js +27 -65
  42. package/dist/esm/image/image-loader.js.map +1 -1
  43. package/dist/esm/image/image-resampler.js +7 -2
  44. package/dist/esm/image/image-resampler.js.map +1 -1
  45. package/dist/esm/image/layout-manager.js +2 -20
  46. package/dist/esm/image/layout-manager.js.map +1 -1
  47. package/dist/esm/image/transform-controller.js.map +1 -1
  48. package/dist/esm/image-editor.js +1474 -135
  49. package/dist/esm/image-editor.js.map +1 -1
  50. package/dist/esm/index.js +1 -1
  51. package/dist/esm/index.js.map +1 -1
  52. package/dist/esm/mask/mask-factory.js +92 -43
  53. package/dist/esm/mask/mask-factory.js.map +1 -1
  54. package/dist/esm/mask/mask-label-manager.js +2 -0
  55. package/dist/esm/mask/mask-label-manager.js.map +1 -1
  56. package/dist/esm/mask/mask-list.js +9 -3
  57. package/dist/esm/mask/mask-list.js.map +1 -1
  58. package/dist/esm/mask/mask-style.js.map +1 -1
  59. package/dist/esm/mosaic/mosaic-controller.js +666 -0
  60. package/dist/esm/mosaic/mosaic-controller.js.map +1 -0
  61. package/dist/esm/mosaic/mosaic-geometry.js +81 -0
  62. package/dist/esm/mosaic/mosaic-geometry.js.map +1 -0
  63. package/dist/esm/mosaic/mosaic-pixelate.js +71 -0
  64. package/dist/esm/mosaic/mosaic-pixelate.js.map +1 -0
  65. package/dist/esm/ui/dom-bindings.js +10 -3
  66. package/dist/esm/ui/dom-bindings.js.map +1 -1
  67. package/dist/esm/utils/image-element-loader.js +55 -0
  68. package/dist/esm/utils/image-element-loader.js.map +1 -0
  69. package/dist/esm/utils/number.js.map +1 -1
  70. package/dist/esm/utils/pointer.js +28 -0
  71. package/dist/esm/utils/pointer.js.map +1 -0
  72. package/dist/types/animation/animation-queue.d.ts.map +1 -1
  73. package/dist/types/annotation/annotation-lock.d.ts +12 -0
  74. package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
  75. package/dist/types/annotation/annotation-manager.d.ts +33 -0
  76. package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
  77. package/dist/types/annotation/annotation-style.d.ts +13 -0
  78. package/dist/types/annotation/annotation-style.d.ts.map +1 -0
  79. package/dist/types/annotation/draw-controller.d.ts +43 -0
  80. package/dist/types/annotation/draw-controller.d.ts.map +1 -0
  81. package/dist/types/annotation/text-controller.d.ts +47 -0
  82. package/dist/types/annotation/text-controller.d.ts.map +1 -0
  83. package/dist/types/core/default-options.d.ts +46 -6
  84. package/dist/types/core/default-options.d.ts.map +1 -1
  85. package/dist/types/core/editor-object-kind.d.ts +29 -0
  86. package/dist/types/core/editor-object-kind.d.ts.map +1 -0
  87. package/dist/types/core/errors.d.ts +12 -2
  88. package/dist/types/core/errors.d.ts.map +1 -1
  89. package/dist/types/core/layer-order.d.ts +21 -0
  90. package/dist/types/core/layer-order.d.ts.map +1 -0
  91. package/dist/types/core/operation-guard.d.ts +2 -0
  92. package/dist/types/core/operation-guard.d.ts.map +1 -1
  93. package/dist/types/core/public-types.d.ts +341 -33
  94. package/dist/types/core/public-types.d.ts.map +1 -1
  95. package/dist/types/core/state-serializer.d.ts +32 -5
  96. package/dist/types/core/state-serializer.d.ts.map +1 -1
  97. package/dist/types/crop/crop-controller.d.ts +6 -7
  98. package/dist/types/crop/crop-controller.d.ts.map +1 -1
  99. package/dist/types/export/export-format.d.ts +5 -33
  100. package/dist/types/export/export-format.d.ts.map +1 -1
  101. package/dist/types/export/export-service.d.ts +24 -15
  102. package/dist/types/export/export-service.d.ts.map +1 -1
  103. package/dist/types/export/overlay-merge-service.d.ts +38 -0
  104. package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
  105. package/dist/types/fabric/fabric-animation.d.ts.map +1 -1
  106. package/dist/types/history/history-manager.d.ts +11 -14
  107. package/dist/types/history/history-manager.d.ts.map +1 -1
  108. package/dist/types/image/image-loader.d.ts +24 -21
  109. package/dist/types/image/image-loader.d.ts.map +1 -1
  110. package/dist/types/image/image-resampler.d.ts +2 -2
  111. package/dist/types/image/image-resampler.d.ts.map +1 -1
  112. package/dist/types/image/layout-manager.d.ts +5 -49
  113. package/dist/types/image/layout-manager.d.ts.map +1 -1
  114. package/dist/types/image/transform-controller.d.ts +6 -9
  115. package/dist/types/image/transform-controller.d.ts.map +1 -1
  116. package/dist/types/image-editor.d.ts +93 -14
  117. package/dist/types/image-editor.d.ts.map +1 -1
  118. package/dist/types/index.d.cts +3 -3
  119. package/dist/types/index.d.cts.map +1 -1
  120. package/dist/types/index.d.ts +3 -3
  121. package/dist/types/index.d.ts.map +1 -1
  122. package/dist/types/mask/mask-factory.d.ts +24 -21
  123. package/dist/types/mask/mask-factory.d.ts.map +1 -1
  124. package/dist/types/mask/mask-label-manager.d.ts +10 -9
  125. package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
  126. package/dist/types/mask/mask-list.d.ts +11 -12
  127. package/dist/types/mask/mask-list.d.ts.map +1 -1
  128. package/dist/types/mask/mask-style.d.ts +19 -20
  129. package/dist/types/mask/mask-style.d.ts.map +1 -1
  130. package/dist/types/mosaic/mosaic-controller.d.ts +82 -0
  131. package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -0
  132. package/dist/types/mosaic/mosaic-geometry.d.ts +29 -0
  133. package/dist/types/mosaic/mosaic-geometry.d.ts.map +1 -0
  134. package/dist/types/mosaic/mosaic-pixelate.d.ts +23 -0
  135. package/dist/types/mosaic/mosaic-pixelate.d.ts.map +1 -0
  136. package/dist/types/ui/dom-bindings.d.ts +3 -1
  137. package/dist/types/ui/dom-bindings.d.ts.map +1 -1
  138. package/dist/types/ui/visibility-state.d.ts +2 -2
  139. package/dist/types/utils/image-element-loader.d.ts +19 -0
  140. package/dist/types/utils/image-element-loader.d.ts.map +1 -0
  141. package/dist/types/utils/number.d.ts +1 -2
  142. package/dist/types/utils/number.d.ts.map +1 -1
  143. package/dist/types/utils/pointer.d.ts +16 -0
  144. package/dist/types/utils/pointer.d.ts.map +1 -0
  145. package/dist/umd/image-editor.umd.js +1 -1
  146. package/dist/umd/image-editor.umd.js.map +1 -1
  147. package/package.json +1 -1
package/README.md CHANGED
@@ -6,15 +6,11 @@
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, 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
- > **v2.0.0 is a behavior-preserving migration.** The v1 deprecated method
14
- > and property aliases have been removed in favor of the canonical names
15
- > documented below. See [`CHANGELOG.md`](./CHANGELOG.md) for the complete
16
- > rename map.
17
-
18
14
  ## Demo
19
15
 
20
16
  [https://bensitu.github.io/image-editor/](https://bensitu.github.io/image-editor/)
@@ -33,8 +29,15 @@ with a stable public surface.
33
29
  interleave
34
30
  - Bounded history stack with idempotent dispose
35
31
  - Crop session with mask preservation toggle and atomic apply/cancel
32
+ - Mosaic mode with circular brush preview, runtime brush/block controls, and
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
36
38
  - Base64 and `File` exports with PNG/JPEG/WebP support, configurable
37
- multiplier, and mask compositing
39
+ multiplier, independent mask/annotation rendering toggles, and state-mutating
40
+ mask/annotation merge APIs
38
41
 
39
42
  ## Requirements
40
43
 
@@ -130,19 +133,50 @@ resolve to `undefined`.
130
133
  <button id="createMaskButton">Add Mask</button>
131
134
  <button id="removeSelectedMaskButton">Remove Mask</button>
132
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>
133
156
 
134
157
  <button id="enterCropModeButton">Crop</button>
135
158
  <button id="applyCropButton">Apply Crop</button>
136
159
  <button id="cancelCropButton">Cancel Crop</button>
137
160
 
138
- <button id="mergeMasksButton">Merge</button>
161
+ <button id="enterMosaicModeButton">Mosaic</button>
162
+ <button id="exitMosaicModeButton">Exit Mosaic</button>
163
+ <label>
164
+ Brush size
165
+ <input id="mosaicBrushSizeInput" type="range" min="8" max="160" step="1" value="48" />
166
+ </label>
167
+ <label>
168
+ Block size
169
+ <input id="mosaicBlockSizeInput" type="range" min="2" max="40" step="1" value="8" />
170
+ </label>
171
+
172
+ <button id="mergeMasksButton">Merge Masks</button>
173
+ <button id="mergeAnnotationsButton">Merge Annotations</button>
139
174
  <button id="downloadImageButton">Download</button>
140
175
  <button id="undoButton">Undo</button>
141
176
  <button id="redoButton">Redo</button>
142
177
  <button id="resetImageTransformButton">Reset</button>
143
178
 
144
179
  <input id="imageInput" type="file" accept="image/*" />
145
- <ul id="maskList"></ul>
146
180
  ```
147
181
 
148
182
  ### TypeScript / ESM
@@ -156,6 +190,18 @@ const editor = new ImageEditor(fabric, {
156
190
  canvasWidth: 800,
157
191
  canvasHeight: 600,
158
192
  backgroundColor: '#ffffff',
193
+ defaultMosaicConfig: {
194
+ brushSize: 48,
195
+ blockSize: 8,
196
+ },
197
+ defaultTextConfig: {
198
+ fill: '#ff0000',
199
+ fontSize: 32,
200
+ },
201
+ defaultDrawConfig: {
202
+ color: '#ff0000',
203
+ brushSize: 8,
204
+ },
159
205
  } satisfies ImageEditorOptions);
160
206
 
161
207
  editor.init({
@@ -169,16 +215,37 @@ editor.init({
169
215
  createMaskButton: 'createMaskButton',
170
216
  removeSelectedMaskButton: 'removeSelectedMaskButton',
171
217
  removeAllMasksButton: 'removeAllMasksButton',
218
+ maskList: 'maskList',
172
219
  enterCropModeButton: 'enterCropModeButton',
173
220
  applyCropButton: 'applyCropButton',
174
221
  cancelCropButton: 'cancelCropButton',
222
+ enterMosaicModeButton: 'enterMosaicModeButton',
223
+ exitMosaicModeButton: 'exitMosaicModeButton',
224
+ mosaicBrushSizeInput: 'mosaicBrushSizeInput',
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',
175
243
  mergeMasksButton: 'mergeMasksButton',
176
244
  downloadImageButton: 'downloadImageButton',
177
245
  undoButton: 'undoButton',
178
246
  redoButton: 'redoButton',
179
247
  resetImageTransformButton: 'resetImageTransformButton',
180
248
  imageInput: 'imageInput',
181
- maskList: 'maskList',
182
249
  });
183
250
 
184
251
  // Load an image programmatically (base64 data URL).
@@ -187,6 +254,9 @@ await editor.loadImage('data:image/jpeg;base64,...');
187
254
  // Add a rectangular mask, then export the result as base64.
188
255
  const mask: MaskConfig = { shape: 'rect', width: 120, height: 80, left: '25%', top: '25%' };
189
256
  editor.createMask(mask);
257
+ editor.createTextAnnotation({ text: 'Label', left: 120, top: 80 });
258
+ editor.enterDrawMode();
259
+ editor.setDrawConfig({ color: '#00aaff', brushSize: 10 });
190
260
 
191
261
  const dataUrl = await editor.exportImageBase64({ fileType: 'png' });
192
262
  ```
@@ -200,18 +270,34 @@ const { ImageEditor } = require('@bensitu/image-editor');
200
270
  const editor = new ImageEditor(fabric, { canvasWidth: 800, canvasHeight: 600 });
201
271
  ```
202
272
 
203
- In v2, `require('@bensitu/image-editor')` returns a namespace object with
204
- `ImageEditor`, `default`, and `isMaskObject`; it does not return the
205
- 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.
206
278
 
207
279
  ## Public API
208
280
 
209
281
  `ImageEditor` is the only public class. The package barrel re-exports it as
210
- both the default export and a named export, alongside `isMaskObject` and the
211
- 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
212
284
  manager, controllers, services, managers, utility modules) are intentionally
213
285
  not exported and may change without notice.
214
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
+
215
301
  ### Constructor
216
302
 
217
303
  ```ts
@@ -232,22 +318,31 @@ new ImageEditor(options?: ImageEditorOptions) // UMD: reads globalThis.fabric
232
318
  | ----------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
233
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. |
234
320
  | `isImageLoaded()` | Returns `true` if a valid image is currently loaded on the canvas. |
235
- | `isBusy()` | Returns `true` while the editor is loading, animating, or in crop mode. |
321
+ | `isBusy()` | Returns `true` while the editor is loading, animating, or in Crop, Mosaic, Text, or Draw mode. |
236
322
  | `setLayoutMode(mode)` | Select the layout strategy for future image loads. `mode` is `'fit'`, `'cover'`, or `'expand'`. |
237
323
 
238
324
  `LoadImageOptions` currently includes `preserveScroll?: boolean` for
239
325
  preserving the container's scroll position across both successful loads and
240
326
  rollback paths.
241
327
 
242
- Use `setLayoutMode()` instead of mutating internal options when a UI lets users
243
- choose how the next image should be placed:
328
+ Use `defaultLayoutMode` to choose the initial image-load strategy, then call
329
+ `setLayoutMode()` when a UI should change how future images are placed:
244
330
 
245
331
  ```ts
246
- editor.setLayoutMode('fit');
332
+ const editor = new ImageEditor(fabric, {
333
+ defaultLayoutMode: 'fit',
334
+ });
335
+
336
+ await editor.loadImage(imageA);
337
+
338
+ // Future loads use cover. The current image is not re-laid out immediately.
247
339
  editor.setLayoutMode('cover');
248
- editor.setLayoutMode('expand');
340
+ await editor.loadImage(imageB);
249
341
  ```
250
342
 
343
+ Invalid JavaScript `defaultLayoutMode` values fall back to `'expand'`.
344
+ Invalid `setLayoutMode()` calls are ignored and preserve the current mode.
345
+
251
346
  File-input helpers accept JPG, PNG, WebP, GIF, and BMP files. GIF and BMP are
252
347
  decoded as static raster input for canvas editing; GIF animation and BMP/GIF
253
348
  source-format preservation are not retained. Export output remains controlled by
@@ -272,6 +367,29 @@ the JPEG, PNG, or WebP export options.
272
367
  `MaskConfig` supports rect, circle, ellipse, polygon, and a custom
273
368
  `fabricGenerator`. Falsy values in `styles` (`0`, `false`, `null`, `''`,
274
369
  `NaN`) are applied verbatim.
370
+ Every mask is marked as `editorObjectKind: 'mask'` and includes required
371
+ `maskId`, `maskUid`, and `maskName` metadata.
372
+
373
+ Use `defaultMaskConfig` to define constructor-level defaults for masks created
374
+ through either `createMask()` or the built-in `createMaskButton`. Per-call
375
+ `createMask(config)` values override `defaultMaskConfig`.
376
+
377
+ ```ts
378
+ const editor = new ImageEditor(fabric, {
379
+ defaultMaskConfig: {
380
+ color: 'rgba(255, 0, 0, 0.35)',
381
+ alpha: 0.35,
382
+ styles: {
383
+ stroke: '#ff0000',
384
+ strokeWidth: 2,
385
+ strokeDashArray: [6, 4],
386
+ },
387
+ },
388
+ });
389
+
390
+ editor.createMask(); // Uses defaultMaskConfig.
391
+ editor.createMask({ color: 'rgba(0, 128, 255, 0.35)' }); // Per-call override.
392
+ ```
275
393
 
276
394
  ### Crop
277
395
 
@@ -281,35 +399,146 @@ the JPEG, PNG, or WebP export options.
281
399
  | `applyCrop()` | Apply the current crop region. Atomic: failure rolls back to the pre-crop snapshot. |
282
400
  | `cancelCrop()` | Cancel crop mode and restore the prior canvas state without pushing a history entry. |
283
401
 
402
+ ### Mosaic mode
403
+
404
+ | Method | Description |
405
+ | -------------------------- | ---------------------------------------------------------------------- |
406
+ | `enterMosaicMode()` | Enter circular-brush Mosaic mode and show the hover preview on canvas. |
407
+ | `exitMosaicMode()` | Leave Mosaic mode and remove preview/session handlers. |
408
+ | `isMosaicMode()` | Returns `true` while a Mosaic session is active. |
409
+ | `getMosaicConfig()` | Returns a defensive copy of the current runtime Mosaic config. |
410
+ | `setMosaicConfig(config)` | Patch current Mosaic config without creating a history entry. |
411
+ | `resetMosaicConfig()` | Restore current Mosaic config from constructor defaults. |
412
+ | `setMosaicBrushSize(size)` | Set brush diameter in canvas pixels. |
413
+ | `setMosaicBlockSize(size)` | Set source-pixel block size; values are floored to integers. |
414
+
415
+ `defaultMosaicConfig` initializes the current runtime Mosaic config. Runtime
416
+ setters update only the current config and never mutate constructor defaults.
417
+ `resetMosaicConfig()` clones the constructor defaults back into the current
418
+ config.
419
+
420
+ ```ts
421
+ const editor = new ImageEditor(fabric, {
422
+ defaultMosaicConfig: {
423
+ brushSize: 48,
424
+ blockSize: 8,
425
+ },
426
+ });
427
+
428
+ editor.init({
429
+ canvas: 'canvas',
430
+ enterMosaicModeButton: 'enterMosaicModeButton',
431
+ exitMosaicModeButton: 'exitMosaicModeButton',
432
+ mosaicBrushSizeInput: 'mosaicBrushSizeInput',
433
+ mosaicBlockSizeInput: 'mosaicBlockSizeInput',
434
+ });
435
+
436
+ editor.enterMosaicMode();
437
+ editor.setMosaicConfig({ brushSize: 64, blockSize: 12 });
438
+ editor.resetMosaicConfig();
439
+ ```
440
+
441
+ `brushSize` is the circular brush diameter in canvas pixels. `blockSize` is
442
+ the source-image pixel block size; larger values produce chunkier pixelation.
443
+ Clicking outside the image is a no-op. Each successful Mosaic click bakes the
444
+ pixelated region into the base image and creates exactly one undo step. Because
445
+ Mosaic edits replace base image pixels rather than adding Fabric overlay
446
+ objects, exported images include the Mosaic naturally while the preview circle
447
+ is never exported or saved in history.
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
+
284
503
  ### Merge and export
285
504
 
286
505
  | Method | Description |
287
506
  | ----------------------------- | ---------------------------------------------------------------------------------------------- |
288
507
  | `mergeMasks()` | Bake masks into the base image atomically. Returns `Promise<void>`. |
508
+ | `mergeAnnotations()` | Bake annotations into the base image atomically. Returns `Promise<void>`. |
289
509
  | `exportImageBase64(options?)` | Returns `Promise<string>` (data URL). Resolves to `''` with a warning when no image is loaded. |
290
510
  | `exportImageFile(options?)` | Returns `Promise<File>`. Rejects when no image is loaded. |
291
- | `downloadImage(fileName?)` | Triggers a browser download. No-op when no image is loaded. |
292
-
293
- `Base64ExportOptions` and `ImageFileExportOptions` separate mask compositing
294
- from export region selection:
295
-
296
- | Option | Default | Description |
297
- | ------------ | --------- | --------------------------------------------------------------------------- |
298
- | `mergeMask` | `true` | Flatten masks into exported pixels. Mask labels are never exported. |
299
- | `exportArea` | `'image'` | `'image'` clips to the image bounding box; `'canvas'` exports the canvas. |
300
- | `fileType` | `'jpeg'` | `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, or matching full MIME strings. |
301
- | `format` | `'jpeg'` | Alias for `fileType` on `exportImageBase64`; `fileType` wins when both set. |
302
- | `quality` | `0.92` | Lossy quality clamped to `[0, 1]`; ignored for PNG. |
303
- | `multiplier` | `1` | Output resolution multiplier. |
304
- | `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`. |
305
527
 
306
528
  ```ts
307
- await editor.exportImageBase64({ exportArea: 'image', mergeMask: true });
308
- await editor.exportImageBase64({ exportArea: 'image', mergeMask: false });
309
- await editor.exportImageBase64({ exportArea: 'canvas', mergeMask: true });
310
- 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.
311
535
  ```
312
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
+
313
542
  ### State and history
314
543
 
315
544
  | Method | Description |
@@ -325,53 +554,59 @@ Pass an `ImageEditorOptions` object as the second constructor argument
325
554
  (or as the only argument when using the UMD global form). Unknown keys are
326
555
  ignored; nested `label` and `crop` objects are deep-merged with the defaults.
327
556
 
328
- | Option | Default | Description |
329
- | ------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
330
- | `canvasWidth` | `800` | Initial and hidden-container fallback canvas width in pixels. |
331
- | `canvasHeight` | `600` | Initial and hidden-container fallback canvas height in pixels. |
332
- | `backgroundColor` | `'transparent'` | Fabric canvas background color. |
333
- | `animationDuration` | `300` | Duration of scale and rotate animations (ms). |
334
- | `minScale` | `0.1` | Minimum scale factor. |
335
- | `maxScale` | `5.0` | Maximum scale factor. |
336
- | `scaleStep` | `0.05` | Scale delta per zoom step. |
337
- | `rotationStep` | `90` | Rotation step in degrees. |
338
- | `expandCanvasToImage` | `true` | Grow the canvas to fit the loaded image (lowest layout precedence). |
339
- | `fitImageToCanvas` | `false` | Fit the image inside the visible workspace viewport (highest layout precedence). |
340
- | `coverImageToCanvas` | `false` | Scale large images down to cover the visible workspace, cap at native size, and expand overflowing canvas axes so the container can scroll. |
341
- | `downsampleOnLoad` | `true` | Downsample large images on load. |
342
- | `downsampleMaxWidth` | `4000` | Max width before downsampling kicks in. |
343
- | `downsampleMaxHeight` | `3000` | Max height before downsampling kicks in. |
344
- | `downsampleQuality` | `0.92` | Lossy quality used when downsampling and exporting. |
345
- | `preserveSourceFormat` | `true` | Preserve PNG/WebP MIME through downsampling unless `downsampleMimeType` is set. |
346
- | `downsampleMimeType` | `null` | Explicit downsample MIME type. Overrides `preserveSourceFormat`. |
347
- | `imageLoadTimeoutMs` | `30000` | Maximum duration for both decode and Fabric image creation during `loadImage`. |
348
- | `exportMultiplier` | `1` | Output resolution multiplier. |
349
- | `maxExportPixels` | `50000000` | Maximum output pixel count after applying the export multiplier. Invalid values fall back to this default. |
350
- | `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. |
351
- | `exportAreaByDefault` | `'image'` | Default export region for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
352
- | `mergeMaskByDefault` | `true` | Default mask compositing behavior for `exportImageBase64`, `exportImageFile`, and `downloadImage`. |
353
- | `defaultMaskWidth` | `50` | Default mask width. |
354
- | `defaultMaskHeight` | `80` | Default mask height. |
355
- | `maskRotatable` | `false` | Allow masks to be rotated by the user. |
356
- | `maskLabelOnSelect` | `true` | Show a label above a selected mask. |
357
- | `maskLabelOffset` | `3` | Pixel offset of the label from the mask's top-left corner. |
358
- | `maskName` | `'mask'` | Prefix used for auto-generated mask names. |
359
- | `groupSelection` | `false` | Allow Fabric multi-object group selection on the canvas. |
360
- | `showPlaceholder` | `true` | Show a placeholder element while no image is loaded. |
361
- | `initialImageBase64` | `null` | Base64 data URL auto-loaded after construction. |
362
- | `defaultDownloadFileName` | `'edited_image.jpg'` | Default file name used by `downloadImage()`. |
363
- | `onImageLoadStart` | `null` | Called before a valid image load begins. |
364
- | `onImageLoaded` | `null` | Called as `(info, context)` once after a successful `loadImage`. Extra arguments are ignored by existing zero-argument JavaScript handlers. |
365
- | `onImageCleared` | `null` | Called when a committed image is replaced or cleared. |
366
- | `onImageChanged` | `null` | Called with a safe editor state snapshot after visible editor state changes. |
367
- | `onBusyChange` | `null` | Called only when the public busy state changes. |
368
- | `onEditorDisposed` | `null` | Called once when `dispose()` performs teardown. |
369
- | `onMasksChanged` | `null` | Called with a shallow copy of current mask objects after the mask collection changes. |
370
- | `onSelectionChange` | `null` | Called with selected mask payload after mask selection changes. |
371
- | `onError` | `null` | Called as `(error, message)` when the editor reports an error. |
372
- | `onWarning` | `null` | Called as `(error, message)` when the editor reports a recoverable warning. |
373
- | `label` | see source | `LabelConfig` for selected-mask labels (`getText`, `textOptions`, `create`). |
374
- | `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. |
375
610
 
376
611
  `crop.exportFileType` defaults to `'source'`. Supported explicit values are
377
612
  `'png'`, `'jpeg'`, `'jpg'`, `'webp'`, and full image MIME strings. PNG is
@@ -379,14 +614,31 @@ lossless and ignores `crop.exportQuality`; JPEG/WebP use `crop.exportQuality`
379
614
  when finite, otherwise `downsampleQuality`, otherwise `0.92`. Choose JPEG/WebP
380
615
  only when smaller intermediate crop output is preferred.
381
616
 
617
+ `defaultMosaicConfig.outputFileType` also defaults to `'source'`. Mosaic commits
618
+ preserve the current image MIME type when known and fall back to PNG when the
619
+ source format cannot be determined. JPEG/WebP commits use
620
+ `defaultMosaicConfig.outputQuality` when finite, otherwise `downsampleQuality`.
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
+
382
633
  ## Example workflow
383
634
 
384
635
  1. Construct `ImageEditor` with options and call `init(idMap)` to wire it up.
385
636
  2. Load an image via `loadImage(base64)` or the bound file input.
386
- 3. Adjust with `scaleImage`, `rotateImage`, `resetImageTransform`, and the
387
- crop session.
388
- 4. Add `createMask` calls and inspect via the `maskList` element.
389
- 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
390
642
  `exportImageBase64`, `exportImageFile`, or `downloadImage` to produce
391
643
  the final output.
392
644
  6. Call `dispose()` when the editor is unmounted.
@@ -447,8 +699,11 @@ import type {
447
699
  ResolvedOptions,
448
700
  LabelConfig,
449
701
  CropConfig,
702
+ MosaicConfig,
703
+ ResolvedMosaicConfig,
450
704
  LoadImageOptions,
451
705
  RemoveAllMasksOptions,
706
+ DefaultMaskConfig,
452
707
  MaskConfig,
453
708
  MaskObject,
454
709
  MaskNumericProp,
@@ -458,6 +713,7 @@ import type {
458
713
  NormalizedImageFormat,
459
714
  ExportArea,
460
715
  CropExportFileType,
716
+ MosaicOutputFileType,
461
717
  Base64ExportOptions,
462
718
  ImageFileExportOptions,
463
719
  ImageInfo,