@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.
- package/README.md +235 -81
- package/dist/cjs/index.cjs +2969 -747
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/esm/annotation/annotation-lock.js +7 -0
- package/dist/esm/annotation/annotation-lock.js.map +1 -0
- package/dist/esm/annotation/annotation-manager.js +217 -0
- package/dist/esm/annotation/annotation-manager.js.map +1 -0
- package/dist/esm/annotation/annotation-style.js +50 -0
- package/dist/esm/annotation/annotation-style.js.map +1 -0
- package/dist/esm/annotation/draw-controller.js +114 -0
- package/dist/esm/annotation/draw-controller.js.map +1 -0
- package/dist/esm/annotation/text-controller.js +234 -0
- package/dist/esm/annotation/text-controller.js.map +1 -0
- package/dist/esm/core/default-options.js +232 -3
- package/dist/esm/core/default-options.js.map +1 -1
- package/dist/esm/core/editor-object-kind.js +37 -0
- package/dist/esm/core/editor-object-kind.js.map +1 -0
- package/dist/esm/core/errors.js +19 -0
- package/dist/esm/core/errors.js.map +1 -1
- package/dist/esm/core/layer-order.js +100 -0
- package/dist/esm/core/layer-order.js.map +1 -0
- package/dist/esm/core/public-types.js +34 -1
- package/dist/esm/core/public-types.js.map +1 -1
- package/dist/esm/core/state-serializer.js +104 -24
- package/dist/esm/core/state-serializer.js.map +1 -1
- package/dist/esm/crop/crop-controller.js +2 -0
- package/dist/esm/crop/crop-controller.js.map +1 -1
- package/dist/esm/export/export-format.js.map +1 -1
- package/dist/esm/export/export-service.js +123 -135
- package/dist/esm/export/export-service.js.map +1 -1
- package/dist/esm/export/overlay-merge-service.js +75 -0
- package/dist/esm/export/overlay-merge-service.js.map +1 -0
- package/dist/esm/history/history-manager.js +2 -2
- package/dist/esm/history/history-manager.js.map +1 -1
- package/dist/esm/image/image-loader.js +18 -49
- package/dist/esm/image/image-loader.js.map +1 -1
- package/dist/esm/image/transform-controller.js.map +1 -1
- package/dist/esm/image-editor.js +1063 -60
- package/dist/esm/image-editor.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/mask/mask-factory.js +39 -14
- package/dist/esm/mask/mask-factory.js.map +1 -1
- package/dist/esm/mask/mask-label-manager.js +2 -0
- package/dist/esm/mask/mask-label-manager.js.map +1 -1
- package/dist/esm/mask/mask-list.js.map +1 -1
- package/dist/esm/mask/mask-style.js.map +1 -1
- package/dist/esm/mosaic/mosaic-controller.js +24 -28
- package/dist/esm/mosaic/mosaic-controller.js.map +1 -1
- package/dist/esm/utils/image-element-loader.js +55 -0
- package/dist/esm/utils/image-element-loader.js.map +1 -0
- package/dist/esm/utils/pointer.js +28 -0
- package/dist/esm/utils/pointer.js.map +1 -0
- package/dist/types/annotation/annotation-lock.d.ts +12 -0
- package/dist/types/annotation/annotation-lock.d.ts.map +1 -0
- package/dist/types/annotation/annotation-manager.d.ts +33 -0
- package/dist/types/annotation/annotation-manager.d.ts.map +1 -0
- package/dist/types/annotation/annotation-style.d.ts +13 -0
- package/dist/types/annotation/annotation-style.d.ts.map +1 -0
- package/dist/types/annotation/draw-controller.d.ts +43 -0
- package/dist/types/annotation/draw-controller.d.ts.map +1 -0
- package/dist/types/annotation/text-controller.d.ts +47 -0
- package/dist/types/annotation/text-controller.d.ts.map +1 -0
- package/dist/types/core/default-options.d.ts +14 -2
- package/dist/types/core/default-options.d.ts.map +1 -1
- package/dist/types/core/editor-object-kind.d.ts +29 -0
- package/dist/types/core/editor-object-kind.d.ts.map +1 -0
- package/dist/types/core/errors.d.ts +11 -1
- package/dist/types/core/errors.d.ts.map +1 -1
- package/dist/types/core/layer-order.d.ts +21 -0
- package/dist/types/core/layer-order.d.ts.map +1 -0
- package/dist/types/core/public-types.d.ts +222 -24
- package/dist/types/core/public-types.d.ts.map +1 -1
- package/dist/types/core/state-serializer.d.ts +30 -5
- package/dist/types/core/state-serializer.d.ts.map +1 -1
- package/dist/types/crop/crop-controller.d.ts +6 -7
- package/dist/types/crop/crop-controller.d.ts.map +1 -1
- package/dist/types/export/export-format.d.ts +5 -33
- package/dist/types/export/export-format.d.ts.map +1 -1
- package/dist/types/export/export-service.d.ts +24 -15
- package/dist/types/export/export-service.d.ts.map +1 -1
- package/dist/types/export/overlay-merge-service.d.ts +38 -0
- package/dist/types/export/overlay-merge-service.d.ts.map +1 -0
- package/dist/types/history/history-manager.d.ts +11 -14
- package/dist/types/history/history-manager.d.ts.map +1 -1
- package/dist/types/image/image-loader.d.ts +22 -17
- package/dist/types/image/image-loader.d.ts.map +1 -1
- package/dist/types/image/image-resampler.d.ts +1 -1
- package/dist/types/image/transform-controller.d.ts +5 -7
- package/dist/types/image/transform-controller.d.ts.map +1 -1
- package/dist/types/image-editor.d.ts +75 -7
- package/dist/types/image-editor.d.ts.map +1 -1
- package/dist/types/index.d.cts +3 -3
- package/dist/types/index.d.cts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/mask/mask-factory.d.ts.map +1 -1
- package/dist/types/mask/mask-label-manager.d.ts +10 -9
- package/dist/types/mask/mask-label-manager.d.ts.map +1 -1
- package/dist/types/mask/mask-list.d.ts +11 -12
- package/dist/types/mask/mask-list.d.ts.map +1 -1
- package/dist/types/mask/mask-style.d.ts +19 -20
- package/dist/types/mask/mask-style.d.ts.map +1 -1
- package/dist/types/mosaic/mosaic-controller.d.ts +3 -3
- package/dist/types/mosaic/mosaic-controller.d.ts.map +1 -1
- package/dist/types/ui/visibility-state.d.ts +2 -2
- package/dist/types/utils/image-element-loader.d.ts +19 -0
- package/dist/types/utils/image-element-loader.d.ts.map +1 -0
- package/dist/types/utils/pointer.d.ts +16 -0
- package/dist/types/utils/pointer.d.ts.map +1 -0
- package/dist/umd/image-editor.umd.js +1 -1
- package/dist/umd/image-editor.umd.js.map +1 -1
- package/package.json +1 -1
package/dist/cjs/index.cjs
CHANGED
|
@@ -176,7 +176,8 @@ const DEFAULT_OPTIONS = {
|
|
|
176
176
|
exportMultiplier: 1,
|
|
177
177
|
maxExportPixels: 50000000,
|
|
178
178
|
exportAreaByDefault: 'image',
|
|
179
|
-
|
|
179
|
+
mergeMasksByDefault: true,
|
|
180
|
+
mergeAnnotationsByDefault: true,
|
|
180
181
|
defaultMaskWidth: 50,
|
|
181
182
|
defaultMaskHeight: 80,
|
|
182
183
|
defaultMaskConfig: EMPTY_DEFAULT_MASK_CONFIG,
|
|
@@ -184,6 +185,8 @@ const DEFAULT_OPTIONS = {
|
|
|
184
185
|
maskLabelOnSelect: true,
|
|
185
186
|
maskLabelOffset: 3,
|
|
186
187
|
maskName: 'mask',
|
|
188
|
+
textAnnotationName: 'text',
|
|
189
|
+
drawAnnotationName: 'draw',
|
|
187
190
|
groupSelection: false,
|
|
188
191
|
showPlaceholder: true,
|
|
189
192
|
initialImageBase64: null,
|
|
@@ -195,6 +198,7 @@ const DEFAULT_OPTIONS = {
|
|
|
195
198
|
onBusyChange: null,
|
|
196
199
|
onEditorDisposed: null,
|
|
197
200
|
onMasksChanged: null,
|
|
201
|
+
onAnnotationsChanged: null,
|
|
198
202
|
onSelectionChange: null,
|
|
199
203
|
onError: null,
|
|
200
204
|
onWarning: null,
|
|
@@ -231,6 +235,37 @@ const DEFAULT_MOSAIC_CONFIG = Object.freeze({
|
|
|
231
235
|
outputFileType: 'source',
|
|
232
236
|
outputQuality: undefined,
|
|
233
237
|
});
|
|
238
|
+
const DEFAULT_TEXT_ANNOTATION_CONFIG = Object.freeze({
|
|
239
|
+
text: 'Text',
|
|
240
|
+
left: undefined,
|
|
241
|
+
top: undefined,
|
|
242
|
+
width: 200,
|
|
243
|
+
fontSize: 32,
|
|
244
|
+
fontFamily: 'sans-serif',
|
|
245
|
+
fontWeight: 'normal',
|
|
246
|
+
fill: '#ff0000',
|
|
247
|
+
backgroundColor: 'rgba(255,255,255,0)',
|
|
248
|
+
textAlign: 'left',
|
|
249
|
+
angle: 0,
|
|
250
|
+
selectable: true,
|
|
251
|
+
evented: true,
|
|
252
|
+
editable: true,
|
|
253
|
+
enterEditing: true,
|
|
254
|
+
annotationHidden: false,
|
|
255
|
+
annotationLocked: false,
|
|
256
|
+
styles: Object.freeze({}),
|
|
257
|
+
});
|
|
258
|
+
const DEFAULT_DRAW_CONFIG = Object.freeze({
|
|
259
|
+
brushSize: 8,
|
|
260
|
+
color: '#ff0000',
|
|
261
|
+
opacity: 1,
|
|
262
|
+
lineCap: 'round',
|
|
263
|
+
lineJoin: 'round',
|
|
264
|
+
selectable: true,
|
|
265
|
+
evented: true,
|
|
266
|
+
annotationHidden: false,
|
|
267
|
+
annotationLocked: false,
|
|
268
|
+
});
|
|
234
269
|
const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
235
270
|
'canvasWidth',
|
|
236
271
|
'canvasHeight',
|
|
@@ -252,7 +287,8 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
252
287
|
'exportMultiplier',
|
|
253
288
|
'maxExportPixels',
|
|
254
289
|
'exportAreaByDefault',
|
|
255
|
-
'
|
|
290
|
+
'mergeMasksByDefault',
|
|
291
|
+
'mergeAnnotationsByDefault',
|
|
256
292
|
'defaultMaskWidth',
|
|
257
293
|
'defaultMaskHeight',
|
|
258
294
|
'defaultMaskConfig',
|
|
@@ -260,6 +296,8 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
260
296
|
'maskLabelOnSelect',
|
|
261
297
|
'maskLabelOffset',
|
|
262
298
|
'maskName',
|
|
299
|
+
'textAnnotationName',
|
|
300
|
+
'drawAnnotationName',
|
|
263
301
|
'groupSelection',
|
|
264
302
|
'showPlaceholder',
|
|
265
303
|
'initialImageBase64',
|
|
@@ -271,12 +309,15 @@ const KNOWN_TOP_LEVEL_KEYS = new Set([
|
|
|
271
309
|
'onBusyChange',
|
|
272
310
|
'onEditorDisposed',
|
|
273
311
|
'onMasksChanged',
|
|
312
|
+
'onAnnotationsChanged',
|
|
274
313
|
'onSelectionChange',
|
|
275
314
|
'onError',
|
|
276
315
|
'onWarning',
|
|
277
316
|
'label',
|
|
278
317
|
'crop',
|
|
279
318
|
'defaultMosaicConfig',
|
|
319
|
+
'defaultTextConfig',
|
|
320
|
+
'defaultDrawConfig',
|
|
280
321
|
]);
|
|
281
322
|
function normalizeCallback(value) {
|
|
282
323
|
return typeof value === 'function' ? value : null;
|
|
@@ -514,6 +555,180 @@ function areResolvedMosaicConfigsEqual(left, right) {
|
|
|
514
555
|
left.outputFileType === right.outputFileType &&
|
|
515
556
|
left.outputQuality === right.outputQuality);
|
|
516
557
|
}
|
|
558
|
+
function cloneResolvedTextAnnotationConfig(config) {
|
|
559
|
+
return {
|
|
560
|
+
...config,
|
|
561
|
+
styles: { ...config.styles },
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function cloneResolvedDrawConfig(config) {
|
|
565
|
+
return { ...config };
|
|
566
|
+
}
|
|
567
|
+
function normalizeTextAlign(value, fallback) {
|
|
568
|
+
return value === 'left' || value === 'center' || value === 'right' || value === 'justify'
|
|
569
|
+
? value
|
|
570
|
+
: fallback;
|
|
571
|
+
}
|
|
572
|
+
function normalizePositiveNumber(value, fallback) {
|
|
573
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
574
|
+
}
|
|
575
|
+
function normalizeBoolean(value, fallback) {
|
|
576
|
+
return typeof value === 'boolean' ? value : fallback;
|
|
577
|
+
}
|
|
578
|
+
function normalizeString(value, fallback) {
|
|
579
|
+
return typeof value === 'string' ? value : fallback;
|
|
580
|
+
}
|
|
581
|
+
function normalizeTextLeftTop(value) {
|
|
582
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : undefined;
|
|
583
|
+
}
|
|
584
|
+
function normalizeTextboxStyles(value) {
|
|
585
|
+
if (!isConfigObject(value))
|
|
586
|
+
return {};
|
|
587
|
+
return { ...value };
|
|
588
|
+
}
|
|
589
|
+
function mergeTextAnnotationConfigPatch(current, patch, fallback = current) {
|
|
590
|
+
const raw = isConfigObject(patch) ? patch : {};
|
|
591
|
+
const next = cloneResolvedTextAnnotationConfig(current);
|
|
592
|
+
if (hasOwn(raw, 'text'))
|
|
593
|
+
next.text = normalizeString(raw.text, fallback.text);
|
|
594
|
+
if (hasOwn(raw, 'left'))
|
|
595
|
+
next.left = normalizeTextLeftTop(raw.left);
|
|
596
|
+
if (hasOwn(raw, 'top'))
|
|
597
|
+
next.top = normalizeTextLeftTop(raw.top);
|
|
598
|
+
if (hasOwn(raw, 'width'))
|
|
599
|
+
next.width = normalizePositiveNumber(raw.width, fallback.width);
|
|
600
|
+
if (hasOwn(raw, 'fontSize')) {
|
|
601
|
+
next.fontSize = normalizePositiveNumber(raw.fontSize, fallback.fontSize);
|
|
602
|
+
}
|
|
603
|
+
if (hasOwn(raw, 'fontFamily')) {
|
|
604
|
+
next.fontFamily = normalizeString(raw.fontFamily, fallback.fontFamily);
|
|
605
|
+
}
|
|
606
|
+
if (hasOwn(raw, 'fontWeight')) {
|
|
607
|
+
next.fontWeight =
|
|
608
|
+
typeof raw.fontWeight === 'string' || typeof raw.fontWeight === 'number'
|
|
609
|
+
? raw.fontWeight
|
|
610
|
+
: fallback.fontWeight;
|
|
611
|
+
}
|
|
612
|
+
if (hasOwn(raw, 'fill'))
|
|
613
|
+
next.fill = normalizeString(raw.fill, fallback.fill);
|
|
614
|
+
if (hasOwn(raw, 'backgroundColor')) {
|
|
615
|
+
next.backgroundColor = normalizeString(raw.backgroundColor, fallback.backgroundColor);
|
|
616
|
+
}
|
|
617
|
+
if (hasOwn(raw, 'textAlign'))
|
|
618
|
+
next.textAlign = normalizeTextAlign(raw.textAlign, fallback.textAlign);
|
|
619
|
+
if (hasOwn(raw, 'angle'))
|
|
620
|
+
next.angle = normalizeFiniteNumber(raw.angle, fallback.angle);
|
|
621
|
+
if (hasOwn(raw, 'selectable'))
|
|
622
|
+
next.selectable = normalizeBoolean(raw.selectable, fallback.selectable);
|
|
623
|
+
if (hasOwn(raw, 'evented'))
|
|
624
|
+
next.evented = normalizeBoolean(raw.evented, fallback.evented);
|
|
625
|
+
if (hasOwn(raw, 'editable'))
|
|
626
|
+
next.editable = normalizeBoolean(raw.editable, fallback.editable);
|
|
627
|
+
if (hasOwn(raw, 'enterEditing')) {
|
|
628
|
+
next.enterEditing = normalizeBoolean(raw.enterEditing, fallback.enterEditing);
|
|
629
|
+
}
|
|
630
|
+
if (hasOwn(raw, 'annotationHidden')) {
|
|
631
|
+
next.annotationHidden = normalizeBoolean(raw.annotationHidden, fallback.annotationHidden);
|
|
632
|
+
}
|
|
633
|
+
if (hasOwn(raw, 'annotationLocked')) {
|
|
634
|
+
next.annotationLocked = normalizeBoolean(raw.annotationLocked, fallback.annotationLocked);
|
|
635
|
+
}
|
|
636
|
+
if (hasOwn(raw, 'styles')) {
|
|
637
|
+
next.styles = {
|
|
638
|
+
...next.styles,
|
|
639
|
+
...normalizeTextboxStyles(raw.styles),
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
return next;
|
|
643
|
+
}
|
|
644
|
+
function normalizeTextAnnotationConfig(input, fallback) {
|
|
645
|
+
if (!isConfigObject(input))
|
|
646
|
+
return cloneResolvedTextAnnotationConfig(fallback);
|
|
647
|
+
return mergeTextAnnotationConfigPatch(fallback, input);
|
|
648
|
+
}
|
|
649
|
+
function normalizeLineCap(value, fallback) {
|
|
650
|
+
return value === 'butt' || value === 'round' || value === 'square' ? value : fallback;
|
|
651
|
+
}
|
|
652
|
+
function normalizeLineJoin(value, fallback) {
|
|
653
|
+
return value === 'bevel' || value === 'round' || value === 'miter' ? value : fallback;
|
|
654
|
+
}
|
|
655
|
+
function normalizeOpacity(value, fallback) {
|
|
656
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
657
|
+
return fallback;
|
|
658
|
+
return Math.max(0, Math.min(1, value));
|
|
659
|
+
}
|
|
660
|
+
function mergeDrawConfigPatch(current, patch, fallback = current) {
|
|
661
|
+
const raw = isConfigObject(patch) ? patch : {};
|
|
662
|
+
const next = cloneResolvedDrawConfig(current);
|
|
663
|
+
if (hasOwn(raw, 'brushSize')) {
|
|
664
|
+
next.brushSize = normalizePositiveNumber(raw.brushSize, fallback.brushSize);
|
|
665
|
+
}
|
|
666
|
+
if (hasOwn(raw, 'color'))
|
|
667
|
+
next.color = normalizeString(raw.color, fallback.color);
|
|
668
|
+
if (hasOwn(raw, 'opacity'))
|
|
669
|
+
next.opacity = normalizeOpacity(raw.opacity, fallback.opacity);
|
|
670
|
+
if (hasOwn(raw, 'lineCap'))
|
|
671
|
+
next.lineCap = normalizeLineCap(raw.lineCap, fallback.lineCap);
|
|
672
|
+
if (hasOwn(raw, 'lineJoin'))
|
|
673
|
+
next.lineJoin = normalizeLineJoin(raw.lineJoin, fallback.lineJoin);
|
|
674
|
+
if (hasOwn(raw, 'selectable'))
|
|
675
|
+
next.selectable = normalizeBoolean(raw.selectable, fallback.selectable);
|
|
676
|
+
if (hasOwn(raw, 'evented'))
|
|
677
|
+
next.evented = normalizeBoolean(raw.evented, fallback.evented);
|
|
678
|
+
if (hasOwn(raw, 'annotationHidden')) {
|
|
679
|
+
next.annotationHidden = normalizeBoolean(raw.annotationHidden, fallback.annotationHidden);
|
|
680
|
+
}
|
|
681
|
+
if (hasOwn(raw, 'annotationLocked')) {
|
|
682
|
+
next.annotationLocked = normalizeBoolean(raw.annotationLocked, fallback.annotationLocked);
|
|
683
|
+
}
|
|
684
|
+
return next;
|
|
685
|
+
}
|
|
686
|
+
function normalizeDrawConfig(input, fallback) {
|
|
687
|
+
if (!isConfigObject(input))
|
|
688
|
+
return cloneResolvedDrawConfig(fallback);
|
|
689
|
+
return mergeDrawConfigPatch(fallback, input);
|
|
690
|
+
}
|
|
691
|
+
function areResolvedTextAnnotationConfigsEqual(left, right) {
|
|
692
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
693
|
+
}
|
|
694
|
+
function areResolvedDrawConfigsEqual(left, right) {
|
|
695
|
+
return (left.brushSize === right.brushSize &&
|
|
696
|
+
left.color === right.color &&
|
|
697
|
+
left.opacity === right.opacity &&
|
|
698
|
+
left.lineCap === right.lineCap &&
|
|
699
|
+
left.lineJoin === right.lineJoin &&
|
|
700
|
+
left.selectable === right.selectable &&
|
|
701
|
+
left.evented === right.evented &&
|
|
702
|
+
left.annotationHidden === right.annotationHidden &&
|
|
703
|
+
left.annotationLocked === right.annotationLocked);
|
|
704
|
+
}
|
|
705
|
+
function getInvalidTextAnnotationConfigFields(input) {
|
|
706
|
+
const raw = isConfigObject(input) ? input : {};
|
|
707
|
+
const invalid = [];
|
|
708
|
+
if (hasOwn(raw, 'text') && typeof raw.text !== 'string')
|
|
709
|
+
invalid.push('text');
|
|
710
|
+
if (hasOwn(raw, 'width') && !isFiniteNumber$1(raw.width))
|
|
711
|
+
invalid.push('width');
|
|
712
|
+
if (hasOwn(raw, 'fontSize') && !isFiniteNumber$1(raw.fontSize))
|
|
713
|
+
invalid.push('fontSize');
|
|
714
|
+
if (hasOwn(raw, 'fontFamily') && typeof raw.fontFamily !== 'string')
|
|
715
|
+
invalid.push('fontFamily');
|
|
716
|
+
if (hasOwn(raw, 'fill') && typeof raw.fill !== 'string') {
|
|
717
|
+
invalid.push('fill');
|
|
718
|
+
}
|
|
719
|
+
return invalid;
|
|
720
|
+
}
|
|
721
|
+
function getInvalidDrawConfigFields(input) {
|
|
722
|
+
const raw = isConfigObject(input) ? input : {};
|
|
723
|
+
const invalid = [];
|
|
724
|
+
if (hasOwn(raw, 'brushSize') && !isFiniteNumber$1(raw.brushSize))
|
|
725
|
+
invalid.push('brushSize');
|
|
726
|
+
if (hasOwn(raw, 'color') && typeof raw.color !== 'string')
|
|
727
|
+
invalid.push('color');
|
|
728
|
+
if (hasOwn(raw, 'opacity') && !isFiniteNumber$1(raw.opacity))
|
|
729
|
+
invalid.push('opacity');
|
|
730
|
+
return invalid;
|
|
731
|
+
}
|
|
517
732
|
function resolveOptions(input) {
|
|
518
733
|
var _a, _b, _c, _d;
|
|
519
734
|
const raw = input !== null && input !== void 0 ? input : {};
|
|
@@ -521,8 +736,13 @@ function resolveOptions(input) {
|
|
|
521
736
|
for (const key of Object.keys(raw)) {
|
|
522
737
|
if (!KNOWN_TOP_LEVEL_KEYS.has(key))
|
|
523
738
|
continue;
|
|
524
|
-
if (key === 'label' ||
|
|
739
|
+
if (key === 'label' ||
|
|
740
|
+
key === 'crop' ||
|
|
741
|
+
key === 'defaultMosaicConfig' ||
|
|
742
|
+
key === 'defaultTextConfig' ||
|
|
743
|
+
key === 'defaultDrawConfig') {
|
|
525
744
|
continue;
|
|
745
|
+
}
|
|
526
746
|
if (key === 'onImageLoadStart' ||
|
|
527
747
|
key === 'onImageLoaded' ||
|
|
528
748
|
key === 'onImageCleared' ||
|
|
@@ -530,6 +750,7 @@ function resolveOptions(input) {
|
|
|
530
750
|
key === 'onBusyChange' ||
|
|
531
751
|
key === 'onEditorDisposed' ||
|
|
532
752
|
key === 'onMasksChanged' ||
|
|
753
|
+
key === 'onAnnotationsChanged' ||
|
|
533
754
|
key === 'onSelectionChange' ||
|
|
534
755
|
key === 'onError' ||
|
|
535
756
|
key === 'onWarning') {
|
|
@@ -625,6 +846,7 @@ function resolveOptions(input) {
|
|
|
625
846
|
resolved.onBusyChange = normalizeCallback(raw.onBusyChange);
|
|
626
847
|
resolved.onEditorDisposed = normalizeCallback(raw.onEditorDisposed);
|
|
627
848
|
resolved.onMasksChanged = normalizeCallback(raw.onMasksChanged);
|
|
849
|
+
resolved.onAnnotationsChanged = normalizeCallback(raw.onAnnotationsChanged);
|
|
628
850
|
resolved.onSelectionChange = normalizeCallback(raw.onSelectionChange);
|
|
629
851
|
resolved.onError = normalizeCallback(raw.onError);
|
|
630
852
|
resolved.onWarning = normalizeCallback(raw.onWarning);
|
|
@@ -668,11 +890,18 @@ function resolveOptions(input) {
|
|
|
668
890
|
Object.freeze(defaultMosaicConfig.previewStrokeDashArray);
|
|
669
891
|
}
|
|
670
892
|
Object.freeze(defaultMosaicConfig);
|
|
893
|
+
const defaultTextConfig = normalizeTextAnnotationConfig(raw.defaultTextConfig, DEFAULT_TEXT_ANNOTATION_CONFIG);
|
|
894
|
+
Object.freeze(defaultTextConfig.styles);
|
|
895
|
+
Object.freeze(defaultTextConfig);
|
|
896
|
+
const defaultDrawConfig = normalizeDrawConfig(raw.defaultDrawConfig, DEFAULT_DRAW_CONFIG);
|
|
897
|
+
Object.freeze(defaultDrawConfig);
|
|
671
898
|
return Object.freeze({
|
|
672
899
|
...resolved,
|
|
673
900
|
label,
|
|
674
901
|
crop,
|
|
675
902
|
defaultMosaicConfig,
|
|
903
|
+
defaultTextConfig,
|
|
904
|
+
defaultDrawConfig,
|
|
676
905
|
});
|
|
677
906
|
}
|
|
678
907
|
|
|
@@ -833,11 +1062,83 @@ class OperationGuard {
|
|
|
833
1062
|
}
|
|
834
1063
|
}
|
|
835
1064
|
|
|
1065
|
+
function isBaseImageObject(object) {
|
|
1066
|
+
return (!!object &&
|
|
1067
|
+
typeof object === 'object' &&
|
|
1068
|
+
object.editorObjectKind === 'baseImage');
|
|
1069
|
+
}
|
|
836
1070
|
function isMaskObject(object) {
|
|
837
|
-
|
|
1071
|
+
const candidate = object;
|
|
1072
|
+
return (!!candidate &&
|
|
1073
|
+
candidate.editorObjectKind === 'mask' &&
|
|
1074
|
+
typeof candidate.maskId === 'number' &&
|
|
1075
|
+
typeof candidate.maskUid === 'string' &&
|
|
1076
|
+
typeof candidate.maskName === 'string');
|
|
1077
|
+
}
|
|
1078
|
+
function isAnnotationObject(object) {
|
|
1079
|
+
const candidate = object;
|
|
1080
|
+
return (!!candidate &&
|
|
1081
|
+
candidate.editorObjectKind === 'annotation' &&
|
|
1082
|
+
typeof candidate.annotationId === 'number' &&
|
|
1083
|
+
typeof candidate.annotationType === 'string' &&
|
|
1084
|
+
typeof candidate.annotationName === 'string');
|
|
1085
|
+
}
|
|
1086
|
+
function isTextAnnotationObject(object) {
|
|
1087
|
+
return isAnnotationObject(object) && object.annotationType === 'text';
|
|
1088
|
+
}
|
|
1089
|
+
function isDrawAnnotationObject(object) {
|
|
1090
|
+
return isAnnotationObject(object) && object.annotationType === 'draw';
|
|
1091
|
+
}
|
|
1092
|
+
function isSessionObject(object) {
|
|
1093
|
+
const candidate = object;
|
|
1094
|
+
return (!!candidate &&
|
|
1095
|
+
candidate.editorObjectKind === 'session' &&
|
|
1096
|
+
typeof candidate.sessionObjectType === 'string');
|
|
1097
|
+
}
|
|
1098
|
+
function isEditableOverlayObject(object) {
|
|
1099
|
+
return isMaskObject(object) || isAnnotationObject(object);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
function markBaseImageObject(image) {
|
|
1103
|
+
const baseImage = image;
|
|
1104
|
+
baseImage.editorObjectKind = 'baseImage';
|
|
1105
|
+
return baseImage;
|
|
1106
|
+
}
|
|
1107
|
+
function markMaskObject(object, meta) {
|
|
1108
|
+
const mask = object;
|
|
1109
|
+
mask.editorObjectKind = 'mask';
|
|
1110
|
+
mask.maskId = meta.maskId;
|
|
1111
|
+
mask.maskUid = meta.maskUid;
|
|
1112
|
+
mask.maskName = meta.maskName;
|
|
1113
|
+
mask.originalAlpha = meta.originalAlpha;
|
|
1114
|
+
if ('originalStroke' in meta)
|
|
1115
|
+
mask.originalStroke = meta.originalStroke;
|
|
1116
|
+
if (typeof meta.originalStrokeWidth === 'number') {
|
|
1117
|
+
mask.originalStrokeWidth = meta.originalStrokeWidth;
|
|
1118
|
+
}
|
|
1119
|
+
return mask;
|
|
1120
|
+
}
|
|
1121
|
+
function markAnnotationObject(object, meta) {
|
|
1122
|
+
var _a, _b;
|
|
1123
|
+
const annotation = object;
|
|
1124
|
+
annotation.editorObjectKind = 'annotation';
|
|
1125
|
+
annotation.annotationId = meta.annotationId;
|
|
1126
|
+
annotation.annotationType = meta.annotationType;
|
|
1127
|
+
annotation.annotationName = meta.annotationName;
|
|
1128
|
+
annotation.annotationHidden = (_a = meta.annotationHidden) !== null && _a !== void 0 ? _a : false;
|
|
1129
|
+
annotation.annotationLocked = (_b = meta.annotationLocked) !== null && _b !== void 0 ? _b : false;
|
|
1130
|
+
return annotation;
|
|
1131
|
+
}
|
|
1132
|
+
function markSessionObject(object, sessionObjectType) {
|
|
1133
|
+
const sessionObject = object;
|
|
1134
|
+
sessionObject.editorObjectKind = 'session';
|
|
1135
|
+
sessionObject.sessionObjectType = sessionObjectType;
|
|
1136
|
+
return sessionObject;
|
|
838
1137
|
}
|
|
839
1138
|
|
|
840
1139
|
const SNAPSHOT_CUSTOM_KEYS = [
|
|
1140
|
+
'editorObjectKind',
|
|
1141
|
+
'sessionObjectType',
|
|
841
1142
|
'maskId',
|
|
842
1143
|
'maskUid',
|
|
843
1144
|
'maskName',
|
|
@@ -855,6 +1156,11 @@ const SNAPSHOT_CUSTOM_KEYS = [
|
|
|
855
1156
|
'cornerColor',
|
|
856
1157
|
'cornerSize',
|
|
857
1158
|
'isMosaicPreview',
|
|
1159
|
+
'annotationId',
|
|
1160
|
+
'annotationType',
|
|
1161
|
+
'annotationName',
|
|
1162
|
+
'annotationHidden',
|
|
1163
|
+
'annotationLocked',
|
|
858
1164
|
];
|
|
859
1165
|
function copySnapshotCustomPropsFromCanvas(canvasObjects, jsonObjects) {
|
|
860
1166
|
if (!Array.isArray(jsonObjects))
|
|
@@ -864,6 +1170,12 @@ function copySnapshotCustomPropsFromCanvas(canvasObjects, jsonObjects) {
|
|
|
864
1170
|
const jsonObject = jsonObjects[index];
|
|
865
1171
|
if (!liveObject || !jsonObject)
|
|
866
1172
|
continue;
|
|
1173
|
+
if (typeof liveObject.editorObjectKind === 'string') {
|
|
1174
|
+
jsonObject.editorObjectKind = liveObject.editorObjectKind;
|
|
1175
|
+
}
|
|
1176
|
+
if (typeof liveObject.sessionObjectType === 'string') {
|
|
1177
|
+
jsonObject.sessionObjectType = liveObject.sessionObjectType;
|
|
1178
|
+
}
|
|
867
1179
|
if (typeof liveObject.maskId === 'number')
|
|
868
1180
|
jsonObject.maskId = liveObject.maskId;
|
|
869
1181
|
if (typeof liveObject.maskUid === 'string')
|
|
@@ -908,9 +1220,24 @@ function copySnapshotCustomPropsFromCanvas(canvasObjects, jsonObjects) {
|
|
|
908
1220
|
jsonObject.maskLabel = true;
|
|
909
1221
|
if (liveObject.isMosaicPreview === true)
|
|
910
1222
|
jsonObject.isMosaicPreview = true;
|
|
1223
|
+
if (typeof liveObject.annotationId === 'number') {
|
|
1224
|
+
jsonObject.annotationId = liveObject.annotationId;
|
|
1225
|
+
}
|
|
1226
|
+
if (typeof liveObject.annotationType === 'string') {
|
|
1227
|
+
jsonObject.annotationType = liveObject.annotationType;
|
|
1228
|
+
}
|
|
1229
|
+
if (typeof liveObject.annotationName === 'string') {
|
|
1230
|
+
jsonObject.annotationName = liveObject.annotationName;
|
|
1231
|
+
}
|
|
1232
|
+
if (typeof liveObject.annotationHidden === 'boolean') {
|
|
1233
|
+
jsonObject.annotationHidden = liveObject.annotationHidden;
|
|
1234
|
+
}
|
|
1235
|
+
if (typeof liveObject.annotationLocked === 'boolean') {
|
|
1236
|
+
jsonObject.annotationLocked = liveObject.annotationLocked;
|
|
1237
|
+
}
|
|
911
1238
|
}
|
|
912
1239
|
}
|
|
913
|
-
function isActiveSelectionObject(object) {
|
|
1240
|
+
function isActiveSelectionObject$2(object) {
|
|
914
1241
|
if (!object)
|
|
915
1242
|
return false;
|
|
916
1243
|
const type = typeof object.type === 'string' ? object.type.toLowerCase() : '';
|
|
@@ -929,22 +1256,34 @@ function saveState(input) {
|
|
|
929
1256
|
: typeof input.activeMaskId === 'number'
|
|
930
1257
|
? input.activeMaskId
|
|
931
1258
|
: null;
|
|
932
|
-
|
|
1259
|
+
const activeAnnotationId = activeObject && isAnnotationObject(activeObject)
|
|
1260
|
+
? activeObject.annotationId
|
|
1261
|
+
: typeof input.activeAnnotationId === 'number'
|
|
1262
|
+
? input.activeAnnotationId
|
|
1263
|
+
: null;
|
|
1264
|
+
if (isActiveSelectionObject$2(activeObject)) {
|
|
933
1265
|
canvas.discardActiveObject();
|
|
934
1266
|
}
|
|
935
1267
|
const jsonObj = canvas.toJSON(SNAPSHOT_CUSTOM_KEYS);
|
|
936
1268
|
copySnapshotCustomPropsFromCanvas(canvas.getObjects(), jsonObj.objects);
|
|
937
1269
|
if (Array.isArray(jsonObj.objects)) {
|
|
938
|
-
jsonObj.objects = jsonObj.objects.filter((o) => o.
|
|
1270
|
+
jsonObj.objects = jsonObj.objects.filter((o) => o.editorObjectKind !== 'session' &&
|
|
1271
|
+
o.isCropRect !== true &&
|
|
1272
|
+
o.maskLabel !== true &&
|
|
1273
|
+
o.isMosaicPreview !== true);
|
|
939
1274
|
}
|
|
940
1275
|
jsonObj._editorState = {
|
|
941
1276
|
currentScale,
|
|
942
1277
|
currentRotation,
|
|
943
1278
|
baseImageScale,
|
|
944
1279
|
currentImageMimeType: (_c = input.currentImageMimeType) !== null && _c !== void 0 ? _c : null,
|
|
1280
|
+
activeObjectKind: activeMaskId !== null ? 'mask' : activeAnnotationId !== null ? 'annotation' : null,
|
|
945
1281
|
};
|
|
946
1282
|
if (activeMaskId !== null)
|
|
947
1283
|
jsonObj._editorState.activeMaskId = activeMaskId;
|
|
1284
|
+
if (activeAnnotationId !== null) {
|
|
1285
|
+
jsonObj._editorState.activeAnnotationId = activeAnnotationId;
|
|
1286
|
+
}
|
|
948
1287
|
return JSON.stringify(jsonObj);
|
|
949
1288
|
}
|
|
950
1289
|
async function loadFromState(input) {
|
|
@@ -960,7 +1299,7 @@ async function loadFromState(input) {
|
|
|
960
1299
|
}
|
|
961
1300
|
await canvas.loadFromJSON(json);
|
|
962
1301
|
const objects = canvas.getObjects();
|
|
963
|
-
|
|
1302
|
+
restoreEditorObjectPropsFromJson(objects, (_a = json.objects) !== null && _a !== void 0 ? _a : []);
|
|
964
1303
|
const editorState = json._editorState && typeof json._editorState === 'object'
|
|
965
1304
|
? {
|
|
966
1305
|
currentScale: typeof json._editorState.currentScale === 'number'
|
|
@@ -977,6 +1316,16 @@ async function loadFromState(input) {
|
|
|
977
1316
|
if (editorState && json._editorState && typeof json._editorState.activeMaskId === 'number') {
|
|
978
1317
|
editorState.activeMaskId = json._editorState.activeMaskId;
|
|
979
1318
|
}
|
|
1319
|
+
if (editorState &&
|
|
1320
|
+
json._editorState &&
|
|
1321
|
+
typeof json._editorState.activeAnnotationId === 'number') {
|
|
1322
|
+
editorState.activeAnnotationId = json._editorState.activeAnnotationId;
|
|
1323
|
+
}
|
|
1324
|
+
if (editorState && json._editorState && 'activeObjectKind' in json._editorState) {
|
|
1325
|
+
const kind = json._editorState.activeObjectKind;
|
|
1326
|
+
editorState.activeObjectKind =
|
|
1327
|
+
kind === 'mask' || kind === 'annotation' || kind === null ? kind : null;
|
|
1328
|
+
}
|
|
980
1329
|
if (editorState && json._editorState && 'currentImageMimeType' in json._editorState) {
|
|
981
1330
|
const mimeType = json._editorState.currentImageMimeType;
|
|
982
1331
|
editorState.currentImageMimeType =
|
|
@@ -987,29 +1336,54 @@ async function loadFromState(input) {
|
|
|
987
1336
|
const maxMaskId = objects
|
|
988
1337
|
.filter(isMaskObject)
|
|
989
1338
|
.reduce((max, maskObject) => Math.max(max, maskObject.maskId), 0);
|
|
990
|
-
const
|
|
1339
|
+
const maxAnnotationId = objects
|
|
1340
|
+
.filter(isAnnotationObject)
|
|
1341
|
+
.reduce((max, annotationObject) => Math.max(max, annotationObject.annotationId), 0);
|
|
1342
|
+
const masks = objects.filter(isMaskObject);
|
|
1343
|
+
const annotations = objects.filter(isAnnotationObject);
|
|
1344
|
+
const originalImage = (_b = objects.find(isBaseImageObject)) !== null && _b !== void 0 ? _b : null;
|
|
991
1345
|
return {
|
|
992
1346
|
editorState,
|
|
993
1347
|
maxMaskId,
|
|
1348
|
+
maxAnnotationId,
|
|
994
1349
|
originalImage,
|
|
995
1350
|
objects,
|
|
1351
|
+
masks,
|
|
1352
|
+
annotations,
|
|
996
1353
|
jsonString,
|
|
997
1354
|
};
|
|
998
1355
|
}
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1356
|
+
function restoreEditorObjectPropsFromJson(canvasObjs, jsonObjs) {
|
|
1357
|
+
var _a, _b, _c, _d;
|
|
1358
|
+
jsonObjs.forEach((jObj, index) => {
|
|
1359
|
+
const canvasObj = canvasObjs[index];
|
|
1360
|
+
if (!canvasObj)
|
|
1361
|
+
return;
|
|
1362
|
+
if (jObj.editorObjectKind === 'baseImage') {
|
|
1363
|
+
markBaseImageObject(canvasObj);
|
|
1364
|
+
return;
|
|
1365
|
+
}
|
|
1366
|
+
if (jObj.editorObjectKind === 'annotation' &&
|
|
1367
|
+
typeof jObj.annotationId === 'number' &&
|
|
1368
|
+
typeof jObj.annotationType === 'string' &&
|
|
1369
|
+
typeof jObj.annotationName === 'string') {
|
|
1370
|
+
markAnnotationObject(canvasObj, {
|
|
1371
|
+
annotationId: jObj.annotationId,
|
|
1372
|
+
annotationType: jObj.annotationType === 'draw' ? 'draw' : 'text',
|
|
1373
|
+
annotationName: jObj.annotationName,
|
|
1374
|
+
annotationHidden: typeof jObj.annotationHidden === 'boolean' ? jObj.annotationHidden : false,
|
|
1375
|
+
annotationLocked: typeof jObj.annotationLocked === 'boolean' ? jObj.annotationLocked : false,
|
|
1376
|
+
});
|
|
1377
|
+
return;
|
|
1378
|
+
}
|
|
1379
|
+
if (jObj.editorObjectKind === 'session' && typeof jObj.sessionObjectType === 'string') {
|
|
1380
|
+
canvasObj.editorObjectKind = 'session';
|
|
1381
|
+
canvasObj.sessionObjectType = jObj.sessionObjectType;
|
|
1382
|
+
}
|
|
1383
|
+
});
|
|
1010
1384
|
const consumedCanvasIndexes = new Set();
|
|
1011
1385
|
for (const jObj of jsonObjs) {
|
|
1012
|
-
if (typeof jObj.maskId !== 'number')
|
|
1386
|
+
if (jObj.editorObjectKind !== 'mask' || typeof jObj.maskId !== 'number')
|
|
1013
1387
|
continue;
|
|
1014
1388
|
const jType = String((_a = jObj.type) !== null && _a !== void 0 ? _a : '');
|
|
1015
1389
|
const jLeft = Number((_b = jObj.left) !== null && _b !== void 0 ? _b : 0);
|
|
@@ -1038,15 +1412,19 @@ function restoreMaskPropsFromJson(canvasObjs, jsonObjs) {
|
|
|
1038
1412
|
consumedCanvasIndexes.add(matchIndex);
|
|
1039
1413
|
const match = canvasObjs[matchIndex];
|
|
1040
1414
|
const maskObject = match;
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
typeof jObj.
|
|
1415
|
+
const originalStroke = 'originalStroke' in jObj
|
|
1416
|
+
? jObj.originalStroke
|
|
1417
|
+
: undefined;
|
|
1418
|
+
markMaskObject(maskObject, {
|
|
1419
|
+
maskId: jObj.maskId,
|
|
1420
|
+
maskUid: typeof jObj.maskUid === 'string' ? jObj.maskUid : `mask-${jObj.maskId}`,
|
|
1421
|
+
maskName: typeof jObj.maskName === 'string' ? jObj.maskName : '',
|
|
1422
|
+
originalAlpha: typeof jObj.originalAlpha === 'number'
|
|
1048
1423
|
? jObj.originalAlpha
|
|
1049
|
-
: ((
|
|
1424
|
+
: ((_d = maskObject.opacity) !== null && _d !== void 0 ? _d : 0.5),
|
|
1425
|
+
originalStroke,
|
|
1426
|
+
originalStrokeWidth: typeof jObj.originalStrokeWidth === 'number' ? jObj.originalStrokeWidth : undefined,
|
|
1427
|
+
});
|
|
1050
1428
|
if ('originalStroke' in jObj) {
|
|
1051
1429
|
maskObject.originalStroke = jObj.originalStroke;
|
|
1052
1430
|
}
|
|
@@ -1134,8 +1512,8 @@ class HistoryManager {
|
|
|
1134
1512
|
});
|
|
1135
1513
|
this.maxSize = maxSize;
|
|
1136
1514
|
}
|
|
1137
|
-
execute(command) {
|
|
1138
|
-
|
|
1515
|
+
async execute(command) {
|
|
1516
|
+
await command.execute();
|
|
1139
1517
|
this.pushAndTrim(command);
|
|
1140
1518
|
}
|
|
1141
1519
|
push(command) {
|
|
@@ -1228,295 +1606,363 @@ function detectFabric(fabricOrOptions, maybeOptions, globalScope = globalThis) {
|
|
|
1228
1606
|
};
|
|
1229
1607
|
}
|
|
1230
1608
|
|
|
1231
|
-
function
|
|
1232
|
-
|
|
1609
|
+
function isAnnotationLocked(annotation) {
|
|
1610
|
+
return annotation.annotationLocked === true;
|
|
1233
1611
|
}
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
super(message);
|
|
1237
|
-
Object.defineProperty(this, "name", {
|
|
1238
|
-
enumerable: true,
|
|
1239
|
-
configurable: true,
|
|
1240
|
-
writable: true,
|
|
1241
|
-
value: 'ImageDecodeError'
|
|
1242
|
-
});
|
|
1243
|
-
Object.defineProperty(this, "originalError", {
|
|
1244
|
-
enumerable: true,
|
|
1245
|
-
configurable: true,
|
|
1246
|
-
writable: true,
|
|
1247
|
-
value: void 0
|
|
1248
|
-
});
|
|
1249
|
-
this.originalError = originalError;
|
|
1250
|
-
fixPrototype(this, ImageDecodeError);
|
|
1251
|
-
}
|
|
1612
|
+
function isAnnotationUnlocked(annotation) {
|
|
1613
|
+
return !isAnnotationLocked(annotation);
|
|
1252
1614
|
}
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
enumerable: true,
|
|
1258
|
-
configurable: true,
|
|
1259
|
-
writable: true,
|
|
1260
|
-
value: 'ImageLoadTimeoutError'
|
|
1261
|
-
});
|
|
1262
|
-
Object.defineProperty(this, "label", {
|
|
1263
|
-
enumerable: true,
|
|
1264
|
-
configurable: true,
|
|
1265
|
-
writable: true,
|
|
1266
|
-
value: void 0
|
|
1267
|
-
});
|
|
1268
|
-
Object.defineProperty(this, "elapsedMs", {
|
|
1269
|
-
enumerable: true,
|
|
1270
|
-
configurable: true,
|
|
1271
|
-
writable: true,
|
|
1272
|
-
value: void 0
|
|
1273
|
-
});
|
|
1274
|
-
this.label = label;
|
|
1275
|
-
this.elapsedMs = elapsedMs;
|
|
1276
|
-
fixPrototype(this, ImageLoadTimeoutError);
|
|
1615
|
+
|
|
1616
|
+
function setObjectProps(object, props) {
|
|
1617
|
+
try {
|
|
1618
|
+
object.set(props);
|
|
1277
1619
|
}
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
constructor(message = 'Failed to obtain a 2D context for downsampling.', originalError = null) {
|
|
1281
|
-
super(message);
|
|
1282
|
-
Object.defineProperty(this, "name", {
|
|
1283
|
-
enumerable: true,
|
|
1284
|
-
configurable: true,
|
|
1285
|
-
writable: true,
|
|
1286
|
-
value: 'DownsampleError'
|
|
1287
|
-
});
|
|
1288
|
-
Object.defineProperty(this, "originalError", {
|
|
1289
|
-
enumerable: true,
|
|
1290
|
-
configurable: true,
|
|
1291
|
-
writable: true,
|
|
1292
|
-
value: void 0
|
|
1293
|
-
});
|
|
1294
|
-
this.originalError = originalError;
|
|
1295
|
-
fixPrototype(this, DownsampleError);
|
|
1620
|
+
catch {
|
|
1621
|
+
Object.assign(object, props);
|
|
1296
1622
|
}
|
|
1297
1623
|
}
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
Object.defineProperty(this, "name", {
|
|
1302
|
-
enumerable: true,
|
|
1303
|
-
configurable: true,
|
|
1304
|
-
writable: true,
|
|
1305
|
-
value: 'MergeMasksError'
|
|
1306
|
-
});
|
|
1307
|
-
Object.defineProperty(this, "originalError", {
|
|
1308
|
-
enumerable: true,
|
|
1309
|
-
configurable: true,
|
|
1310
|
-
writable: true,
|
|
1311
|
-
value: void 0
|
|
1312
|
-
});
|
|
1313
|
-
this.originalError = originalError;
|
|
1314
|
-
fixPrototype(this, MergeMasksError);
|
|
1315
|
-
}
|
|
1624
|
+
function syncTextEditability(annotation, editable) {
|
|
1625
|
+
const textObject = annotation;
|
|
1626
|
+
textObject.editable = editable;
|
|
1316
1627
|
}
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1628
|
+
function syncAnnotationRuntimeState(annotation) {
|
|
1629
|
+
var _a;
|
|
1630
|
+
const hidden = annotation.annotationHidden === true;
|
|
1631
|
+
const locked = isAnnotationLocked(annotation);
|
|
1632
|
+
setObjectProps(annotation, {
|
|
1633
|
+
visible: !hidden,
|
|
1634
|
+
selectable: locked ? false : true,
|
|
1635
|
+
evented: locked ? false : true,
|
|
1636
|
+
hasControls: !locked,
|
|
1637
|
+
lockMovementX: locked,
|
|
1638
|
+
lockMovementY: locked,
|
|
1639
|
+
lockScalingX: locked,
|
|
1640
|
+
lockScalingY: locked,
|
|
1641
|
+
lockRotation: locked,
|
|
1642
|
+
});
|
|
1643
|
+
if (!locked) {
|
|
1644
|
+
setObjectProps(annotation, {
|
|
1645
|
+
selectable: true,
|
|
1646
|
+
evented: true,
|
|
1647
|
+
hasControls: true,
|
|
1648
|
+
lockMovementX: false,
|
|
1649
|
+
lockMovementY: false,
|
|
1650
|
+
lockScalingX: false,
|
|
1651
|
+
lockScalingY: false,
|
|
1652
|
+
lockRotation: false,
|
|
1331
1653
|
});
|
|
1332
|
-
this.originalError = originalError;
|
|
1333
|
-
fixPrototype(this, CropApplyError);
|
|
1334
1654
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
constructor(operation = 'exportImageFile') {
|
|
1338
|
-
super(`Cannot ${operation}: no image is loaded on the canvas.`);
|
|
1339
|
-
Object.defineProperty(this, "name", {
|
|
1340
|
-
enumerable: true,
|
|
1341
|
-
configurable: true,
|
|
1342
|
-
writable: true,
|
|
1343
|
-
value: 'ExportNotReadyError'
|
|
1344
|
-
});
|
|
1345
|
-
Object.defineProperty(this, "operation", {
|
|
1346
|
-
enumerable: true,
|
|
1347
|
-
configurable: true,
|
|
1348
|
-
writable: true,
|
|
1349
|
-
value: void 0
|
|
1350
|
-
});
|
|
1351
|
-
this.operation = operation;
|
|
1352
|
-
fixPrototype(this, ExportNotReadyError);
|
|
1655
|
+
if (isTextAnnotationObject(annotation)) {
|
|
1656
|
+
syncTextEditability(annotation, !locked);
|
|
1353
1657
|
}
|
|
1658
|
+
(_a = annotation.setCoords) === null || _a === void 0 ? void 0 : _a.call(annotation);
|
|
1354
1659
|
}
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
super(message);
|
|
1358
|
-
Object.defineProperty(this, "name", {
|
|
1359
|
-
enumerable: true,
|
|
1360
|
-
configurable: true,
|
|
1361
|
-
writable: true,
|
|
1362
|
-
value: 'ExportError'
|
|
1363
|
-
});
|
|
1364
|
-
Object.defineProperty(this, "originalError", {
|
|
1365
|
-
enumerable: true,
|
|
1366
|
-
configurable: true,
|
|
1367
|
-
writable: true,
|
|
1368
|
-
value: void 0
|
|
1369
|
-
});
|
|
1370
|
-
this.originalError = originalError;
|
|
1371
|
-
fixPrototype(this, ExportError);
|
|
1372
|
-
}
|
|
1660
|
+
function syncAnnotationRuntimeStates(annotations) {
|
|
1661
|
+
annotations.forEach(syncAnnotationRuntimeState);
|
|
1373
1662
|
}
|
|
1374
1663
|
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
const
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
var _a;
|
|
1385
|
-
const strokeWidth = Number(mask.originalStrokeWidth);
|
|
1386
|
-
const opacity = Number(mask.originalAlpha);
|
|
1387
|
-
return {
|
|
1388
|
-
stroke: (_a = mask.originalStroke) !== null && _a !== void 0 ? _a : DEFAULT_STROKE_FALLBACK,
|
|
1389
|
-
strokeWidth: Number.isFinite(strokeWidth) ? strokeWidth : DEFAULT_STROKE_WIDTH_FALLBACK,
|
|
1390
|
-
opacity: Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK,
|
|
1391
|
-
};
|
|
1392
|
-
}
|
|
1393
|
-
function getMaskHoverStyle(mask) {
|
|
1394
|
-
const opacity = Number(mask.originalAlpha);
|
|
1395
|
-
const baseAlpha = Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK;
|
|
1396
|
-
return {
|
|
1397
|
-
stroke: HOVER_STROKE,
|
|
1398
|
-
strokeWidth: HOVER_STROKE_WIDTH,
|
|
1399
|
-
opacity: Math.min(baseAlpha + HOVER_OPACITY_BUMP, 1),
|
|
1400
|
-
};
|
|
1664
|
+
function isActiveSelectionObject$1(object) {
|
|
1665
|
+
if (!object)
|
|
1666
|
+
return false;
|
|
1667
|
+
const type = typeof object.type === 'string' ? object.type.toLowerCase() : '';
|
|
1668
|
+
if (type === 'activeselection')
|
|
1669
|
+
return true;
|
|
1670
|
+
const isType = object.isType;
|
|
1671
|
+
return (typeof isType === 'function' &&
|
|
1672
|
+
(isType.call(object, 'ActiveSelection') || isType.call(object, 'activeSelection')));
|
|
1401
1673
|
}
|
|
1402
|
-
function
|
|
1403
|
-
|
|
1674
|
+
function getActiveSelectionObjects(canvas) {
|
|
1675
|
+
const active = canvas.getActiveObject();
|
|
1676
|
+
if (!active)
|
|
1677
|
+
return [];
|
|
1678
|
+
if (!isActiveSelectionObject$1(active))
|
|
1679
|
+
return [active];
|
|
1680
|
+
const getObjects = active.getObjects;
|
|
1681
|
+
return typeof getObjects === 'function' ? getObjects.call(active) : [];
|
|
1682
|
+
}
|
|
1683
|
+
function getAnnotations(canvas) {
|
|
1684
|
+
return canvas.getObjects().filter(isAnnotationObject).slice();
|
|
1685
|
+
}
|
|
1686
|
+
function getSelectedAnnotations(canvas) {
|
|
1687
|
+
return getActiveSelectionObjects(canvas).filter(isAnnotationObject);
|
|
1688
|
+
}
|
|
1689
|
+
function snapshotAnnotation(annotation) {
|
|
1690
|
+
return JSON.stringify({
|
|
1691
|
+
text: annotation.text,
|
|
1692
|
+
fontSize: annotation.fontSize,
|
|
1693
|
+
fontFamily: annotation.fontFamily,
|
|
1694
|
+
fontWeight: annotation.fontWeight,
|
|
1695
|
+
fill: annotation.fill,
|
|
1696
|
+
backgroundColor: annotation.backgroundColor,
|
|
1697
|
+
textAlign: annotation.textAlign,
|
|
1698
|
+
width: annotation.width,
|
|
1699
|
+
stroke: annotation.stroke,
|
|
1700
|
+
strokeWidth: annotation.strokeWidth,
|
|
1701
|
+
opacity: annotation.opacity,
|
|
1702
|
+
visible: annotation.visible,
|
|
1703
|
+
selectable: annotation.selectable,
|
|
1704
|
+
evented: annotation.evented,
|
|
1705
|
+
annotationHidden: annotation.annotationHidden,
|
|
1706
|
+
annotationLocked: annotation.annotationLocked,
|
|
1707
|
+
});
|
|
1404
1708
|
}
|
|
1405
|
-
function
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
}
|
|
1709
|
+
function setAnnotationProps(annotation, props) {
|
|
1710
|
+
try {
|
|
1711
|
+
annotation.set(props);
|
|
1712
|
+
}
|
|
1713
|
+
catch {
|
|
1714
|
+
Object.assign(annotation, props);
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
function updateTextAnnotation(annotation, config) {
|
|
1718
|
+
const props = {};
|
|
1719
|
+
const raw = config;
|
|
1720
|
+
if (typeof raw.text === 'string')
|
|
1721
|
+
props.text = raw.text;
|
|
1722
|
+
if (typeof raw.fontSize === 'number' && Number.isFinite(raw.fontSize) && raw.fontSize > 0) {
|
|
1723
|
+
props.fontSize = raw.fontSize;
|
|
1724
|
+
}
|
|
1725
|
+
if (typeof raw.fontFamily === 'string')
|
|
1726
|
+
props.fontFamily = raw.fontFamily;
|
|
1727
|
+
if (typeof raw.fontWeight === 'string' || typeof raw.fontWeight === 'number') {
|
|
1728
|
+
props.fontWeight = raw.fontWeight;
|
|
1729
|
+
}
|
|
1730
|
+
if (typeof raw.fill === 'string')
|
|
1731
|
+
props.fill = raw.fill;
|
|
1732
|
+
if (typeof raw.backgroundColor === 'string')
|
|
1733
|
+
props.backgroundColor = raw.backgroundColor;
|
|
1734
|
+
if (raw.textAlign === 'left' ||
|
|
1735
|
+
raw.textAlign === 'center' ||
|
|
1736
|
+
raw.textAlign === 'right' ||
|
|
1737
|
+
raw.textAlign === 'justify') {
|
|
1738
|
+
props.textAlign = raw.textAlign;
|
|
1739
|
+
}
|
|
1740
|
+
if (typeof raw.width === 'number' && Number.isFinite(raw.width) && raw.width > 0) {
|
|
1741
|
+
props.width = raw.width;
|
|
1742
|
+
}
|
|
1743
|
+
if (Object.keys(props).length > 0)
|
|
1744
|
+
setAnnotationProps(annotation, props);
|
|
1745
|
+
}
|
|
1746
|
+
function updateDrawAnnotation(annotation, config) {
|
|
1747
|
+
const props = {};
|
|
1748
|
+
const raw = config;
|
|
1749
|
+
if (typeof raw.stroke === 'string')
|
|
1750
|
+
props.stroke = raw.stroke;
|
|
1751
|
+
if (typeof raw.strokeWidth === 'number' &&
|
|
1752
|
+
Number.isFinite(raw.strokeWidth) &&
|
|
1753
|
+
raw.strokeWidth > 0) {
|
|
1754
|
+
props.strokeWidth = raw.strokeWidth;
|
|
1755
|
+
}
|
|
1756
|
+
if (typeof raw.opacity === 'number' && Number.isFinite(raw.opacity)) {
|
|
1757
|
+
props.opacity = Math.max(0, Math.min(1, raw.opacity));
|
|
1758
|
+
}
|
|
1759
|
+
if (Object.keys(props).length > 0)
|
|
1760
|
+
setAnnotationProps(annotation, props);
|
|
1761
|
+
}
|
|
1762
|
+
function updateAnnotationObject(annotation, config) {
|
|
1763
|
+
const before = snapshotAnnotation(annotation);
|
|
1764
|
+
const raw = config;
|
|
1765
|
+
if (typeof raw.annotationHidden === 'boolean') {
|
|
1766
|
+
annotation.annotationHidden = raw.annotationHidden;
|
|
1767
|
+
}
|
|
1768
|
+
if (typeof raw.annotationLocked === 'boolean') {
|
|
1769
|
+
annotation.annotationLocked = raw.annotationLocked;
|
|
1770
|
+
}
|
|
1771
|
+
const lockedAfter = isAnnotationLocked(annotation);
|
|
1772
|
+
if (!lockedAfter) {
|
|
1773
|
+
if (typeof raw.selectable === 'boolean')
|
|
1774
|
+
annotation.selectable = raw.selectable;
|
|
1775
|
+
if (typeof raw.evented === 'boolean')
|
|
1776
|
+
annotation.evented = raw.evented;
|
|
1777
|
+
if (isTextAnnotationObject(annotation))
|
|
1778
|
+
updateTextAnnotation(annotation, config);
|
|
1779
|
+
if (isDrawAnnotationObject(annotation))
|
|
1780
|
+
updateDrawAnnotation(annotation, config);
|
|
1781
|
+
}
|
|
1782
|
+
syncAnnotationRuntimeState(annotation);
|
|
1783
|
+
return snapshotAnnotation(annotation) !== before;
|
|
1784
|
+
}
|
|
1785
|
+
function updateAnnotation(context, annotationId, config) {
|
|
1786
|
+
const target = getAnnotations(context.canvas).find((annotation) => annotation.annotationId === annotationId);
|
|
1787
|
+
if (!target)
|
|
1788
|
+
return false;
|
|
1789
|
+
const changed = updateAnnotationObject(target, config);
|
|
1790
|
+
if (!changed)
|
|
1791
|
+
return false;
|
|
1792
|
+
context.canvas.requestRenderAll();
|
|
1793
|
+
context.saveCanvasState();
|
|
1794
|
+
context.updateUi();
|
|
1795
|
+
return true;
|
|
1412
1796
|
}
|
|
1413
|
-
function
|
|
1414
|
-
const
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
(
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
tagged.on('mouseout', mouseout);
|
|
1427
|
-
tagged.imageEditorMaskHandlers = { mouseover, mouseout };
|
|
1797
|
+
function updateSelectedAnnotation(context, config) {
|
|
1798
|
+
const selectedAnnotations = getSelectedAnnotations(context.canvas);
|
|
1799
|
+
if (selectedAnnotations.length === 0)
|
|
1800
|
+
return false;
|
|
1801
|
+
const changed = selectedAnnotations
|
|
1802
|
+
.map((annotation) => updateAnnotationObject(annotation, config))
|
|
1803
|
+
.some(Boolean);
|
|
1804
|
+
if (!changed)
|
|
1805
|
+
return false;
|
|
1806
|
+
context.canvas.requestRenderAll();
|
|
1807
|
+
context.saveCanvasState();
|
|
1808
|
+
context.updateUi();
|
|
1809
|
+
return true;
|
|
1428
1810
|
}
|
|
1429
|
-
function
|
|
1430
|
-
|
|
1431
|
-
const
|
|
1432
|
-
if (
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
}
|
|
1437
|
-
catch {
|
|
1438
|
-
}
|
|
1439
|
-
delete tagged.imageEditorMaskHandlers;
|
|
1440
|
-
}
|
|
1441
|
-
const patch = {};
|
|
1442
|
-
if (!Number.isFinite(Number(tagged.originalAlpha))) {
|
|
1443
|
-
const opacity = Number(tagged.opacity);
|
|
1444
|
-
patch.originalAlpha = Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK;
|
|
1445
|
-
}
|
|
1446
|
-
if (tagged.originalStroke == null) {
|
|
1447
|
-
patch.originalStroke = (_a = tagged.stroke) !== null && _a !== void 0 ? _a : DEFAULT_STROKE_FALLBACK;
|
|
1448
|
-
}
|
|
1449
|
-
if (!Number.isFinite(Number(tagged.originalStrokeWidth))) {
|
|
1450
|
-
const sw = Number(tagged.strokeWidth);
|
|
1451
|
-
patch.originalStrokeWidth = Number.isFinite(sw) ? sw : DEFAULT_STROKE_WIDTH_FALLBACK;
|
|
1811
|
+
function removeAnnotationObjects(context, objects, options = {}) {
|
|
1812
|
+
const force = options.force === true;
|
|
1813
|
+
const removable = objects.filter((annotation) => force || isAnnotationUnlocked(annotation));
|
|
1814
|
+
if (removable.length === 0)
|
|
1815
|
+
return 0;
|
|
1816
|
+
for (const annotation of removable) {
|
|
1817
|
+
context.canvas.remove(annotation);
|
|
1452
1818
|
}
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1819
|
+
context.canvas.discardActiveObject();
|
|
1820
|
+
context.canvas.renderAll();
|
|
1821
|
+
if (options.saveHistory !== false)
|
|
1822
|
+
context.saveCanvasState();
|
|
1823
|
+
context.updateUi();
|
|
1824
|
+
return removable.length;
|
|
1456
1825
|
}
|
|
1457
|
-
function
|
|
1458
|
-
|
|
1459
|
-
|
|
1826
|
+
function removeSelectedAnnotation(context) {
|
|
1827
|
+
return removeAnnotationObjects(context, getSelectedAnnotations(context.canvas));
|
|
1828
|
+
}
|
|
1829
|
+
function removeAllAnnotations(context, options = {}) {
|
|
1830
|
+
return removeAnnotationObjects(context, getAnnotations(context.canvas), options);
|
|
1831
|
+
}
|
|
1832
|
+
function getAnnotationListDocument(context) {
|
|
1833
|
+
var _a, _b, _c, _d, _e;
|
|
1834
|
+
const canvasLike = context.canvas;
|
|
1835
|
+
return ((_e = (_c = (_b = (_a = canvasLike === null || canvasLike === void 0 ? void 0 : canvasLike.getElement) === null || _a === void 0 ? void 0 : _a.call(canvasLike)) === null || _b === void 0 ? void 0 : _b.ownerDocument) !== null && _c !== void 0 ? _c : (_d = canvasLike === null || canvasLike === void 0 ? void 0 : canvasLike.lowerCanvasEl) === null || _d === void 0 ? void 0 : _d.ownerDocument) !== null && _e !== void 0 ? _e : document);
|
|
1836
|
+
}
|
|
1837
|
+
function renderAnnotationList(context) {
|
|
1838
|
+
const listId = context.getListElementId();
|
|
1839
|
+
if (!listId)
|
|
1460
1840
|
return;
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1841
|
+
const ownerDocument = getAnnotationListDocument(context);
|
|
1842
|
+
const listEl = ownerDocument.getElementById(listId);
|
|
1843
|
+
if (!listEl || !context.canvas)
|
|
1844
|
+
return;
|
|
1845
|
+
listEl.innerHTML = '';
|
|
1846
|
+
const canvas = context.canvas;
|
|
1847
|
+
getAnnotations(canvas).forEach((annotation) => {
|
|
1848
|
+
const item = ownerDocument.createElement('li');
|
|
1849
|
+
item.className = 'list-group-item annotation-item';
|
|
1850
|
+
item.textContent = annotation.annotationName;
|
|
1851
|
+
item.dataset.annotationId = String(annotation.annotationId);
|
|
1852
|
+
item.onclick = () => {
|
|
1853
|
+
const id = Number(item.dataset.annotationId);
|
|
1854
|
+
if (!Number.isFinite(id))
|
|
1855
|
+
return;
|
|
1856
|
+
const target = getAnnotations(canvas).find((candidate) => candidate.annotationId === id);
|
|
1857
|
+
if (!target)
|
|
1858
|
+
return;
|
|
1859
|
+
canvas.setActiveObject(target);
|
|
1860
|
+
context.onAnnotationSelected(target);
|
|
1861
|
+
};
|
|
1862
|
+
listEl.appendChild(item);
|
|
1863
|
+
});
|
|
1468
1864
|
}
|
|
1469
|
-
function
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
};
|
|
1865
|
+
function updateAnnotationListSelection(context, selectedAnnotation) {
|
|
1866
|
+
const listId = context.getListElementId();
|
|
1867
|
+
if (!listId)
|
|
1868
|
+
return;
|
|
1869
|
+
const listEl = getAnnotationListDocument(context).getElementById(listId);
|
|
1870
|
+
if (!listEl)
|
|
1871
|
+
return;
|
|
1872
|
+
const selectedId = selectedAnnotation ? String(selectedAnnotation.annotationId) : null;
|
|
1873
|
+
listEl.querySelectorAll('.annotation-item').forEach((item) => {
|
|
1874
|
+
item.classList.toggle('active', selectedId !== null && item.dataset.annotationId === selectedId);
|
|
1875
|
+
});
|
|
1481
1876
|
}
|
|
1482
|
-
|
|
1877
|
+
|
|
1878
|
+
function isLegacySessionObject(object) {
|
|
1879
|
+
const candidate = object;
|
|
1880
|
+
return (candidate.isCropRect === true ||
|
|
1881
|
+
candidate.maskLabel === true ||
|
|
1882
|
+
candidate.isMosaicPreview === true);
|
|
1883
|
+
}
|
|
1884
|
+
function moveObjectTo(canvas, object, index) {
|
|
1885
|
+
const canvasWithLayerApi = canvas;
|
|
1886
|
+
if (typeof canvasWithLayerApi.moveObjectTo === 'function') {
|
|
1887
|
+
canvasWithLayerApi.moveObjectTo(object, index);
|
|
1888
|
+
return;
|
|
1889
|
+
}
|
|
1483
1890
|
try {
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
fill: backup.fill,
|
|
1487
|
-
strokeWidth: backup.strokeWidth,
|
|
1488
|
-
stroke: backup.stroke,
|
|
1489
|
-
selectable: backup.selectable,
|
|
1490
|
-
evented: backup.evented,
|
|
1491
|
-
lockRotation: backup.lockRotation,
|
|
1492
|
-
});
|
|
1493
|
-
if (typeof backup.object.setCoords === 'function') {
|
|
1494
|
-
backup.object.setCoords();
|
|
1495
|
-
}
|
|
1891
|
+
canvas.remove(object);
|
|
1892
|
+
canvas.insertAt(index, object);
|
|
1496
1893
|
}
|
|
1497
1894
|
catch {
|
|
1895
|
+
canvas.add(object);
|
|
1498
1896
|
}
|
|
1499
1897
|
}
|
|
1500
|
-
|
|
1501
|
-
if (!
|
|
1502
|
-
|
|
1503
|
-
const masks = context.canvas.getObjects().filter(isMaskObject);
|
|
1504
|
-
const backups = masks.map(captureMaskStyleBackup);
|
|
1505
|
-
try {
|
|
1506
|
-
masks.forEach((mask, index) => mutator(mask, index));
|
|
1507
|
-
return await callback();
|
|
1508
|
-
}
|
|
1509
|
-
finally {
|
|
1510
|
-
for (const backup of backups)
|
|
1511
|
-
restoreMaskStyleBackup(backup);
|
|
1898
|
+
function ensureOnCanvas(canvas, object) {
|
|
1899
|
+
if (!canvas.getObjects().includes(object)) {
|
|
1900
|
+
canvas.add(object);
|
|
1512
1901
|
}
|
|
1513
1902
|
}
|
|
1514
|
-
function
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1903
|
+
function withoutObject(canvas, object) {
|
|
1904
|
+
return canvas.getObjects().filter((candidate) => candidate !== object);
|
|
1905
|
+
}
|
|
1906
|
+
function findFirstSessionIndex(objects) {
|
|
1907
|
+
return objects.findIndex((object) => isSessionObject(object) || isLegacySessionObject(object));
|
|
1908
|
+
}
|
|
1909
|
+
function getOrderedGroups(canvas) {
|
|
1910
|
+
const baseImages = [];
|
|
1911
|
+
const overlays = [];
|
|
1912
|
+
const sessions = [];
|
|
1913
|
+
const others = [];
|
|
1914
|
+
for (const object of canvas.getObjects()) {
|
|
1915
|
+
if (isBaseImageObject(object)) {
|
|
1916
|
+
baseImages.push(object);
|
|
1917
|
+
}
|
|
1918
|
+
else if (isEditableOverlayObject(object)) {
|
|
1919
|
+
overlays.push(object);
|
|
1920
|
+
}
|
|
1921
|
+
else if (isSessionObject(object) || isLegacySessionObject(object)) {
|
|
1922
|
+
sessions.push(object);
|
|
1923
|
+
}
|
|
1924
|
+
else {
|
|
1925
|
+
others.push(object);
|
|
1926
|
+
}
|
|
1519
1927
|
}
|
|
1928
|
+
return { baseImages, overlays, sessions, others };
|
|
1929
|
+
}
|
|
1930
|
+
function normalizeLayerOrder(canvas) {
|
|
1931
|
+
const groups = getOrderedGroups(canvas);
|
|
1932
|
+
const ordered = [
|
|
1933
|
+
...groups.baseImages,
|
|
1934
|
+
...groups.others,
|
|
1935
|
+
...groups.overlays,
|
|
1936
|
+
...groups.sessions,
|
|
1937
|
+
];
|
|
1938
|
+
ordered.forEach((object, index) => {
|
|
1939
|
+
moveObjectTo(canvas, object, index);
|
|
1940
|
+
});
|
|
1941
|
+
}
|
|
1942
|
+
function placeMaskObject(canvas, mask) {
|
|
1943
|
+
ensureOnCanvas(canvas, mask);
|
|
1944
|
+
const objects = withoutObject(canvas, mask);
|
|
1945
|
+
const firstSessionIndex = findFirstSessionIndex(objects);
|
|
1946
|
+
moveObjectTo(canvas, mask, firstSessionIndex === -1 ? objects.length : firstSessionIndex);
|
|
1947
|
+
}
|
|
1948
|
+
function placeAnnotationObject(canvas, annotation) {
|
|
1949
|
+
ensureOnCanvas(canvas, annotation);
|
|
1950
|
+
const objects = withoutObject(canvas, annotation);
|
|
1951
|
+
const firstSessionIndex = findFirstSessionIndex(objects);
|
|
1952
|
+
moveObjectTo(canvas, annotation, firstSessionIndex === -1 ? objects.length : firstSessionIndex);
|
|
1953
|
+
}
|
|
1954
|
+
function getEditableOverlayRange(canvas) {
|
|
1955
|
+
const objects = canvas.getObjects();
|
|
1956
|
+
const overlayIndexes = objects
|
|
1957
|
+
.map((object, index) => ({ object, index }))
|
|
1958
|
+
.filter(({ object }) => isEditableOverlayObject(object));
|
|
1959
|
+
if (overlayIndexes.length === 0)
|
|
1960
|
+
return { start: -1, end: -1, overlays: [] };
|
|
1961
|
+
return {
|
|
1962
|
+
start: overlayIndexes[0].index,
|
|
1963
|
+
end: overlayIndexes[overlayIndexes.length - 1].index,
|
|
1964
|
+
overlays: overlayIndexes.map(({ object }) => object),
|
|
1965
|
+
};
|
|
1520
1966
|
}
|
|
1521
1967
|
|
|
1522
1968
|
function hasMeaningfulCanvasRegion(rect, canvasWidth, canvasHeight) {
|
|
@@ -1574,37 +2020,734 @@ function getClampedCanvasRegion(rect, canvasWidth, canvasHeight, options = {}) {
|
|
|
1574
2020
|
height: Math.max(1, bottom - top),
|
|
1575
2021
|
};
|
|
1576
2022
|
}
|
|
1577
|
-
function hasFractionalCanvasEdge(value) {
|
|
1578
|
-
const numericValue = Number(value);
|
|
1579
|
-
if (!Number.isFinite(numericValue))
|
|
1580
|
-
return false;
|
|
1581
|
-
return Math.abs(numericValue - Math.round(numericValue)) > 0.01;
|
|
2023
|
+
function hasFractionalCanvasEdge(value) {
|
|
2024
|
+
const numericValue = Number(value);
|
|
2025
|
+
if (!Number.isFinite(numericValue))
|
|
2026
|
+
return false;
|
|
2027
|
+
return Math.abs(numericValue - Math.round(numericValue)) > 0.01;
|
|
2028
|
+
}
|
|
2029
|
+
function getPartialExportEdges(bounds, angle = 0) {
|
|
2030
|
+
if (!bounds)
|
|
2031
|
+
return null;
|
|
2032
|
+
const normalizedAngle = Math.abs((Number(angle) || 0) % 90);
|
|
2033
|
+
const isAxisAligned = normalizedAngle < 0.01 || Math.abs(normalizedAngle - 90) < 0.01;
|
|
2034
|
+
if (!isAxisAligned)
|
|
2035
|
+
return null;
|
|
2036
|
+
const left = Number(bounds.left) || 0;
|
|
2037
|
+
const top = Number(bounds.top) || 0;
|
|
2038
|
+
return {
|
|
2039
|
+
left: hasFractionalCanvasEdge(left),
|
|
2040
|
+
top: hasFractionalCanvasEdge(top),
|
|
2041
|
+
right: hasFractionalCanvasEdge(left + (Number(bounds.width) || 0)),
|
|
2042
|
+
bottom: hasFractionalCanvasEdge(top + (Number(bounds.height) || 0)),
|
|
2043
|
+
};
|
|
2044
|
+
}
|
|
2045
|
+
function getObjectBBox(object) {
|
|
2046
|
+
object.setCoords();
|
|
2047
|
+
const boundingRect = object.getBoundingRect();
|
|
2048
|
+
return {
|
|
2049
|
+
left: boundingRect.left,
|
|
2050
|
+
top: boundingRect.top,
|
|
2051
|
+
width: boundingRect.width,
|
|
2052
|
+
height: boundingRect.height,
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
|
|
2056
|
+
function resolveNumeric(val, axis, fallback, canvas, options) {
|
|
2057
|
+
if (typeof val === 'number') {
|
|
2058
|
+
return val;
|
|
2059
|
+
}
|
|
2060
|
+
if (typeof val === 'function') {
|
|
2061
|
+
return val(canvas, options);
|
|
2062
|
+
}
|
|
2063
|
+
if (typeof val === 'string' && val.endsWith('%')) {
|
|
2064
|
+
const pct = parseFloat(val);
|
|
2065
|
+
if (!Number.isFinite(pct)) {
|
|
2066
|
+
return fallback;
|
|
2067
|
+
}
|
|
2068
|
+
const dim = axis === 'x' ? canvas.getWidth() : canvas.getHeight();
|
|
2069
|
+
return Math.floor(dim * (pct / 100));
|
|
2070
|
+
}
|
|
2071
|
+
return fallback;
|
|
2072
|
+
}
|
|
2073
|
+
function coercePoint(pt) {
|
|
2074
|
+
if (Array.isArray(pt)) {
|
|
2075
|
+
return { x: Number(pt[0]), y: Number(pt[1]) };
|
|
2076
|
+
}
|
|
2077
|
+
return { x: Number(pt.x), y: Number(pt.y) };
|
|
2078
|
+
}
|
|
2079
|
+
|
|
2080
|
+
function isFinitePoint(value) {
|
|
2081
|
+
const point = value;
|
|
2082
|
+
return (!!point &&
|
|
2083
|
+
typeof point.x === 'number' &&
|
|
2084
|
+
Number.isFinite(point.x) &&
|
|
2085
|
+
typeof point.y === 'number' &&
|
|
2086
|
+
Number.isFinite(point.y));
|
|
2087
|
+
}
|
|
2088
|
+
function getPointerFromFabricEvent(canvas, event) {
|
|
2089
|
+
const fabricEvent = event && typeof event === 'object'
|
|
2090
|
+
? event
|
|
2091
|
+
: null;
|
|
2092
|
+
if (!fabricEvent)
|
|
2093
|
+
return null;
|
|
2094
|
+
if (isFinitePoint(fabricEvent.scenePoint))
|
|
2095
|
+
return { ...fabricEvent.scenePoint };
|
|
2096
|
+
if (isFinitePoint(fabricEvent.pointer))
|
|
2097
|
+
return { ...fabricEvent.pointer };
|
|
2098
|
+
if (isFinitePoint(fabricEvent.absolutePointer))
|
|
2099
|
+
return { ...fabricEvent.absolutePointer };
|
|
2100
|
+
if (fabricEvent.e && typeof canvas.getPointer === 'function') {
|
|
2101
|
+
const pointer = canvas.getPointer(fabricEvent.e);
|
|
2102
|
+
if (isFinitePoint(pointer))
|
|
2103
|
+
return { ...pointer };
|
|
2104
|
+
}
|
|
2105
|
+
return null;
|
|
2106
|
+
}
|
|
2107
|
+
|
|
2108
|
+
function resolveDefaultTextPosition(context) {
|
|
2109
|
+
const image = context.getOriginalImage();
|
|
2110
|
+
if (image) {
|
|
2111
|
+
const bounds = getObjectBBox(image);
|
|
2112
|
+
return { left: Math.round(bounds.left + 10), top: Math.round(bounds.top + 10) };
|
|
2113
|
+
}
|
|
2114
|
+
return { left: 10, top: 10 };
|
|
2115
|
+
}
|
|
2116
|
+
function resolveTextCreationConfig(context, config) {
|
|
2117
|
+
var _a, _b;
|
|
2118
|
+
const base = mergeTextAnnotationConfigPatch(context.getTextConfig(), config);
|
|
2119
|
+
const fallback = resolveDefaultTextPosition(context);
|
|
2120
|
+
const leftInput = (_a = config.left) !== null && _a !== void 0 ? _a : base.left;
|
|
2121
|
+
const topInput = (_b = config.top) !== null && _b !== void 0 ? _b : base.top;
|
|
2122
|
+
return {
|
|
2123
|
+
...base,
|
|
2124
|
+
left: resolveNumeric(leftInput, 'x', fallback.left, context.canvas, context.options),
|
|
2125
|
+
top: resolveNumeric(topInput, 'y', fallback.top, context.canvas, context.options),
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
function nextAnnotationMeta(context, config) {
|
|
2129
|
+
const annotationId = context.getAnnotationCounter() + 1;
|
|
2130
|
+
context.setAnnotationCounter(annotationId);
|
|
2131
|
+
return {
|
|
2132
|
+
annotationId,
|
|
2133
|
+
annotationName: `${context.options.textAnnotationName}${annotationId}`,
|
|
2134
|
+
annotationHidden: config.annotationHidden,
|
|
2135
|
+
annotationLocked: config.annotationLocked,
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
function attachTextEditingHandlers(context, annotation) {
|
|
2139
|
+
const textObject = annotation;
|
|
2140
|
+
if (textObject.imageEditorTextEditingHandlers) {
|
|
2141
|
+
try {
|
|
2142
|
+
textObject.off('editing:entered', textObject.imageEditorTextEditingHandlers.entered);
|
|
2143
|
+
textObject.off('editing:exited', textObject.imageEditorTextEditingHandlers.exited);
|
|
2144
|
+
}
|
|
2145
|
+
catch {
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
const entered = () => {
|
|
2149
|
+
var _a;
|
|
2150
|
+
textObject.imageEditorTextEditingInitialText = String((_a = textObject.text) !== null && _a !== void 0 ? _a : '');
|
|
2151
|
+
textObject.imageEditorTextEditingCancel = false;
|
|
2152
|
+
};
|
|
2153
|
+
const exited = () => {
|
|
2154
|
+
var _a;
|
|
2155
|
+
const initial = textObject.imageEditorTextEditingInitialText;
|
|
2156
|
+
const finalText = String((_a = textObject.text) !== null && _a !== void 0 ? _a : '');
|
|
2157
|
+
const cancel = textObject.imageEditorTextEditingCancel === true;
|
|
2158
|
+
if (cancel && initial !== undefined) {
|
|
2159
|
+
textObject.set({ text: initial });
|
|
2160
|
+
}
|
|
2161
|
+
delete textObject.imageEditorTextEditingInitialText;
|
|
2162
|
+
delete textObject.imageEditorTextEditingCancel;
|
|
2163
|
+
if (!cancel && initial !== undefined && initial !== finalText) {
|
|
2164
|
+
context.saveCanvasState();
|
|
2165
|
+
const callbackContext = context.buildCallbackContext('createTextAnnotation');
|
|
2166
|
+
context.emitAnnotationsChanged(callbackContext);
|
|
2167
|
+
context.emitImageChanged(callbackContext);
|
|
2168
|
+
}
|
|
2169
|
+
};
|
|
2170
|
+
textObject.on('editing:entered', entered);
|
|
2171
|
+
textObject.on('editing:exited', exited);
|
|
2172
|
+
textObject.imageEditorTextEditingHandlers = { entered, exited };
|
|
2173
|
+
}
|
|
2174
|
+
function selectAllText(annotation) {
|
|
2175
|
+
var _a;
|
|
2176
|
+
const textObject = annotation;
|
|
2177
|
+
const textLength = String((_a = textObject.text) !== null && _a !== void 0 ? _a : '').length;
|
|
2178
|
+
if (textLength <= 0)
|
|
2179
|
+
return;
|
|
2180
|
+
if (typeof textObject.selectAll === 'function') {
|
|
2181
|
+
textObject.selectAll();
|
|
2182
|
+
return;
|
|
2183
|
+
}
|
|
2184
|
+
if (typeof textObject.setSelectionStart === 'function' &&
|
|
2185
|
+
typeof textObject.setSelectionEnd === 'function') {
|
|
2186
|
+
textObject.setSelectionStart(0);
|
|
2187
|
+
textObject.setSelectionEnd(textLength);
|
|
2188
|
+
return;
|
|
2189
|
+
}
|
|
2190
|
+
textObject.selectionStart = 0;
|
|
2191
|
+
textObject.selectionEnd = textLength;
|
|
2192
|
+
}
|
|
2193
|
+
function createTextAnnotation(context, config = {}) {
|
|
2194
|
+
var _a, _b;
|
|
2195
|
+
if (!context.isImageLoaded())
|
|
2196
|
+
return null;
|
|
2197
|
+
const resolved = resolveTextCreationConfig(context, config);
|
|
2198
|
+
const textbox = new context.fabric.Textbox(resolved.text, {
|
|
2199
|
+
left: resolved.left,
|
|
2200
|
+
top: resolved.top,
|
|
2201
|
+
width: resolved.width,
|
|
2202
|
+
fontSize: resolved.fontSize,
|
|
2203
|
+
fontFamily: resolved.fontFamily,
|
|
2204
|
+
fontWeight: resolved.fontWeight,
|
|
2205
|
+
fill: resolved.fill,
|
|
2206
|
+
backgroundColor: resolved.backgroundColor,
|
|
2207
|
+
textAlign: resolved.textAlign,
|
|
2208
|
+
angle: resolved.angle,
|
|
2209
|
+
selectable: resolved.selectable,
|
|
2210
|
+
evented: resolved.evented,
|
|
2211
|
+
editable: resolved.editable,
|
|
2212
|
+
originX: 'left',
|
|
2213
|
+
originY: 'top',
|
|
2214
|
+
...resolved.styles,
|
|
2215
|
+
});
|
|
2216
|
+
const meta = nextAnnotationMeta(context, resolved);
|
|
2217
|
+
const annotation = markAnnotationObject(textbox, {
|
|
2218
|
+
annotationId: meta.annotationId,
|
|
2219
|
+
annotationType: 'text',
|
|
2220
|
+
annotationName: meta.annotationName,
|
|
2221
|
+
annotationHidden: meta.annotationHidden,
|
|
2222
|
+
annotationLocked: meta.annotationLocked,
|
|
2223
|
+
});
|
|
2224
|
+
syncAnnotationRuntimeState(annotation);
|
|
2225
|
+
attachTextEditingHandlers(context, annotation);
|
|
2226
|
+
placeAnnotationObject(context.canvas, annotation);
|
|
2227
|
+
if (resolved.selectable !== false && isAnnotationUnlocked(annotation)) {
|
|
2228
|
+
context.canvas.setActiveObject(annotation);
|
|
2229
|
+
}
|
|
2230
|
+
context.canvas.renderAll();
|
|
2231
|
+
context.updateAnnotationList();
|
|
2232
|
+
context.saveCanvasState();
|
|
2233
|
+
const callbackContext = context.buildCallbackContext('createTextAnnotation');
|
|
2234
|
+
context.emitAnnotationsChanged(callbackContext);
|
|
2235
|
+
context.emitImageChanged(callbackContext);
|
|
2236
|
+
if (resolved.enterEditing && isAnnotationUnlocked(annotation)) {
|
|
2237
|
+
(_b = (_a = annotation).enterEditing) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
2238
|
+
selectAllText(annotation);
|
|
2239
|
+
}
|
|
2240
|
+
return annotation;
|
|
2241
|
+
}
|
|
2242
|
+
function handleTextModePointer(context, event) {
|
|
2243
|
+
var _a, _b;
|
|
2244
|
+
const fabricEvent = event;
|
|
2245
|
+
const target = fabricEvent.target;
|
|
2246
|
+
if (target) {
|
|
2247
|
+
if (isTextAnnotationObject(target) && isAnnotationUnlocked(target)) {
|
|
2248
|
+
context.canvas.setActiveObject(target);
|
|
2249
|
+
(_b = (_a = target).enterEditing) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
2250
|
+
}
|
|
2251
|
+
else if (isEditableOverlayObject(target)) {
|
|
2252
|
+
context.canvas.setActiveObject(target);
|
|
2253
|
+
}
|
|
2254
|
+
return;
|
|
2255
|
+
}
|
|
2256
|
+
const pointer = getPointerFromFabricEvent(context.canvas, event);
|
|
2257
|
+
if (!pointer)
|
|
2258
|
+
return;
|
|
2259
|
+
createTextAnnotation(context, {
|
|
2260
|
+
left: pointer.x,
|
|
2261
|
+
top: pointer.y,
|
|
2262
|
+
});
|
|
2263
|
+
}
|
|
2264
|
+
function enterTextMode(context) {
|
|
2265
|
+
if (context.getTextSession())
|
|
2266
|
+
return;
|
|
2267
|
+
if (!context.isImageLoaded())
|
|
2268
|
+
return;
|
|
2269
|
+
const { canvas } = context;
|
|
2270
|
+
const previousCanvasSelection = !!canvas.selection;
|
|
2271
|
+
const previousDefaultCursor = canvas.defaultCursor;
|
|
2272
|
+
canvas.selection = true;
|
|
2273
|
+
canvas.defaultCursor = 'text';
|
|
2274
|
+
const callback = (event) => handleTextModePointer(context, event);
|
|
2275
|
+
canvas.on('mouse:down', callback);
|
|
2276
|
+
const session = {
|
|
2277
|
+
mode: 'text',
|
|
2278
|
+
previousCanvasSelection,
|
|
2279
|
+
previousDefaultCursor,
|
|
2280
|
+
handlers: [{ eventName: 'mouse:down', callback }],
|
|
2281
|
+
dispose: () => {
|
|
2282
|
+
try {
|
|
2283
|
+
canvas.off('mouse:down', callback);
|
|
2284
|
+
}
|
|
2285
|
+
catch {
|
|
2286
|
+
}
|
|
2287
|
+
canvas.selection = previousCanvasSelection;
|
|
2288
|
+
canvas.defaultCursor = previousDefaultCursor !== null && previousDefaultCursor !== void 0 ? previousDefaultCursor : 'default';
|
|
2289
|
+
},
|
|
2290
|
+
};
|
|
2291
|
+
const preview = new context.fabric.Rect({
|
|
2292
|
+
left: -1,
|
|
2293
|
+
top: -1,
|
|
2294
|
+
width: 1,
|
|
2295
|
+
height: 1,
|
|
2296
|
+
selectable: false,
|
|
2297
|
+
evented: false,
|
|
2298
|
+
visible: false,
|
|
2299
|
+
excludeFromExport: true,
|
|
2300
|
+
});
|
|
2301
|
+
markSessionObject(preview, 'textPreview');
|
|
2302
|
+
context.setTextSession(session);
|
|
2303
|
+
context.updateUi();
|
|
2304
|
+
}
|
|
2305
|
+
function exitTextMode(context) {
|
|
2306
|
+
const session = context.getTextSession();
|
|
2307
|
+
if (!session)
|
|
2308
|
+
return;
|
|
2309
|
+
session.dispose();
|
|
2310
|
+
context.setTextSession(null);
|
|
2311
|
+
context.canvas.requestRenderAll();
|
|
2312
|
+
context.updateUi();
|
|
2313
|
+
}
|
|
2314
|
+
function finalizeActiveTextEditing(context, options) {
|
|
2315
|
+
var _a;
|
|
2316
|
+
const active = context.canvas.getActiveObject();
|
|
2317
|
+
if (!active || !isTextAnnotationObject(active))
|
|
2318
|
+
return;
|
|
2319
|
+
const textObject = active;
|
|
2320
|
+
if (textObject.isEditing !== true)
|
|
2321
|
+
return;
|
|
2322
|
+
textObject.imageEditorTextEditingCancel = !options.commit;
|
|
2323
|
+
(_a = textObject.exitEditing) === null || _a === void 0 ? void 0 : _a.call(textObject);
|
|
2324
|
+
context.canvas.requestRenderAll();
|
|
2325
|
+
}
|
|
2326
|
+
function attachTextEditingHandlersToAnnotations(context, annotations) {
|
|
2327
|
+
annotations.filter(isTextAnnotationObject).forEach((annotation) => {
|
|
2328
|
+
attachTextEditingHandlers(context, annotation);
|
|
2329
|
+
});
|
|
2330
|
+
}
|
|
2331
|
+
|
|
2332
|
+
function colorWithOpacity(color, opacity) {
|
|
2333
|
+
const alpha = Math.max(0, Math.min(1, opacity));
|
|
2334
|
+
if (alpha >= 1)
|
|
2335
|
+
return color;
|
|
2336
|
+
if (/^#([0-9a-f]{6})$/i.test(color)) {
|
|
2337
|
+
const hex = color.slice(1);
|
|
2338
|
+
const r = Number.parseInt(hex.slice(0, 2), 16);
|
|
2339
|
+
const g = Number.parseInt(hex.slice(2, 4), 16);
|
|
2340
|
+
const b = Number.parseInt(hex.slice(4, 6), 16);
|
|
2341
|
+
return `rgba(${r},${g},${b},${alpha})`;
|
|
2342
|
+
}
|
|
2343
|
+
return color;
|
|
2344
|
+
}
|
|
2345
|
+
function configureBrush(context) {
|
|
2346
|
+
const config = context.getDrawConfig();
|
|
2347
|
+
const canvasWithBrush = context.canvas;
|
|
2348
|
+
canvasWithBrush.freeDrawingBrush = new context.fabric.PencilBrush(context.canvas);
|
|
2349
|
+
canvasWithBrush.freeDrawingBrush.width = config.brushSize;
|
|
2350
|
+
canvasWithBrush.freeDrawingBrush.color = colorWithOpacity(config.color, config.opacity);
|
|
2351
|
+
canvasWithBrush.freeDrawingBrush.strokeLineCap = config.lineCap;
|
|
2352
|
+
canvasWithBrush.freeDrawingBrush.strokeLineJoin = config.lineJoin;
|
|
2353
|
+
}
|
|
2354
|
+
function markPathAsDrawAnnotation(context, path) {
|
|
2355
|
+
const config = context.getDrawConfig();
|
|
2356
|
+
const annotationId = context.getAnnotationCounter() + 1;
|
|
2357
|
+
context.setAnnotationCounter(annotationId);
|
|
2358
|
+
path.set({
|
|
2359
|
+
selectable: config.selectable,
|
|
2360
|
+
evented: config.evented,
|
|
2361
|
+
opacity: config.opacity,
|
|
2362
|
+
stroke: config.color,
|
|
2363
|
+
strokeWidth: config.brushSize,
|
|
2364
|
+
});
|
|
2365
|
+
const annotation = markAnnotationObject(path, {
|
|
2366
|
+
annotationId,
|
|
2367
|
+
annotationType: 'draw',
|
|
2368
|
+
annotationName: `${context.options.drawAnnotationName}${annotationId}`,
|
|
2369
|
+
annotationHidden: config.annotationHidden,
|
|
2370
|
+
annotationLocked: config.annotationLocked,
|
|
2371
|
+
});
|
|
2372
|
+
syncAnnotationRuntimeState(annotation);
|
|
2373
|
+
return annotation;
|
|
2374
|
+
}
|
|
2375
|
+
function handlePathCreated(context, event) {
|
|
2376
|
+
const path = event.path;
|
|
2377
|
+
if (!path)
|
|
2378
|
+
return;
|
|
2379
|
+
const annotation = markPathAsDrawAnnotation(context, path);
|
|
2380
|
+
placeAnnotationObject(context.canvas, annotation);
|
|
2381
|
+
context.canvas.setActiveObject(annotation);
|
|
2382
|
+
context.canvas.renderAll();
|
|
2383
|
+
context.updateAnnotationList();
|
|
2384
|
+
context.saveCanvasState();
|
|
2385
|
+
const callbackContext = context.buildCallbackContext('enterDrawMode');
|
|
2386
|
+
context.emitAnnotationsChanged(callbackContext);
|
|
2387
|
+
context.emitImageChanged(callbackContext);
|
|
2388
|
+
}
|
|
2389
|
+
function enterDrawMode(context) {
|
|
2390
|
+
if (context.getDrawSession())
|
|
2391
|
+
return;
|
|
2392
|
+
if (!context.isImageLoaded())
|
|
2393
|
+
return;
|
|
2394
|
+
const { canvas } = context;
|
|
2395
|
+
const canvasWithDrawing = canvas;
|
|
2396
|
+
const previousDrawingMode = !!canvasWithDrawing.isDrawingMode;
|
|
2397
|
+
const previousBrush = canvasWithDrawing.freeDrawingBrush;
|
|
2398
|
+
const previousCanvasSelection = !!canvas.selection;
|
|
2399
|
+
const previousDefaultCursor = canvas.defaultCursor;
|
|
2400
|
+
canvas.selection = false;
|
|
2401
|
+
canvas.defaultCursor = 'crosshair';
|
|
2402
|
+
canvasWithDrawing.isDrawingMode = true;
|
|
2403
|
+
configureBrush(context);
|
|
2404
|
+
const callback = (event) => handlePathCreated(context, event);
|
|
2405
|
+
canvas.on('path:created', callback);
|
|
2406
|
+
const session = {
|
|
2407
|
+
mode: 'draw',
|
|
2408
|
+
previousDrawingMode,
|
|
2409
|
+
previousBrush,
|
|
2410
|
+
previousCanvasSelection,
|
|
2411
|
+
previousDefaultCursor,
|
|
2412
|
+
handlers: [{ eventName: 'path:created', callback }],
|
|
2413
|
+
dispose: () => {
|
|
2414
|
+
try {
|
|
2415
|
+
canvas.off('path:created', callback);
|
|
2416
|
+
}
|
|
2417
|
+
catch {
|
|
2418
|
+
}
|
|
2419
|
+
canvasWithDrawing.isDrawingMode = previousDrawingMode;
|
|
2420
|
+
canvasWithDrawing.freeDrawingBrush = previousBrush;
|
|
2421
|
+
canvas.selection = previousCanvasSelection;
|
|
2422
|
+
canvas.defaultCursor = previousDefaultCursor !== null && previousDefaultCursor !== void 0 ? previousDefaultCursor : 'default';
|
|
2423
|
+
},
|
|
2424
|
+
};
|
|
2425
|
+
context.setDrawSession(session);
|
|
2426
|
+
context.updateUi();
|
|
2427
|
+
}
|
|
2428
|
+
function exitDrawMode(context) {
|
|
2429
|
+
const session = context.getDrawSession();
|
|
2430
|
+
if (!session)
|
|
2431
|
+
return;
|
|
2432
|
+
session.dispose();
|
|
2433
|
+
context.setDrawSession(null);
|
|
2434
|
+
context.canvas.requestRenderAll();
|
|
2435
|
+
context.updateUi();
|
|
2436
|
+
}
|
|
2437
|
+
function updateDrawBrush(context) {
|
|
2438
|
+
if (!context.getDrawSession())
|
|
2439
|
+
return;
|
|
2440
|
+
configureBrush(context);
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
function fixPrototype(self, ctor) {
|
|
2444
|
+
Object.setPrototypeOf(self, ctor.prototype);
|
|
2445
|
+
}
|
|
2446
|
+
class ImageDecodeError extends Error {
|
|
2447
|
+
constructor(message = 'Failed to decode image data URL.', originalError = null) {
|
|
2448
|
+
super(message);
|
|
2449
|
+
Object.defineProperty(this, "name", {
|
|
2450
|
+
enumerable: true,
|
|
2451
|
+
configurable: true,
|
|
2452
|
+
writable: true,
|
|
2453
|
+
value: 'ImageDecodeError'
|
|
2454
|
+
});
|
|
2455
|
+
Object.defineProperty(this, "originalError", {
|
|
2456
|
+
enumerable: true,
|
|
2457
|
+
configurable: true,
|
|
2458
|
+
writable: true,
|
|
2459
|
+
value: void 0
|
|
2460
|
+
});
|
|
2461
|
+
this.originalError = originalError;
|
|
2462
|
+
fixPrototype(this, ImageDecodeError);
|
|
2463
|
+
}
|
|
2464
|
+
}
|
|
2465
|
+
class ImageLoadTimeoutError extends Error {
|
|
2466
|
+
constructor(label, elapsedMs) {
|
|
2467
|
+
super(`Image load timed out after ${elapsedMs}ms during ${label}`);
|
|
2468
|
+
Object.defineProperty(this, "name", {
|
|
2469
|
+
enumerable: true,
|
|
2470
|
+
configurable: true,
|
|
2471
|
+
writable: true,
|
|
2472
|
+
value: 'ImageLoadTimeoutError'
|
|
2473
|
+
});
|
|
2474
|
+
Object.defineProperty(this, "label", {
|
|
2475
|
+
enumerable: true,
|
|
2476
|
+
configurable: true,
|
|
2477
|
+
writable: true,
|
|
2478
|
+
value: void 0
|
|
2479
|
+
});
|
|
2480
|
+
Object.defineProperty(this, "elapsedMs", {
|
|
2481
|
+
enumerable: true,
|
|
2482
|
+
configurable: true,
|
|
2483
|
+
writable: true,
|
|
2484
|
+
value: void 0
|
|
2485
|
+
});
|
|
2486
|
+
this.label = label;
|
|
2487
|
+
this.elapsedMs = elapsedMs;
|
|
2488
|
+
fixPrototype(this, ImageLoadTimeoutError);
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
class DownsampleError extends Error {
|
|
2492
|
+
constructor(message = 'Failed to obtain a 2D context for downsampling.', originalError = null) {
|
|
2493
|
+
super(message);
|
|
2494
|
+
Object.defineProperty(this, "name", {
|
|
2495
|
+
enumerable: true,
|
|
2496
|
+
configurable: true,
|
|
2497
|
+
writable: true,
|
|
2498
|
+
value: 'DownsampleError'
|
|
2499
|
+
});
|
|
2500
|
+
Object.defineProperty(this, "originalError", {
|
|
2501
|
+
enumerable: true,
|
|
2502
|
+
configurable: true,
|
|
2503
|
+
writable: true,
|
|
2504
|
+
value: void 0
|
|
2505
|
+
});
|
|
2506
|
+
this.originalError = originalError;
|
|
2507
|
+
fixPrototype(this, DownsampleError);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
class MergeMasksError extends Error {
|
|
2511
|
+
constructor(message = 'Failed to merge masks into the image.', originalError = null) {
|
|
2512
|
+
super(message);
|
|
2513
|
+
Object.defineProperty(this, "name", {
|
|
2514
|
+
enumerable: true,
|
|
2515
|
+
configurable: true,
|
|
2516
|
+
writable: true,
|
|
2517
|
+
value: 'MergeMasksError'
|
|
2518
|
+
});
|
|
2519
|
+
Object.defineProperty(this, "originalError", {
|
|
2520
|
+
enumerable: true,
|
|
2521
|
+
configurable: true,
|
|
2522
|
+
writable: true,
|
|
2523
|
+
value: void 0
|
|
2524
|
+
});
|
|
2525
|
+
this.originalError = originalError;
|
|
2526
|
+
fixPrototype(this, MergeMasksError);
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
class MergeAnnotationsError extends Error {
|
|
2530
|
+
constructor(message = 'Failed to merge annotations into the image.', originalError = null) {
|
|
2531
|
+
super(message);
|
|
2532
|
+
Object.defineProperty(this, "name", {
|
|
2533
|
+
enumerable: true,
|
|
2534
|
+
configurable: true,
|
|
2535
|
+
writable: true,
|
|
2536
|
+
value: 'MergeAnnotationsError'
|
|
2537
|
+
});
|
|
2538
|
+
Object.defineProperty(this, "originalError", {
|
|
2539
|
+
enumerable: true,
|
|
2540
|
+
configurable: true,
|
|
2541
|
+
writable: true,
|
|
2542
|
+
value: void 0
|
|
2543
|
+
});
|
|
2544
|
+
this.originalError = originalError;
|
|
2545
|
+
fixPrototype(this, MergeAnnotationsError);
|
|
2546
|
+
}
|
|
2547
|
+
}
|
|
2548
|
+
class CropApplyError extends Error {
|
|
2549
|
+
constructor(message = 'Failed to apply crop to the image.', originalError = null) {
|
|
2550
|
+
super(message);
|
|
2551
|
+
Object.defineProperty(this, "name", {
|
|
2552
|
+
enumerable: true,
|
|
2553
|
+
configurable: true,
|
|
2554
|
+
writable: true,
|
|
2555
|
+
value: 'CropApplyError'
|
|
2556
|
+
});
|
|
2557
|
+
Object.defineProperty(this, "originalError", {
|
|
2558
|
+
enumerable: true,
|
|
2559
|
+
configurable: true,
|
|
2560
|
+
writable: true,
|
|
2561
|
+
value: void 0
|
|
2562
|
+
});
|
|
2563
|
+
this.originalError = originalError;
|
|
2564
|
+
fixPrototype(this, CropApplyError);
|
|
2565
|
+
}
|
|
2566
|
+
}
|
|
2567
|
+
class ExportNotReadyError extends Error {
|
|
2568
|
+
constructor(operation = 'exportImageFile') {
|
|
2569
|
+
super(`Cannot ${operation}: no image is loaded on the canvas.`);
|
|
2570
|
+
Object.defineProperty(this, "name", {
|
|
2571
|
+
enumerable: true,
|
|
2572
|
+
configurable: true,
|
|
2573
|
+
writable: true,
|
|
2574
|
+
value: 'ExportNotReadyError'
|
|
2575
|
+
});
|
|
2576
|
+
Object.defineProperty(this, "operation", {
|
|
2577
|
+
enumerable: true,
|
|
2578
|
+
configurable: true,
|
|
2579
|
+
writable: true,
|
|
2580
|
+
value: void 0
|
|
2581
|
+
});
|
|
2582
|
+
this.operation = operation;
|
|
2583
|
+
fixPrototype(this, ExportNotReadyError);
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
class ExportError extends Error {
|
|
2587
|
+
constructor(message = 'Failed to export image.', originalError = null) {
|
|
2588
|
+
super(message);
|
|
2589
|
+
Object.defineProperty(this, "name", {
|
|
2590
|
+
enumerable: true,
|
|
2591
|
+
configurable: true,
|
|
2592
|
+
writable: true,
|
|
2593
|
+
value: 'ExportError'
|
|
2594
|
+
});
|
|
2595
|
+
Object.defineProperty(this, "originalError", {
|
|
2596
|
+
enumerable: true,
|
|
2597
|
+
configurable: true,
|
|
2598
|
+
writable: true,
|
|
2599
|
+
value: void 0
|
|
2600
|
+
});
|
|
2601
|
+
this.originalError = originalError;
|
|
2602
|
+
fixPrototype(this, ExportError);
|
|
2603
|
+
}
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
const SELECTED_STROKE = '#ff0000';
|
|
2607
|
+
const SELECTED_STROKE_WIDTH = 1;
|
|
2608
|
+
const HOVER_STROKE = '#ff5500';
|
|
2609
|
+
const HOVER_STROKE_WIDTH = 2;
|
|
2610
|
+
const HOVER_OPACITY_BUMP = 0.2;
|
|
2611
|
+
const DEFAULT_STROKE_FALLBACK = '#ccc';
|
|
2612
|
+
const DEFAULT_STROKE_WIDTH_FALLBACK = 1;
|
|
2613
|
+
const DEFAULT_ALPHA_FALLBACK = 0.5;
|
|
2614
|
+
function getMaskNormalStyle(mask) {
|
|
2615
|
+
var _a;
|
|
2616
|
+
const strokeWidth = Number(mask.originalStrokeWidth);
|
|
2617
|
+
const opacity = Number(mask.originalAlpha);
|
|
2618
|
+
return {
|
|
2619
|
+
stroke: (_a = mask.originalStroke) !== null && _a !== void 0 ? _a : DEFAULT_STROKE_FALLBACK,
|
|
2620
|
+
strokeWidth: Number.isFinite(strokeWidth) ? strokeWidth : DEFAULT_STROKE_WIDTH_FALLBACK,
|
|
2621
|
+
opacity: Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK,
|
|
2622
|
+
};
|
|
2623
|
+
}
|
|
2624
|
+
function getMaskHoverStyle(mask) {
|
|
2625
|
+
const opacity = Number(mask.originalAlpha);
|
|
2626
|
+
const baseAlpha = Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK;
|
|
2627
|
+
return {
|
|
2628
|
+
stroke: HOVER_STROKE,
|
|
2629
|
+
strokeWidth: HOVER_STROKE_WIDTH,
|
|
2630
|
+
opacity: Math.min(baseAlpha + HOVER_OPACITY_BUMP, 1),
|
|
2631
|
+
};
|
|
2632
|
+
}
|
|
2633
|
+
function applyMaskSelectedStyle(mask) {
|
|
2634
|
+
mask.set({ stroke: SELECTED_STROKE, strokeWidth: SELECTED_STROKE_WIDTH });
|
|
2635
|
+
}
|
|
2636
|
+
function applyMaskUnselectedStyle(mask) {
|
|
2637
|
+
var _a;
|
|
2638
|
+
const strokeWidth = Number(mask.originalStrokeWidth);
|
|
2639
|
+
mask.set({
|
|
2640
|
+
stroke: (_a = mask.originalStroke) !== null && _a !== void 0 ? _a : DEFAULT_STROKE_FALLBACK,
|
|
2641
|
+
strokeWidth: Number.isFinite(strokeWidth) ? strokeWidth : DEFAULT_STROKE_WIDTH_FALLBACK,
|
|
2642
|
+
});
|
|
2643
|
+
}
|
|
2644
|
+
function attachMaskHoverHandlers(mask) {
|
|
2645
|
+
const tagged = mask;
|
|
2646
|
+
const mouseover = () => {
|
|
2647
|
+
var _a;
|
|
2648
|
+
tagged.set(getMaskHoverStyle(tagged));
|
|
2649
|
+
(_a = tagged.canvas) === null || _a === void 0 ? void 0 : _a.requestRenderAll();
|
|
2650
|
+
};
|
|
2651
|
+
const mouseout = () => {
|
|
2652
|
+
var _a;
|
|
2653
|
+
tagged.set(getMaskNormalStyle(tagged));
|
|
2654
|
+
(_a = tagged.canvas) === null || _a === void 0 ? void 0 : _a.requestRenderAll();
|
|
2655
|
+
};
|
|
2656
|
+
tagged.on('mouseover', mouseover);
|
|
2657
|
+
tagged.on('mouseout', mouseout);
|
|
2658
|
+
tagged.imageEditorMaskHandlers = { mouseover, mouseout };
|
|
2659
|
+
}
|
|
2660
|
+
function reattachMaskHoverHandlers(mask) {
|
|
2661
|
+
var _a;
|
|
2662
|
+
const tagged = mask;
|
|
2663
|
+
if (tagged.imageEditorMaskHandlers) {
|
|
2664
|
+
try {
|
|
2665
|
+
tagged.off('mouseover', tagged.imageEditorMaskHandlers.mouseover);
|
|
2666
|
+
tagged.off('mouseout', tagged.imageEditorMaskHandlers.mouseout);
|
|
2667
|
+
}
|
|
2668
|
+
catch {
|
|
2669
|
+
}
|
|
2670
|
+
delete tagged.imageEditorMaskHandlers;
|
|
2671
|
+
}
|
|
2672
|
+
const patch = {};
|
|
2673
|
+
if (!Number.isFinite(Number(tagged.originalAlpha))) {
|
|
2674
|
+
const opacity = Number(tagged.opacity);
|
|
2675
|
+
patch.originalAlpha = Number.isFinite(opacity) ? opacity : DEFAULT_ALPHA_FALLBACK;
|
|
2676
|
+
}
|
|
2677
|
+
if (tagged.originalStroke == null) {
|
|
2678
|
+
patch.originalStroke = (_a = tagged.stroke) !== null && _a !== void 0 ? _a : DEFAULT_STROKE_FALLBACK;
|
|
2679
|
+
}
|
|
2680
|
+
if (!Number.isFinite(Number(tagged.originalStrokeWidth))) {
|
|
2681
|
+
const sw = Number(tagged.strokeWidth);
|
|
2682
|
+
patch.originalStrokeWidth = Number.isFinite(sw) ? sw : DEFAULT_STROKE_WIDTH_FALLBACK;
|
|
2683
|
+
}
|
|
2684
|
+
if (Object.keys(patch).length > 0)
|
|
2685
|
+
tagged.set(patch);
|
|
2686
|
+
attachMaskHoverHandlers(tagged);
|
|
2687
|
+
}
|
|
2688
|
+
function detachMaskHoverHandlers(mask) {
|
|
2689
|
+
const tagged = mask;
|
|
2690
|
+
if (!tagged.imageEditorMaskHandlers)
|
|
2691
|
+
return;
|
|
2692
|
+
try {
|
|
2693
|
+
tagged.off('mouseover', tagged.imageEditorMaskHandlers.mouseover);
|
|
2694
|
+
tagged.off('mouseout', tagged.imageEditorMaskHandlers.mouseout);
|
|
2695
|
+
}
|
|
2696
|
+
catch {
|
|
2697
|
+
}
|
|
2698
|
+
delete tagged.imageEditorMaskHandlers;
|
|
1582
2699
|
}
|
|
1583
|
-
function
|
|
1584
|
-
|
|
1585
|
-
return null;
|
|
1586
|
-
const normalizedAngle = Math.abs((Number(angle) || 0) % 90);
|
|
1587
|
-
const isAxisAligned = normalizedAngle < 0.01 || Math.abs(normalizedAngle - 90) < 0.01;
|
|
1588
|
-
if (!isAxisAligned)
|
|
1589
|
-
return null;
|
|
1590
|
-
const left = Number(bounds.left) || 0;
|
|
1591
|
-
const top = Number(bounds.top) || 0;
|
|
2700
|
+
function captureMaskStyleBackup(mask) {
|
|
2701
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
1592
2702
|
return {
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
2703
|
+
object: mask,
|
|
2704
|
+
opacity: (_a = mask.opacity) !== null && _a !== void 0 ? _a : 1,
|
|
2705
|
+
fill: ((_b = mask.fill) !== null && _b !== void 0 ? _b : null),
|
|
2706
|
+
strokeWidth: (_c = mask.strokeWidth) !== null && _c !== void 0 ? _c : 0,
|
|
2707
|
+
stroke: ((_d = mask.stroke) !== null && _d !== void 0 ? _d : null),
|
|
2708
|
+
selectable: (_e = mask.selectable) !== null && _e !== void 0 ? _e : true,
|
|
2709
|
+
evented: (_f = mask.evented) !== null && _f !== void 0 ? _f : true,
|
|
2710
|
+
lockRotation: (_g = mask.lockRotation) !== null && _g !== void 0 ? _g : false,
|
|
1597
2711
|
};
|
|
1598
2712
|
}
|
|
1599
|
-
function
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
2713
|
+
function restoreMaskStyleBackup(backup) {
|
|
2714
|
+
try {
|
|
2715
|
+
backup.object.set({
|
|
2716
|
+
opacity: backup.opacity,
|
|
2717
|
+
fill: backup.fill,
|
|
2718
|
+
strokeWidth: backup.strokeWidth,
|
|
2719
|
+
stroke: backup.stroke,
|
|
2720
|
+
selectable: backup.selectable,
|
|
2721
|
+
evented: backup.evented,
|
|
2722
|
+
lockRotation: backup.lockRotation,
|
|
2723
|
+
});
|
|
2724
|
+
if (typeof backup.object.setCoords === 'function') {
|
|
2725
|
+
backup.object.setCoords();
|
|
2726
|
+
}
|
|
2727
|
+
}
|
|
2728
|
+
catch {
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2731
|
+
async function withMaskStyleBackup(context, mutator, callback) {
|
|
2732
|
+
if (!context.canvas)
|
|
2733
|
+
return await callback();
|
|
2734
|
+
const masks = context.canvas.getObjects().filter(isMaskObject);
|
|
2735
|
+
const backups = masks.map(captureMaskStyleBackup);
|
|
2736
|
+
try {
|
|
2737
|
+
masks.forEach((mask, index) => mutator(mask, index));
|
|
2738
|
+
return await callback();
|
|
2739
|
+
}
|
|
2740
|
+
finally {
|
|
2741
|
+
for (const backup of backups)
|
|
2742
|
+
restoreMaskStyleBackup(backup);
|
|
2743
|
+
}
|
|
2744
|
+
}
|
|
2745
|
+
function applyCropHideMaskStyle(mask) {
|
|
2746
|
+
try {
|
|
2747
|
+
mask.set({ opacity: 0, evented: false, selectable: false });
|
|
2748
|
+
}
|
|
2749
|
+
catch {
|
|
2750
|
+
}
|
|
1608
2751
|
}
|
|
1609
2752
|
|
|
1610
2753
|
const CROP_RECT_FILL = 'rgba(0,0,0,0.12)';
|
|
@@ -1839,6 +2982,7 @@ function enterCropMode(context) {
|
|
|
1839
2982
|
cropRect.setControlVisible('mtr', false);
|
|
1840
2983
|
}
|
|
1841
2984
|
canvas.add(cropRect);
|
|
2985
|
+
markSessionObject(cropRect, 'cropRect');
|
|
1842
2986
|
cropRect.isCropRect = true;
|
|
1843
2987
|
canvas.bringObjectToFront(cropRect);
|
|
1844
2988
|
canvas.setActiveObject(cropRect);
|
|
@@ -2238,29 +3382,6 @@ function getCanvasDocument$2(context) {
|
|
|
2238
3382
|
const element = (_b = (_a = context.canvas).getElement) === null || _b === void 0 ? void 0 : _b.call(_a);
|
|
2239
3383
|
return ((_e = (_c = element === null || element === void 0 ? void 0 : element.ownerDocument) !== null && _c !== void 0 ? _c : (_d = context.canvas.lowerCanvasEl) === null || _d === void 0 ? void 0 : _d.ownerDocument) !== null && _e !== void 0 ? _e : document);
|
|
2240
3384
|
}
|
|
2241
|
-
function isFinitePoint(value) {
|
|
2242
|
-
const point = value;
|
|
2243
|
-
return (!!point &&
|
|
2244
|
-
typeof point.x === 'number' &&
|
|
2245
|
-
Number.isFinite(point.x) &&
|
|
2246
|
-
typeof point.y === 'number' &&
|
|
2247
|
-
Number.isFinite(point.y));
|
|
2248
|
-
}
|
|
2249
|
-
function getPointerFromFabricEvent(canvas, event) {
|
|
2250
|
-
const fabricEvent = event;
|
|
2251
|
-
if (isFinitePoint(fabricEvent.scenePoint))
|
|
2252
|
-
return fabricEvent.scenePoint;
|
|
2253
|
-
if (isFinitePoint(fabricEvent.pointer))
|
|
2254
|
-
return fabricEvent.pointer;
|
|
2255
|
-
if (isFinitePoint(fabricEvent.absolutePointer))
|
|
2256
|
-
return fabricEvent.absolutePointer;
|
|
2257
|
-
if (fabricEvent.e && typeof canvas.getPointer === 'function') {
|
|
2258
|
-
const pointer = canvas.getPointer(fabricEvent.e);
|
|
2259
|
-
if (isFinitePoint(pointer))
|
|
2260
|
-
return pointer;
|
|
2261
|
-
}
|
|
2262
|
-
return null;
|
|
2263
|
-
}
|
|
2264
3385
|
function safeRender(canvas) {
|
|
2265
3386
|
try {
|
|
2266
3387
|
canvas.requestRenderAll();
|
|
@@ -2274,7 +3395,6 @@ function safeRender(canvas) {
|
|
|
2274
3395
|
}
|
|
2275
3396
|
}
|
|
2276
3397
|
function createPreviewCircle(context) {
|
|
2277
|
-
var _a;
|
|
2278
3398
|
const config = context.getMosaicConfig();
|
|
2279
3399
|
const circle = new context.fabric.Circle({
|
|
2280
3400
|
left: 0,
|
|
@@ -2285,13 +3405,16 @@ function createPreviewCircle(context) {
|
|
|
2285
3405
|
fill: config.previewFill,
|
|
2286
3406
|
stroke: config.previewStroke,
|
|
2287
3407
|
strokeWidth: config.previewStrokeWidth,
|
|
2288
|
-
strokeDashArray:
|
|
3408
|
+
strokeDashArray: config.previewStrokeDashArray
|
|
3409
|
+
? [...config.previewStrokeDashArray]
|
|
3410
|
+
: undefined,
|
|
2289
3411
|
selectable: false,
|
|
2290
3412
|
evented: false,
|
|
2291
3413
|
excludeFromExport: true,
|
|
2292
3414
|
objectCaching: false,
|
|
2293
3415
|
visible: false,
|
|
2294
3416
|
});
|
|
3417
|
+
markSessionObject(circle, 'mosaicPreviewCircle');
|
|
2295
3418
|
circle.isMosaicPreview = true;
|
|
2296
3419
|
return circle;
|
|
2297
3420
|
}
|
|
@@ -2334,6 +3457,7 @@ function createPreviewImage(context, sourceImage, rasterCache) {
|
|
|
2334
3457
|
objectCaching: false,
|
|
2335
3458
|
visible: true,
|
|
2336
3459
|
});
|
|
3460
|
+
markSessionObject(image, 'mosaicPreviewImage');
|
|
2337
3461
|
image.isMosaicPreview = true;
|
|
2338
3462
|
return image;
|
|
2339
3463
|
}
|
|
@@ -2385,6 +3509,18 @@ function removePreviewImage(context, session) {
|
|
|
2385
3509
|
}
|
|
2386
3510
|
session.previewImage = null;
|
|
2387
3511
|
}
|
|
3512
|
+
function releaseMosaicRasterCache(session) {
|
|
3513
|
+
const cache = session.rasterCache;
|
|
3514
|
+
if (!cache)
|
|
3515
|
+
return;
|
|
3516
|
+
try {
|
|
3517
|
+
cache.offscreenCanvas.width = 0;
|
|
3518
|
+
cache.offscreenCanvas.height = 0;
|
|
3519
|
+
}
|
|
3520
|
+
catch {
|
|
3521
|
+
}
|
|
3522
|
+
session.rasterCache = null;
|
|
3523
|
+
}
|
|
2388
3524
|
function hidePreview(context) {
|
|
2389
3525
|
var _a;
|
|
2390
3526
|
const circle = (_a = context.getMosaicSession()) === null || _a === void 0 ? void 0 : _a.previewCircle;
|
|
@@ -2548,7 +3684,7 @@ function replaceBaseImage(context, oldImage, newImage, mimeType) {
|
|
|
2548
3684
|
canvas.add(newImage);
|
|
2549
3685
|
newAdded = true;
|
|
2550
3686
|
canvas.sendObjectToBack(newImage);
|
|
2551
|
-
context.setOriginalImage(newImage);
|
|
3687
|
+
context.setOriginalImage(markBaseImageObject(newImage));
|
|
2552
3688
|
context.setCurrentImageMimeType(mimeType);
|
|
2553
3689
|
canvas.renderAll();
|
|
2554
3690
|
}
|
|
@@ -2868,6 +4004,7 @@ function exitMosaicMode(context) {
|
|
|
2868
4004
|
detachCanvasHandlers(context, session);
|
|
2869
4005
|
removePreviewCircle(context, session);
|
|
2870
4006
|
removePreviewImage(context, session);
|
|
4007
|
+
releaseMosaicRasterCache(session);
|
|
2871
4008
|
restoreObjectStates(session);
|
|
2872
4009
|
context.canvas.selection = !!session.prevSelection;
|
|
2873
4010
|
context.canvas.defaultCursor = (_a = session.prevDefaultCursor) !== null && _a !== void 0 ? _a : 'default';
|
|
@@ -2875,7 +4012,6 @@ function exitMosaicMode(context) {
|
|
|
2875
4012
|
context.canvas.renderAll();
|
|
2876
4013
|
}
|
|
2877
4014
|
function updateMosaicPreview(context) {
|
|
2878
|
-
var _a;
|
|
2879
4015
|
const session = context.getMosaicSession();
|
|
2880
4016
|
const circle = session === null || session === void 0 ? void 0 : session.previewCircle;
|
|
2881
4017
|
if (!session || !circle)
|
|
@@ -2886,12 +4022,141 @@ function updateMosaicPreview(context) {
|
|
|
2886
4022
|
fill: config.previewFill,
|
|
2887
4023
|
stroke: config.previewStroke,
|
|
2888
4024
|
strokeWidth: config.previewStrokeWidth,
|
|
2889
|
-
strokeDashArray:
|
|
4025
|
+
strokeDashArray: config.previewStrokeDashArray
|
|
4026
|
+
? [...config.previewStrokeDashArray]
|
|
4027
|
+
: undefined,
|
|
2890
4028
|
});
|
|
2891
4029
|
context.canvas.bringObjectToFront(circle);
|
|
2892
4030
|
safeRender(context.canvas);
|
|
2893
4031
|
}
|
|
2894
4032
|
|
|
4033
|
+
function startImageElementLoad(dataUrl, options) {
|
|
4034
|
+
const imageElement = new Image();
|
|
4035
|
+
if (options.crossOrigin !== undefined) {
|
|
4036
|
+
imageElement.crossOrigin = options.crossOrigin;
|
|
4037
|
+
}
|
|
4038
|
+
const cleanup = (clearSource = false) => {
|
|
4039
|
+
if (typeof imageElement.removeEventListener === 'function') {
|
|
4040
|
+
imageElement.removeEventListener('load', handleLoad);
|
|
4041
|
+
imageElement.removeEventListener('error', handleError);
|
|
4042
|
+
}
|
|
4043
|
+
else {
|
|
4044
|
+
imageElement.onload = null;
|
|
4045
|
+
imageElement.onerror = null;
|
|
4046
|
+
}
|
|
4047
|
+
if (clearSource) {
|
|
4048
|
+
try {
|
|
4049
|
+
imageElement.src = '';
|
|
4050
|
+
}
|
|
4051
|
+
catch {
|
|
4052
|
+
}
|
|
4053
|
+
}
|
|
4054
|
+
};
|
|
4055
|
+
const handleLoad = () => {
|
|
4056
|
+
var _a, _b;
|
|
4057
|
+
const validationError = (_b = (_a = options.validate) === null || _a === void 0 ? void 0 : _a.call(options, imageElement)) !== null && _b !== void 0 ? _b : null;
|
|
4058
|
+
if (validationError) {
|
|
4059
|
+
cleanup(true);
|
|
4060
|
+
rejectImage(validationError);
|
|
4061
|
+
return;
|
|
4062
|
+
}
|
|
4063
|
+
cleanup(false);
|
|
4064
|
+
resolveImage(imageElement);
|
|
4065
|
+
};
|
|
4066
|
+
const handleError = (event) => {
|
|
4067
|
+
cleanup(true);
|
|
4068
|
+
rejectImage(options.createError(event));
|
|
4069
|
+
};
|
|
4070
|
+
let resolveImage;
|
|
4071
|
+
let rejectImage;
|
|
4072
|
+
const promise = new Promise((resolve, reject) => {
|
|
4073
|
+
resolveImage = resolve;
|
|
4074
|
+
rejectImage = reject;
|
|
4075
|
+
if (typeof imageElement.addEventListener === 'function') {
|
|
4076
|
+
imageElement.addEventListener('load', handleLoad, { once: true });
|
|
4077
|
+
imageElement.addEventListener('error', handleError, { once: true });
|
|
4078
|
+
}
|
|
4079
|
+
else {
|
|
4080
|
+
imageElement.onload = handleLoad;
|
|
4081
|
+
imageElement.onerror = handleError;
|
|
4082
|
+
}
|
|
4083
|
+
imageElement.src = dataUrl;
|
|
4084
|
+
});
|
|
4085
|
+
return { promise, cleanup };
|
|
4086
|
+
}
|
|
4087
|
+
|
|
4088
|
+
function createMergeError(operation, error) {
|
|
4089
|
+
if (operation === 'mergeAnnotations') {
|
|
4090
|
+
if (error instanceof MergeAnnotationsError)
|
|
4091
|
+
return error;
|
|
4092
|
+
const message = error instanceof Error
|
|
4093
|
+
? `mergeAnnotations failed: ${error.message}`
|
|
4094
|
+
: 'mergeAnnotations failed';
|
|
4095
|
+
return new MergeAnnotationsError(message, error);
|
|
4096
|
+
}
|
|
4097
|
+
if (error instanceof MergeMasksError)
|
|
4098
|
+
return error;
|
|
4099
|
+
const message = error instanceof Error ? `mergeMasks failed: ${error.message}` : 'mergeMasks failed';
|
|
4100
|
+
return new MergeMasksError(message, error);
|
|
4101
|
+
}
|
|
4102
|
+
function detachObjects(canvas, objects) {
|
|
4103
|
+
for (const object of objects) {
|
|
4104
|
+
if (!canvas.getObjects().includes(object))
|
|
4105
|
+
continue;
|
|
4106
|
+
canvas.remove(object);
|
|
4107
|
+
}
|
|
4108
|
+
canvas.discardActiveObject();
|
|
4109
|
+
canvas.renderAll();
|
|
4110
|
+
}
|
|
4111
|
+
async function flattenOverlayGroupToBaseImage(context, options) {
|
|
4112
|
+
if (!context.isImageLoaded())
|
|
4113
|
+
return;
|
|
4114
|
+
if (options.getTargets().length === 0)
|
|
4115
|
+
return;
|
|
4116
|
+
const beforeSnapshot = context.captureSnapshot();
|
|
4117
|
+
const preservedObjects = options.getPreservedObjects();
|
|
4118
|
+
const preScrollTop = context.containerElement ? context.containerElement.scrollTop : null;
|
|
4119
|
+
const preScrollLeft = context.containerElement ? context.containerElement.scrollLeft : null;
|
|
4120
|
+
try {
|
|
4121
|
+
detachObjects(context.canvas, preservedObjects);
|
|
4122
|
+
const exportedDataUrl = await context.exportImageBase64(options.exportOptions);
|
|
4123
|
+
if (!exportedDataUrl) {
|
|
4124
|
+
throw createMergeError(options.operation, `${options.operation}: exportImageBase64 returned an empty data URL.`);
|
|
4125
|
+
}
|
|
4126
|
+
options.removeTargetsNoHistory();
|
|
4127
|
+
await context.loadImage(exportedDataUrl, { preserveScroll: true });
|
|
4128
|
+
await options.restorePreservedObjects(preservedObjects);
|
|
4129
|
+
normalizeLayerOrder(context.canvas);
|
|
4130
|
+
context.canvas.renderAll();
|
|
4131
|
+
context.updateInputs();
|
|
4132
|
+
context.updateUi();
|
|
4133
|
+
if (context.containerElement) {
|
|
4134
|
+
try {
|
|
4135
|
+
if (preScrollTop !== null)
|
|
4136
|
+
context.containerElement.scrollTop = preScrollTop;
|
|
4137
|
+
if (preScrollLeft !== null)
|
|
4138
|
+
context.containerElement.scrollLeft = preScrollLeft;
|
|
4139
|
+
}
|
|
4140
|
+
catch (scrollError) {
|
|
4141
|
+
console.warn(`[ImageEditor] ${options.operation}: scroll restore failed`, scrollError);
|
|
4142
|
+
}
|
|
4143
|
+
}
|
|
4144
|
+
const afterSnapshot = context.captureSnapshot();
|
|
4145
|
+
if (beforeSnapshot && afterSnapshot && beforeSnapshot !== afterSnapshot) {
|
|
4146
|
+
context.historyManager.push(new Command(() => context.loadFromState(afterSnapshot), () => context.loadFromState(beforeSnapshot)));
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
catch (error) {
|
|
4150
|
+
try {
|
|
4151
|
+
await context.loadFromState(beforeSnapshot);
|
|
4152
|
+
}
|
|
4153
|
+
catch (rollbackError) {
|
|
4154
|
+
console.warn(`[ImageEditor] ${options.operation}: rollback failed`, rollbackError);
|
|
4155
|
+
}
|
|
4156
|
+
throw createMergeError(options.operation, error);
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
|
|
2895
4160
|
function resolveMultiplier(requested, fallback) {
|
|
2896
4161
|
const num = Number(requested);
|
|
2897
4162
|
if (Number.isFinite(num) && num > 0)
|
|
@@ -2908,13 +4173,31 @@ function resolveExportOptions(context, options) {
|
|
|
2908
4173
|
const providedOptions = options !== null && options !== void 0 ? options : {};
|
|
2909
4174
|
return {
|
|
2910
4175
|
exportArea: resolveExportArea(providedOptions.exportArea, context.options.exportAreaByDefault),
|
|
2911
|
-
|
|
2912
|
-
? providedOptions.
|
|
2913
|
-
: context.options.
|
|
4176
|
+
mergeMasks: typeof providedOptions.mergeMasks === 'boolean'
|
|
4177
|
+
? providedOptions.mergeMasks
|
|
4178
|
+
: context.options.mergeMasksByDefault,
|
|
4179
|
+
mergeAnnotations: typeof providedOptions.mergeAnnotations === 'boolean'
|
|
4180
|
+
? providedOptions.mergeAnnotations
|
|
4181
|
+
: context.options.mergeAnnotationsByDefault,
|
|
2914
4182
|
multiplier: resolveMultiplier(providedOptions.multiplier, context.options.exportMultiplier),
|
|
2915
4183
|
format: resolveExportFormat(providedOptions, context.options.downsampleQuality),
|
|
2916
4184
|
};
|
|
2917
4185
|
}
|
|
4186
|
+
function resolveDownloadOptions(context, options) {
|
|
4187
|
+
var _a;
|
|
4188
|
+
const providedOptions = typeof options === 'string'
|
|
4189
|
+
? { fileName: options }
|
|
4190
|
+
: (options !== null && options !== void 0 ? options : {});
|
|
4191
|
+
return {
|
|
4192
|
+
fileName: (_a = providedOptions.fileName) !== null && _a !== void 0 ? _a : context.options.defaultDownloadFileName,
|
|
4193
|
+
exportOptions: {
|
|
4194
|
+
exportArea: context.options.exportAreaByDefault,
|
|
4195
|
+
mergeMasks: providedOptions.mergeMasks,
|
|
4196
|
+
mergeAnnotations: providedOptions.mergeAnnotations,
|
|
4197
|
+
multiplier: context.options.exportMultiplier,
|
|
4198
|
+
},
|
|
4199
|
+
};
|
|
4200
|
+
}
|
|
2918
4201
|
function readCanvasDimension(canvas, getterName, propertyName) {
|
|
2919
4202
|
const canvasLike = canvas;
|
|
2920
4203
|
const getter = canvasLike[getterName];
|
|
@@ -2954,25 +4237,26 @@ function computeExportRegion(context, exportArea) {
|
|
|
2954
4237
|
partialEdges: getPartialExportEdges(bounds, Number(originalImage.angle) || 0),
|
|
2955
4238
|
};
|
|
2956
4239
|
}
|
|
2957
|
-
async function withMaskExportState(context,
|
|
2958
|
-
if (!
|
|
2959
|
-
return
|
|
4240
|
+
async function withMaskExportState(context, mergeMasks, callback) {
|
|
4241
|
+
if (!mergeMasks) {
|
|
4242
|
+
return withObjectsHidden(context.canvas, isMaskObject, callback);
|
|
4243
|
+
}
|
|
2960
4244
|
return withMaskStyleBackup({ canvas: context.canvas, options: context.options }, applyExportBakeInStyle, callback);
|
|
2961
4245
|
}
|
|
2962
|
-
async function
|
|
2963
|
-
const backups = getCanvasObjects(
|
|
2964
|
-
.filter(
|
|
2965
|
-
.map((
|
|
2966
|
-
|
|
2967
|
-
visible:
|
|
4246
|
+
async function withObjectsHidden(canvas, predicate, callback) {
|
|
4247
|
+
const backups = getCanvasObjects(canvas)
|
|
4248
|
+
.filter(predicate)
|
|
4249
|
+
.map((object) => ({
|
|
4250
|
+
object,
|
|
4251
|
+
visible: object.visible,
|
|
2968
4252
|
}));
|
|
2969
4253
|
for (const backup of backups) {
|
|
2970
4254
|
try {
|
|
2971
|
-
if (typeof backup.
|
|
2972
|
-
backup.
|
|
4255
|
+
if (typeof backup.object.set === 'function') {
|
|
4256
|
+
backup.object.set({ visible: false });
|
|
2973
4257
|
}
|
|
2974
4258
|
else {
|
|
2975
|
-
backup.
|
|
4259
|
+
backup.object.visible = false;
|
|
2976
4260
|
}
|
|
2977
4261
|
}
|
|
2978
4262
|
catch {
|
|
@@ -2984,18 +4268,31 @@ async function withMasksHidden(context, callback) {
|
|
|
2984
4268
|
finally {
|
|
2985
4269
|
for (const backup of backups) {
|
|
2986
4270
|
try {
|
|
2987
|
-
if (typeof backup.
|
|
2988
|
-
backup.
|
|
4271
|
+
if (typeof backup.object.set === 'function') {
|
|
4272
|
+
backup.object.set({ visible: backup.visible });
|
|
2989
4273
|
}
|
|
2990
4274
|
else {
|
|
2991
|
-
backup.
|
|
4275
|
+
backup.object.visible = backup.visible;
|
|
2992
4276
|
}
|
|
2993
4277
|
}
|
|
2994
4278
|
catch {
|
|
2995
4279
|
}
|
|
2996
4280
|
}
|
|
4281
|
+
requestRender(canvas);
|
|
2997
4282
|
}
|
|
2998
4283
|
}
|
|
4284
|
+
async function withSessionObjectsHidden(context, callback) {
|
|
4285
|
+
return withObjectsHidden(context.canvas, (object) => isSessionObject(object) ||
|
|
4286
|
+
object.isCropRect === true ||
|
|
4287
|
+
object.maskLabel === true ||
|
|
4288
|
+
object.isMosaicPreview === true, callback);
|
|
4289
|
+
}
|
|
4290
|
+
async function withAnnotationsExportState(context, mergeAnnotations, callback) {
|
|
4291
|
+
if (!mergeAnnotations) {
|
|
4292
|
+
return withObjectsHidden(context.canvas, isAnnotationObject, callback);
|
|
4293
|
+
}
|
|
4294
|
+
return withObjectsHidden(context.canvas, (object) => isAnnotationObject(object) && object.annotationHidden === true, callback);
|
|
4295
|
+
}
|
|
2999
4296
|
function getCanvasObjects(canvas) {
|
|
3000
4297
|
try {
|
|
3001
4298
|
return canvas.getObjects();
|
|
@@ -3105,67 +4402,40 @@ function applyExportBakeInStyle(mask) {
|
|
|
3105
4402
|
}
|
|
3106
4403
|
function renderCanvasToDataUrl(canvas, format, quality, multiplier, region) {
|
|
3107
4404
|
const fabricOptions = {
|
|
3108
|
-
format,
|
|
3109
|
-
multiplier,
|
|
3110
|
-
};
|
|
3111
|
-
if (quality !== undefined)
|
|
3112
|
-
fabricOptions.quality = quality;
|
|
3113
|
-
if (region) {
|
|
3114
|
-
fabricOptions.left = region.left;
|
|
3115
|
-
fabricOptions.top = region.top;
|
|
3116
|
-
fabricOptions.width = region.width;
|
|
3117
|
-
fabricOptions.height = region.height;
|
|
3118
|
-
}
|
|
3119
|
-
return canvas.toDataURL(fabricOptions);
|
|
3120
|
-
}
|
|
3121
|
-
function hasPartialEdges(edges) {
|
|
3122
|
-
return !!edges && (edges.left || edges.top || edges.right || edges.bottom);
|
|
3123
|
-
}
|
|
3124
|
-
function getImageDimensions(imageElement) {
|
|
3125
|
-
return {
|
|
3126
|
-
width: Math.max(1, imageElement.naturalWidth || imageElement.width || 1),
|
|
3127
|
-
height: Math.max(1, imageElement.naturalHeight || imageElement.height || 1),
|
|
3128
|
-
};
|
|
3129
|
-
}
|
|
3130
|
-
function loadImageElement(dataUrl) {
|
|
3131
|
-
return
|
|
3132
|
-
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
if (typeof imageElement.removeEventListener === 'function') {
|
|
3136
|
-
imageElement.removeEventListener('load', handleLoad);
|
|
3137
|
-
imageElement.removeEventListener('error', handleError);
|
|
3138
|
-
}
|
|
3139
|
-
else {
|
|
3140
|
-
imageElement.onload = null;
|
|
3141
|
-
imageElement.onerror = null;
|
|
3142
|
-
}
|
|
3143
|
-
};
|
|
3144
|
-
const handleLoad = () => {
|
|
3145
|
-
cleanup();
|
|
3146
|
-
resolve(imageElement);
|
|
3147
|
-
};
|
|
3148
|
-
const handleError = () => {
|
|
3149
|
-
cleanup();
|
|
3150
|
-
reject(new Error('Failed to decode export data URL'));
|
|
3151
|
-
};
|
|
3152
|
-
if (typeof imageElement.addEventListener === 'function') {
|
|
3153
|
-
imageElement.addEventListener('load', handleLoad, { once: true });
|
|
3154
|
-
imageElement.addEventListener('error', handleError, { once: true });
|
|
3155
|
-
}
|
|
3156
|
-
else {
|
|
3157
|
-
imageElement.onload = handleLoad;
|
|
3158
|
-
imageElement.onerror = handleError;
|
|
3159
|
-
}
|
|
3160
|
-
imageElement.src = dataUrl;
|
|
3161
|
-
});
|
|
4405
|
+
format,
|
|
4406
|
+
multiplier,
|
|
4407
|
+
};
|
|
4408
|
+
if (quality !== undefined)
|
|
4409
|
+
fabricOptions.quality = quality;
|
|
4410
|
+
if (region) {
|
|
4411
|
+
fabricOptions.left = region.left;
|
|
4412
|
+
fabricOptions.top = region.top;
|
|
4413
|
+
fabricOptions.width = region.width;
|
|
4414
|
+
fabricOptions.height = region.height;
|
|
4415
|
+
}
|
|
4416
|
+
return canvas.toDataURL(fabricOptions);
|
|
4417
|
+
}
|
|
4418
|
+
function hasPartialEdges(edges) {
|
|
4419
|
+
return !!edges && (edges.left || edges.top || edges.right || edges.bottom);
|
|
4420
|
+
}
|
|
4421
|
+
function getImageDimensions(imageElement) {
|
|
4422
|
+
return {
|
|
4423
|
+
width: Math.max(1, imageElement.naturalWidth || imageElement.width || 1),
|
|
4424
|
+
height: Math.max(1, imageElement.naturalHeight || imageElement.height || 1),
|
|
4425
|
+
};
|
|
4426
|
+
}
|
|
4427
|
+
function loadImageElement(dataUrl) {
|
|
4428
|
+
return startImageElementLoad(dataUrl, {
|
|
4429
|
+
crossOrigin: 'anonymous',
|
|
4430
|
+
createError: () => new Error('Failed to decode export data URL'),
|
|
4431
|
+
}).promise;
|
|
3162
4432
|
}
|
|
3163
|
-
async function sealPartialTransparentEdges(dataUrl, edges, target) {
|
|
4433
|
+
async function sealPartialTransparentEdges(dataUrl, edges, target, ownerDocument) {
|
|
3164
4434
|
if (!hasPartialEdges(edges))
|
|
3165
4435
|
return dataUrl;
|
|
3166
4436
|
const imageElement = await loadImageElement(dataUrl);
|
|
3167
4437
|
const { width, height } = getImageDimensions(imageElement);
|
|
3168
|
-
const offscreenCanvas =
|
|
4438
|
+
const offscreenCanvas = ownerDocument.createElement('canvas');
|
|
3169
4439
|
offscreenCanvas.width = width;
|
|
3170
4440
|
offscreenCanvas.height = height;
|
|
3171
4441
|
const canvasContext = offscreenCanvas.getContext('2d');
|
|
@@ -3212,14 +4482,14 @@ async function sealPartialTransparentEdges(dataUrl, edges, target) {
|
|
|
3212
4482
|
? offscreenCanvas.toDataURL(target.mimeType)
|
|
3213
4483
|
: offscreenCanvas.toDataURL(target.mimeType, target.quality);
|
|
3214
4484
|
}
|
|
3215
|
-
function getJpegBackgroundColor(backgroundColor) {
|
|
3216
|
-
return resolveCanvasFillStyle(backgroundColor);
|
|
4485
|
+
function getJpegBackgroundColor(backgroundColor, ownerDocument) {
|
|
4486
|
+
return resolveCanvasFillStyle(backgroundColor, ownerDocument);
|
|
3217
4487
|
}
|
|
3218
|
-
function resolveCanvasFillStyle(backgroundColor, fallback = '#ffffff') {
|
|
4488
|
+
function resolveCanvasFillStyle(backgroundColor, ownerDocument, fallback = '#ffffff') {
|
|
3219
4489
|
const value = String(backgroundColor !== null && backgroundColor !== void 0 ? backgroundColor : '').trim();
|
|
3220
4490
|
if (!value || isTransparentCssColor(value))
|
|
3221
4491
|
return '#ffffff';
|
|
3222
|
-
const context = createColorValidationContext();
|
|
4492
|
+
const context = createColorValidationContext(ownerDocument);
|
|
3223
4493
|
if (!context)
|
|
3224
4494
|
return fallback;
|
|
3225
4495
|
context.fillStyle = '#000001';
|
|
@@ -3236,21 +4506,23 @@ function resolveCanvasFillStyle(backgroundColor, fallback = '#ffffff') {
|
|
|
3236
4506
|
return secondResolved;
|
|
3237
4507
|
return fallback;
|
|
3238
4508
|
}
|
|
3239
|
-
function createColorValidationContext() {
|
|
4509
|
+
function createColorValidationContext(ownerDocument) {
|
|
3240
4510
|
try {
|
|
3241
|
-
|
|
3242
|
-
return null;
|
|
3243
|
-
}
|
|
3244
|
-
return document.createElement('canvas').getContext('2d');
|
|
4511
|
+
return ownerDocument.createElement('canvas').getContext('2d');
|
|
3245
4512
|
}
|
|
3246
4513
|
catch {
|
|
3247
4514
|
return null;
|
|
3248
4515
|
}
|
|
3249
4516
|
}
|
|
3250
4517
|
function getCanvasDocument$1(canvas) {
|
|
3251
|
-
var _a, _b, _c, _d
|
|
4518
|
+
var _a, _b, _c, _d;
|
|
3252
4519
|
const canvasLike = canvas;
|
|
3253
|
-
|
|
4520
|
+
const ownerDocument = (_c = (_b = (_a = canvasLike.getElement) === null || _a === void 0 ? void 0 : _a.call(canvasLike)) === null || _b === void 0 ? void 0 : _b.ownerDocument) !== null && _c !== void 0 ? _c : (_d = canvasLike.lowerCanvasEl) === null || _d === void 0 ? void 0 : _d.ownerDocument;
|
|
4521
|
+
if (ownerDocument)
|
|
4522
|
+
return ownerDocument;
|
|
4523
|
+
if (typeof document !== 'undefined')
|
|
4524
|
+
return document;
|
|
4525
|
+
throw new Error('Document is unavailable for export canvas creation.');
|
|
3254
4526
|
}
|
|
3255
4527
|
function isTransparentCssColor(value) {
|
|
3256
4528
|
const normalized = value.trim().toLowerCase();
|
|
@@ -3279,16 +4551,16 @@ function isZeroCssAlpha(value) {
|
|
|
3279
4551
|
const numericAlpha = Number.parseFloat(alpha);
|
|
3280
4552
|
return Number.isFinite(numericAlpha) && numericAlpha === 0;
|
|
3281
4553
|
}
|
|
3282
|
-
async function convertDataUrlToOpaqueJpeg(dataUrl, backgroundColor, quality) {
|
|
4554
|
+
async function convertDataUrlToOpaqueJpeg(dataUrl, backgroundColor, quality, ownerDocument) {
|
|
3283
4555
|
const imageElement = await loadImageElement(dataUrl);
|
|
3284
4556
|
const { width, height } = getImageDimensions(imageElement);
|
|
3285
|
-
const offscreenCanvas =
|
|
4557
|
+
const offscreenCanvas = ownerDocument.createElement('canvas');
|
|
3286
4558
|
offscreenCanvas.width = width;
|
|
3287
4559
|
offscreenCanvas.height = height;
|
|
3288
4560
|
const canvasContext = offscreenCanvas.getContext('2d');
|
|
3289
4561
|
if (!canvasContext)
|
|
3290
4562
|
throw new Error('2D canvas context is unavailable');
|
|
3291
|
-
canvasContext.fillStyle = getJpegBackgroundColor(backgroundColor);
|
|
4563
|
+
canvasContext.fillStyle = getJpegBackgroundColor(backgroundColor, ownerDocument);
|
|
3292
4564
|
canvasContext.fillRect(0, 0, width, height);
|
|
3293
4565
|
canvasContext.drawImage(imageElement, 0, 0, width, height);
|
|
3294
4566
|
return offscreenCanvas.toDataURL('image/jpeg', quality);
|
|
@@ -3319,13 +4591,14 @@ function dataUrlToBytes(dataUrl) {
|
|
|
3319
4591
|
}
|
|
3320
4592
|
throw new Error('No base64 decoder is available for exportImageFile.');
|
|
3321
4593
|
}
|
|
3322
|
-
async function reencodeDataUrlAs(sourceDataUrl, target, backgroundColor) {
|
|
4594
|
+
async function reencodeDataUrlAs(sourceDataUrl, target, backgroundColor, canvas) {
|
|
3323
4595
|
if (sourceDataUrl.startsWith(`data:${target.mimeType}`)) {
|
|
3324
4596
|
return sourceDataUrl;
|
|
3325
4597
|
}
|
|
3326
4598
|
const imageElement = await loadImageElement(sourceDataUrl);
|
|
3327
4599
|
const { width, height } = getImageDimensions(imageElement);
|
|
3328
|
-
const
|
|
4600
|
+
const ownerDocument = getCanvasDocument$1(canvas);
|
|
4601
|
+
const offscreenCanvas = ownerDocument.createElement('canvas');
|
|
3329
4602
|
offscreenCanvas.width = width;
|
|
3330
4603
|
offscreenCanvas.height = height;
|
|
3331
4604
|
const canvasContext = offscreenCanvas.getContext('2d');
|
|
@@ -3333,7 +4606,7 @@ async function reencodeDataUrlAs(sourceDataUrl, target, backgroundColor) {
|
|
|
3333
4606
|
throw new Error('Unable to acquire 2D context for export conversion');
|
|
3334
4607
|
}
|
|
3335
4608
|
if (target.format === 'jpeg') {
|
|
3336
|
-
canvasContext.fillStyle = getJpegBackgroundColor(backgroundColor);
|
|
4609
|
+
canvasContext.fillStyle = getJpegBackgroundColor(backgroundColor, ownerDocument);
|
|
3337
4610
|
canvasContext.fillRect(0, 0, width, height);
|
|
3338
4611
|
}
|
|
3339
4612
|
canvasContext.drawImage(imageElement, 0, 0, width, height);
|
|
@@ -3356,14 +4629,16 @@ async function exportImageBase64(context, options) {
|
|
|
3356
4629
|
assertExportPixelBudget(context, resolved.multiplier, region);
|
|
3357
4630
|
const renderFormat = region && resolved.format.format === 'jpeg' ? 'png' : resolved.format.format;
|
|
3358
4631
|
const renderQuality = renderFormat === 'png' ? undefined : resolved.format.quality;
|
|
3359
|
-
let dataUrl = await withMaskExportState(context, resolved.
|
|
4632
|
+
let dataUrl = await withSessionObjectsHidden(context, async () => withMaskExportState(context, resolved.mergeMasks, async () => withAnnotationsExportState(context, resolved.mergeAnnotations, async () => renderCanvasToDataUrl(context.canvas, renderFormat, renderQuality, resolved.multiplier, region))));
|
|
3360
4633
|
if (region) {
|
|
3361
4634
|
const sealedFormat = resolved.format.format === 'jpeg'
|
|
3362
4635
|
? { format: 'png', mimeType: 'image/png', quality: undefined }
|
|
3363
4636
|
: resolved.format;
|
|
3364
|
-
|
|
4637
|
+
if (hasPartialEdges(partialEdges)) {
|
|
4638
|
+
dataUrl = await sealPartialTransparentEdges(dataUrl, partialEdges, sealedFormat, getCanvasDocument$1(context.canvas));
|
|
4639
|
+
}
|
|
3365
4640
|
if (resolved.format.format === 'jpeg') {
|
|
3366
|
-
dataUrl = await convertDataUrlToOpaqueJpeg(dataUrl, context.options.backgroundColor, resolved.format.quality);
|
|
4641
|
+
dataUrl = await convertDataUrlToOpaqueJpeg(dataUrl, context.options.backgroundColor, resolved.format.quality, getCanvasDocument$1(context.canvas));
|
|
3367
4642
|
}
|
|
3368
4643
|
}
|
|
3369
4644
|
return dataUrl;
|
|
@@ -3385,7 +4660,8 @@ async function exportImageFile(context, options) {
|
|
|
3385
4660
|
const resolved = resolveExportFormat(providedOptions, context.options.downsampleQuality);
|
|
3386
4661
|
const base64 = await exportImageBase64(context, {
|
|
3387
4662
|
exportArea: providedOptions.exportArea,
|
|
3388
|
-
|
|
4663
|
+
mergeMasks: providedOptions.mergeMasks,
|
|
4664
|
+
mergeAnnotations: providedOptions.mergeAnnotations,
|
|
3389
4665
|
multiplier: providedOptions.multiplier,
|
|
3390
4666
|
quality: providedOptions.quality,
|
|
3391
4667
|
fileType: providedOptions.fileType,
|
|
@@ -3393,7 +4669,7 @@ async function exportImageFile(context, options) {
|
|
|
3393
4669
|
if (!base64) {
|
|
3394
4670
|
throw new ExportNotReadyError('exportImageFile');
|
|
3395
4671
|
}
|
|
3396
|
-
const finalDataUrl = await reencodeDataUrlAs(base64, resolved, context.options.backgroundColor);
|
|
4672
|
+
const finalDataUrl = await reencodeDataUrlAs(base64, resolved, context.options.backgroundColor, context.canvas);
|
|
3397
4673
|
let bytes;
|
|
3398
4674
|
try {
|
|
3399
4675
|
bytes = dataUrlToBytes(finalDataUrl);
|
|
@@ -3403,23 +4679,19 @@ async function exportImageFile(context, options) {
|
|
|
3403
4679
|
}
|
|
3404
4680
|
return new File([bytes], fileName, { type: resolved.mimeType });
|
|
3405
4681
|
}
|
|
3406
|
-
function downloadImage(context,
|
|
4682
|
+
function downloadImage(context, options) {
|
|
3407
4683
|
if (!context.isImageLoaded()) {
|
|
3408
4684
|
warnNoImageLoaded('downloadImage');
|
|
3409
4685
|
return;
|
|
3410
4686
|
}
|
|
3411
|
-
const
|
|
3412
|
-
void exportImageBase64(context,
|
|
3413
|
-
exportArea: context.options.exportAreaByDefault,
|
|
3414
|
-
mergeMask: context.options.mergeMaskByDefault,
|
|
3415
|
-
multiplier: context.options.exportMultiplier,
|
|
3416
|
-
})
|
|
4687
|
+
const resolved = resolveDownloadOptions(context, options);
|
|
4688
|
+
void exportImageBase64(context, resolved.exportOptions)
|
|
3417
4689
|
.then((dataUrl) => {
|
|
3418
4690
|
if (!dataUrl)
|
|
3419
4691
|
return;
|
|
3420
4692
|
const ownerDocument = getCanvasDocument$1(context.canvas);
|
|
3421
4693
|
const link = ownerDocument.createElement('a');
|
|
3422
|
-
link.download =
|
|
4694
|
+
link.download = resolved.fileName;
|
|
3423
4695
|
link.href = dataUrl;
|
|
3424
4696
|
const body = ownerDocument.body;
|
|
3425
4697
|
body.appendChild(link);
|
|
@@ -3436,60 +4708,40 @@ function downloadImage(context, fileName) {
|
|
|
3436
4708
|
});
|
|
3437
4709
|
}
|
|
3438
4710
|
async function mergeMasks(context) {
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
.getObjects()
|
|
3443
|
-
.filter((o) => 'maskId' in o && typeof o.maskId === 'number');
|
|
3444
|
-
if (masks.length === 0)
|
|
3445
|
-
return;
|
|
3446
|
-
const beforeSnapshot = context.saveState();
|
|
3447
|
-
context.canvas.discardActiveObject();
|
|
3448
|
-
context.canvas.renderAll();
|
|
3449
|
-
const preScrollTop = context.containerElement ? context.containerElement.scrollTop : null;
|
|
3450
|
-
const preScrollLeft = context.containerElement ? context.containerElement.scrollLeft : null;
|
|
3451
|
-
try {
|
|
3452
|
-
const merged = await exportImageBase64(context, {
|
|
4711
|
+
await flattenOverlayGroupToBaseImage(context, {
|
|
4712
|
+
operation: 'mergeMasks',
|
|
4713
|
+
exportOptions: {
|
|
3453
4714
|
exportArea: 'image',
|
|
3454
|
-
|
|
4715
|
+
mergeMasks: true,
|
|
4716
|
+
mergeAnnotations: false,
|
|
3455
4717
|
multiplier: context.options.exportMultiplier,
|
|
3456
4718
|
fileType: 'png',
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
await context.loadFromState(beforeSnapshot);
|
|
3484
|
-
}
|
|
3485
|
-
catch (rollbackError) {
|
|
3486
|
-
console.warn('[ImageEditor] mergeMasks: rollback failed', rollbackError);
|
|
3487
|
-
}
|
|
3488
|
-
if (error instanceof MergeMasksError)
|
|
3489
|
-
throw error;
|
|
3490
|
-
const message = error instanceof Error ? `mergeMasks failed: ${error.message}` : 'mergeMasks failed';
|
|
3491
|
-
throw new MergeMasksError(message, error);
|
|
3492
|
-
}
|
|
4719
|
+
},
|
|
4720
|
+
getTargets: () => context.canvas.getObjects().filter(isMaskObject),
|
|
4721
|
+
getPreservedObjects: () => context.getAnnotations(),
|
|
4722
|
+
removeTargetsNoHistory: () => {
|
|
4723
|
+
context.removeAllMasksNoHistory();
|
|
4724
|
+
},
|
|
4725
|
+
restorePreservedObjects: (objects) => context.restoreAnnotations(objects),
|
|
4726
|
+
});
|
|
4727
|
+
}
|
|
4728
|
+
async function mergeAnnotations(context) {
|
|
4729
|
+
await flattenOverlayGroupToBaseImage(context, {
|
|
4730
|
+
operation: 'mergeAnnotations',
|
|
4731
|
+
exportOptions: {
|
|
4732
|
+
exportArea: 'image',
|
|
4733
|
+
mergeMasks: false,
|
|
4734
|
+
mergeAnnotations: true,
|
|
4735
|
+
multiplier: context.options.exportMultiplier,
|
|
4736
|
+
fileType: 'png',
|
|
4737
|
+
},
|
|
4738
|
+
getTargets: () => context.canvas.getObjects().filter(isAnnotationObject),
|
|
4739
|
+
getPreservedObjects: () => context.getMasks(),
|
|
4740
|
+
removeTargetsNoHistory: () => {
|
|
4741
|
+
context.removeAllAnnotationsNoHistory();
|
|
4742
|
+
},
|
|
4743
|
+
restorePreservedObjects: (objects) => context.restoreMasks(objects),
|
|
4744
|
+
});
|
|
3493
4745
|
}
|
|
3494
4746
|
|
|
3495
4747
|
function forceReflow(element) {
|
|
@@ -3723,6 +4975,7 @@ async function loadImage(context, imageBase64, loadOptions = {}) {
|
|
|
3723
4975
|
lastSnapshot: context.getLastSnapshot(),
|
|
3724
4976
|
canvasJson: serializeCanvas(context.canvas),
|
|
3725
4977
|
maskCounter: context.getMaskCounter(),
|
|
4978
|
+
annotationCounter: context.getAnnotationCounter(),
|
|
3726
4979
|
currentScale: context.getCurrentScale(),
|
|
3727
4980
|
currentRotation: context.getCurrentRotation(),
|
|
3728
4981
|
baseImageScale: context.getBaseImageScale(),
|
|
@@ -3744,23 +4997,25 @@ async function loadImage(context, imageBase64, loadOptions = {}) {
|
|
|
3744
4997
|
context.canvas.discardActiveObject();
|
|
3745
4998
|
context.canvas.clear();
|
|
3746
4999
|
context.canvas.backgroundColor = context.options.backgroundColor;
|
|
3747
|
-
fabricImage
|
|
5000
|
+
const baseImage = markBaseImageObject(fabricImage);
|
|
5001
|
+
baseImage.set({
|
|
3748
5002
|
originX: 'left',
|
|
3749
5003
|
originY: 'top',
|
|
3750
5004
|
selectable: false,
|
|
3751
5005
|
evented: false,
|
|
3752
5006
|
});
|
|
3753
|
-
const layout = computeLayout(context,
|
|
5007
|
+
const layout = computeLayout(context, baseImage);
|
|
3754
5008
|
applyCanvasDimensions(context.canvas, layout.canvasWidth, layout.canvasHeight, context.containerElement);
|
|
3755
|
-
|
|
3756
|
-
|
|
3757
|
-
context.canvas.add(
|
|
3758
|
-
context.canvas.sendObjectToBack(
|
|
3759
|
-
context.setOriginalImage(
|
|
5009
|
+
baseImage.set({ left: layout.imageLeft, top: layout.imageTop });
|
|
5010
|
+
baseImage.scale(layout.imageScale);
|
|
5011
|
+
context.canvas.add(baseImage);
|
|
5012
|
+
context.canvas.sendObjectToBack(baseImage);
|
|
5013
|
+
context.setOriginalImage(baseImage);
|
|
3760
5014
|
context.setBaseImageScale(layout.baseImageScale);
|
|
3761
5015
|
context.setCurrentScale(1);
|
|
3762
5016
|
context.setCurrentRotation(0);
|
|
3763
5017
|
context.setMaskCounter(0);
|
|
5018
|
+
context.setAnnotationCounter(0);
|
|
3764
5019
|
context.setIsImageLoadedToCanvas(true);
|
|
3765
5020
|
context.setCurrentImageMimeType(loadSource.mimeType);
|
|
3766
5021
|
context.canvas.renderAll();
|
|
@@ -3793,49 +5048,12 @@ async function loadImage(context, imageBase64, loadOptions = {}) {
|
|
|
3793
5048
|
}
|
|
3794
5049
|
}
|
|
3795
5050
|
function startImageDecode(dataUrl) {
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
}
|
|
3802
|
-
else {
|
|
3803
|
-
imageElement.onload = null;
|
|
3804
|
-
imageElement.onerror = null;
|
|
3805
|
-
}
|
|
3806
|
-
if (clearSource) {
|
|
3807
|
-
imageElement.src = '';
|
|
3808
|
-
}
|
|
3809
|
-
};
|
|
3810
|
-
const handleLoad = () => {
|
|
3811
|
-
if (!hasNaturalImageDimensions(imageElement)) {
|
|
3812
|
-
cleanup(true);
|
|
3813
|
-
rejectImage(new ImageDecodeError('Failed to decode image data URL: image has no natural dimensions.', null));
|
|
3814
|
-
return;
|
|
3815
|
-
}
|
|
3816
|
-
cleanup(false);
|
|
3817
|
-
resolveImage(imageElement);
|
|
3818
|
-
};
|
|
3819
|
-
const handleError = (e) => {
|
|
3820
|
-
cleanup(true);
|
|
3821
|
-
rejectImage(new ImageDecodeError('Failed to decode image data URL.', e));
|
|
3822
|
-
};
|
|
3823
|
-
let resolveImage;
|
|
3824
|
-
let rejectImage;
|
|
3825
|
-
const promise = new Promise((resolve, reject) => {
|
|
3826
|
-
resolveImage = resolve;
|
|
3827
|
-
rejectImage = reject;
|
|
3828
|
-
if (typeof imageElement.addEventListener === 'function') {
|
|
3829
|
-
imageElement.addEventListener('load', handleLoad, { once: true });
|
|
3830
|
-
imageElement.addEventListener('error', handleError, { once: true });
|
|
3831
|
-
}
|
|
3832
|
-
else {
|
|
3833
|
-
imageElement.onload = handleLoad;
|
|
3834
|
-
imageElement.onerror = handleError;
|
|
3835
|
-
}
|
|
3836
|
-
imageElement.src = dataUrl;
|
|
5051
|
+
return startImageElementLoad(dataUrl, {
|
|
5052
|
+
validate: (imageElement) => hasNaturalImageDimensions(imageElement)
|
|
5053
|
+
? null
|
|
5054
|
+
: new ImageDecodeError('Failed to decode image data URL: image has no natural dimensions.', null),
|
|
5055
|
+
createError: (event) => new ImageDecodeError('Failed to decode image data URL.', event),
|
|
3837
5056
|
});
|
|
3838
|
-
return { promise, cleanup };
|
|
3839
5057
|
}
|
|
3840
5058
|
function hasNaturalImageDimensions(imageElement) {
|
|
3841
5059
|
return (Number.isFinite(imageElement.naturalWidth) &&
|
|
@@ -3913,6 +5131,7 @@ async function replayRollback(context, bundle) {
|
|
|
3913
5131
|
context.setIsImageLoadedToCanvas(bundle.isImageLoadedToCanvas);
|
|
3914
5132
|
context.setLastSnapshot(bundle.lastSnapshot);
|
|
3915
5133
|
context.setMaskCounter(bundle.maskCounter);
|
|
5134
|
+
context.setAnnotationCounter(bundle.annotationCounter);
|
|
3916
5135
|
context.setCurrentScale(bundle.currentScale);
|
|
3917
5136
|
context.setCurrentRotation(bundle.currentRotation);
|
|
3918
5137
|
context.setBaseImageScale(bundle.baseImageScale);
|
|
@@ -4155,30 +5374,6 @@ function computeTopLeftPoint(object) {
|
|
|
4155
5374
|
return { x: boundingRect.left, y: boundingRect.top };
|
|
4156
5375
|
}
|
|
4157
5376
|
|
|
4158
|
-
function resolveNumeric(val, axis, fallback, canvas, options) {
|
|
4159
|
-
if (typeof val === 'number') {
|
|
4160
|
-
return val;
|
|
4161
|
-
}
|
|
4162
|
-
if (typeof val === 'function') {
|
|
4163
|
-
return val(canvas, options);
|
|
4164
|
-
}
|
|
4165
|
-
if (typeof val === 'string' && val.endsWith('%')) {
|
|
4166
|
-
const pct = parseFloat(val);
|
|
4167
|
-
if (!Number.isFinite(pct)) {
|
|
4168
|
-
return fallback;
|
|
4169
|
-
}
|
|
4170
|
-
const dim = axis === 'x' ? canvas.getWidth() : canvas.getHeight();
|
|
4171
|
-
return Math.floor(dim * (pct / 100));
|
|
4172
|
-
}
|
|
4173
|
-
return fallback;
|
|
4174
|
-
}
|
|
4175
|
-
function coercePoint(pt) {
|
|
4176
|
-
if (Array.isArray(pt)) {
|
|
4177
|
-
return { x: Number(pt[0]), y: Number(pt[1]) };
|
|
4178
|
-
}
|
|
4179
|
-
return { x: Number(pt.x), y: Number(pt.y) };
|
|
4180
|
-
}
|
|
4181
|
-
|
|
4182
5377
|
const POLYGON_AREA_EPSILON = 1e-6;
|
|
4183
5378
|
function createMaskUid(maskId) {
|
|
4184
5379
|
return `mask-${maskId}`;
|
|
@@ -4473,18 +5668,19 @@ function createMask(context, config = {}) {
|
|
|
4473
5668
|
if ('strokeDashArray' in styles) {
|
|
4474
5669
|
maskObject.strokeDashArray = styles.strokeDashArray;
|
|
4475
5670
|
}
|
|
4476
|
-
maskObject.originalAlpha = resolvedConfig.alpha;
|
|
4477
|
-
maskObject.originalStroke = maskObject.stroke;
|
|
4478
|
-
maskObject.originalStrokeWidth = maskObject.strokeWidth;
|
|
4479
|
-
attachMaskHoverHandlers(maskObject);
|
|
4480
5671
|
const nextId = context.getMaskCounter() + 1;
|
|
4481
5672
|
context.setMaskCounter(nextId);
|
|
4482
|
-
maskObject
|
|
4483
|
-
|
|
4484
|
-
|
|
5673
|
+
markMaskObject(maskObject, {
|
|
5674
|
+
maskId: nextId,
|
|
5675
|
+
maskUid: createMaskUid(nextId),
|
|
5676
|
+
maskName: `${options.maskName}${nextId}`,
|
|
5677
|
+
originalAlpha: resolvedConfig.alpha,
|
|
5678
|
+
originalStroke: maskObject.stroke,
|
|
5679
|
+
originalStrokeWidth: maskObject.strokeWidth,
|
|
5680
|
+
});
|
|
5681
|
+
attachMaskHoverHandlers(maskObject);
|
|
4485
5682
|
context.setLastMask(maskObject);
|
|
4486
|
-
canvas
|
|
4487
|
-
canvas.bringObjectToFront(maskObject);
|
|
5683
|
+
placeMaskObject(canvas, maskObject);
|
|
4488
5684
|
context.updateMaskList();
|
|
4489
5685
|
if (resolvedConfig.selectable !== false) {
|
|
4490
5686
|
canvas.setActiveObject(maskObject);
|
|
@@ -4501,13 +5697,35 @@ function createMask(context, config = {}) {
|
|
|
4501
5697
|
}
|
|
4502
5698
|
return maskObject;
|
|
4503
5699
|
}
|
|
5700
|
+
function isActiveSelectionObject(object) {
|
|
5701
|
+
if (!object)
|
|
5702
|
+
return false;
|
|
5703
|
+
const type = typeof object.type === 'string' ? object.type.toLowerCase() : '';
|
|
5704
|
+
if (type === 'activeselection')
|
|
5705
|
+
return true;
|
|
5706
|
+
const isType = object.isType;
|
|
5707
|
+
return (typeof isType === 'function' &&
|
|
5708
|
+
(isType.call(object, 'ActiveSelection') || isType.call(object, 'activeSelection')));
|
|
5709
|
+
}
|
|
5710
|
+
function getSelectedMaskObjects(canvas) {
|
|
5711
|
+
const active = canvas.getActiveObject();
|
|
5712
|
+
if (!active)
|
|
5713
|
+
return [];
|
|
5714
|
+
if (!isActiveSelectionObject(active))
|
|
5715
|
+
return isMaskObject(active) ? [active] : [];
|
|
5716
|
+
const getObjects = active.getObjects;
|
|
5717
|
+
const objects = typeof getObjects === 'function' ? getObjects.call(active) : [];
|
|
5718
|
+
return objects.filter(isMaskObject);
|
|
5719
|
+
}
|
|
4504
5720
|
function removeSelectedMask(context) {
|
|
4505
|
-
const
|
|
4506
|
-
if (
|
|
5721
|
+
const selectedMasks = getSelectedMaskObjects(context.canvas);
|
|
5722
|
+
if (selectedMasks.length === 0)
|
|
4507
5723
|
return;
|
|
4508
|
-
|
|
4509
|
-
|
|
4510
|
-
|
|
5724
|
+
for (const mask of selectedMasks) {
|
|
5725
|
+
context.removeLabelForMask(mask);
|
|
5726
|
+
detachMaskHoverHandlers(mask);
|
|
5727
|
+
context.canvas.remove(mask);
|
|
5728
|
+
}
|
|
4511
5729
|
context.canvas.discardActiveObject();
|
|
4512
5730
|
context.updateMaskList();
|
|
4513
5731
|
context.canvas.renderAll();
|
|
@@ -4584,6 +5802,7 @@ function createLabelForMask(context, mask) {
|
|
|
4584
5802
|
};
|
|
4585
5803
|
labelTextObject = new fabricModule.FabricText(labelText, textOptions);
|
|
4586
5804
|
}
|
|
5805
|
+
markSessionObject(labelTextObject, 'maskLabel');
|
|
4587
5806
|
labelTextObject.maskLabel = true;
|
|
4588
5807
|
mask.labelObject = labelTextObject;
|
|
4589
5808
|
canvas.add(labelTextObject);
|
|
@@ -4845,6 +6064,22 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
4845
6064
|
'removeSelectedMaskButton',
|
|
4846
6065
|
'removeAllMasksButton',
|
|
4847
6066
|
'mergeMasksButton',
|
|
6067
|
+
'mergeAnnotationsButton',
|
|
6068
|
+
'enterTextModeButton',
|
|
6069
|
+
'exitTextModeButton',
|
|
6070
|
+
'textColorInput',
|
|
6071
|
+
'textFontSizeInput',
|
|
6072
|
+
'enterDrawModeButton',
|
|
6073
|
+
'exitDrawModeButton',
|
|
6074
|
+
'drawColorInput',
|
|
6075
|
+
'drawBrushSizeInput',
|
|
6076
|
+
'removeSelectedAnnotationButton',
|
|
6077
|
+
'removeAllAnnotationsButton',
|
|
6078
|
+
'deleteSelectedObjectButton',
|
|
6079
|
+
'bringSelectedObjectForwardButton',
|
|
6080
|
+
'sendSelectedObjectBackwardButton',
|
|
6081
|
+
'bringSelectedObjectToFrontButton',
|
|
6082
|
+
'sendSelectedObjectToBackButton',
|
|
4848
6083
|
'downloadImageButton',
|
|
4849
6084
|
'zoomInButton',
|
|
4850
6085
|
'zoomOutButton',
|
|
@@ -4862,6 +6097,16 @@ const CROP_MODE_CONTROL_KEYS = [
|
|
|
4862
6097
|
];
|
|
4863
6098
|
const CROP_MODE_ENABLED_KEYS = ['applyCropButton', 'cancelCropButton'];
|
|
4864
6099
|
const CROP_SESSION_ALLOWED_OPERATIONS = new Set(['applyCrop', 'cancelCrop']);
|
|
6100
|
+
const TEXT_MODE_ENABLED_KEYS = [
|
|
6101
|
+
'exitTextModeButton',
|
|
6102
|
+
'textColorInput',
|
|
6103
|
+
'textFontSizeInput',
|
|
6104
|
+
];
|
|
6105
|
+
const DRAW_MODE_ENABLED_KEYS = [
|
|
6106
|
+
'exitDrawModeButton',
|
|
6107
|
+
'drawColorInput',
|
|
6108
|
+
'drawBrushSizeInput',
|
|
6109
|
+
];
|
|
4865
6110
|
const MOSAIC_MODE_CONTROL_KEYS = [
|
|
4866
6111
|
'scalePercentageInput',
|
|
4867
6112
|
'rotateLeftDegreesInput',
|
|
@@ -4872,6 +6117,22 @@ const MOSAIC_MODE_CONTROL_KEYS = [
|
|
|
4872
6117
|
'removeSelectedMaskButton',
|
|
4873
6118
|
'removeAllMasksButton',
|
|
4874
6119
|
'mergeMasksButton',
|
|
6120
|
+
'mergeAnnotationsButton',
|
|
6121
|
+
'enterTextModeButton',
|
|
6122
|
+
'exitTextModeButton',
|
|
6123
|
+
'textColorInput',
|
|
6124
|
+
'textFontSizeInput',
|
|
6125
|
+
'enterDrawModeButton',
|
|
6126
|
+
'exitDrawModeButton',
|
|
6127
|
+
'drawColorInput',
|
|
6128
|
+
'drawBrushSizeInput',
|
|
6129
|
+
'removeSelectedAnnotationButton',
|
|
6130
|
+
'removeAllAnnotationsButton',
|
|
6131
|
+
'deleteSelectedObjectButton',
|
|
6132
|
+
'bringSelectedObjectForwardButton',
|
|
6133
|
+
'sendSelectedObjectBackwardButton',
|
|
6134
|
+
'bringSelectedObjectToFrontButton',
|
|
6135
|
+
'sendSelectedObjectToBackButton',
|
|
4875
6136
|
'downloadImageButton',
|
|
4876
6137
|
'zoomInButton',
|
|
4877
6138
|
'zoomOutButton',
|
|
@@ -4914,6 +6175,29 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
4914
6175
|
'removeSelectedMask',
|
|
4915
6176
|
'removeAllMasks',
|
|
4916
6177
|
'mergeMasks',
|
|
6178
|
+
'createTextAnnotation',
|
|
6179
|
+
'enterTextMode',
|
|
6180
|
+
'exitTextMode',
|
|
6181
|
+
'setTextConfig',
|
|
6182
|
+
'resetTextConfig',
|
|
6183
|
+
'setTextColor',
|
|
6184
|
+
'setTextFontSize',
|
|
6185
|
+
'enterDrawMode',
|
|
6186
|
+
'exitDrawMode',
|
|
6187
|
+
'setDrawConfig',
|
|
6188
|
+
'resetDrawConfig',
|
|
6189
|
+
'setDrawColor',
|
|
6190
|
+
'setDrawBrushSize',
|
|
6191
|
+
'updateSelectedAnnotation',
|
|
6192
|
+
'updateAnnotation',
|
|
6193
|
+
'removeSelectedAnnotation',
|
|
6194
|
+
'removeAllAnnotations',
|
|
6195
|
+
'deleteSelectedObject',
|
|
6196
|
+
'mergeAnnotations',
|
|
6197
|
+
'bringSelectedObjectForward',
|
|
6198
|
+
'sendSelectedObjectBackward',
|
|
6199
|
+
'bringSelectedObjectToFront',
|
|
6200
|
+
'sendSelectedObjectToBack',
|
|
4917
6201
|
'enterCropMode',
|
|
4918
6202
|
'applyCrop',
|
|
4919
6203
|
'cancelCrop',
|
|
@@ -4931,6 +6215,27 @@ const IMAGE_EDITOR_OPERATIONS = new Set([
|
|
|
4931
6215
|
'downloadImage',
|
|
4932
6216
|
'dispose',
|
|
4933
6217
|
]);
|
|
6218
|
+
const TOOL_MODE_ALLOWED_OPERATIONS = {
|
|
6219
|
+
crop: CROP_SESSION_ALLOWED_OPERATIONS,
|
|
6220
|
+
mosaic: MOSAIC_SESSION_ALLOWED_OPERATIONS,
|
|
6221
|
+
text: new Set([
|
|
6222
|
+
'exitTextMode',
|
|
6223
|
+
'createTextAnnotation',
|
|
6224
|
+
'setTextConfig',
|
|
6225
|
+
'resetTextConfig',
|
|
6226
|
+
'setTextColor',
|
|
6227
|
+
'setTextFontSize',
|
|
6228
|
+
'saveState',
|
|
6229
|
+
]),
|
|
6230
|
+
draw: new Set([
|
|
6231
|
+
'exitDrawMode',
|
|
6232
|
+
'setDrawConfig',
|
|
6233
|
+
'resetDrawConfig',
|
|
6234
|
+
'setDrawColor',
|
|
6235
|
+
'setDrawBrushSize',
|
|
6236
|
+
'saveState',
|
|
6237
|
+
]),
|
|
6238
|
+
};
|
|
4934
6239
|
function isImageEditorOperation(value) {
|
|
4935
6240
|
return value !== null && IMAGE_EDITOR_OPERATIONS.has(value);
|
|
4936
6241
|
}
|
|
@@ -4973,6 +6278,30 @@ class ImageEditor {
|
|
|
4973
6278
|
writable: true,
|
|
4974
6279
|
value: void 0
|
|
4975
6280
|
});
|
|
6281
|
+
Object.defineProperty(this, "defaultTextConfig", {
|
|
6282
|
+
enumerable: true,
|
|
6283
|
+
configurable: true,
|
|
6284
|
+
writable: true,
|
|
6285
|
+
value: void 0
|
|
6286
|
+
});
|
|
6287
|
+
Object.defineProperty(this, "currentTextConfig", {
|
|
6288
|
+
enumerable: true,
|
|
6289
|
+
configurable: true,
|
|
6290
|
+
writable: true,
|
|
6291
|
+
value: void 0
|
|
6292
|
+
});
|
|
6293
|
+
Object.defineProperty(this, "defaultDrawConfig", {
|
|
6294
|
+
enumerable: true,
|
|
6295
|
+
configurable: true,
|
|
6296
|
+
writable: true,
|
|
6297
|
+
value: void 0
|
|
6298
|
+
});
|
|
6299
|
+
Object.defineProperty(this, "currentDrawConfig", {
|
|
6300
|
+
enumerable: true,
|
|
6301
|
+
configurable: true,
|
|
6302
|
+
writable: true,
|
|
6303
|
+
value: void 0
|
|
6304
|
+
});
|
|
4976
6305
|
Object.defineProperty(this, "canvas", {
|
|
4977
6306
|
enumerable: true,
|
|
4978
6307
|
configurable: true,
|
|
@@ -5069,6 +6398,12 @@ class ImageEditor {
|
|
|
5069
6398
|
writable: true,
|
|
5070
6399
|
value: null
|
|
5071
6400
|
});
|
|
6401
|
+
Object.defineProperty(this, "annotationCounter", {
|
|
6402
|
+
enumerable: true,
|
|
6403
|
+
configurable: true,
|
|
6404
|
+
writable: true,
|
|
6405
|
+
value: 0
|
|
6406
|
+
});
|
|
5072
6407
|
Object.defineProperty(this, "lastSnapshot", {
|
|
5073
6408
|
enumerable: true,
|
|
5074
6409
|
configurable: true,
|
|
@@ -5117,12 +6452,36 @@ class ImageEditor {
|
|
|
5117
6452
|
writable: true,
|
|
5118
6453
|
value: null
|
|
5119
6454
|
});
|
|
6455
|
+
Object.defineProperty(this, "textSession", {
|
|
6456
|
+
enumerable: true,
|
|
6457
|
+
configurable: true,
|
|
6458
|
+
writable: true,
|
|
6459
|
+
value: null
|
|
6460
|
+
});
|
|
6461
|
+
Object.defineProperty(this, "drawSession", {
|
|
6462
|
+
enumerable: true,
|
|
6463
|
+
configurable: true,
|
|
6464
|
+
writable: true,
|
|
6465
|
+
value: null
|
|
6466
|
+
});
|
|
5120
6467
|
Object.defineProperty(this, "domBindings", {
|
|
5121
6468
|
enumerable: true,
|
|
5122
6469
|
configurable: true,
|
|
5123
6470
|
writable: true,
|
|
5124
6471
|
value: null
|
|
5125
6472
|
});
|
|
6473
|
+
Object.defineProperty(this, "keyboardDocument", {
|
|
6474
|
+
enumerable: true,
|
|
6475
|
+
configurable: true,
|
|
6476
|
+
writable: true,
|
|
6477
|
+
value: null
|
|
6478
|
+
});
|
|
6479
|
+
Object.defineProperty(this, "keyboardHandler", {
|
|
6480
|
+
enumerable: true,
|
|
6481
|
+
configurable: true,
|
|
6482
|
+
writable: true,
|
|
6483
|
+
value: null
|
|
6484
|
+
});
|
|
5126
6485
|
Object.defineProperty(this, "isDisposed", {
|
|
5127
6486
|
enumerable: true,
|
|
5128
6487
|
configurable: true,
|
|
@@ -5160,6 +6519,10 @@ class ImageEditor {
|
|
|
5160
6519
|
this.currentLayoutMode = this.options.layoutMode;
|
|
5161
6520
|
this.defaultMosaicConfig = this.options.defaultMosaicConfig;
|
|
5162
6521
|
this.currentMosaicConfig = cloneResolvedMosaicConfig(this.defaultMosaicConfig);
|
|
6522
|
+
this.defaultTextConfig = this.options.defaultTextConfig;
|
|
6523
|
+
this.currentTextConfig = cloneResolvedTextAnnotationConfig(this.defaultTextConfig);
|
|
6524
|
+
this.defaultDrawConfig = this.options.defaultDrawConfig;
|
|
6525
|
+
this.currentDrawConfig = cloneResolvedDrawConfig(this.defaultDrawConfig);
|
|
5163
6526
|
const rawDefaultLayoutMode = detected.options
|
|
5164
6527
|
.defaultLayoutMode;
|
|
5165
6528
|
if (rawDefaultLayoutMode !== undefined && !isLayoutMode(rawDefaultLayoutMode)) {
|
|
@@ -5196,6 +6559,23 @@ class ImageEditor {
|
|
|
5196
6559
|
removeSelectedMaskButton: 'removeSelectedMaskButton',
|
|
5197
6560
|
removeAllMasksButton: 'removeAllMasksButton',
|
|
5198
6561
|
mergeMasksButton: 'mergeMasksButton',
|
|
6562
|
+
annotationList: 'annotationList',
|
|
6563
|
+
enterTextModeButton: 'enterTextModeButton',
|
|
6564
|
+
exitTextModeButton: 'exitTextModeButton',
|
|
6565
|
+
textColorInput: 'textColorInput',
|
|
6566
|
+
textFontSizeInput: 'textFontSizeInput',
|
|
6567
|
+
enterDrawModeButton: 'enterDrawModeButton',
|
|
6568
|
+
exitDrawModeButton: 'exitDrawModeButton',
|
|
6569
|
+
drawColorInput: 'drawColorInput',
|
|
6570
|
+
drawBrushSizeInput: 'drawBrushSizeInput',
|
|
6571
|
+
removeSelectedAnnotationButton: 'removeSelectedAnnotationButton',
|
|
6572
|
+
removeAllAnnotationsButton: 'removeAllAnnotationsButton',
|
|
6573
|
+
deleteSelectedObjectButton: 'deleteSelectedObjectButton',
|
|
6574
|
+
mergeAnnotationsButton: 'mergeAnnotationsButton',
|
|
6575
|
+
bringSelectedObjectForwardButton: 'bringSelectedObjectForwardButton',
|
|
6576
|
+
sendSelectedObjectBackwardButton: 'sendSelectedObjectBackwardButton',
|
|
6577
|
+
bringSelectedObjectToFrontButton: 'bringSelectedObjectToFrontButton',
|
|
6578
|
+
sendSelectedObjectToBackButton: 'sendSelectedObjectToBackButton',
|
|
5199
6579
|
downloadImageButton: 'downloadImageButton',
|
|
5200
6580
|
maskList: 'maskList',
|
|
5201
6581
|
zoomInButton: 'zoomInButton',
|
|
@@ -5214,12 +6594,13 @@ class ImageEditor {
|
|
|
5214
6594
|
uploadArea: 'uploadArea',
|
|
5215
6595
|
};
|
|
5216
6596
|
this.elements = { ...defaults, ...idMap };
|
|
5217
|
-
this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed, () => { var _a, _b; return (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document; });
|
|
5218
6597
|
this.initCanvas();
|
|
6598
|
+
this.domBindings = new DomBindings((key) => this.elements[key], () => this.isDisposed, () => { var _a, _b; return (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document; });
|
|
5219
6599
|
this.transformController = new TransformController(this.buildTransformContext());
|
|
5220
6600
|
this.bindDomEvents();
|
|
5221
6601
|
this.updateInputs();
|
|
5222
6602
|
this.updateMaskList();
|
|
6603
|
+
this.updateAnnotationList();
|
|
5223
6604
|
this.updateUi();
|
|
5224
6605
|
if (this.options.initialImageBase64) {
|
|
5225
6606
|
void this.loadImage(this.options.initialImageBase64).catch(() => {
|
|
@@ -5271,20 +6652,24 @@ class ImageEditor {
|
|
|
5271
6652
|
});
|
|
5272
6653
|
this.canvas.on('selection:cleared', () => this.handleSelectionChanged([]));
|
|
5273
6654
|
const onObjectEvent = (e) => {
|
|
5274
|
-
if (e.target
|
|
5275
|
-
this.
|
|
6655
|
+
if (e.target)
|
|
6656
|
+
this.handleObjectMovingScalingRotating(e.target);
|
|
5276
6657
|
};
|
|
5277
6658
|
const onObjectModified = (e) => {
|
|
5278
|
-
if (
|
|
5279
|
-
|
|
5280
|
-
this.syncMaskLabel(e.target);
|
|
5281
|
-
this.saveState();
|
|
6659
|
+
if (e.target)
|
|
6660
|
+
this.handleObjectModified(e.target);
|
|
5282
6661
|
};
|
|
5283
6662
|
this.canvas.on('object:moving', onObjectEvent);
|
|
5284
6663
|
this.canvas.on('object:scaling', onObjectEvent);
|
|
5285
6664
|
this.canvas.on('object:rotating', onObjectEvent);
|
|
5286
6665
|
this.canvas.on('object:modified', onObjectModified);
|
|
5287
6666
|
}
|
|
6667
|
+
getLiveCanvasOrThrow(operationName) {
|
|
6668
|
+
if (this.isDisposed || !this.canvas) {
|
|
6669
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" after dispose.`);
|
|
6670
|
+
}
|
|
6671
|
+
return this.canvas;
|
|
6672
|
+
}
|
|
5288
6673
|
bindDomEvents() {
|
|
5289
6674
|
this.bindElementIfExists('uploadArea', 'click', () => {
|
|
5290
6675
|
var _a;
|
|
@@ -5319,6 +6704,42 @@ class ImageEditor {
|
|
|
5319
6704
|
this.bindElementIfExists('mergeMasksButton', 'click', () => {
|
|
5320
6705
|
void this.mergeMasks();
|
|
5321
6706
|
});
|
|
6707
|
+
this.bindElementIfExists('mergeAnnotationsButton', 'click', () => {
|
|
6708
|
+
void this.mergeAnnotations();
|
|
6709
|
+
});
|
|
6710
|
+
this.bindElementIfExists('enterTextModeButton', 'click', () => {
|
|
6711
|
+
this.enterTextMode();
|
|
6712
|
+
});
|
|
6713
|
+
this.bindElementIfExists('exitTextModeButton', 'click', () => {
|
|
6714
|
+
this.exitTextMode();
|
|
6715
|
+
});
|
|
6716
|
+
this.bindElementIfExists('enterDrawModeButton', 'click', () => {
|
|
6717
|
+
this.enterDrawMode();
|
|
6718
|
+
});
|
|
6719
|
+
this.bindElementIfExists('exitDrawModeButton', 'click', () => {
|
|
6720
|
+
this.exitDrawMode();
|
|
6721
|
+
});
|
|
6722
|
+
this.bindElementIfExists('removeSelectedAnnotationButton', 'click', () => {
|
|
6723
|
+
this.removeSelectedAnnotation();
|
|
6724
|
+
});
|
|
6725
|
+
this.bindElementIfExists('removeAllAnnotationsButton', 'click', () => {
|
|
6726
|
+
this.removeAllAnnotations();
|
|
6727
|
+
});
|
|
6728
|
+
this.bindElementIfExists('deleteSelectedObjectButton', 'click', () => {
|
|
6729
|
+
this.deleteSelectedObject();
|
|
6730
|
+
});
|
|
6731
|
+
this.bindElementIfExists('bringSelectedObjectForwardButton', 'click', () => {
|
|
6732
|
+
this.bringSelectedObjectForward();
|
|
6733
|
+
});
|
|
6734
|
+
this.bindElementIfExists('sendSelectedObjectBackwardButton', 'click', () => {
|
|
6735
|
+
this.sendSelectedObjectBackward();
|
|
6736
|
+
});
|
|
6737
|
+
this.bindElementIfExists('bringSelectedObjectToFrontButton', 'click', () => {
|
|
6738
|
+
this.bringSelectedObjectToFront();
|
|
6739
|
+
});
|
|
6740
|
+
this.bindElementIfExists('sendSelectedObjectToBackButton', 'click', () => {
|
|
6741
|
+
this.sendSelectedObjectToBack();
|
|
6742
|
+
});
|
|
5322
6743
|
this.bindElementIfExists('downloadImageButton', 'click', () => {
|
|
5323
6744
|
this.downloadImage();
|
|
5324
6745
|
});
|
|
@@ -5385,10 +6806,91 @@ class ImageEditor {
|
|
|
5385
6806
|
bindMosaicSizeInput('mosaicBlockSizeInput', (value) => {
|
|
5386
6807
|
this.setMosaicBlockSize(value);
|
|
5387
6808
|
});
|
|
6809
|
+
const bindStringInput = (key, applyValue) => {
|
|
6810
|
+
const handler = (event) => {
|
|
6811
|
+
applyValue(event.target.value);
|
|
6812
|
+
};
|
|
6813
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
6814
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
6815
|
+
};
|
|
6816
|
+
const bindNumberInput = (key, applyValue) => {
|
|
6817
|
+
const handler = (event) => {
|
|
6818
|
+
applyValue(parseFloat(event.target.value));
|
|
6819
|
+
};
|
|
6820
|
+
this.bindElementIfExists(key, 'input', handler);
|
|
6821
|
+
this.bindElementIfExists(key, 'change', handler);
|
|
6822
|
+
};
|
|
6823
|
+
bindStringInput('textColorInput', (value) => this.applyTextColorInput(value));
|
|
6824
|
+
bindNumberInput('textFontSizeInput', (value) => this.applyTextFontSizeInput(value));
|
|
6825
|
+
bindStringInput('drawColorInput', (value) => this.applyDrawColorInput(value));
|
|
6826
|
+
bindNumberInput('drawBrushSizeInput', (value) => this.applyDrawBrushSizeInput(value));
|
|
6827
|
+
this.bindKeyboardEvents();
|
|
6828
|
+
}
|
|
6829
|
+
bindElementIfExists(key, event, handler) {
|
|
6830
|
+
var _a;
|
|
6831
|
+
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.bindIfExists(key, event, handler);
|
|
6832
|
+
}
|
|
6833
|
+
bindKeyboardEvents() {
|
|
6834
|
+
var _a, _b;
|
|
6835
|
+
const ownerDocument = (_b = (_a = this.canvasElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : document;
|
|
6836
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
6837
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
6838
|
+
}
|
|
6839
|
+
this.keyboardDocument = ownerDocument;
|
|
6840
|
+
this.keyboardHandler = (event) => this.handleKeyboardEvent(event);
|
|
6841
|
+
ownerDocument.addEventListener('keydown', this.keyboardHandler);
|
|
6842
|
+
}
|
|
6843
|
+
isNativeTextInputActive() {
|
|
6844
|
+
var _a;
|
|
6845
|
+
const activeElement = (_a = this.keyboardDocument) === null || _a === void 0 ? void 0 : _a.activeElement;
|
|
6846
|
+
if (!activeElement)
|
|
6847
|
+
return false;
|
|
6848
|
+
const tagName = activeElement.tagName.toLowerCase();
|
|
6849
|
+
return (tagName === 'input' ||
|
|
6850
|
+
tagName === 'textarea' ||
|
|
6851
|
+
tagName === 'select' ||
|
|
6852
|
+
activeElement.isContentEditable === true);
|
|
6853
|
+
}
|
|
6854
|
+
isFabricTextEditingActive() {
|
|
6855
|
+
var _a;
|
|
6856
|
+
const activeObject = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
6857
|
+
return !!(activeObject &&
|
|
6858
|
+
isTextAnnotationObject(activeObject) &&
|
|
6859
|
+
activeObject.isEditing === true);
|
|
6860
|
+
}
|
|
6861
|
+
handleKeyboardEvent(event) {
|
|
6862
|
+
if (this.isDisposed)
|
|
6863
|
+
return;
|
|
6864
|
+
if (event.key === 'Delete' || event.key === 'Backspace') {
|
|
6865
|
+
if (this.isNativeTextInputActive() || this.isFabricTextEditingActive())
|
|
6866
|
+
return;
|
|
6867
|
+
this.deleteSelectedObject();
|
|
6868
|
+
return;
|
|
6869
|
+
}
|
|
6870
|
+
if (event.key !== 'Escape')
|
|
6871
|
+
return;
|
|
6872
|
+
if (this.isFabricTextEditingActive() && this.canvas) {
|
|
6873
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: false });
|
|
6874
|
+
event.preventDefault();
|
|
6875
|
+
return;
|
|
6876
|
+
}
|
|
6877
|
+
if (this.textSession) {
|
|
6878
|
+
this.exitTextMode();
|
|
6879
|
+
}
|
|
6880
|
+
else if (this.drawSession) {
|
|
6881
|
+
this.exitDrawMode();
|
|
6882
|
+
}
|
|
6883
|
+
else if (this.mosaicSession) {
|
|
6884
|
+
this.exitMosaicMode();
|
|
6885
|
+
}
|
|
6886
|
+
else if (this.cropSession) {
|
|
6887
|
+
this.cancelCrop();
|
|
6888
|
+
}
|
|
5388
6889
|
}
|
|
5389
|
-
|
|
5390
|
-
|
|
5391
|
-
|
|
6890
|
+
finalizeActiveTextEditingIfNeeded() {
|
|
6891
|
+
if (!this.canvas || !this.isFabricTextEditingActive())
|
|
6892
|
+
return;
|
|
6893
|
+
finalizeActiveTextEditing(this.buildTextControllerContext(), { commit: true });
|
|
5392
6894
|
}
|
|
5393
6895
|
async loadImageFile(file) {
|
|
5394
6896
|
const inputId = this.elements.imageInput;
|
|
@@ -5431,9 +6933,11 @@ class ImageEditor {
|
|
|
5431
6933
|
return;
|
|
5432
6934
|
if (!this.canRunIdleOperation('loadImage', options))
|
|
5433
6935
|
return;
|
|
6936
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
5434
6937
|
const callbackContext = this.getOperationContext('loadImage', options);
|
|
5435
6938
|
const previousImage = this.originalImage;
|
|
5436
6939
|
const hadMasks = this.getMasks().length > 0;
|
|
6940
|
+
const hadAnnotations = this.getAnnotations().length > 0;
|
|
5437
6941
|
this.emitOptionCallback('onImageLoadStart', [callbackContext]);
|
|
5438
6942
|
this.operationGuard.beginLoading();
|
|
5439
6943
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -5462,6 +6966,10 @@ class ImageEditor {
|
|
|
5462
6966
|
setMaskCounter: (v) => {
|
|
5463
6967
|
this.maskCounter = v;
|
|
5464
6968
|
},
|
|
6969
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
6970
|
+
setAnnotationCounter: (v) => {
|
|
6971
|
+
this.annotationCounter = v;
|
|
6972
|
+
},
|
|
5465
6973
|
getCurrentScale: () => this.currentScale,
|
|
5466
6974
|
setCurrentScale: (v) => {
|
|
5467
6975
|
this.currentScale = v;
|
|
@@ -5494,6 +7002,7 @@ class ImageEditor {
|
|
|
5494
7002
|
this.lastMask = null;
|
|
5495
7003
|
this.updateInputs();
|
|
5496
7004
|
this.updateMaskList();
|
|
7005
|
+
this.updateAnnotationList();
|
|
5497
7006
|
this.updateUi();
|
|
5498
7007
|
if (previousImage && previousImage !== this.originalImage) {
|
|
5499
7008
|
this.emitOptionCallback('onImageCleared', [previousImage, callbackContext]);
|
|
@@ -5505,6 +7014,9 @@ class ImageEditor {
|
|
|
5505
7014
|
if (hadMasks) {
|
|
5506
7015
|
this.emitMasksChanged(callbackContext);
|
|
5507
7016
|
}
|
|
7017
|
+
if (hadAnnotations) {
|
|
7018
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
7019
|
+
}
|
|
5508
7020
|
this.emitImageChanged(callbackContext);
|
|
5509
7021
|
}
|
|
5510
7022
|
getInternalOperationToken(options) {
|
|
@@ -5529,15 +7041,11 @@ class ImageEditor {
|
|
|
5529
7041
|
assertIdleForOperation(operationName, options) {
|
|
5530
7042
|
const token = this.getInternalOperationToken(options);
|
|
5531
7043
|
this.operationGuard.assertIdleForOperation(operationName, token);
|
|
5532
|
-
|
|
5533
|
-
|
|
5534
|
-
!CROP_SESSION_ALLOWED_OPERATIONS.has(operationName)) {
|
|
5535
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while crop mode is active.`);
|
|
5536
|
-
}
|
|
5537
|
-
if (this.mosaicSession &&
|
|
7044
|
+
const activeToolMode = this.getActiveToolMode();
|
|
7045
|
+
if (activeToolMode &&
|
|
5538
7046
|
!this.operationGuard.isOwnOperation(token) &&
|
|
5539
|
-
!
|
|
5540
|
-
throw new Error(`[ImageEditor] Cannot run "${operationName}" while
|
|
7047
|
+
!TOOL_MODE_ALLOWED_OPERATIONS[activeToolMode].has(operationName)) {
|
|
7048
|
+
throw new Error(`[ImageEditor] Cannot run "${operationName}" while ${activeToolMode} mode is active.`);
|
|
5541
7049
|
}
|
|
5542
7050
|
if (this.animQueue.isBusy() && !this.canRunDuringAnimationQueue(options)) {
|
|
5543
7051
|
throw new Error(`[ImageEditor] Cannot run "${operationName}" while an animation is queued.`);
|
|
@@ -5570,10 +7078,7 @@ class ImageEditor {
|
|
|
5570
7078
|
((_b = this.originalImage.height) !== null && _b !== void 0 ? _b : 0) > 0);
|
|
5571
7079
|
}
|
|
5572
7080
|
isBusy() {
|
|
5573
|
-
return
|
|
5574
|
-
this.animQueue.isBusy() ||
|
|
5575
|
-
this.cropSession !== null ||
|
|
5576
|
-
this.mosaicSession !== null);
|
|
7081
|
+
return this.operationGuard.isBusy() || this.animQueue.isBusy() || this.isToolModeActive();
|
|
5577
7082
|
}
|
|
5578
7083
|
setLayoutMode(mode) {
|
|
5579
7084
|
if (!isLayoutMode(mode)) {
|
|
@@ -5648,11 +7153,35 @@ class ImageEditor {
|
|
|
5648
7153
|
return [];
|
|
5649
7154
|
return this.canvas.getObjects().filter(isMaskObject).slice();
|
|
5650
7155
|
}
|
|
7156
|
+
getAnnotations() {
|
|
7157
|
+
if (!this.canvas)
|
|
7158
|
+
return [];
|
|
7159
|
+
return getAnnotations(this.canvas);
|
|
7160
|
+
}
|
|
5651
7161
|
getMaskCollectionSignature() {
|
|
5652
7162
|
return this.getMasks()
|
|
5653
7163
|
.map((mask) => `${mask.maskId}:${mask.maskName}`)
|
|
5654
7164
|
.join('|');
|
|
5655
7165
|
}
|
|
7166
|
+
getAnnotationCollectionSignature() {
|
|
7167
|
+
return this.getAnnotations()
|
|
7168
|
+
.map((annotation) => `${annotation.annotationId}:${annotation.annotationName}`)
|
|
7169
|
+
.join('|');
|
|
7170
|
+
}
|
|
7171
|
+
getActiveToolMode() {
|
|
7172
|
+
if (this.cropSession)
|
|
7173
|
+
return 'crop';
|
|
7174
|
+
if (this.mosaicSession)
|
|
7175
|
+
return 'mosaic';
|
|
7176
|
+
if (this.textSession)
|
|
7177
|
+
return 'text';
|
|
7178
|
+
if (this.drawSession)
|
|
7179
|
+
return 'draw';
|
|
7180
|
+
return null;
|
|
7181
|
+
}
|
|
7182
|
+
isToolModeActive() {
|
|
7183
|
+
return this.getActiveToolMode() !== null;
|
|
7184
|
+
}
|
|
5656
7185
|
getEditorState() {
|
|
5657
7186
|
const canvasWidth = this.canvas ? this.canvas.getWidth() : 0;
|
|
5658
7187
|
const canvasHeight = this.canvas ? this.canvas.getHeight() : 0;
|
|
@@ -5661,11 +7190,15 @@ class ImageEditor {
|
|
|
5661
7190
|
hasImage: image !== null,
|
|
5662
7191
|
image,
|
|
5663
7192
|
maskCount: this.getMasks().length,
|
|
7193
|
+
annotationCount: this.getAnnotations().length,
|
|
5664
7194
|
currentScale: this.currentScale,
|
|
5665
7195
|
currentRotation: this.currentRotation,
|
|
5666
7196
|
isBusy: this.isBusy(),
|
|
7197
|
+
activeToolMode: this.getActiveToolMode(),
|
|
5667
7198
|
isCropMode: this.cropSession !== null,
|
|
5668
7199
|
isMosaicMode: this.mosaicSession !== null,
|
|
7200
|
+
isTextMode: this.textSession !== null,
|
|
7201
|
+
isDrawMode: this.drawSession !== null,
|
|
5669
7202
|
canUndo: this.historyManager.canUndo(),
|
|
5670
7203
|
canRedo: this.historyManager.canRedo(),
|
|
5671
7204
|
canvasWidth,
|
|
@@ -5678,6 +7211,9 @@ class ImageEditor {
|
|
|
5678
7211
|
emitMasksChanged(context) {
|
|
5679
7212
|
this.emitOptionCallback('onMasksChanged', [this.getMasks(), context]);
|
|
5680
7213
|
}
|
|
7214
|
+
emitAnnotationsChanged(context) {
|
|
7215
|
+
this.emitOptionCallback('onAnnotationsChanged', [this.getAnnotations(), context]);
|
|
7216
|
+
}
|
|
5681
7217
|
emitBusyChangeIfChanged(context) {
|
|
5682
7218
|
const isBusy = this.isBusy();
|
|
5683
7219
|
if (this.lastEmittedIsBusy === isBusy)
|
|
@@ -5686,11 +7222,20 @@ class ImageEditor {
|
|
|
5686
7222
|
this.emitOptionCallback('onBusyChange', [isBusy, context]);
|
|
5687
7223
|
}
|
|
5688
7224
|
buildSelection(selected) {
|
|
5689
|
-
var _a;
|
|
7225
|
+
var _a, _b;
|
|
5690
7226
|
const selectedMasks = selected.filter(isMaskObject);
|
|
7227
|
+
const selectedAnnotations = selected.filter(isAnnotationObject);
|
|
7228
|
+
const selectedObjectKind = selectedMasks.length === 1 && selectedAnnotations.length === 0
|
|
7229
|
+
? 'mask'
|
|
7230
|
+
: selectedAnnotations.length === 1 && selectedMasks.length === 0
|
|
7231
|
+
? 'annotation'
|
|
7232
|
+
: null;
|
|
5691
7233
|
return {
|
|
5692
7234
|
selectedMask: (_a = selectedMasks[0]) !== null && _a !== void 0 ? _a : null,
|
|
5693
7235
|
selectedMasks,
|
|
7236
|
+
selectedAnnotation: (_b = selectedAnnotations[0]) !== null && _b !== void 0 ? _b : null,
|
|
7237
|
+
selectedAnnotations,
|
|
7238
|
+
selectedObjectKind,
|
|
5694
7239
|
};
|
|
5695
7240
|
}
|
|
5696
7241
|
withSelectionChangeContext(context, callback) {
|
|
@@ -5729,7 +7274,7 @@ class ImageEditor {
|
|
|
5729
7274
|
applyCanvasDimensions(this.canvas, widthPx, heightPx, this.containerElement);
|
|
5730
7275
|
}
|
|
5731
7276
|
alignObjectBoundingBoxToCanvasTopLeft(object) {
|
|
5732
|
-
var _a, _b;
|
|
7277
|
+
var _a, _b, _c;
|
|
5733
7278
|
object.setCoords();
|
|
5734
7279
|
const boundingRect = object.getBoundingRect();
|
|
5735
7280
|
object.set({
|
|
@@ -5737,7 +7282,7 @@ class ImageEditor {
|
|
|
5737
7282
|
top: ((_b = object.top) !== null && _b !== void 0 ? _b : 0) - boundingRect.top,
|
|
5738
7283
|
});
|
|
5739
7284
|
object.setCoords();
|
|
5740
|
-
this.canvas.renderAll();
|
|
7285
|
+
(_c = this.canvas) === null || _c === void 0 ? void 0 : _c.renderAll();
|
|
5741
7286
|
}
|
|
5742
7287
|
measureLayoutViewport(scrollbarSize) {
|
|
5743
7288
|
return this.viewportCache.measure(this.containerElement, {
|
|
@@ -5745,7 +7290,13 @@ class ImageEditor {
|
|
|
5745
7290
|
height: this.options.canvasHeight,
|
|
5746
7291
|
}, scrollbarSize);
|
|
5747
7292
|
}
|
|
5748
|
-
|
|
7293
|
+
getScrollbarStableViewportCanvasSize(viewport) {
|
|
7294
|
+
return {
|
|
7295
|
+
width: Math.max(1, viewport.width - 1),
|
|
7296
|
+
height: Math.max(1, viewport.height - 1),
|
|
7297
|
+
};
|
|
7298
|
+
}
|
|
7299
|
+
updateCanvasSizeToImageBounds(options = {}) {
|
|
5749
7300
|
var _a, _b;
|
|
5750
7301
|
if (!this.originalImage)
|
|
5751
7302
|
return;
|
|
@@ -5753,13 +7304,26 @@ class ImageEditor {
|
|
|
5753
7304
|
const boundingRect = this.originalImage.getBoundingRect();
|
|
5754
7305
|
const scrollbarSize = measureScrollbarSize((_b = (_a = this.containerElement) === null || _a === void 0 ? void 0 : _a.ownerDocument) !== null && _b !== void 0 ? _b : null);
|
|
5755
7306
|
const viewport = this.measureLayoutViewport(scrollbarSize);
|
|
7307
|
+
const shouldStabilizeContainedViewport = options.stabilizeContainedViewport !== false;
|
|
7308
|
+
const imageFitsViewport = boundingRect.width <= viewport.width + LAYOUT_EPSILON &&
|
|
7309
|
+
boundingRect.height <= viewport.height + LAYOUT_EPSILON;
|
|
5756
7310
|
if (this.currentLayoutMode === 'fit' || this.currentLayoutMode === 'cover') {
|
|
7311
|
+
if (imageFitsViewport) {
|
|
7312
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
7313
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
7314
|
+
: viewport;
|
|
7315
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
7316
|
+
return;
|
|
7317
|
+
}
|
|
5757
7318
|
const canvasSize = computeScrollableCanvasSize(boundingRect.width, boundingRect.height, viewport, scrollbarSize);
|
|
5758
7319
|
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
5759
7320
|
return;
|
|
5760
7321
|
}
|
|
5761
|
-
if (
|
|
5762
|
-
|
|
7322
|
+
if (imageFitsViewport) {
|
|
7323
|
+
const canvasSize = shouldStabilizeContainedViewport
|
|
7324
|
+
? this.getScrollbarStableViewportCanvasSize(viewport)
|
|
7325
|
+
: viewport;
|
|
7326
|
+
this.setCanvasSizePx(canvasSize.width, canvasSize.height);
|
|
5763
7327
|
return;
|
|
5764
7328
|
}
|
|
5765
7329
|
this.setCanvasSizePx(Math.max(viewport.width, Math.ceil(boundingRect.width)), Math.max(viewport.height, Math.ceil(boundingRect.height)));
|
|
@@ -5859,7 +7423,7 @@ class ImageEditor {
|
|
|
5859
7423
|
}
|
|
5860
7424
|
buildTransformContext() {
|
|
5861
7425
|
return {
|
|
5862
|
-
canvas: this.
|
|
7426
|
+
canvas: this.getLiveCanvasOrThrow('buildTransformContext'),
|
|
5863
7427
|
options: this.options,
|
|
5864
7428
|
guard: this.operationGuard,
|
|
5865
7429
|
getOriginalImage: () => this.originalImage,
|
|
@@ -6011,6 +7575,7 @@ class ImageEditor {
|
|
|
6011
7575
|
const context = this.buildCallbackContext(activeRestoreOperation !== null && activeRestoreOperation !== void 0 ? activeRestoreOperation : 'loadFromState', activeRestoreOperation === 'undo' || activeRestoreOperation === 'redo');
|
|
6012
7576
|
const previousImage = this.originalImage;
|
|
6013
7577
|
const previousMaskSignature = this.getMaskCollectionSignature();
|
|
7578
|
+
const previousAnnotationSignature = this.getAnnotationCollectionSignature();
|
|
6014
7579
|
try {
|
|
6015
7580
|
const restoredState = await loadFromState({
|
|
6016
7581
|
canvas: this.canvas,
|
|
@@ -6033,6 +7598,7 @@ class ImageEditor {
|
|
|
6033
7598
|
this.canvas.sendObjectToBack(this.originalImage);
|
|
6034
7599
|
}
|
|
6035
7600
|
this.maskCounter = restoredState.maxMaskId;
|
|
7601
|
+
this.annotationCounter = restoredState.maxAnnotationId;
|
|
6036
7602
|
const editorState = restoredState.editorState;
|
|
6037
7603
|
if (editorState) {
|
|
6038
7604
|
this.currentScale = editorState.currentScale;
|
|
@@ -6050,22 +7616,25 @@ class ImageEditor {
|
|
|
6050
7616
|
}
|
|
6051
7617
|
this.isImageLoadedToCanvas = !!this.originalImage;
|
|
6052
7618
|
if (this.originalImage && this.shouldNormalizeCanvasSizeAfterStateRestore()) {
|
|
6053
|
-
this.updateCanvasSizeToImageBounds();
|
|
7619
|
+
this.updateCanvasSizeToImageBounds({ stabilizeContainedViewport: false });
|
|
6054
7620
|
this.alignObjectBoundingBoxToCanvasTopLeft(this.originalImage);
|
|
6055
7621
|
}
|
|
6056
7622
|
if (this.originalImage) {
|
|
6057
7623
|
this.settleFitCoverScrollbarsAfterStateRestore();
|
|
6058
7624
|
}
|
|
6059
|
-
const restoredMasks = restoredState.
|
|
7625
|
+
const restoredMasks = restoredState.masks;
|
|
6060
7626
|
this.lastMask = restoredMasks.reduce((lastMask, maskObject) => !lastMask || maskObject.maskId > lastMask.maskId ? maskObject : lastMask, null);
|
|
6061
7627
|
restoredMasks.forEach((maskObject) => {
|
|
6062
7628
|
applyMaskUnselectedStyle(maskObject);
|
|
6063
7629
|
reattachMaskHoverHandlers(maskObject);
|
|
6064
7630
|
});
|
|
7631
|
+
syncAnnotationRuntimeStates(restoredState.annotations);
|
|
7632
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), restoredState.annotations);
|
|
6065
7633
|
this.lastSnapshot = this.captureSnapshotInternal();
|
|
6066
7634
|
this.canvas.renderAll();
|
|
6067
7635
|
this.updateInputs();
|
|
6068
7636
|
this.updateMaskList();
|
|
7637
|
+
this.updateAnnotationList();
|
|
6069
7638
|
this.updateUi();
|
|
6070
7639
|
if (previousImage && previousImage !== this.originalImage) {
|
|
6071
7640
|
this.emitOptionCallback('onImageCleared', [previousImage, context]);
|
|
@@ -6073,17 +7642,32 @@ class ImageEditor {
|
|
|
6073
7642
|
if (previousMaskSignature !== this.getMaskCollectionSignature()) {
|
|
6074
7643
|
this.emitMasksChanged(context);
|
|
6075
7644
|
}
|
|
7645
|
+
if (previousAnnotationSignature !== this.getAnnotationCollectionSignature()) {
|
|
7646
|
+
this.emitAnnotationsChanged(context);
|
|
7647
|
+
}
|
|
6076
7648
|
this.emitImageChanged(context);
|
|
7649
|
+
const canvas = this.getLiveCanvasOrThrow('loadFromState');
|
|
6077
7650
|
const activeMaskId = editorState === null || editorState === void 0 ? void 0 : editorState.activeMaskId;
|
|
6078
|
-
|
|
7651
|
+
const activeAnnotationId = editorState === null || editorState === void 0 ? void 0 : editorState.activeAnnotationId;
|
|
7652
|
+
if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'mask' && typeof activeMaskId === 'number') {
|
|
6079
7653
|
const activeMask = restoredMasks.find((maskObject) => maskObject.maskId === activeMaskId);
|
|
6080
7654
|
if (activeMask) {
|
|
6081
7655
|
this.withSelectionChangeContext(context, () => {
|
|
6082
|
-
|
|
7656
|
+
canvas.setActiveObject(activeMask);
|
|
6083
7657
|
this.handleSelectionChanged([activeMask]);
|
|
6084
7658
|
});
|
|
6085
7659
|
}
|
|
6086
7660
|
}
|
|
7661
|
+
else if ((editorState === null || editorState === void 0 ? void 0 : editorState.activeObjectKind) === 'annotation' &&
|
|
7662
|
+
typeof activeAnnotationId === 'number') {
|
|
7663
|
+
const activeAnnotation = restoredState.annotations.find((annotation) => annotation.annotationId === activeAnnotationId);
|
|
7664
|
+
if (activeAnnotation) {
|
|
7665
|
+
this.withSelectionChangeContext(context, () => {
|
|
7666
|
+
canvas.setActiveObject(activeAnnotation);
|
|
7667
|
+
this.handleSelectionChanged([activeAnnotation]);
|
|
7668
|
+
});
|
|
7669
|
+
}
|
|
7670
|
+
}
|
|
6087
7671
|
}
|
|
6088
7672
|
catch (error) {
|
|
6089
7673
|
reportError(this.options, error, 'Failed to restore canvas state.');
|
|
@@ -6094,24 +7678,26 @@ class ImageEditor {
|
|
|
6094
7678
|
this.saveStateInternal();
|
|
6095
7679
|
}
|
|
6096
7680
|
saveStateInternal(options) {
|
|
6097
|
-
var _a, _b;
|
|
7681
|
+
var _a, _b, _c;
|
|
6098
7682
|
if (!this.canvas || this.shouldSuppressSaveState)
|
|
6099
7683
|
return;
|
|
6100
7684
|
if (!this.canRunIdleOperation('saveState', options))
|
|
6101
7685
|
return;
|
|
6102
7686
|
const activeObj = this.canvas.getActiveObject();
|
|
6103
7687
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
7688
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
6104
7689
|
this.hideAllMaskLabels();
|
|
6105
7690
|
try {
|
|
6106
7691
|
const after = saveState({
|
|
6107
7692
|
canvas: this.canvas,
|
|
6108
7693
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
7694
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
6109
7695
|
currentScale: this.currentScale,
|
|
6110
7696
|
currentRotation: this.currentRotation,
|
|
6111
7697
|
baseImageScale: this.baseImageScale,
|
|
6112
7698
|
currentImageMimeType: this.currentImageMimeType,
|
|
6113
7699
|
});
|
|
6114
|
-
const before = (
|
|
7700
|
+
const before = (_c = this.lastSnapshot) !== null && _c !== void 0 ? _c : after;
|
|
6115
7701
|
if (after === before) {
|
|
6116
7702
|
return;
|
|
6117
7703
|
}
|
|
@@ -6127,25 +7713,32 @@ class ImageEditor {
|
|
|
6127
7713
|
reportWarning(this.options, error, 'Failed to capture canvas snapshot.');
|
|
6128
7714
|
}
|
|
6129
7715
|
finally {
|
|
6130
|
-
this.
|
|
7716
|
+
this.restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation);
|
|
6131
7717
|
this.updateUi();
|
|
6132
7718
|
}
|
|
6133
7719
|
}
|
|
6134
|
-
|
|
7720
|
+
restoreActiveObjectAfterSnapshot(activeObj, activeMask, activeAnnotation) {
|
|
6135
7721
|
if (!this.canvas)
|
|
6136
7722
|
return;
|
|
6137
7723
|
const maskToRestore = activeObj && isMaskObject(activeObj) ? activeObj : activeMask;
|
|
6138
|
-
|
|
7724
|
+
const annotationToRestore = activeObj && isAnnotationObject(activeObj) ? activeObj : activeAnnotation;
|
|
7725
|
+
if (maskToRestore && this.canvas.getObjects().includes(maskToRestore)) {
|
|
7726
|
+
this.canvas.setActiveObject(maskToRestore);
|
|
7727
|
+
this.showLabelForMask(maskToRestore);
|
|
7728
|
+
this.updateMaskListSelection(maskToRestore);
|
|
6139
7729
|
return;
|
|
6140
|
-
|
|
6141
|
-
this.
|
|
6142
|
-
|
|
7730
|
+
}
|
|
7731
|
+
if (annotationToRestore && this.canvas.getObjects().includes(annotationToRestore)) {
|
|
7732
|
+
this.canvas.setActiveObject(annotationToRestore);
|
|
7733
|
+
this.updateAnnotationListSelection(annotationToRestore);
|
|
7734
|
+
}
|
|
6143
7735
|
}
|
|
6144
7736
|
undo() {
|
|
6145
7737
|
if (this.isDisposed)
|
|
6146
7738
|
return Promise.resolve();
|
|
6147
7739
|
if (!this.canRunIdleOperation('undo'))
|
|
6148
7740
|
return Promise.resolve();
|
|
7741
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6149
7742
|
const context = this.buildCallbackContext('undo', true);
|
|
6150
7743
|
const job = this.animQueue.add(async () => {
|
|
6151
7744
|
if (this.isDisposed)
|
|
@@ -6169,6 +7762,7 @@ class ImageEditor {
|
|
|
6169
7762
|
return Promise.resolve();
|
|
6170
7763
|
if (!this.canRunIdleOperation('redo'))
|
|
6171
7764
|
return Promise.resolve();
|
|
7765
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6172
7766
|
const context = this.buildCallbackContext('redo', true);
|
|
6173
7767
|
const job = this.animQueue.add(async () => {
|
|
6174
7768
|
if (this.isDisposed)
|
|
@@ -6204,153 +7798,593 @@ class ImageEditor {
|
|
|
6204
7798
|
removeSelectedMask() {
|
|
6205
7799
|
if (!this.canvas)
|
|
6206
7800
|
return;
|
|
6207
|
-
if (!this.canRunIdleOperation('removeSelectedMask'))
|
|
7801
|
+
if (!this.canRunIdleOperation('removeSelectedMask'))
|
|
7802
|
+
return;
|
|
7803
|
+
const before = this.getMasks().length;
|
|
7804
|
+
const callbackContext = this.buildCallbackContext('removeSelectedMask', false);
|
|
7805
|
+
const removeMaskContext = this.buildRemoveMaskContext();
|
|
7806
|
+
this.withSelectionChangeContext(callbackContext, () => removeSelectedMask(removeMaskContext));
|
|
7807
|
+
this.updateUi();
|
|
7808
|
+
if (this.getMasks().length !== before) {
|
|
7809
|
+
this.emitMasksChanged(callbackContext);
|
|
7810
|
+
this.emitImageChanged(callbackContext);
|
|
7811
|
+
}
|
|
7812
|
+
}
|
|
7813
|
+
removeAllMasks(options = {}) {
|
|
7814
|
+
if (!this.canvas)
|
|
7815
|
+
return;
|
|
7816
|
+
if (!this.canRunIdleOperation('removeAllMasks', options))
|
|
7817
|
+
return;
|
|
7818
|
+
const before = this.getMasks().length;
|
|
7819
|
+
const callbackContext = this.buildCallbackContext('removeAllMasks', false);
|
|
7820
|
+
const removeMaskContext = this.buildRemoveMaskContext();
|
|
7821
|
+
this.withSelectionChangeContext(callbackContext, () => removeAllMasks(removeMaskContext, options));
|
|
7822
|
+
this.updateUi();
|
|
7823
|
+
if (this.getMasks().length !== before) {
|
|
7824
|
+
this.emitMasksChanged(callbackContext);
|
|
7825
|
+
this.emitImageChanged(callbackContext);
|
|
7826
|
+
}
|
|
7827
|
+
}
|
|
7828
|
+
buildCreateMaskContext() {
|
|
7829
|
+
return {
|
|
7830
|
+
fabric: this.fabricModule,
|
|
7831
|
+
canvas: this.getLiveCanvasOrThrow('createMask'),
|
|
7832
|
+
options: this.getRuntimeOptions(),
|
|
7833
|
+
getLastMask: () => this.lastMask,
|
|
7834
|
+
setLastMask: (maskObject) => {
|
|
7835
|
+
this.lastMask = maskObject;
|
|
7836
|
+
},
|
|
7837
|
+
getMaskCounter: () => this.maskCounter,
|
|
7838
|
+
setMaskCounter: (n) => {
|
|
7839
|
+
this.maskCounter = n;
|
|
7840
|
+
},
|
|
7841
|
+
updateMaskList: () => {
|
|
7842
|
+
this.updateMaskList();
|
|
7843
|
+
},
|
|
7844
|
+
saveCanvasState: () => {
|
|
7845
|
+
this.saveState();
|
|
7846
|
+
},
|
|
7847
|
+
expandCanvasIfNeeded: (widthPx, heightPx) => {
|
|
7848
|
+
this.setCanvasSizePx(widthPx, heightPx);
|
|
7849
|
+
},
|
|
7850
|
+
};
|
|
7851
|
+
}
|
|
7852
|
+
buildRemoveMaskContext() {
|
|
7853
|
+
return {
|
|
7854
|
+
canvas: this.getLiveCanvasOrThrow('removeMask'),
|
|
7855
|
+
removeLabelForMask: (mask) => {
|
|
7856
|
+
this.removeLabelForMask(mask);
|
|
7857
|
+
},
|
|
7858
|
+
updateMaskList: () => {
|
|
7859
|
+
this.updateMaskList();
|
|
7860
|
+
},
|
|
7861
|
+
saveCanvasState: () => {
|
|
7862
|
+
this.saveState();
|
|
7863
|
+
},
|
|
7864
|
+
setLastMask: (maskObject) => {
|
|
7865
|
+
this.lastMask = maskObject;
|
|
7866
|
+
},
|
|
7867
|
+
};
|
|
7868
|
+
}
|
|
7869
|
+
buildMaskLabelContext() {
|
|
7870
|
+
if (!this.canvas)
|
|
7871
|
+
return null;
|
|
7872
|
+
return { fabric: this.fabricModule, canvas: this.canvas, options: this.options };
|
|
7873
|
+
}
|
|
7874
|
+
removeLabelForMask(mask) {
|
|
7875
|
+
const context = this.buildMaskLabelContext();
|
|
7876
|
+
if (!context)
|
|
7877
|
+
return;
|
|
7878
|
+
removeLabelForMask(context, mask);
|
|
7879
|
+
}
|
|
7880
|
+
createLabelForMask(mask) {
|
|
7881
|
+
const context = this.buildMaskLabelContext();
|
|
7882
|
+
if (!context)
|
|
7883
|
+
return;
|
|
7884
|
+
createLabelForMask(context, mask);
|
|
7885
|
+
}
|
|
7886
|
+
hideAllMaskLabels() {
|
|
7887
|
+
const context = this.buildMaskLabelContext();
|
|
7888
|
+
if (!context)
|
|
7889
|
+
return;
|
|
7890
|
+
hideAllMaskLabels(context);
|
|
7891
|
+
}
|
|
7892
|
+
syncMaskLabel(mask) {
|
|
7893
|
+
const context = this.buildMaskLabelContext();
|
|
7894
|
+
if (!context)
|
|
7895
|
+
return;
|
|
7896
|
+
syncMaskLabel(context, mask);
|
|
7897
|
+
}
|
|
7898
|
+
showLabelForMask(mask) {
|
|
7899
|
+
const context = this.buildMaskLabelContext();
|
|
7900
|
+
if (!context)
|
|
7901
|
+
return;
|
|
7902
|
+
showLabelForMask(context, mask);
|
|
7903
|
+
}
|
|
7904
|
+
handleObjectMovingScalingRotating(target) {
|
|
7905
|
+
if (isMaskObject(target)) {
|
|
7906
|
+
this.syncMaskLabel(target);
|
|
7907
|
+
}
|
|
7908
|
+
}
|
|
7909
|
+
handleObjectModified(target) {
|
|
7910
|
+
if (isMaskObject(target)) {
|
|
7911
|
+
this.syncMaskLabel(target);
|
|
7912
|
+
const context = this.buildCallbackContext('saveState', false);
|
|
7913
|
+
this.saveState();
|
|
7914
|
+
this.emitMasksChanged(context);
|
|
7915
|
+
this.emitImageChanged(context);
|
|
7916
|
+
return;
|
|
7917
|
+
}
|
|
7918
|
+
if (isAnnotationObject(target)) {
|
|
7919
|
+
if (isAnnotationLocked(target))
|
|
7920
|
+
return;
|
|
7921
|
+
const context = this.buildCallbackContext('updateAnnotation', false);
|
|
7922
|
+
this.saveState();
|
|
7923
|
+
this.emitAnnotationsChanged(context);
|
|
7924
|
+
this.emitImageChanged(context);
|
|
7925
|
+
}
|
|
7926
|
+
}
|
|
7927
|
+
handleSelectionChanged(selected) {
|
|
7928
|
+
var _a, _b, _c, _d;
|
|
7929
|
+
if (!this.canvas)
|
|
7930
|
+
return;
|
|
7931
|
+
const selectedMask = (_a = selected.find(isMaskObject)) !== null && _a !== void 0 ? _a : null;
|
|
7932
|
+
const selectedAnnotation = (_b = selected.find(isAnnotationObject)) !== null && _b !== void 0 ? _b : null;
|
|
7933
|
+
const masks = this.canvas.getObjects().filter(isMaskObject);
|
|
7934
|
+
masks.forEach((maskObject) => {
|
|
7935
|
+
if (maskObject !== selectedMask) {
|
|
7936
|
+
if (maskObject.labelObject) {
|
|
7937
|
+
this.removeLabelForMask(maskObject);
|
|
7938
|
+
}
|
|
7939
|
+
applyMaskUnselectedStyle(maskObject);
|
|
7940
|
+
}
|
|
7941
|
+
else {
|
|
7942
|
+
applyMaskSelectedStyle(maskObject);
|
|
7943
|
+
}
|
|
7944
|
+
});
|
|
7945
|
+
if (selectedMask)
|
|
7946
|
+
this.showLabelForMask(selectedMask);
|
|
7947
|
+
this.updateMaskListSelection(selectedMask);
|
|
7948
|
+
this.updateAnnotationListSelection(selectedAnnotation);
|
|
7949
|
+
this.canvas.requestRenderAll();
|
|
7950
|
+
this.updateUi();
|
|
7951
|
+
const context = (_c = this.nextSelectionChangeContext) !== null && _c !== void 0 ? _c : this.buildCallbackContext((_d = this.activeStateRestoreOperation) !== null && _d !== void 0 ? _d : 'createMask', this.activeStateRestoreOperation === 'undo' ||
|
|
7952
|
+
this.activeStateRestoreOperation === 'redo');
|
|
7953
|
+
this.emitOptionCallback('onSelectionChange', [this.buildSelection(selected), context]);
|
|
7954
|
+
}
|
|
7955
|
+
buildMaskListContext() {
|
|
7956
|
+
return {
|
|
7957
|
+
canvas: this.canvas,
|
|
7958
|
+
getListElementId: () => this.elements.maskList,
|
|
7959
|
+
onMaskSelected: (mask) => this.handleSelectionChanged([mask]),
|
|
7960
|
+
};
|
|
7961
|
+
}
|
|
7962
|
+
updateMaskList() {
|
|
7963
|
+
renderMaskList(this.buildMaskListContext());
|
|
7964
|
+
}
|
|
7965
|
+
updateMaskListSelection(selectedMask) {
|
|
7966
|
+
updateMaskListSelection(this.buildMaskListContext(), selectedMask);
|
|
7967
|
+
}
|
|
7968
|
+
enterTextMode() {
|
|
7969
|
+
if (!this.canvas)
|
|
7970
|
+
return;
|
|
7971
|
+
if (!this.canRunIdleOperation('enterTextMode'))
|
|
7972
|
+
return;
|
|
7973
|
+
if (this.isToolModeActive())
|
|
7974
|
+
return;
|
|
7975
|
+
enterTextMode(this.buildTextControllerContext());
|
|
7976
|
+
const callbackContext = this.buildCallbackContext('enterTextMode', false);
|
|
7977
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
7978
|
+
this.emitImageChanged(callbackContext);
|
|
7979
|
+
}
|
|
7980
|
+
exitTextMode() {
|
|
7981
|
+
if (!this.canvas || !this.textSession)
|
|
7982
|
+
return;
|
|
7983
|
+
if (!this.canRunIdleOperation('exitTextMode'))
|
|
7984
|
+
return;
|
|
7985
|
+
exitTextMode(this.buildTextControllerContext());
|
|
7986
|
+
const callbackContext = this.buildCallbackContext('exitTextMode', false);
|
|
7987
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
7988
|
+
this.emitImageChanged(callbackContext);
|
|
7989
|
+
}
|
|
7990
|
+
isTextMode() {
|
|
7991
|
+
return this.textSession !== null;
|
|
7992
|
+
}
|
|
7993
|
+
createTextAnnotation(config = {}) {
|
|
7994
|
+
if (!this.canvas)
|
|
7995
|
+
return null;
|
|
7996
|
+
if (!this.canRunIdleOperation('createTextAnnotation'))
|
|
7997
|
+
return null;
|
|
7998
|
+
return createTextAnnotation(this.buildTextControllerContext(), config);
|
|
7999
|
+
}
|
|
8000
|
+
enterDrawMode() {
|
|
8001
|
+
if (!this.canvas)
|
|
8002
|
+
return;
|
|
8003
|
+
if (!this.canRunIdleOperation('enterDrawMode'))
|
|
8004
|
+
return;
|
|
8005
|
+
if (this.isToolModeActive())
|
|
8006
|
+
return;
|
|
8007
|
+
enterDrawMode(this.buildDrawControllerContext());
|
|
8008
|
+
const callbackContext = this.buildCallbackContext('enterDrawMode', false);
|
|
8009
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
8010
|
+
this.emitImageChanged(callbackContext);
|
|
8011
|
+
}
|
|
8012
|
+
exitDrawMode() {
|
|
8013
|
+
if (!this.canvas || !this.drawSession)
|
|
8014
|
+
return;
|
|
8015
|
+
if (!this.canRunIdleOperation('exitDrawMode'))
|
|
8016
|
+
return;
|
|
8017
|
+
exitDrawMode(this.buildDrawControllerContext());
|
|
8018
|
+
const callbackContext = this.buildCallbackContext('exitDrawMode', false);
|
|
8019
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
8020
|
+
this.emitImageChanged(callbackContext);
|
|
8021
|
+
}
|
|
8022
|
+
isDrawMode() {
|
|
8023
|
+
return this.drawSession !== null;
|
|
8024
|
+
}
|
|
8025
|
+
getTextConfig() {
|
|
8026
|
+
return cloneResolvedTextAnnotationConfig(this.currentTextConfig);
|
|
8027
|
+
}
|
|
8028
|
+
setTextConfig(config) {
|
|
8029
|
+
this.applyTextConfigPatch(config, 'setTextConfig');
|
|
8030
|
+
}
|
|
8031
|
+
resetTextConfig() {
|
|
8032
|
+
this.applyTextConfigPatch(this.defaultTextConfig, 'resetTextConfig');
|
|
8033
|
+
}
|
|
8034
|
+
setTextColor(color) {
|
|
8035
|
+
this.applyTextConfigPatch({ fill: color }, 'setTextColor');
|
|
8036
|
+
}
|
|
8037
|
+
setTextFontSize(size) {
|
|
8038
|
+
this.applyTextConfigPatch({ fontSize: size }, 'setTextFontSize');
|
|
8039
|
+
}
|
|
8040
|
+
getDrawConfig() {
|
|
8041
|
+
return cloneResolvedDrawConfig(this.currentDrawConfig);
|
|
8042
|
+
}
|
|
8043
|
+
setDrawConfig(config) {
|
|
8044
|
+
this.applyDrawConfigPatch(config, 'setDrawConfig');
|
|
8045
|
+
}
|
|
8046
|
+
resetDrawConfig() {
|
|
8047
|
+
this.applyDrawConfigPatch(this.defaultDrawConfig, 'resetDrawConfig');
|
|
8048
|
+
}
|
|
8049
|
+
setDrawColor(color) {
|
|
8050
|
+
this.applyDrawConfigPatch({ color }, 'setDrawColor');
|
|
8051
|
+
}
|
|
8052
|
+
setDrawBrushSize(size) {
|
|
8053
|
+
this.applyDrawConfigPatch({ brushSize: size }, 'setDrawBrushSize');
|
|
8054
|
+
}
|
|
8055
|
+
removeSelectedAnnotation() {
|
|
8056
|
+
if (!this.canvas)
|
|
8057
|
+
return;
|
|
8058
|
+
if (!this.canRunIdleOperation('removeSelectedAnnotation'))
|
|
8059
|
+
return;
|
|
8060
|
+
const before = this.getAnnotations().length;
|
|
8061
|
+
const callbackContext = this.buildCallbackContext('removeSelectedAnnotation', false);
|
|
8062
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
8063
|
+
removeSelectedAnnotation(this.buildAnnotationManagerContext());
|
|
8064
|
+
});
|
|
8065
|
+
this.updateAnnotationList();
|
|
8066
|
+
this.updateUi();
|
|
8067
|
+
if (this.getAnnotations().length !== before) {
|
|
8068
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8069
|
+
this.emitImageChanged(callbackContext);
|
|
8070
|
+
}
|
|
8071
|
+
}
|
|
8072
|
+
removeAllAnnotations(options = {}) {
|
|
8073
|
+
if (!this.canvas)
|
|
8074
|
+
return;
|
|
8075
|
+
if (!this.canRunIdleOperation('removeAllAnnotations', options))
|
|
8076
|
+
return;
|
|
8077
|
+
const before = this.getAnnotations().length;
|
|
8078
|
+
const callbackContext = this.buildCallbackContext('removeAllAnnotations', false);
|
|
8079
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
8080
|
+
removeAllAnnotations(this.buildAnnotationManagerContext(), options);
|
|
8081
|
+
});
|
|
8082
|
+
this.updateAnnotationList();
|
|
8083
|
+
this.updateUi();
|
|
8084
|
+
if (this.getAnnotations().length !== before) {
|
|
8085
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8086
|
+
this.emitImageChanged(callbackContext);
|
|
8087
|
+
}
|
|
8088
|
+
}
|
|
8089
|
+
updateAnnotation(annotationId, config) {
|
|
8090
|
+
if (!this.canvas)
|
|
8091
|
+
return;
|
|
8092
|
+
if (!this.canRunIdleOperation('updateAnnotation'))
|
|
8093
|
+
return;
|
|
8094
|
+
const callbackContext = this.buildCallbackContext('updateAnnotation', false);
|
|
8095
|
+
const changed = updateAnnotation(this.buildAnnotationManagerContext(), annotationId, config);
|
|
8096
|
+
if (changed) {
|
|
8097
|
+
this.updateAnnotationList();
|
|
8098
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8099
|
+
this.emitImageChanged(callbackContext);
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8102
|
+
updateSelectedAnnotation(config) {
|
|
8103
|
+
if (!this.canvas)
|
|
8104
|
+
return;
|
|
8105
|
+
if (!this.canRunIdleOperation('updateSelectedAnnotation'))
|
|
6208
8106
|
return;
|
|
6209
|
-
const
|
|
6210
|
-
const
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
if (this.getMasks().length !== before) {
|
|
6215
|
-
this.emitMasksChanged(callbackContext);
|
|
8107
|
+
const callbackContext = this.buildCallbackContext('updateSelectedAnnotation', false);
|
|
8108
|
+
const changed = updateSelectedAnnotation(this.buildAnnotationManagerContext(), config);
|
|
8109
|
+
if (changed) {
|
|
8110
|
+
this.updateAnnotationList();
|
|
8111
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
6216
8112
|
this.emitImageChanged(callbackContext);
|
|
6217
8113
|
}
|
|
6218
8114
|
}
|
|
6219
|
-
|
|
8115
|
+
deleteSelectedObject() {
|
|
6220
8116
|
if (!this.canvas)
|
|
6221
8117
|
return;
|
|
6222
|
-
if (!this.canRunIdleOperation('
|
|
8118
|
+
if (!this.canRunIdleOperation('deleteSelectedObject'))
|
|
6223
8119
|
return;
|
|
6224
|
-
|
|
6225
|
-
const
|
|
6226
|
-
const
|
|
6227
|
-
|
|
8120
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
8121
|
+
const selectedObjects = this.getSelectedCanvasObjects();
|
|
8122
|
+
const selectedMasks = selectedObjects.filter(isMaskObject);
|
|
8123
|
+
const selectedAnnotations = selectedObjects.filter((object) => isAnnotationObject(object) && isAnnotationUnlocked(object));
|
|
8124
|
+
if (selectedMasks.length === 0 && selectedAnnotations.length === 0)
|
|
8125
|
+
return;
|
|
8126
|
+
const canvas = this.getLiveCanvasOrThrow('deleteSelectedObject');
|
|
8127
|
+
const callbackContext = this.buildCallbackContext('deleteSelectedObject', false);
|
|
8128
|
+
this.withSelectionChangeContext(callbackContext, () => {
|
|
8129
|
+
for (const mask of selectedMasks) {
|
|
8130
|
+
this.removeLabelForMask(mask);
|
|
8131
|
+
canvas.remove(mask);
|
|
8132
|
+
}
|
|
8133
|
+
removeAnnotationObjects(this.buildAnnotationManagerContext(), selectedAnnotations, {
|
|
8134
|
+
saveHistory: false,
|
|
8135
|
+
force: true,
|
|
8136
|
+
});
|
|
8137
|
+
canvas.discardActiveObject();
|
|
8138
|
+
canvas.renderAll();
|
|
8139
|
+
this.saveState();
|
|
8140
|
+
});
|
|
8141
|
+
this.updateMaskList();
|
|
8142
|
+
this.updateAnnotationList();
|
|
6228
8143
|
this.updateUi();
|
|
6229
|
-
if (
|
|
8144
|
+
if (selectedMasks.length > 0)
|
|
6230
8145
|
this.emitMasksChanged(callbackContext);
|
|
6231
|
-
|
|
6232
|
-
|
|
8146
|
+
if (selectedAnnotations.length > 0)
|
|
8147
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8148
|
+
this.emitImageChanged(callbackContext);
|
|
6233
8149
|
}
|
|
6234
|
-
|
|
8150
|
+
bringSelectedObjectForward() {
|
|
8151
|
+
this.moveSelectedEditableObject('bringSelectedObjectForward');
|
|
8152
|
+
}
|
|
8153
|
+
sendSelectedObjectBackward() {
|
|
8154
|
+
this.moveSelectedEditableObject('sendSelectedObjectBackward');
|
|
8155
|
+
}
|
|
8156
|
+
bringSelectedObjectToFront() {
|
|
8157
|
+
this.moveSelectedEditableObject('bringSelectedObjectToFront');
|
|
8158
|
+
}
|
|
8159
|
+
sendSelectedObjectToBack() {
|
|
8160
|
+
this.moveSelectedEditableObject('sendSelectedObjectToBack');
|
|
8161
|
+
}
|
|
8162
|
+
buildAnnotationManagerContext() {
|
|
6235
8163
|
return {
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
getLastMask: () => this.lastMask,
|
|
6240
|
-
setLastMask: (maskObject) => {
|
|
6241
|
-
this.lastMask = maskObject;
|
|
6242
|
-
},
|
|
6243
|
-
getMaskCounter: () => this.maskCounter,
|
|
6244
|
-
setMaskCounter: (n) => {
|
|
6245
|
-
this.maskCounter = n;
|
|
6246
|
-
},
|
|
6247
|
-
updateMaskList: () => {
|
|
6248
|
-
this.updateMaskList();
|
|
6249
|
-
},
|
|
6250
|
-
saveCanvasState: () => {
|
|
6251
|
-
this.saveState();
|
|
6252
|
-
},
|
|
6253
|
-
expandCanvasIfNeeded: (widthPx, heightPx) => {
|
|
6254
|
-
this.setCanvasSizePx(widthPx, heightPx);
|
|
6255
|
-
},
|
|
8164
|
+
canvas: this.getLiveCanvasOrThrow('annotationManager'),
|
|
8165
|
+
saveCanvasState: () => this.saveState(),
|
|
8166
|
+
updateUi: () => this.updateUi(),
|
|
6256
8167
|
};
|
|
6257
8168
|
}
|
|
6258
|
-
|
|
8169
|
+
buildAnnotationListContext() {
|
|
6259
8170
|
return {
|
|
6260
8171
|
canvas: this.canvas,
|
|
6261
|
-
|
|
6262
|
-
|
|
8172
|
+
getListElementId: () => this.elements.annotationList,
|
|
8173
|
+
onAnnotationSelected: (annotation) => this.handleSelectionChanged([annotation]),
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
updateAnnotationList() {
|
|
8177
|
+
renderAnnotationList(this.buildAnnotationListContext());
|
|
8178
|
+
}
|
|
8179
|
+
updateAnnotationListSelection(selectedAnnotation) {
|
|
8180
|
+
updateAnnotationListSelection(this.buildAnnotationListContext(), selectedAnnotation);
|
|
8181
|
+
}
|
|
8182
|
+
buildTextControllerContext() {
|
|
8183
|
+
return {
|
|
8184
|
+
fabric: this.fabricModule,
|
|
8185
|
+
canvas: this.getLiveCanvasOrThrow('textController'),
|
|
8186
|
+
options: this.options,
|
|
8187
|
+
getOriginalImage: () => this.originalImage,
|
|
8188
|
+
getTextConfig: () => this.currentTextConfig,
|
|
8189
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
8190
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
8191
|
+
setAnnotationCounter: (value) => {
|
|
8192
|
+
this.annotationCounter = value;
|
|
6263
8193
|
},
|
|
6264
|
-
|
|
6265
|
-
|
|
8194
|
+
getTextSession: () => this.textSession,
|
|
8195
|
+
setTextSession: (session) => {
|
|
8196
|
+
this.textSession = session;
|
|
6266
8197
|
},
|
|
6267
|
-
saveCanvasState: () =>
|
|
6268
|
-
|
|
8198
|
+
saveCanvasState: () => this.saveState(),
|
|
8199
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
8200
|
+
updateUi: () => this.updateUi(),
|
|
8201
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
8202
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
8203
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
8204
|
+
};
|
|
8205
|
+
}
|
|
8206
|
+
buildDrawControllerContext() {
|
|
8207
|
+
return {
|
|
8208
|
+
fabric: this.fabricModule,
|
|
8209
|
+
canvas: this.getLiveCanvasOrThrow('drawController'),
|
|
8210
|
+
options: this.options,
|
|
8211
|
+
getDrawConfig: () => this.currentDrawConfig,
|
|
8212
|
+
isImageLoaded: () => this.isImageLoaded(),
|
|
8213
|
+
getAnnotationCounter: () => this.annotationCounter,
|
|
8214
|
+
setAnnotationCounter: (value) => {
|
|
8215
|
+
this.annotationCounter = value;
|
|
6269
8216
|
},
|
|
6270
|
-
|
|
6271
|
-
|
|
8217
|
+
getDrawSession: () => this.drawSession,
|
|
8218
|
+
setDrawSession: (session) => {
|
|
8219
|
+
this.drawSession = session;
|
|
6272
8220
|
},
|
|
8221
|
+
saveCanvasState: () => this.saveState(),
|
|
8222
|
+
updateAnnotationList: () => this.updateAnnotationList(),
|
|
8223
|
+
updateUi: () => this.updateUi(),
|
|
8224
|
+
emitAnnotationsChanged: (context) => this.emitAnnotationsChanged(context),
|
|
8225
|
+
emitImageChanged: (context) => this.emitImageChanged(context),
|
|
8226
|
+
buildCallbackContext: (operation) => this.buildCallbackContext(operation, false),
|
|
6273
8227
|
};
|
|
6274
8228
|
}
|
|
6275
|
-
|
|
6276
|
-
if (!this.
|
|
6277
|
-
return
|
|
6278
|
-
|
|
8229
|
+
applyTextConfigPatch(config, operation) {
|
|
8230
|
+
if (!this.canRunIdleOperation(operation))
|
|
8231
|
+
return;
|
|
8232
|
+
const invalidFields = getInvalidTextAnnotationConfigFields(config);
|
|
8233
|
+
if (invalidFields.length > 0) {
|
|
8234
|
+
reportWarning(this.options, null, `${operation} ignored invalid Text config fields: ${invalidFields.join(', ')}.`);
|
|
8235
|
+
}
|
|
8236
|
+
const next = mergeTextAnnotationConfigPatch(this.currentTextConfig, config, this.defaultTextConfig);
|
|
8237
|
+
if (areResolvedTextAnnotationConfigsEqual(this.currentTextConfig, next))
|
|
8238
|
+
return;
|
|
8239
|
+
this.currentTextConfig = next;
|
|
8240
|
+
this.updateInputs();
|
|
8241
|
+
this.updateUi();
|
|
8242
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
6279
8243
|
}
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
if (!context)
|
|
8244
|
+
applyDrawConfigPatch(config, operation) {
|
|
8245
|
+
if (!this.canRunIdleOperation(operation))
|
|
6283
8246
|
return;
|
|
6284
|
-
|
|
8247
|
+
const invalidFields = getInvalidDrawConfigFields(config);
|
|
8248
|
+
if (invalidFields.length > 0) {
|
|
8249
|
+
reportWarning(this.options, null, `${operation} ignored invalid Draw config fields: ${invalidFields.join(', ')}.`);
|
|
8250
|
+
}
|
|
8251
|
+
const next = mergeDrawConfigPatch(this.currentDrawConfig, config, this.defaultDrawConfig);
|
|
8252
|
+
if (areResolvedDrawConfigsEqual(this.currentDrawConfig, next))
|
|
8253
|
+
return;
|
|
8254
|
+
this.currentDrawConfig = next;
|
|
8255
|
+
updateDrawBrush(this.buildDrawControllerContext());
|
|
8256
|
+
this.updateInputs();
|
|
8257
|
+
this.updateUi();
|
|
8258
|
+
this.emitImageChanged(this.buildCallbackContext(operation, false));
|
|
6285
8259
|
}
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
if (
|
|
8260
|
+
applyTextColorInput(color) {
|
|
8261
|
+
var _a;
|
|
8262
|
+
if (this.isTextMode()) {
|
|
8263
|
+
this.setTextColor(color);
|
|
6289
8264
|
return;
|
|
6290
|
-
|
|
8265
|
+
}
|
|
8266
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
8267
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
8268
|
+
this.updateSelectedAnnotation({ fill: color });
|
|
8269
|
+
return;
|
|
8270
|
+
}
|
|
8271
|
+
this.setTextColor(color);
|
|
6291
8272
|
}
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
if (
|
|
8273
|
+
applyTextFontSizeInput(size) {
|
|
8274
|
+
var _a;
|
|
8275
|
+
if (this.isTextMode()) {
|
|
8276
|
+
this.setTextFontSize(size);
|
|
6295
8277
|
return;
|
|
6296
|
-
|
|
8278
|
+
}
|
|
8279
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
8280
|
+
if (selected && isTextAnnotationObject(selected)) {
|
|
8281
|
+
this.updateSelectedAnnotation({ fontSize: size });
|
|
8282
|
+
return;
|
|
8283
|
+
}
|
|
8284
|
+
this.setTextFontSize(size);
|
|
6297
8285
|
}
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
if (
|
|
8286
|
+
applyDrawColorInput(color) {
|
|
8287
|
+
var _a;
|
|
8288
|
+
if (this.isDrawMode()) {
|
|
8289
|
+
this.setDrawColor(color);
|
|
6301
8290
|
return;
|
|
6302
|
-
|
|
8291
|
+
}
|
|
8292
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
8293
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
8294
|
+
this.updateSelectedAnnotation({ stroke: color });
|
|
8295
|
+
return;
|
|
8296
|
+
}
|
|
8297
|
+
this.setDrawColor(color);
|
|
6303
8298
|
}
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
if (
|
|
8299
|
+
applyDrawBrushSizeInput(size) {
|
|
8300
|
+
var _a;
|
|
8301
|
+
if (this.isDrawMode()) {
|
|
8302
|
+
this.setDrawBrushSize(size);
|
|
6307
8303
|
return;
|
|
6308
|
-
|
|
8304
|
+
}
|
|
8305
|
+
const selected = (_a = this.canvas) === null || _a === void 0 ? void 0 : _a.getActiveObject();
|
|
8306
|
+
if (selected && isDrawAnnotationObject(selected)) {
|
|
8307
|
+
this.updateSelectedAnnotation({ strokeWidth: size });
|
|
8308
|
+
return;
|
|
8309
|
+
}
|
|
8310
|
+
this.setDrawBrushSize(size);
|
|
6309
8311
|
}
|
|
6310
|
-
|
|
8312
|
+
getSelectedCanvasObjects() {
|
|
6311
8313
|
var _a, _b, _c;
|
|
8314
|
+
if (!this.canvas)
|
|
8315
|
+
return [];
|
|
8316
|
+
const activeObject = this.canvas.getActiveObject();
|
|
8317
|
+
if (!activeObject)
|
|
8318
|
+
return [];
|
|
8319
|
+
const type = typeof activeObject.type === 'string' ? activeObject.type.toLowerCase() : '';
|
|
8320
|
+
const isActiveSelection = type === 'activeselection' ||
|
|
8321
|
+
((_c = (_b = (_a = activeObject).isType) === null || _b === void 0 ? void 0 : _b.call(_a, 'ActiveSelection')) !== null && _c !== void 0 ? _c : false);
|
|
8322
|
+
if (!isActiveSelection)
|
|
8323
|
+
return [activeObject];
|
|
8324
|
+
const getObjects = activeObject
|
|
8325
|
+
.getObjects;
|
|
8326
|
+
return typeof getObjects === 'function' ? getObjects.call(activeObject) : [];
|
|
8327
|
+
}
|
|
8328
|
+
moveSelectedEditableObject(operation) {
|
|
6312
8329
|
if (!this.canvas)
|
|
6313
8330
|
return;
|
|
6314
|
-
|
|
6315
|
-
|
|
6316
|
-
|
|
6317
|
-
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
}
|
|
6321
|
-
applyMaskUnselectedStyle(maskObject);
|
|
6322
|
-
}
|
|
6323
|
-
else {
|
|
6324
|
-
applyMaskSelectedStyle(maskObject);
|
|
8331
|
+
if (!this.canRunIdleOperation(operation))
|
|
8332
|
+
return;
|
|
8333
|
+
const selected = this.getSelectedCanvasObjects().filter(isEditableOverlayObject);
|
|
8334
|
+
if (selected.length !== 1) {
|
|
8335
|
+
if (selected.length > 1) {
|
|
8336
|
+
reportWarning(this.options, null, `${operation} skipped: ActiveSelection layer moves are not supported.`);
|
|
6325
8337
|
}
|
|
8338
|
+
return;
|
|
8339
|
+
}
|
|
8340
|
+
const object = selected[0];
|
|
8341
|
+
const range = getEditableOverlayRange(this.canvas);
|
|
8342
|
+
const overlays = range.overlays;
|
|
8343
|
+
const currentOverlayIndex = overlays.indexOf(object);
|
|
8344
|
+
if (currentOverlayIndex < 0)
|
|
8345
|
+
return;
|
|
8346
|
+
let nextOverlayIndex = currentOverlayIndex;
|
|
8347
|
+
if (operation === 'bringSelectedObjectForward') {
|
|
8348
|
+
nextOverlayIndex = Math.min(overlays.length - 1, currentOverlayIndex + 1);
|
|
8349
|
+
}
|
|
8350
|
+
else if (operation === 'sendSelectedObjectBackward') {
|
|
8351
|
+
nextOverlayIndex = Math.max(0, currentOverlayIndex - 1);
|
|
8352
|
+
}
|
|
8353
|
+
else if (operation === 'bringSelectedObjectToFront') {
|
|
8354
|
+
nextOverlayIndex = overlays.length - 1;
|
|
8355
|
+
}
|
|
8356
|
+
else if (operation === 'sendSelectedObjectToBack') {
|
|
8357
|
+
nextOverlayIndex = 0;
|
|
8358
|
+
}
|
|
8359
|
+
if (nextOverlayIndex === currentOverlayIndex)
|
|
8360
|
+
return;
|
|
8361
|
+
const reordered = overlays.slice();
|
|
8362
|
+
reordered.splice(currentOverlayIndex, 1);
|
|
8363
|
+
reordered.splice(nextOverlayIndex, 0, object);
|
|
8364
|
+
reordered.forEach((overlay, index) => {
|
|
8365
|
+
var _a, _b;
|
|
8366
|
+
(_b = (_a = this.canvas).moveObjectTo) === null || _b === void 0 ? void 0 : _b.call(_a, overlay, range.start + index);
|
|
6326
8367
|
});
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
this.
|
|
6330
|
-
this.
|
|
8368
|
+
normalizeLayerOrder(this.canvas);
|
|
8369
|
+
this.canvas.setActiveObject(object);
|
|
8370
|
+
this.canvas.renderAll();
|
|
8371
|
+
this.saveState();
|
|
8372
|
+
this.updateMaskList();
|
|
8373
|
+
this.updateAnnotationList();
|
|
6331
8374
|
this.updateUi();
|
|
6332
|
-
const context =
|
|
6333
|
-
|
|
6334
|
-
|
|
6335
|
-
|
|
6336
|
-
|
|
6337
|
-
|
|
6338
|
-
canvas: this.canvas,
|
|
6339
|
-
getListElementId: () => this.elements.maskList,
|
|
6340
|
-
onMaskSelected: (mask) => this.handleSelectionChanged([mask]),
|
|
6341
|
-
};
|
|
6342
|
-
}
|
|
6343
|
-
updateMaskList() {
|
|
6344
|
-
renderMaskList(this.buildMaskListContext());
|
|
6345
|
-
}
|
|
6346
|
-
updateMaskListSelection(selectedMask) {
|
|
6347
|
-
updateMaskListSelection(this.buildMaskListContext(), selectedMask);
|
|
8375
|
+
const context = this.buildCallbackContext(operation, false);
|
|
8376
|
+
if (isMaskObject(object))
|
|
8377
|
+
this.emitMasksChanged(context);
|
|
8378
|
+
if (isAnnotationObject(object))
|
|
8379
|
+
this.emitAnnotationsChanged(context);
|
|
8380
|
+
this.emitImageChanged(context);
|
|
6348
8381
|
}
|
|
6349
8382
|
async mergeMasks() {
|
|
6350
8383
|
if (!this.canvas)
|
|
6351
8384
|
return;
|
|
6352
8385
|
if (!this.canRunIdleOperation('mergeMasks'))
|
|
6353
8386
|
return;
|
|
8387
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6354
8388
|
const hasMasks = this.canvas.getObjects().some(isMaskObject);
|
|
6355
8389
|
if (!hasMasks)
|
|
6356
8390
|
return;
|
|
@@ -6363,7 +8397,11 @@ class ImageEditor {
|
|
|
6363
8397
|
await mergeMasks(mergeMasksContext);
|
|
6364
8398
|
this.updateInputs();
|
|
6365
8399
|
this.updateMaskList();
|
|
8400
|
+
this.updateAnnotationList();
|
|
6366
8401
|
this.emitMasksChanged(callbackContext);
|
|
8402
|
+
if (this.getAnnotations().length > 0) {
|
|
8403
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8404
|
+
}
|
|
6367
8405
|
this.emitImageChanged(callbackContext);
|
|
6368
8406
|
}
|
|
6369
8407
|
finally {
|
|
@@ -6372,17 +8410,18 @@ class ImageEditor {
|
|
|
6372
8410
|
this.updateUi();
|
|
6373
8411
|
}
|
|
6374
8412
|
}
|
|
6375
|
-
downloadImage(
|
|
8413
|
+
downloadImage(options) {
|
|
6376
8414
|
if (!this.canvas)
|
|
6377
8415
|
return;
|
|
6378
8416
|
if (!this.canRunIdleOperation('downloadImage'))
|
|
6379
8417
|
return;
|
|
8418
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6380
8419
|
const callbackContext = this.buildCallbackContext('downloadImage', false);
|
|
6381
8420
|
const operationToken = this.operationGuard.beginBusyOperation('downloadImage');
|
|
6382
8421
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
6383
8422
|
const exportContext = this.buildExportServiceContext();
|
|
6384
8423
|
try {
|
|
6385
|
-
downloadImage(exportContext,
|
|
8424
|
+
downloadImage(exportContext, options);
|
|
6386
8425
|
}
|
|
6387
8426
|
finally {
|
|
6388
8427
|
this.operationGuard.endBusyOperation(operationToken);
|
|
@@ -6394,6 +8433,7 @@ class ImageEditor {
|
|
|
6394
8433
|
return '';
|
|
6395
8434
|
if (!this.canRunIdleOperation('exportImageBase64', options))
|
|
6396
8435
|
return '';
|
|
8436
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6397
8437
|
const callbackContext = this.buildCallbackContext('exportImageBase64', false);
|
|
6398
8438
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageBase64');
|
|
6399
8439
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -6408,6 +8448,7 @@ class ImageEditor {
|
|
|
6408
8448
|
}
|
|
6409
8449
|
async exportImageFile(options) {
|
|
6410
8450
|
this.assertIdleForOperation('exportImageFile', options);
|
|
8451
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
6411
8452
|
const callbackContext = this.buildCallbackContext('exportImageFile', false);
|
|
6412
8453
|
const operationToken = this.operationGuard.beginBusyOperation('exportImageFile');
|
|
6413
8454
|
this.emitBusyChangeIfChanged(callbackContext);
|
|
@@ -6423,7 +8464,7 @@ class ImageEditor {
|
|
|
6423
8464
|
buildExportServiceContext() {
|
|
6424
8465
|
return {
|
|
6425
8466
|
fabric: this.fabricModule,
|
|
6426
|
-
canvas: this.
|
|
8467
|
+
canvas: this.getLiveCanvasOrThrow('export'),
|
|
6427
8468
|
options: this.options,
|
|
6428
8469
|
isImageLoaded: () => this.isImageLoaded(),
|
|
6429
8470
|
getOriginalImage: () => this.originalImage,
|
|
@@ -6439,24 +8480,74 @@ class ImageEditor {
|
|
|
6439
8480
|
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
6440
8481
|
this.restoreMergedImageDisplayGeometry(geometry);
|
|
6441
8482
|
},
|
|
6442
|
-
|
|
8483
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
6443
8484
|
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
8485
|
+
exportImageBase64: (options) => exportImageBase64(this.buildExportServiceContext(), options),
|
|
8486
|
+
updateUi: () => this.updateUi(),
|
|
8487
|
+
updateInputs: () => this.updateInputs(),
|
|
6444
8488
|
removeAllMasksNoHistory: () => {
|
|
6445
8489
|
const context = this.buildRemoveMaskContext();
|
|
6446
8490
|
removeAllMasks(context, { saveHistory: false });
|
|
6447
8491
|
},
|
|
8492
|
+
getAnnotations: () => this.getAnnotations(),
|
|
8493
|
+
restoreAnnotations: (objects) => {
|
|
8494
|
+
const canvas = this.getLiveCanvasOrThrow('restoreAnnotations');
|
|
8495
|
+
objects.forEach((annotation) => {
|
|
8496
|
+
canvas.add(annotation);
|
|
8497
|
+
});
|
|
8498
|
+
syncAnnotationRuntimeStates(objects);
|
|
8499
|
+
attachTextEditingHandlersToAnnotations(this.buildTextControllerContext(), objects);
|
|
8500
|
+
this.annotationCounter = Math.max(this.annotationCounter, ...objects.map((annotation) => annotation.annotationId), 0);
|
|
8501
|
+
this.updateAnnotationList();
|
|
8502
|
+
},
|
|
8503
|
+
};
|
|
8504
|
+
}
|
|
8505
|
+
buildMergeAnnotationsContext(operationToken) {
|
|
8506
|
+
return {
|
|
8507
|
+
...this.buildExportServiceContext(),
|
|
8508
|
+
historyManager: this.historyManager,
|
|
8509
|
+
containerElement: this.containerElement,
|
|
8510
|
+
loadImage: async (base64, providedOptions) => {
|
|
8511
|
+
const geometry = this.captureImageDisplayGeometry();
|
|
8512
|
+
await this.loadImageInternal(base64, this.withInternalOperationOptions(operationToken, providedOptions !== null && providedOptions !== void 0 ? providedOptions : {}));
|
|
8513
|
+
this.restoreMergedImageDisplayGeometry(geometry);
|
|
8514
|
+
},
|
|
8515
|
+
captureSnapshot: () => this.captureSnapshotInternal(),
|
|
8516
|
+
loadFromState: (snapshot) => this.loadFromStateInternal(snapshot, this.withInternalOperationOptions(operationToken, this.withAnimationQueueBypass())),
|
|
8517
|
+
exportImageBase64: (options) => exportImageBase64(this.buildExportServiceContext(), options),
|
|
8518
|
+
updateUi: () => this.updateUi(),
|
|
8519
|
+
updateInputs: () => this.updateInputs(),
|
|
8520
|
+
removeAllAnnotationsNoHistory: () => {
|
|
8521
|
+
removeAllAnnotations(this.buildAnnotationManagerContext(), {
|
|
8522
|
+
saveHistory: false,
|
|
8523
|
+
force: true,
|
|
8524
|
+
});
|
|
8525
|
+
},
|
|
8526
|
+
getMasks: () => this.getMasks(),
|
|
8527
|
+
restoreMasks: (objects) => {
|
|
8528
|
+
const canvas = this.getLiveCanvasOrThrow('restoreMasks');
|
|
8529
|
+
objects.forEach((mask) => {
|
|
8530
|
+
canvas.add(mask);
|
|
8531
|
+
reattachMaskHoverHandlers(mask);
|
|
8532
|
+
});
|
|
8533
|
+
this.lastMask = objects.reduce((lastMask, mask) => !lastMask || mask.maskId > lastMask.maskId ? mask : lastMask, null);
|
|
8534
|
+
this.maskCounter = Math.max(this.maskCounter, ...objects.map((mask) => mask.maskId), 0);
|
|
8535
|
+
this.updateMaskList();
|
|
8536
|
+
},
|
|
6448
8537
|
};
|
|
6449
8538
|
}
|
|
6450
8539
|
captureSnapshotInternal() {
|
|
6451
|
-
var _a;
|
|
8540
|
+
var _a, _b;
|
|
6452
8541
|
if (!this.canvas) {
|
|
6453
8542
|
throw new Error('[ImageEditor] Cannot capture canvas snapshot before init or after dispose.');
|
|
6454
8543
|
}
|
|
6455
8544
|
const activeMask = this.getActiveMaskForSnapshot();
|
|
8545
|
+
const activeAnnotation = this.getActiveAnnotationForSnapshot();
|
|
6456
8546
|
this.hideAllMaskLabels();
|
|
6457
8547
|
return saveState({
|
|
6458
8548
|
canvas: this.canvas,
|
|
6459
8549
|
activeMaskId: (_a = activeMask === null || activeMask === void 0 ? void 0 : activeMask.maskId) !== null && _a !== void 0 ? _a : null,
|
|
8550
|
+
activeAnnotationId: (_b = activeAnnotation === null || activeAnnotation === void 0 ? void 0 : activeAnnotation.annotationId) !== null && _b !== void 0 ? _b : null,
|
|
6460
8551
|
currentScale: this.currentScale,
|
|
6461
8552
|
currentRotation: this.currentRotation,
|
|
6462
8553
|
baseImageScale: this.baseImageScale,
|
|
@@ -6475,6 +8566,12 @@ class ImageEditor {
|
|
|
6475
8566
|
.filter((object) => isMaskObject(object) && !!object.labelObject);
|
|
6476
8567
|
return labeledMasks.length === 1 ? ((_a = labeledMasks[0]) !== null && _a !== void 0 ? _a : null) : null;
|
|
6477
8568
|
}
|
|
8569
|
+
getActiveAnnotationForSnapshot() {
|
|
8570
|
+
if (!this.canvas)
|
|
8571
|
+
return null;
|
|
8572
|
+
const activeObject = this.canvas.getActiveObject();
|
|
8573
|
+
return activeObject && isAnnotationObject(activeObject) ? activeObject : null;
|
|
8574
|
+
}
|
|
6478
8575
|
enterMosaicMode() {
|
|
6479
8576
|
if (!this.canvas || !this.originalImage)
|
|
6480
8577
|
return;
|
|
@@ -6558,7 +8655,7 @@ class ImageEditor {
|
|
|
6558
8655
|
buildMosaicControllerContext() {
|
|
6559
8656
|
return {
|
|
6560
8657
|
fabric: this.fabricModule,
|
|
6561
|
-
canvas: this.
|
|
8658
|
+
canvas: this.getLiveCanvasOrThrow('mosaicController'),
|
|
6562
8659
|
options: this.options,
|
|
6563
8660
|
historyManager: this.historyManager,
|
|
6564
8661
|
getMosaicConfig: () => cloneResolvedMosaicConfig(this.currentMosaicConfig),
|
|
@@ -6658,7 +8755,7 @@ class ImageEditor {
|
|
|
6658
8755
|
buildCropControllerContext(operationToken) {
|
|
6659
8756
|
return {
|
|
6660
8757
|
fabric: this.fabricModule,
|
|
6661
|
-
canvas: this.
|
|
8758
|
+
canvas: this.getLiveCanvasOrThrow('cropController'),
|
|
6662
8759
|
options: this.options,
|
|
6663
8760
|
historyManager: this.historyManager,
|
|
6664
8761
|
isImageLoaded: () => this.isImageLoaded(),
|
|
@@ -6680,26 +8777,82 @@ class ImageEditor {
|
|
|
6680
8777
|
},
|
|
6681
8778
|
};
|
|
6682
8779
|
}
|
|
8780
|
+
syncInputValue(inputElement, value) {
|
|
8781
|
+
if (!inputElement)
|
|
8782
|
+
return;
|
|
8783
|
+
const ownerDocument = inputElement.ownerDocument;
|
|
8784
|
+
if (ownerDocument.activeElement === inputElement && !inputElement.readOnly)
|
|
8785
|
+
return;
|
|
8786
|
+
if (inputElement.value !== value)
|
|
8787
|
+
inputElement.value = value;
|
|
8788
|
+
}
|
|
6683
8789
|
updateInputs() {
|
|
6684
8790
|
const scaleId = this.elements.scalePercentageInput;
|
|
6685
8791
|
if (scaleId) {
|
|
6686
8792
|
const scaleInputElement = document.getElementById(scaleId);
|
|
6687
|
-
|
|
6688
|
-
scaleInputElement.value = String(Math.round(this.currentScale * 100));
|
|
6689
|
-
}
|
|
8793
|
+
this.syncInputValue(scaleInputElement, String(Math.round(this.currentScale * 100)));
|
|
6690
8794
|
}
|
|
6691
8795
|
const mosaicConfig = this.getMosaicConfig();
|
|
6692
8796
|
const mosaicBrushSizeInputId = this.elements.mosaicBrushSizeInput;
|
|
6693
8797
|
if (mosaicBrushSizeInputId) {
|
|
6694
8798
|
const brushInput = document.getElementById(mosaicBrushSizeInputId);
|
|
6695
|
-
|
|
6696
|
-
brushInput.value = String(mosaicConfig.brushSize);
|
|
8799
|
+
this.syncInputValue(brushInput, String(mosaicConfig.brushSize));
|
|
6697
8800
|
}
|
|
6698
8801
|
const mosaicBlockSizeInputId = this.elements.mosaicBlockSizeInput;
|
|
6699
8802
|
if (mosaicBlockSizeInputId) {
|
|
6700
8803
|
const blockInput = document.getElementById(mosaicBlockSizeInputId);
|
|
6701
|
-
|
|
6702
|
-
|
|
8804
|
+
this.syncInputValue(blockInput, String(mosaicConfig.blockSize));
|
|
8805
|
+
}
|
|
8806
|
+
const textConfig = this.getTextConfig();
|
|
8807
|
+
const textColorInputId = this.elements.textColorInput;
|
|
8808
|
+
if (textColorInputId) {
|
|
8809
|
+
const textColorInput = document.getElementById(textColorInputId);
|
|
8810
|
+
this.syncInputValue(textColorInput, textConfig.fill);
|
|
8811
|
+
}
|
|
8812
|
+
const textFontSizeInputId = this.elements.textFontSizeInput;
|
|
8813
|
+
if (textFontSizeInputId) {
|
|
8814
|
+
const fontInput = document.getElementById(textFontSizeInputId);
|
|
8815
|
+
this.syncInputValue(fontInput, String(textConfig.fontSize));
|
|
8816
|
+
}
|
|
8817
|
+
const drawConfig = this.getDrawConfig();
|
|
8818
|
+
const drawColorInputId = this.elements.drawColorInput;
|
|
8819
|
+
if (drawColorInputId) {
|
|
8820
|
+
const drawColorInput = document.getElementById(drawColorInputId);
|
|
8821
|
+
this.syncInputValue(drawColorInput, drawConfig.color);
|
|
8822
|
+
}
|
|
8823
|
+
const drawBrushSizeInputId = this.elements.drawBrushSizeInput;
|
|
8824
|
+
if (drawBrushSizeInputId) {
|
|
8825
|
+
const brushInput = document.getElementById(drawBrushSizeInputId);
|
|
8826
|
+
this.syncInputValue(brushInput, String(drawConfig.brushSize));
|
|
8827
|
+
}
|
|
8828
|
+
}
|
|
8829
|
+
async mergeAnnotations() {
|
|
8830
|
+
if (!this.canvas)
|
|
8831
|
+
return;
|
|
8832
|
+
if (!this.canRunIdleOperation('mergeAnnotations'))
|
|
8833
|
+
return;
|
|
8834
|
+
this.finalizeActiveTextEditingIfNeeded();
|
|
8835
|
+
const hasAnnotations = this.canvas.getObjects().some(isAnnotationObject);
|
|
8836
|
+
if (!hasAnnotations)
|
|
8837
|
+
return;
|
|
8838
|
+
const callbackContext = this.buildCallbackContext('mergeAnnotations', false);
|
|
8839
|
+
const operationToken = this.operationGuard.beginBusyOperation('mergeAnnotations');
|
|
8840
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
8841
|
+
this.updateUi();
|
|
8842
|
+
try {
|
|
8843
|
+
await mergeAnnotations(this.buildMergeAnnotationsContext(operationToken));
|
|
8844
|
+
this.updateInputs();
|
|
8845
|
+
this.updateMaskList();
|
|
8846
|
+
this.updateAnnotationList();
|
|
8847
|
+
this.emitAnnotationsChanged(callbackContext);
|
|
8848
|
+
if (this.getMasks().length > 0)
|
|
8849
|
+
this.emitMasksChanged(callbackContext);
|
|
8850
|
+
this.emitImageChanged(callbackContext);
|
|
8851
|
+
}
|
|
8852
|
+
finally {
|
|
8853
|
+
this.operationGuard.endBusyOperation(operationToken);
|
|
8854
|
+
this.emitBusyChangeIfChanged(callbackContext);
|
|
8855
|
+
this.updateUi();
|
|
6703
8856
|
}
|
|
6704
8857
|
}
|
|
6705
8858
|
updateUi() {
|
|
@@ -6708,14 +8861,20 @@ class ImageEditor {
|
|
|
6708
8861
|
return;
|
|
6709
8862
|
const hasImage = !!this.originalImage;
|
|
6710
8863
|
const masks = hasImage ? this.canvas.getObjects().filter(isMaskObject) : [];
|
|
8864
|
+
const annotations = hasImage ? this.canvas.getObjects().filter(isAnnotationObject) : [];
|
|
6711
8865
|
const hasMasks = masks.length > 0;
|
|
8866
|
+
const hasAnnotations = annotations.length > 0;
|
|
6712
8867
|
const activeObject = this.canvas.getActiveObject();
|
|
6713
8868
|
const hasSelectedMask = !!(activeObject && isMaskObject(activeObject));
|
|
8869
|
+
const hasSelectedAnnotation = !!(activeObject && isAnnotationObject(activeObject));
|
|
8870
|
+
const hasSelectedEditableObject = !!activeObject && isEditableOverlayObject(activeObject);
|
|
6714
8871
|
const isDefaultTransform = this.currentScale === 1 && this.currentRotation === 0;
|
|
6715
8872
|
const canUndo = this.historyManager.canUndo();
|
|
6716
8873
|
const canRedo = this.historyManager.canRedo();
|
|
6717
8874
|
const isInCropMode = this.cropSession !== null;
|
|
6718
8875
|
const isInMosaicMode = this.mosaicSession !== null;
|
|
8876
|
+
const isInTextMode = this.textSession !== null;
|
|
8877
|
+
const isInDrawMode = this.drawSession !== null;
|
|
6719
8878
|
const isBusy = this.operationGuard.isBusy() || this.animQueue.isBusy();
|
|
6720
8879
|
const isMosaicApplying = ((_a = this.mosaicSession) === null || _a === void 0 ? void 0 : _a.isApplying) === true;
|
|
6721
8880
|
if (isInCropMode) {
|
|
@@ -6724,6 +8883,18 @@ class ImageEditor {
|
|
|
6724
8883
|
});
|
|
6725
8884
|
return;
|
|
6726
8885
|
}
|
|
8886
|
+
if (isInTextMode) {
|
|
8887
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
8888
|
+
this.setControlEnabled(key, !isBusy && TEXT_MODE_ENABLED_KEYS.includes(key));
|
|
8889
|
+
});
|
|
8890
|
+
return;
|
|
8891
|
+
}
|
|
8892
|
+
if (isInDrawMode) {
|
|
8893
|
+
CROP_MODE_CONTROL_KEYS.forEach((key) => {
|
|
8894
|
+
this.setControlEnabled(key, !isBusy && DRAW_MODE_ENABLED_KEYS.includes(key));
|
|
8895
|
+
});
|
|
8896
|
+
return;
|
|
8897
|
+
}
|
|
6727
8898
|
if (isInMosaicMode) {
|
|
6728
8899
|
MOSAIC_MODE_CONTROL_KEYS.forEach((key) => {
|
|
6729
8900
|
this.setControlEnabled(key, !isBusy && !isMosaicApplying && MOSAIC_MODE_ENABLED_KEYS.includes(key));
|
|
@@ -6742,15 +8913,31 @@ class ImageEditor {
|
|
|
6742
8913
|
this.setControlEnabled('removeSelectedMaskButton', hasSelectedMask && !isBusy);
|
|
6743
8914
|
this.setControlEnabled('removeAllMasksButton', hasMasks && !isBusy);
|
|
6744
8915
|
this.setControlEnabled('mergeMasksButton', hasImage && hasMasks && !isBusy);
|
|
8916
|
+
this.setControlEnabled('removeSelectedAnnotationButton', hasSelectedAnnotation && !isBusy);
|
|
8917
|
+
this.setControlEnabled('removeAllAnnotationsButton', hasAnnotations && !isBusy);
|
|
8918
|
+
this.setControlEnabled('deleteSelectedObjectButton', hasSelectedEditableObject && !isBusy);
|
|
8919
|
+
this.setControlEnabled('mergeAnnotationsButton', hasImage && hasAnnotations && !isBusy);
|
|
8920
|
+
this.setControlEnabled('bringSelectedObjectForwardButton', hasSelectedEditableObject && !isBusy);
|
|
8921
|
+
this.setControlEnabled('sendSelectedObjectBackwardButton', hasSelectedEditableObject && !isBusy);
|
|
8922
|
+
this.setControlEnabled('bringSelectedObjectToFrontButton', hasSelectedEditableObject && !isBusy);
|
|
8923
|
+
this.setControlEnabled('sendSelectedObjectToBackButton', hasSelectedEditableObject && !isBusy);
|
|
6745
8924
|
this.setControlEnabled('downloadImageButton', hasImage && !isBusy);
|
|
6746
8925
|
this.setControlEnabled('resetImageTransformButton', hasImage && !isDefaultTransform && !isBusy);
|
|
6747
8926
|
this.setControlEnabled('undoButton', hasImage && !isBusy && canUndo);
|
|
6748
8927
|
this.setControlEnabled('redoButton', hasImage && !isBusy && canRedo);
|
|
6749
8928
|
this.setControlEnabled('enterCropModeButton', hasImage && !isBusy);
|
|
6750
8929
|
this.setControlEnabled('enterMosaicModeButton', hasImage && !isBusy);
|
|
8930
|
+
this.setControlEnabled('enterTextModeButton', hasImage && !isBusy);
|
|
8931
|
+
this.setControlEnabled('enterDrawModeButton', hasImage && !isBusy);
|
|
6751
8932
|
this.setControlEnabled('exitMosaicModeButton', false);
|
|
8933
|
+
this.setControlEnabled('exitTextModeButton', false);
|
|
8934
|
+
this.setControlEnabled('exitDrawModeButton', false);
|
|
6752
8935
|
this.setControlEnabled('mosaicBrushSizeInput', !this.isDisposed);
|
|
6753
8936
|
this.setControlEnabled('mosaicBlockSizeInput', !this.isDisposed);
|
|
8937
|
+
this.setControlEnabled('textColorInput', !this.isDisposed);
|
|
8938
|
+
this.setControlEnabled('textFontSizeInput', !this.isDisposed);
|
|
8939
|
+
this.setControlEnabled('drawColorInput', !this.isDisposed);
|
|
8940
|
+
this.setControlEnabled('drawBrushSizeInput', !this.isDisposed);
|
|
6754
8941
|
this.setControlEnabled('imageInput', !isBusy);
|
|
6755
8942
|
this.setControlEnabled('applyCropButton', false);
|
|
6756
8943
|
this.setControlEnabled('cancelCropButton', false);
|
|
@@ -6765,7 +8952,10 @@ class ImageEditor {
|
|
|
6765
8952
|
return;
|
|
6766
8953
|
this.recordElementOriginalState(key, controlElement);
|
|
6767
8954
|
if ('disabled' in controlElement) {
|
|
6768
|
-
|
|
8955
|
+
const formControl = controlElement;
|
|
8956
|
+
const nextDisabled = !isEnabled;
|
|
8957
|
+
if (formControl.disabled !== nextDisabled)
|
|
8958
|
+
formControl.disabled = nextDisabled;
|
|
6769
8959
|
return;
|
|
6770
8960
|
}
|
|
6771
8961
|
if (!isEnabled) {
|
|
@@ -6838,6 +9028,15 @@ class ImageEditor {
|
|
|
6838
9028
|
this.operationGuard.markDisposed();
|
|
6839
9029
|
this.animQueue.clear();
|
|
6840
9030
|
(_a = this.domBindings) === null || _a === void 0 ? void 0 : _a.removeAll();
|
|
9031
|
+
if (this.keyboardHandler && this.keyboardDocument) {
|
|
9032
|
+
try {
|
|
9033
|
+
this.keyboardDocument.removeEventListener('keydown', this.keyboardHandler);
|
|
9034
|
+
}
|
|
9035
|
+
catch {
|
|
9036
|
+
}
|
|
9037
|
+
}
|
|
9038
|
+
this.keyboardHandler = null;
|
|
9039
|
+
this.keyboardDocument = null;
|
|
6841
9040
|
this.restoreElementOriginalStates();
|
|
6842
9041
|
if (this.cropSession && this.canvas) {
|
|
6843
9042
|
try {
|
|
@@ -6856,6 +9055,22 @@ class ImageEditor {
|
|
|
6856
9055
|
}
|
|
6857
9056
|
this.mosaicSession = null;
|
|
6858
9057
|
}
|
|
9058
|
+
if (this.textSession && this.canvas) {
|
|
9059
|
+
try {
|
|
9060
|
+
exitTextMode(this.buildTextControllerContext());
|
|
9061
|
+
}
|
|
9062
|
+
catch {
|
|
9063
|
+
}
|
|
9064
|
+
this.textSession = null;
|
|
9065
|
+
}
|
|
9066
|
+
if (this.drawSession && this.canvas) {
|
|
9067
|
+
try {
|
|
9068
|
+
exitDrawMode(this.buildDrawControllerContext());
|
|
9069
|
+
}
|
|
9070
|
+
catch {
|
|
9071
|
+
}
|
|
9072
|
+
this.drawSession = null;
|
|
9073
|
+
}
|
|
6859
9074
|
if (this.canvas) {
|
|
6860
9075
|
try {
|
|
6861
9076
|
void Promise.resolve(this.canvas.dispose()).catch(() => {
|
|
@@ -6871,6 +9086,7 @@ class ImageEditor {
|
|
|
6871
9086
|
this.currentImageMimeType = null;
|
|
6872
9087
|
this.lastMask = null;
|
|
6873
9088
|
this.maskCounter = 0;
|
|
9089
|
+
this.annotationCounter = 0;
|
|
6874
9090
|
this.currentScale = 1;
|
|
6875
9091
|
this.currentRotation = 0;
|
|
6876
9092
|
this.baseImageScale = 1;
|
|
@@ -6888,5 +9104,11 @@ class ImageEditor {
|
|
|
6888
9104
|
|
|
6889
9105
|
exports.ImageEditor = ImageEditor;
|
|
6890
9106
|
exports.default = ImageEditor;
|
|
9107
|
+
exports.isAnnotationObject = isAnnotationObject;
|
|
9108
|
+
exports.isBaseImageObject = isBaseImageObject;
|
|
9109
|
+
exports.isDrawAnnotationObject = isDrawAnnotationObject;
|
|
9110
|
+
exports.isEditableOverlayObject = isEditableOverlayObject;
|
|
6891
9111
|
exports.isMaskObject = isMaskObject;
|
|
9112
|
+
exports.isSessionObject = isSessionObject;
|
|
9113
|
+
exports.isTextAnnotationObject = isTextAnnotationObject;
|
|
6892
9114
|
//# sourceMappingURL=index.cjs.map
|