@bensitu/image-editor 1.4.1 → 1.4.2
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/dist/image-editor.esm.js +239 -66
- package/dist/image-editor.esm.js.map +3 -3
- package/dist/image-editor.esm.min.js +3 -3
- package/dist/image-editor.esm.min.js.map +3 -3
- package/dist/image-editor.esm.min.mjs +3 -3
- package/dist/image-editor.esm.min.mjs.map +3 -3
- package/dist/image-editor.esm.mjs +239 -66
- package/dist/image-editor.esm.mjs.map +3 -3
- package/dist/image-editor.js +239 -66
- package/dist/image-editor.js.map +3 -3
- package/dist/image-editor.min.js +2 -2
- package/dist/image-editor.min.js.map +3 -3
- package/image-editor.d.ts +1 -0
- package/package.json +2 -1
- package/src/image-editor.js +272 -70
|
@@ -5,13 +5,13 @@ import fabricModule from "fabric";
|
|
|
5
5
|
/**
|
|
6
6
|
* @file image-editor.js
|
|
7
7
|
* @module image-editor
|
|
8
|
-
* @version 1.4.
|
|
8
|
+
* @version 1.4.2
|
|
9
9
|
* @author Ben Situ
|
|
10
10
|
* @license MIT
|
|
11
11
|
* @description Lightweight canvas-based image editor with masking/transform/export support.
|
|
12
12
|
*/
|
|
13
13
|
var fabric = null;
|
|
14
|
-
var INTERNAL_OPERATION_TOKEN = /* @__PURE__ */ Symbol("ImageEditorInternalOperation");
|
|
14
|
+
var INTERNAL_OPERATION_TOKEN = /* @__PURE__ */ Symbol.for("ImageEditorInternalOperation");
|
|
15
15
|
function getGlobalScope() {
|
|
16
16
|
if (typeof globalThis !== "undefined") return globalThis;
|
|
17
17
|
if (typeof self !== "undefined") return self;
|
|
@@ -575,12 +575,14 @@ var ImageEditor = class {
|
|
|
575
575
|
const imageElement = await this._createImageElement(imageBase64);
|
|
576
576
|
if (this._disposed || !this.canvas) throw new Error("Editor was disposed while loading image");
|
|
577
577
|
let loadSource = imageBase64;
|
|
578
|
-
|
|
579
|
-
|
|
578
|
+
const downsampleMaxWidth = Number(this.options.downsampleMaxWidth);
|
|
579
|
+
const downsampleMaxHeight = Number(this.options.downsampleMaxHeight);
|
|
580
|
+
if (this.options.downsampleOnLoad && downsampleMaxWidth > 0 && downsampleMaxHeight > 0) {
|
|
581
|
+
const shouldResize = imageElement.naturalWidth > downsampleMaxWidth || imageElement.naturalHeight > downsampleMaxHeight;
|
|
580
582
|
if (shouldResize) {
|
|
581
583
|
const ratio = Math.min(
|
|
582
|
-
|
|
583
|
-
|
|
584
|
+
downsampleMaxWidth / imageElement.naturalWidth,
|
|
585
|
+
downsampleMaxHeight / imageElement.naturalHeight
|
|
584
586
|
);
|
|
585
587
|
const targetWidth = Math.round(imageElement.naturalWidth * ratio);
|
|
586
588
|
const targetHeight = Math.round(imageElement.naturalHeight * ratio);
|
|
@@ -592,6 +594,8 @@ var ImageEditor = class {
|
|
|
592
594
|
imageBase64
|
|
593
595
|
);
|
|
594
596
|
}
|
|
597
|
+
} else if (this.options.downsampleOnLoad) {
|
|
598
|
+
this._reportWarning("loadImage: downsample limits must be positive numbers; using the original image");
|
|
595
599
|
}
|
|
596
600
|
const fabricImage = await this._createFabricImageFromURL(loadSource);
|
|
597
601
|
if (this._disposed || !this.canvas) throw new Error("Editor was disposed while loading image");
|
|
@@ -669,6 +673,15 @@ var ImageEditor = class {
|
|
|
669
673
|
const fabricInstance2 = ensureFabric();
|
|
670
674
|
return !!(this.originalImage && fabricInstance2 && this.originalImage instanceof fabricInstance2.Image && this.originalImage.width > 0 && this.originalImage.height > 0);
|
|
671
675
|
}
|
|
676
|
+
/**
|
|
677
|
+
* Checks whether the editor is in a temporary non-mutating state.
|
|
678
|
+
*
|
|
679
|
+
* @returns {boolean} True while loading, animating, cropping, or running a compound operation.
|
|
680
|
+
* @public
|
|
681
|
+
*/
|
|
682
|
+
isBusy() {
|
|
683
|
+
return !!(this.isAnimating || this._cropMode || this._isLoading || this._activeOperationToken || this.animationQueue && this.animationQueue.isBusy());
|
|
684
|
+
}
|
|
672
685
|
/**
|
|
673
686
|
* Creates an HTMLImageElement from a given data URL.
|
|
674
687
|
*
|
|
@@ -740,7 +753,6 @@ var ImageEditor = class {
|
|
|
740
753
|
_captureLoadImageTransaction() {
|
|
741
754
|
return {
|
|
742
755
|
canvasState: this._serializeCanvasState(),
|
|
743
|
-
originalImage: this.originalImage,
|
|
744
756
|
baseImageScale: this.baseImageScale,
|
|
745
757
|
currentScale: this.currentScale,
|
|
746
758
|
currentRotation: this.currentRotation,
|
|
@@ -826,12 +838,19 @@ var ImageEditor = class {
|
|
|
826
838
|
* @private
|
|
827
839
|
*/
|
|
828
840
|
_resampleImageToDataURL(imageElement, targetWidth, targetHeight, quality = 0.92, sourceDataUrl = null) {
|
|
841
|
+
const sourceWidth = Math.max(1, Number(imageElement && (imageElement.naturalWidth || imageElement.width)) || 0);
|
|
842
|
+
const sourceHeight = Math.max(1, Number(imageElement && (imageElement.naturalHeight || imageElement.height)) || 0);
|
|
843
|
+
const safeTargetWidth = Math.round(Number(targetWidth));
|
|
844
|
+
const safeTargetHeight = Math.round(Number(targetHeight));
|
|
845
|
+
if (!Number.isFinite(safeTargetWidth) || !Number.isFinite(safeTargetHeight) || safeTargetWidth <= 0 || safeTargetHeight <= 0) {
|
|
846
|
+
throw new Error("Invalid image resample target dimensions");
|
|
847
|
+
}
|
|
829
848
|
const offscreenCanvas = document.createElement("canvas");
|
|
830
|
-
offscreenCanvas.width =
|
|
831
|
-
offscreenCanvas.height =
|
|
849
|
+
offscreenCanvas.width = safeTargetWidth;
|
|
850
|
+
offscreenCanvas.height = safeTargetHeight;
|
|
832
851
|
const context = offscreenCanvas.getContext("2d");
|
|
833
852
|
if (!context) throw new Error("2D canvas context is unavailable");
|
|
834
|
-
context.drawImage(imageElement, 0, 0,
|
|
853
|
+
context.drawImage(imageElement, 0, 0, sourceWidth, sourceHeight, 0, 0, safeTargetWidth, safeTargetHeight);
|
|
835
854
|
return offscreenCanvas.toDataURL(this._getDownsampleMimeType(sourceDataUrl), quality);
|
|
836
855
|
}
|
|
837
856
|
_getDataUrlMimeType(dataUrl) {
|
|
@@ -1332,10 +1351,42 @@ var ImageEditor = class {
|
|
|
1332
1351
|
}
|
|
1333
1352
|
_getJpegBackgroundColor() {
|
|
1334
1353
|
const backgroundColor = String(this.options.backgroundColor || "").trim();
|
|
1335
|
-
if (!backgroundColor || backgroundColor
|
|
1336
|
-
if (/^rgba\([^)]*,\s*0(?:\.0+)?\s*\)$/i.test(backgroundColor)) return "#ffffff";
|
|
1354
|
+
if (!backgroundColor || this._isTransparentCssColor(backgroundColor)) return "#ffffff";
|
|
1337
1355
|
return backgroundColor;
|
|
1338
1356
|
}
|
|
1357
|
+
_isTransparentCssColor(color) {
|
|
1358
|
+
const normalizedColor = String(color || "").trim().toLowerCase();
|
|
1359
|
+
if (!normalizedColor || normalizedColor === "transparent") return true;
|
|
1360
|
+
const hexAlphaMatch = normalizedColor.match(/^#(?:[0-9a-f]{3}([0-9a-f])|[0-9a-f]{6}([0-9a-f]{2}))$/i);
|
|
1361
|
+
if (hexAlphaMatch) {
|
|
1362
|
+
const alpha = hexAlphaMatch[1] || hexAlphaMatch[2];
|
|
1363
|
+
return alpha === "0" || alpha === "00";
|
|
1364
|
+
}
|
|
1365
|
+
const slashAlphaMatch = normalizedColor.match(/^(?:rgba?|hsla?)\([^)]*\/\s*([^)]+)\)$/i);
|
|
1366
|
+
if (slashAlphaMatch) return this._isZeroCssAlpha(slashAlphaMatch[1]);
|
|
1367
|
+
const commaAlphaMatch = normalizedColor.match(/^(?:rgba|hsla)\((.*)\)$/i);
|
|
1368
|
+
if (commaAlphaMatch) {
|
|
1369
|
+
const parts = commaAlphaMatch[1].split(",");
|
|
1370
|
+
if (parts.length >= 4) return this._isZeroCssAlpha(parts[parts.length - 1]);
|
|
1371
|
+
}
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
_isZeroCssAlpha(alphaValue) {
|
|
1375
|
+
const normalizedAlpha = String(alphaValue || "").trim();
|
|
1376
|
+
if (!normalizedAlpha) return false;
|
|
1377
|
+
if (normalizedAlpha.endsWith("%")) return Number.parseFloat(normalizedAlpha) === 0;
|
|
1378
|
+
return Number(normalizedAlpha) === 0;
|
|
1379
|
+
}
|
|
1380
|
+
_decodeBase64Payload(base64Payload) {
|
|
1381
|
+
const payload = String(base64Payload || "");
|
|
1382
|
+
if (typeof atob === "function") {
|
|
1383
|
+
return Uint8Array.from(atob(payload), (char) => char.charCodeAt(0));
|
|
1384
|
+
}
|
|
1385
|
+
if (typeof Buffer !== "undefined" && typeof Buffer.from === "function") {
|
|
1386
|
+
return new Uint8Array(Buffer.from(payload, "base64"));
|
|
1387
|
+
}
|
|
1388
|
+
throw new Error("Base64 decoding is unavailable");
|
|
1389
|
+
}
|
|
1339
1390
|
/**
|
|
1340
1391
|
* Gets the top-left corner coordinates of the given object.
|
|
1341
1392
|
* Used for geometry calculations (e.g., scale, rotate).
|
|
@@ -1778,6 +1829,9 @@ var ImageEditor = class {
|
|
|
1778
1829
|
try {
|
|
1779
1830
|
const state = typeof serializedState === "string" ? JSON.parse(serializedState) : serializedState;
|
|
1780
1831
|
const editorMetadata = state && state.imageEditorMetadata ? state.imageEditorMetadata : null;
|
|
1832
|
+
if (editorMetadata && Object.prototype.hasOwnProperty.call(editorMetadata, "version") && Number(editorMetadata.version) !== 1) {
|
|
1833
|
+
this._reportWarning(`loadFromState: unsupported editor metadata version ${editorMetadata.version}`);
|
|
1834
|
+
}
|
|
1781
1835
|
this.canvas.loadFromJSON(state, async () => {
|
|
1782
1836
|
try {
|
|
1783
1837
|
if (this._disposed || !this.canvas) {
|
|
@@ -1856,12 +1910,12 @@ var ImageEditor = class {
|
|
|
1856
1910
|
}
|
|
1857
1911
|
_waitForImageElementReady(imageElement) {
|
|
1858
1912
|
if (!imageElement) return Promise.resolve();
|
|
1859
|
-
|
|
1913
|
+
const hasLoadedDimensions = (Number(imageElement.naturalWidth) > 0 || Number(imageElement.width) > 0) && (Number(imageElement.naturalHeight) > 0 || Number(imageElement.height) > 0);
|
|
1914
|
+
if (hasLoadedDimensions) return Promise.resolve();
|
|
1915
|
+
if (imageElement.complete) return Promise.reject(new Error("Image could not be loaded while restoring state"));
|
|
1860
1916
|
return new Promise((resolve, reject) => {
|
|
1861
1917
|
let isSettled = false;
|
|
1862
|
-
|
|
1863
|
-
settle(() => reject(new Error("Image load timed out while restoring state")));
|
|
1864
|
-
}, this._getSafeTimeoutMs(this.options.imageLoadTimeoutMs));
|
|
1918
|
+
let timerId;
|
|
1865
1919
|
const settle = (callback) => {
|
|
1866
1920
|
if (isSettled) return;
|
|
1867
1921
|
isSettled = true;
|
|
@@ -1875,8 +1929,20 @@ var ImageEditor = class {
|
|
|
1875
1929
|
}
|
|
1876
1930
|
callback();
|
|
1877
1931
|
};
|
|
1878
|
-
const handleLoad = () =>
|
|
1879
|
-
|
|
1932
|
+
const handleLoad = () => {
|
|
1933
|
+
const didLoad = (Number(imageElement.naturalWidth) > 0 || Number(imageElement.width) > 0) && (Number(imageElement.naturalHeight) > 0 || Number(imageElement.height) > 0);
|
|
1934
|
+
settle(() => {
|
|
1935
|
+
if (didLoad) {
|
|
1936
|
+
resolve();
|
|
1937
|
+
} else {
|
|
1938
|
+
reject(new Error("Image could not be loaded while restoring state"));
|
|
1939
|
+
}
|
|
1940
|
+
});
|
|
1941
|
+
};
|
|
1942
|
+
const handleError = (error) => settle(() => reject(error instanceof Error ? error : new Error("Image could not be loaded while restoring state")));
|
|
1943
|
+
timerId = setTimeout(() => {
|
|
1944
|
+
settle(() => reject(new Error("Image load timed out while restoring state")));
|
|
1945
|
+
}, this._getSafeTimeoutMs(this.options.imageLoadTimeoutMs));
|
|
1880
1946
|
if (typeof imageElement.addEventListener === "function") {
|
|
1881
1947
|
imageElement.addEventListener("load", handleLoad, { once: true });
|
|
1882
1948
|
imageElement.addEventListener("error", handleError, { once: true });
|
|
@@ -2148,6 +2214,10 @@ var ImageEditor = class {
|
|
|
2148
2214
|
});
|
|
2149
2215
|
}
|
|
2150
2216
|
}
|
|
2217
|
+
if (!mask || typeof mask.set !== "function" || typeof mask.setCoords !== "function") {
|
|
2218
|
+
this._reportWarning("fabricGenerator returned an invalid Fabric object");
|
|
2219
|
+
return null;
|
|
2220
|
+
}
|
|
2151
2221
|
const styles = maskConfig.styles || {};
|
|
2152
2222
|
const hasStyle = (property) => Object.prototype.hasOwnProperty.call(styles, property);
|
|
2153
2223
|
const maskSettings = {
|
|
@@ -2275,6 +2345,93 @@ var ImageEditor = class {
|
|
|
2275
2345
|
}
|
|
2276
2346
|
}
|
|
2277
2347
|
}
|
|
2348
|
+
_captureMaskLabelBackups(masks) {
|
|
2349
|
+
if (!this.canvas) return [];
|
|
2350
|
+
const canvasObjects = new Set(this.canvas.getObjects());
|
|
2351
|
+
return (masks || []).map((mask) => {
|
|
2352
|
+
const label = mask && mask.__label ? mask.__label : null;
|
|
2353
|
+
return {
|
|
2354
|
+
mask,
|
|
2355
|
+
label,
|
|
2356
|
+
hadLabel: !!label,
|
|
2357
|
+
labelInCanvas: !!label && canvasObjects.has(label),
|
|
2358
|
+
visible: label ? label.visible : void 0
|
|
2359
|
+
};
|
|
2360
|
+
});
|
|
2361
|
+
}
|
|
2362
|
+
_restoreMaskLabelBackups(labelBackups) {
|
|
2363
|
+
if (!this.canvas || !Array.isArray(labelBackups)) return;
|
|
2364
|
+
const canvasObjects = new Set(this.canvas.getObjects());
|
|
2365
|
+
labelBackups.forEach((backup) => {
|
|
2366
|
+
if (!backup || !backup.mask) return;
|
|
2367
|
+
try {
|
|
2368
|
+
if (!backup.hadLabel) {
|
|
2369
|
+
if (backup.mask.__label) this._removeLabelForMask(backup.mask);
|
|
2370
|
+
return;
|
|
2371
|
+
}
|
|
2372
|
+
backup.mask.__label = backup.label;
|
|
2373
|
+
if (!backup.label) return;
|
|
2374
|
+
if (backup.labelInCanvas && !canvasObjects.has(backup.label)) {
|
|
2375
|
+
this.canvas.add(backup.label);
|
|
2376
|
+
canvasObjects.add(backup.label);
|
|
2377
|
+
}
|
|
2378
|
+
if (backup.visible !== void 0) backup.label.set({ visible: backup.visible });
|
|
2379
|
+
if (backup.labelInCanvas) this.canvas.bringToFront(backup.label);
|
|
2380
|
+
this._syncMaskLabel(backup.mask);
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
void error;
|
|
2383
|
+
}
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
_captureActiveObjectBackup() {
|
|
2387
|
+
if (!this.canvas) return null;
|
|
2388
|
+
const activeObject = this.canvas.getActiveObject();
|
|
2389
|
+
if (!activeObject) return null;
|
|
2390
|
+
const selectedObjects = typeof activeObject.getObjects === "function" ? activeObject.getObjects() : [activeObject];
|
|
2391
|
+
return { activeObject, selectedObjects };
|
|
2392
|
+
}
|
|
2393
|
+
_restoreActiveObjectBackup(activeObjectBackup) {
|
|
2394
|
+
if (!this.canvas || !activeObjectBackup || !activeObjectBackup.activeObject) return;
|
|
2395
|
+
const canvasObjects = this.canvas.getObjects();
|
|
2396
|
+
const selectedObjects = Array.isArray(activeObjectBackup.selectedObjects) ? activeObjectBackup.selectedObjects : [];
|
|
2397
|
+
const canRestore = selectedObjects.length ? selectedObjects.every((object) => canvasObjects.includes(object)) : canvasObjects.includes(activeObjectBackup.activeObject);
|
|
2398
|
+
if (!canRestore) return;
|
|
2399
|
+
try {
|
|
2400
|
+
this.canvas.setActiveObject(activeObjectBackup.activeObject);
|
|
2401
|
+
} catch (error) {
|
|
2402
|
+
void error;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2405
|
+
_captureMaskExportBackups(masks) {
|
|
2406
|
+
return (masks || []).map((mask) => ({
|
|
2407
|
+
object: mask,
|
|
2408
|
+
visible: mask.visible,
|
|
2409
|
+
opacity: mask.opacity,
|
|
2410
|
+
fill: mask.fill,
|
|
2411
|
+
strokeWidth: mask.strokeWidth,
|
|
2412
|
+
stroke: mask.stroke,
|
|
2413
|
+
selectable: mask.selectable,
|
|
2414
|
+
lockRotation: mask.lockRotation
|
|
2415
|
+
}));
|
|
2416
|
+
}
|
|
2417
|
+
_restoreMaskExportBackups(maskBackups) {
|
|
2418
|
+
(maskBackups || []).forEach((backup) => {
|
|
2419
|
+
try {
|
|
2420
|
+
backup.object.set({
|
|
2421
|
+
visible: backup.visible,
|
|
2422
|
+
opacity: backup.opacity,
|
|
2423
|
+
fill: backup.fill,
|
|
2424
|
+
strokeWidth: backup.strokeWidth,
|
|
2425
|
+
stroke: backup.stroke,
|
|
2426
|
+
selectable: backup.selectable,
|
|
2427
|
+
lockRotation: backup.lockRotation
|
|
2428
|
+
});
|
|
2429
|
+
backup.object.setCoords();
|
|
2430
|
+
} catch (error) {
|
|
2431
|
+
void error;
|
|
2432
|
+
}
|
|
2433
|
+
});
|
|
2434
|
+
}
|
|
2278
2435
|
/**
|
|
2279
2436
|
* Returns a stable zero-based creation index for label callbacks.
|
|
2280
2437
|
*
|
|
@@ -2347,10 +2504,14 @@ var ImageEditor = class {
|
|
|
2347
2504
|
_hideAllMaskLabels() {
|
|
2348
2505
|
if (!this.canvas) return;
|
|
2349
2506
|
const canvasObjects = this.canvas.getObjects();
|
|
2507
|
+
const canvasObjectSet = new Set(canvasObjects);
|
|
2350
2508
|
const labels = canvasObjects.filter((object) => object.maskLabel);
|
|
2351
2509
|
labels.forEach((label) => {
|
|
2352
2510
|
try {
|
|
2353
|
-
if (
|
|
2511
|
+
if (canvasObjectSet.has(label)) {
|
|
2512
|
+
this.canvas.remove(label);
|
|
2513
|
+
canvasObjectSet.delete(label);
|
|
2514
|
+
}
|
|
2354
2515
|
} catch (error) {
|
|
2355
2516
|
void error;
|
|
2356
2517
|
}
|
|
@@ -2520,6 +2681,9 @@ var ImageEditor = class {
|
|
|
2520
2681
|
fileType: "png"
|
|
2521
2682
|
}));
|
|
2522
2683
|
this.removeAllMasks(this._withInternalOperationOptions(operationToken, { saveHistory: false }));
|
|
2684
|
+
if (this.canvas.getObjects().some((object) => object.maskId)) {
|
|
2685
|
+
throw new Error("Masks could not be removed during merge");
|
|
2686
|
+
}
|
|
2523
2687
|
await this.loadImage(merged, this._withInternalOperationOptions(operationToken, {
|
|
2524
2688
|
preserveScroll: true,
|
|
2525
2689
|
resetMaskCounter: false
|
|
@@ -2593,7 +2757,11 @@ var ImageEditor = class {
|
|
|
2593
2757
|
const format = this._normalizeImageFormat(options.fileType || options.format);
|
|
2594
2758
|
if (!exportImageArea) {
|
|
2595
2759
|
const masks2 = this.canvas.getObjects().filter((object) => object.maskId || object.maskLabel);
|
|
2760
|
+
const editableMasks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2596
2761
|
const maskVisibilityBackups = masks2.map((mask) => ({ object: mask, visible: mask.visible }));
|
|
2762
|
+
const maskStyleBackups2 = this._captureMaskExportBackups(editableMasks);
|
|
2763
|
+
const labelBackups2 = this._captureMaskLabelBackups(editableMasks);
|
|
2764
|
+
const activeObjectBackup2 = this._captureActiveObjectBackup();
|
|
2597
2765
|
try {
|
|
2598
2766
|
masks2.forEach((mask) => {
|
|
2599
2767
|
mask.set({ visible: false });
|
|
@@ -2618,19 +2786,16 @@ var ImageEditor = class {
|
|
|
2618
2786
|
void error;
|
|
2619
2787
|
}
|
|
2620
2788
|
});
|
|
2789
|
+
this._restoreMaskExportBackups(maskStyleBackups2);
|
|
2790
|
+
this._restoreMaskLabelBackups(labelBackups2);
|
|
2791
|
+
this._restoreActiveObjectBackup(activeObjectBackup2);
|
|
2621
2792
|
this.canvas.renderAll();
|
|
2622
2793
|
}
|
|
2623
2794
|
}
|
|
2624
2795
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2625
|
-
const maskStyleBackups =
|
|
2626
|
-
|
|
2627
|
-
|
|
2628
|
-
fill: mask.fill,
|
|
2629
|
-
strokeWidth: mask.strokeWidth,
|
|
2630
|
-
stroke: mask.stroke,
|
|
2631
|
-
selectable: mask.selectable,
|
|
2632
|
-
lockRotation: mask.lockRotation
|
|
2633
|
-
}));
|
|
2796
|
+
const maskStyleBackups = this._captureMaskExportBackups(masks);
|
|
2797
|
+
const labelBackups = this._captureMaskLabelBackups(masks);
|
|
2798
|
+
const activeObjectBackup = this._captureActiveObjectBackup();
|
|
2634
2799
|
let finalBase64;
|
|
2635
2800
|
try {
|
|
2636
2801
|
masks.forEach((mask) => this._removeLabelForMask(mask));
|
|
@@ -2652,21 +2817,9 @@ var ImageEditor = class {
|
|
|
2652
2817
|
sealPartialEdges: this._getPartialExportEdges(imageBounds)
|
|
2653
2818
|
});
|
|
2654
2819
|
} finally {
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
opacity: backup.opacity,
|
|
2659
|
-
fill: backup.fill,
|
|
2660
|
-
strokeWidth: backup.strokeWidth,
|
|
2661
|
-
stroke: backup.stroke,
|
|
2662
|
-
selectable: backup.selectable,
|
|
2663
|
-
lockRotation: backup.lockRotation
|
|
2664
|
-
});
|
|
2665
|
-
backup.object.setCoords();
|
|
2666
|
-
} catch (error) {
|
|
2667
|
-
void error;
|
|
2668
|
-
}
|
|
2669
|
-
});
|
|
2820
|
+
this._restoreMaskExportBackups(maskStyleBackups);
|
|
2821
|
+
this._restoreMaskLabelBackups(labelBackups);
|
|
2822
|
+
this._restoreActiveObjectBackup(activeObjectBackup);
|
|
2670
2823
|
this.canvas.renderAll();
|
|
2671
2824
|
}
|
|
2672
2825
|
return finalBase64;
|
|
@@ -2750,13 +2903,8 @@ var ImageEditor = class {
|
|
|
2750
2903
|
imageElement.src = imageBase64;
|
|
2751
2904
|
});
|
|
2752
2905
|
}
|
|
2753
|
-
const
|
|
2906
|
+
const bytes = this._decodeBase64Payload(imageDataUrl.split(",")[1]);
|
|
2754
2907
|
const mime = `image/${safeFileType}`;
|
|
2755
|
-
let byteIndex = binaryString.length;
|
|
2756
|
-
const bytes = new Uint8Array(byteIndex);
|
|
2757
|
-
while (byteIndex--) {
|
|
2758
|
-
bytes[byteIndex] = binaryString.charCodeAt(byteIndex);
|
|
2759
|
-
}
|
|
2760
2908
|
return new File([bytes], fileName, { type: mime });
|
|
2761
2909
|
}
|
|
2762
2910
|
_clearMaskPlacementMemory() {
|
|
@@ -2903,6 +3051,30 @@ var ImageEditor = class {
|
|
|
2903
3051
|
const nextScaleY = Math.min(maxCropHeight / cropHeight, Math.max(minCropHeight / cropHeight, Number(cropRect.scaleY) || 1));
|
|
2904
3052
|
cropRect.set({ scaleX: nextScaleX, scaleY: nextScaleY });
|
|
2905
3053
|
cropRect.setCoords();
|
|
3054
|
+
const cropBounds = cropRect.getBoundingRect(true, true);
|
|
3055
|
+
const imageLeft = Number(imageBounds.left) || 0;
|
|
3056
|
+
const imageTop = Number(imageBounds.top) || 0;
|
|
3057
|
+
const imageRight = imageLeft + (Number(imageBounds.width) || 0);
|
|
3058
|
+
const imageBottom = imageTop + (Number(imageBounds.height) || 0);
|
|
3059
|
+
let deltaX = 0;
|
|
3060
|
+
let deltaY = 0;
|
|
3061
|
+
if (cropBounds.left < imageLeft) {
|
|
3062
|
+
deltaX = imageLeft - cropBounds.left;
|
|
3063
|
+
} else if (cropBounds.left + cropBounds.width > imageRight) {
|
|
3064
|
+
deltaX = imageRight - (cropBounds.left + cropBounds.width);
|
|
3065
|
+
}
|
|
3066
|
+
if (cropBounds.top < imageTop) {
|
|
3067
|
+
deltaY = imageTop - cropBounds.top;
|
|
3068
|
+
} else if (cropBounds.top + cropBounds.height > imageBottom) {
|
|
3069
|
+
deltaY = imageBottom - (cropBounds.top + cropBounds.height);
|
|
3070
|
+
}
|
|
3071
|
+
if (deltaX || deltaY) {
|
|
3072
|
+
cropRect.set({
|
|
3073
|
+
left: (Number(cropRect.left) || 0) + deltaX,
|
|
3074
|
+
top: (Number(cropRect.top) || 0) + deltaY
|
|
3075
|
+
});
|
|
3076
|
+
cropRect.setCoords();
|
|
3077
|
+
}
|
|
2906
3078
|
this.canvas.requestRenderAll();
|
|
2907
3079
|
} catch (error) {
|
|
2908
3080
|
void error;
|
|
@@ -2970,19 +3142,15 @@ var ImageEditor = class {
|
|
|
2970
3142
|
const masks = this.canvas.getObjects().filter((object) => object.maskId);
|
|
2971
3143
|
if (masks && masks.length) {
|
|
2972
3144
|
masks.forEach((mask) => {
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
preservedMasks.push(mask);
|
|
2983
|
-
}
|
|
2984
|
-
} catch (error) {
|
|
2985
|
-
this._reportWarning("applyCrop: failed to remove mask", error);
|
|
3145
|
+
mask.setCoords();
|
|
3146
|
+
const maskBounds = mask.getBoundingRect(true, true);
|
|
3147
|
+
const intersectsCrop = maskBounds.left < cropRegion.sourceX + cropRegion.sourceWidth && maskBounds.left + maskBounds.width > cropRegion.sourceX && maskBounds.top < cropRegion.sourceY + cropRegion.sourceHeight && maskBounds.top + maskBounds.height > cropRegion.sourceY;
|
|
3148
|
+
this._removeLabelForMask(mask);
|
|
3149
|
+
this.canvas.remove(mask);
|
|
3150
|
+
if (shouldPreserveMasks && intersectsCrop) {
|
|
3151
|
+
this._translateObjectByCanvasOffset(mask, -cropRegion.sourceX, -cropRegion.sourceY);
|
|
3152
|
+
mask.set({ visible: true });
|
|
3153
|
+
preservedMasks.push(mask);
|
|
2986
3154
|
}
|
|
2987
3155
|
});
|
|
2988
3156
|
this._clearMaskPlacementMemory();
|
|
@@ -2990,7 +3158,8 @@ var ImageEditor = class {
|
|
|
2990
3158
|
this.canvas.renderAll();
|
|
2991
3159
|
}
|
|
2992
3160
|
} catch (error) {
|
|
2993
|
-
this.
|
|
3161
|
+
await this._restoreStateAfterCropFailure(beforeJson, "applyCrop: failed to prepare masks", error);
|
|
3162
|
+
return;
|
|
2994
3163
|
}
|
|
2995
3164
|
this._removeCropRect();
|
|
2996
3165
|
this._cropMode = false;
|
|
@@ -3066,7 +3235,7 @@ var ImageEditor = class {
|
|
|
3066
3235
|
const canUndo = this.historyManager?.canUndo();
|
|
3067
3236
|
const canRedo = this.historyManager?.canRedo();
|
|
3068
3237
|
const isInCropMode = !!this._cropMode;
|
|
3069
|
-
const isBusy = this.
|
|
3238
|
+
const isBusy = this.isBusy();
|
|
3070
3239
|
if (isInCropMode) {
|
|
3071
3240
|
for (const key of Object.keys(this.elements || {})) {
|
|
3072
3241
|
const element = this._getElement(key);
|
|
@@ -3094,6 +3263,10 @@ var ImageEditor = class {
|
|
|
3094
3263
|
this._setDisabled("cropBtn", !hasImage || isBusy);
|
|
3095
3264
|
this._setDisabled("applyCropBtn", true);
|
|
3096
3265
|
this._setDisabled("cancelCropBtn", true);
|
|
3266
|
+
this._setDisabled("scaleRate", !hasImage || isBusy);
|
|
3267
|
+
this._setDisabled("rotationLeftInput", !hasImage || isBusy);
|
|
3268
|
+
this._setDisabled("rotationRightInput", !hasImage || isBusy);
|
|
3269
|
+
this._setDisabled("maskList", !hasImage || isBusy);
|
|
3097
3270
|
this._setDisabled("imageInput", isBusy);
|
|
3098
3271
|
this._setDisabled("uploadArea", isBusy);
|
|
3099
3272
|
}
|
|
@@ -3408,9 +3581,9 @@ var HistoryManager = class {
|
|
|
3408
3581
|
execute(command) {
|
|
3409
3582
|
const result = command.execute();
|
|
3410
3583
|
if (result && typeof result.then === "function") {
|
|
3411
|
-
return Promise.resolve(result).then(() => {
|
|
3584
|
+
return this.enqueue(() => Promise.resolve(result).then(() => {
|
|
3412
3585
|
this.push(command);
|
|
3413
|
-
});
|
|
3586
|
+
}));
|
|
3414
3587
|
}
|
|
3415
3588
|
this.push(command);
|
|
3416
3589
|
return result;
|