feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -81,11 +81,7 @@ export class LayerSort {
|
||||
zIndexMap.set(layer.fabricObject.id, currentZIndex++);
|
||||
} else if (!layer.isBackground && !layer.isFixed) {
|
||||
// 普通图层
|
||||
currentZIndex = this.processLayerObjects(
|
||||
layer,
|
||||
currentZIndex,
|
||||
zIndexMap
|
||||
);
|
||||
currentZIndex = this.processLayerObjects(layer, currentZIndex, zIndexMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,8 +138,7 @@ export class LayerSort {
|
||||
getChildLayersInOrder(parentLayerId) {
|
||||
// 获取所有子图层
|
||||
const childLayers =
|
||||
this.layers.value.filter((layer) => layer.id === parentLayerId)
|
||||
?.children || [];
|
||||
this.layers.value.filter((layer) => layer.id === parentLayerId)?.children || [];
|
||||
|
||||
return childLayers;
|
||||
}
|
||||
@@ -194,9 +189,7 @@ export class LayerSort {
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
const item = sortedObjects[i];
|
||||
const currentIndex = this.canvas
|
||||
.getObjects()
|
||||
.indexOf(item.object);
|
||||
const currentIndex = this.canvas.getObjects().indexOf(item.object);
|
||||
if (currentIndex !== i && currentIndex !== -1) {
|
||||
this.canvas.moveTo(item.object, i);
|
||||
}
|
||||
@@ -288,23 +281,17 @@ export class LayerSort {
|
||||
return this.layers.value.length; // 背景图层插入到最后
|
||||
} else if (newLayer.isFixed) {
|
||||
// 固定图层插入到背景图层之前
|
||||
const bgIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgIndex = this.layers.value.findIndex((layer) => layer.isBackground);
|
||||
return bgIndex !== -1 ? bgIndex : this.layers.value.length;
|
||||
} else {
|
||||
// 普通图层插入到固定图层之前
|
||||
const fixedIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.isFixed
|
||||
);
|
||||
const fixedIndex = this.layers.value.findIndex((layer) => layer.isFixed);
|
||||
return fixedIndex !== -1 ? fixedIndex : this.layers.value.length;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果指定了目标图层,插入到目标图层之前
|
||||
const targetIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === targetLayerId
|
||||
);
|
||||
const targetIndex = this.layers.value.findIndex((layer) => layer.id === targetLayerId);
|
||||
return targetIndex !== -1 ? targetIndex : this.layers.value.length;
|
||||
}
|
||||
|
||||
@@ -532,9 +519,7 @@ export class LayerSort {
|
||||
async smartSort(targetLayerIds = null) {
|
||||
const layersToSort = targetLayerIds
|
||||
? this.layers.value.filter((layer) => targetLayerIds.includes(layer.id))
|
||||
: this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
: this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
if (layersToSort.length <= 1) return true;
|
||||
|
||||
@@ -556,9 +541,7 @@ export class LayerSort {
|
||||
|
||||
// 更新图层顺序
|
||||
const sortedLayerIds = layersToSort.map((layer) => layer.id);
|
||||
const otherLayers = this.layers.value.filter(
|
||||
(layer) => !sortedLayerIds.includes(layer.id)
|
||||
);
|
||||
const otherLayers = this.layers.value.filter((layer) => !sortedLayerIds.includes(layer.id));
|
||||
|
||||
// 重新组织图层数组:保持背景层和固定层的位置
|
||||
const newLayers = [];
|
||||
@@ -733,12 +716,9 @@ export const LayerSortUtils = {
|
||||
* @returns {number} 排序权重
|
||||
*/
|
||||
getLayerSortWeight(layer) {
|
||||
if (layer.isBackground)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
|
||||
if (layer.isFixed)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
|
||||
if (layer.children?.length > 0)
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
|
||||
if (layer.isBackground) return LayerSortConstants.LAYER_PRIORITY[LayerType.BACKGROUND];
|
||||
if (layer.isFixed) return LayerSortConstants.LAYER_PRIORITY[LayerType.FIXED];
|
||||
if (layer.children?.length > 0) return LayerSortConstants.LAYER_PRIORITY[LayerType.GROUP];
|
||||
return LayerSortConstants.LAYER_PRIORITY[LayerType.NORMAL];
|
||||
},
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ export const createCanvas = (elementId, options = {}) => {
|
||||
const canvas = new fabric.Canvas(elementId, {
|
||||
enableRetinaScaling: canvasConfig.enableRetinaScaling,
|
||||
renderOnAddRemove: false,
|
||||
enableRetinaScaling: true,
|
||||
preserveObjectStacking: true, // 保持对象堆叠顺序
|
||||
// skipOffscreen: true, // 跳过离屏渲染
|
||||
imageSmoothingEnabled: true, // 启用图像平滑 - 抗锯齿
|
||||
|
||||
@@ -6,12 +6,7 @@ export function deepCompare(obj1, obj2) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (
|
||||
obj1 === null ||
|
||||
obj2 === null ||
|
||||
typeof obj1 !== "object" ||
|
||||
typeof obj2 !== "object"
|
||||
) {
|
||||
if (obj1 === null || obj2 === null || typeof obj1 !== "object" || typeof obj2 !== "object") {
|
||||
return { _value: obj2, _oldValue: obj1 };
|
||||
}
|
||||
|
||||
@@ -78,7 +73,7 @@ export function deepClone(obj) {
|
||||
if (typeof obj === "object") {
|
||||
const cloned = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
cloned[key] = deepClone(obj[key]);
|
||||
}
|
||||
}
|
||||
@@ -228,11 +223,7 @@ export function formatDuration(milliseconds) {
|
||||
* @returns {boolean} 是否有效
|
||||
*/
|
||||
export function isValidCommand(command) {
|
||||
return (
|
||||
command &&
|
||||
typeof command === "object" &&
|
||||
typeof command.execute === "function"
|
||||
);
|
||||
return command && typeof command === "object" && typeof command.execute === "function";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -286,7 +277,7 @@ export function getObjectDepth(obj) {
|
||||
|
||||
let maxDepth = 0;
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
const depth = getObjectDepth(obj[key]);
|
||||
maxDepth = Math.max(maxDepth, depth);
|
||||
}
|
||||
@@ -313,8 +304,7 @@ export function checkBrowserSupport() {
|
||||
return {
|
||||
WeakRef: typeof WeakRef !== "undefined",
|
||||
FinalizationRegistry: typeof FinalizationRegistry !== "undefined",
|
||||
PerformanceMemory:
|
||||
typeof performance !== "undefined" && !!performance.memory,
|
||||
PerformanceMemory: typeof performance !== "undefined" && !!performance.memory,
|
||||
RequestIdleCallback: typeof requestIdleCallback !== "undefined",
|
||||
IntersectionObserver: typeof IntersectionObserver !== "undefined",
|
||||
ResizeObserver: typeof ResizeObserver !== "undefined",
|
||||
@@ -337,12 +327,7 @@ export function delay(ms) {
|
||||
* @returns {Promise} 执行结果
|
||||
*/
|
||||
export async function retry(fn, options = {}) {
|
||||
const {
|
||||
retries = 3,
|
||||
delay: delayMs = 1000,
|
||||
backoff = 1.5,
|
||||
shouldRetry = () => true,
|
||||
} = options;
|
||||
const { retries = 3, delay: delayMs = 1000, backoff = 1.5, shouldRetry = () => true } = options;
|
||||
|
||||
let attempt = 0;
|
||||
let currentDelay = delayMs;
|
||||
@@ -377,9 +362,7 @@ export async function batchProcess(items, processor, options = {}) {
|
||||
|
||||
for (let i = 0; i < items.length; i += batchSize) {
|
||||
const batch = items.slice(i, i + batchSize);
|
||||
const batchResults = await Promise.all(
|
||||
batch.map((item) => processor(item))
|
||||
);
|
||||
const batchResults = await Promise.all(batch.map((item) => processor(item)));
|
||||
|
||||
results.push(...batchResults);
|
||||
|
||||
|
||||
@@ -35,14 +35,8 @@ function initAligningGuidelines(canvas) {
|
||||
ctx.lineWidth = aligningLineWidth;
|
||||
ctx.strokeStyle = aligningLineColor;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
x1 * zoom + viewportTransform[4],
|
||||
y1 * zoom + viewportTransform[5]
|
||||
);
|
||||
ctx.lineTo(
|
||||
x2 * zoom + viewportTransform[4],
|
||||
y2 * zoom + viewportTransform[5]
|
||||
);
|
||||
ctx.moveTo(x1 * zoom + viewportTransform[4], y1 * zoom + viewportTransform[5]);
|
||||
ctx.lineTo(x2 * zoom + viewportTransform[4], y2 * zoom + viewportTransform[5]);
|
||||
ctx.stroke();
|
||||
ctx.restore();
|
||||
}
|
||||
@@ -50,11 +44,7 @@ function initAligningGuidelines(canvas) {
|
||||
function isInRange(value1, value2) {
|
||||
value1 = Math.round(value1);
|
||||
value2 = Math.round(value2);
|
||||
for (
|
||||
var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
for (var i = value1 - aligningLineMargin, len = value1 + aligningLineMargin; i <= len; i++) {
|
||||
if (i === value2) {
|
||||
return true;
|
||||
}
|
||||
@@ -77,8 +67,7 @@ function initAligningGuidelines(canvas) {
|
||||
activeObjectLeft = activeObjectCenter.x,
|
||||
activeObjectTop = activeObjectCenter.y,
|
||||
activeObjectBoundingRect = activeObject.getBoundingRect(),
|
||||
activeObjectHeight =
|
||||
activeObjectBoundingRect.height / viewportTransform[3],
|
||||
activeObjectHeight = activeObjectBoundingRect.height / viewportTransform[3],
|
||||
activeObjectWidth = activeObjectBoundingRect.width / viewportTransform[0],
|
||||
horizontalInTheRange = false,
|
||||
verticalInTheRange = false,
|
||||
@@ -100,12 +89,7 @@ function initAligningGuidelines(canvas) {
|
||||
objectWidth = objectBoundingRect.width / viewportTransform[0];
|
||||
|
||||
// snaps if the right side of the active object touches the left side of the object
|
||||
if (
|
||||
isInRange(
|
||||
activeObjectLeft + activeObjectWidth / 2,
|
||||
objectLeft - objectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(activeObjectLeft + activeObjectWidth / 2, objectLeft - objectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
@@ -120,22 +104,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft - objectWidth / 2 - activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft - objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the left side of the active object touches the right side of the object
|
||||
if (
|
||||
isInRange(
|
||||
activeObjectLeft - activeObjectWidth / 2,
|
||||
objectLeft + objectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(activeObjectLeft - activeObjectWidth / 2, objectLeft + objectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
@@ -150,22 +126,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft + objectWidth / 2 + activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft + objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the bottom of the object touches the top of the active object
|
||||
if (
|
||||
isInRange(
|
||||
objectTop + objectHeight / 2,
|
||||
activeObjectTop - activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
@@ -180,22 +148,14 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop + objectHeight / 2 + activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 + activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snaps if the top of the object touches the bottom of the active object
|
||||
if (
|
||||
isInRange(
|
||||
objectTop - objectHeight / 2,
|
||||
activeObjectTop + activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
@@ -210,10 +170,7 @@ function initAligningGuidelines(canvas) {
|
||||
});
|
||||
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop - objectHeight / 2 - activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 - activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -241,12 +198,7 @@ function initAligningGuidelines(canvas) {
|
||||
}
|
||||
|
||||
// snap by the left edge
|
||||
if (
|
||||
isInRange(
|
||||
objectLeft - objectWidth / 2,
|
||||
activeObjectLeft - activeObjectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectLeft - objectWidth / 2, activeObjectLeft - activeObjectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft - objectWidth / 2,
|
||||
@@ -260,22 +212,14 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft - objectWidth / 2 + activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft - objectWidth / 2 + activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snap by the right edge
|
||||
if (
|
||||
isInRange(
|
||||
objectLeft + objectWidth / 2,
|
||||
activeObjectLeft + activeObjectWidth / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectLeft + objectWidth / 2, activeObjectLeft + activeObjectWidth / 2)) {
|
||||
verticalInTheRange = true;
|
||||
verticalLines.push({
|
||||
x: objectLeft + objectWidth / 2,
|
||||
@@ -289,10 +233,7 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectTop - activeObjectHeight / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
objectLeft + objectWidth / 2 - activeObjectWidth / 2,
|
||||
activeObjectTop
|
||||
),
|
||||
new fabric.Point(objectLeft + objectWidth / 2 - activeObjectWidth / 2, activeObjectTop),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -320,12 +261,7 @@ function initAligningGuidelines(canvas) {
|
||||
}
|
||||
|
||||
// snap by the top edge
|
||||
if (
|
||||
isInRange(
|
||||
objectTop - objectHeight / 2,
|
||||
activeObjectTop - activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop - objectHeight / 2, activeObjectTop - activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop - objectHeight / 2,
|
||||
@@ -339,22 +275,14 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop - objectHeight / 2 + activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop - objectHeight / 2 + activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
}
|
||||
|
||||
// snap by the bottom edge
|
||||
if (
|
||||
isInRange(
|
||||
objectTop + objectHeight / 2,
|
||||
activeObjectTop + activeObjectHeight / 2
|
||||
)
|
||||
) {
|
||||
if (isInRange(objectTop + objectHeight / 2, activeObjectTop + activeObjectHeight / 2)) {
|
||||
horizontalInTheRange = true;
|
||||
horizontalLines.push({
|
||||
y: objectTop + objectHeight / 2,
|
||||
@@ -368,10 +296,7 @@ function initAligningGuidelines(canvas) {
|
||||
: activeObjectLeft - activeObjectWidth / 2 - aligningLineOffset,
|
||||
});
|
||||
activeObject.setPositionByOrigin(
|
||||
new fabric.Point(
|
||||
activeObjectLeft,
|
||||
objectTop + objectHeight / 2 - activeObjectHeight / 2
|
||||
),
|
||||
new fabric.Point(activeObjectLeft, objectTop + objectHeight / 2 - activeObjectHeight / 2),
|
||||
"center",
|
||||
"center"
|
||||
);
|
||||
@@ -397,7 +322,7 @@ function initAligningGuidelines(canvas) {
|
||||
for (var i = verticalLines.length; i--; ) {
|
||||
drawVerticalLine(verticalLines[i]);
|
||||
}
|
||||
for (var i = horizontalLines.length; i--; ) {
|
||||
for (let i = horizontalLines.length; i--; ) {
|
||||
drawHorizontalLine(horizontalLines[i]);
|
||||
}
|
||||
|
||||
@@ -433,16 +358,14 @@ export function initCenteringGuidelines(canvas) {
|
||||
viewportTransform;
|
||||
|
||||
for (
|
||||
var i = canvasWidthCenter - centerLineMargin,
|
||||
len = canvasWidthCenter + centerLineMargin;
|
||||
var i = canvasWidthCenter - centerLineMargin, len = canvasWidthCenter + centerLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
canvasWidthCenterMap[Math.round(i)] = true;
|
||||
}
|
||||
for (
|
||||
var i = canvasHeightCenter - centerLineMargin,
|
||||
len = canvasHeightCenter + centerLineMargin;
|
||||
let i = canvasHeightCenter - centerLineMargin, len = canvasHeightCenter + centerLineMargin;
|
||||
i <= len;
|
||||
i++
|
||||
) {
|
||||
@@ -450,21 +373,11 @@ export function initCenteringGuidelines(canvas) {
|
||||
}
|
||||
|
||||
function showVerticalCenterLine() {
|
||||
showCenterLine(
|
||||
canvasWidthCenter + 0.5,
|
||||
0,
|
||||
canvasWidthCenter + 0.5,
|
||||
canvasHeight
|
||||
);
|
||||
showCenterLine(canvasWidthCenter + 0.5, 0, canvasWidthCenter + 0.5, canvasHeight);
|
||||
}
|
||||
|
||||
function showHorizontalCenterLine() {
|
||||
showCenterLine(
|
||||
0,
|
||||
canvasHeightCenter + 0.5,
|
||||
canvasWidth,
|
||||
canvasHeightCenter + 0.5
|
||||
);
|
||||
showCenterLine(0, canvasHeightCenter + 0.5, canvasWidth, canvasHeightCenter + 0.5);
|
||||
}
|
||||
|
||||
function showCenterLine(x1, y1, x2, y2) {
|
||||
@@ -493,9 +406,8 @@ export function initCenteringGuidelines(canvas) {
|
||||
|
||||
if (!transform) return;
|
||||
|
||||
(isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap),
|
||||
(isInHorizontalCenter =
|
||||
Math.round(objectCenter.y) in canvasHeightCenterMap);
|
||||
((isInVerticalCenter = Math.round(objectCenter.x) in canvasWidthCenterMap),
|
||||
(isInHorizontalCenter = Math.round(objectCenter.y) in canvasHeightCenterMap));
|
||||
|
||||
if (isInHorizontalCenter || isInVerticalCenter) {
|
||||
object.setPositionByOrigin(
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
/* eslint-disable no-async-promise-executor */
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { LayerType, OperationType, createBitmapLayer } from "./layerHelper";
|
||||
// 导入新的复合命令
|
||||
import { CreateImageLayerCommand } from "../commands/LayerCommands";
|
||||
// 导入新的命令
|
||||
import {
|
||||
ChangeFixedImageCommand,
|
||||
AddImageToLayerCommand,
|
||||
} from "../commands/LayerCommands";
|
||||
import { ChangeFixedImageCommand, AddImageToLayerCommand } from "../commands/LayerCommands";
|
||||
import { generateId } from "./helper";
|
||||
import { isBoolean } from "lodash-es";
|
||||
|
||||
/**
|
||||
* 加载并处理图片
|
||||
@@ -101,9 +100,7 @@ export async function createImageLayer({
|
||||
if (isBoolean(undoable)) createImageLayerCmd.undoable = undoable; // 是否撤销
|
||||
|
||||
// 执行复合命令
|
||||
const newLayerId = await layerManager.commandManager.execute(
|
||||
createImageLayerCmd
|
||||
);
|
||||
const newLayerId = await layerManager.commandManager.execute(createImageLayerCmd);
|
||||
|
||||
return newLayerId;
|
||||
} catch (error) {
|
||||
@@ -120,11 +117,7 @@ export async function createImageLayer({
|
||||
* @param {Object} options.fabricImage - 新的图像对象
|
||||
* @returns {Promise<boolean>} 是否成功更改
|
||||
*/
|
||||
export async function changeFixedImage({
|
||||
layerManager,
|
||||
fixedLayerId,
|
||||
fabricImage,
|
||||
} = {}) {
|
||||
export async function changeFixedImage({ layerManager, fixedLayerId, fabricImage } = {}) {
|
||||
if (!layerManager || !fixedLayerId || !fabricImage) {
|
||||
console.error("更改固定图层图像:参数无效");
|
||||
return false;
|
||||
@@ -141,9 +134,7 @@ export async function changeFixedImage({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const result = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const result = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (result) {
|
||||
console.log(`✅ 成功更改固定图层 "${fixedLayerId}" 的图像`);
|
||||
@@ -192,9 +183,7 @@ export async function addImageToLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const resultLayerId = await layerManager.commandManager.execute(
|
||||
addImageToLayerCmd
|
||||
);
|
||||
const resultLayerId = await layerManager.commandManager.execute(addImageToLayerCmd);
|
||||
|
||||
if (resultLayerId) {
|
||||
if (targetLayerId) {
|
||||
@@ -219,10 +208,7 @@ export async function addImageToLayer({
|
||||
* @param {Object} options - 配置选项
|
||||
* @returns {Promise<string>} 新图层ID的Promise
|
||||
*/
|
||||
export function loadImageUrlToLayer(
|
||||
{ imageUrl, layerManager, canvas, toolManager },
|
||||
options = {}
|
||||
) {
|
||||
export function loadImageUrlToLayer({ imageUrl, layerManager, canvas, toolManager }, options = {}) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
if (!imageUrl || !layerManager || !canvas) {
|
||||
reject(new Error("参数无效"));
|
||||
@@ -231,9 +217,7 @@ export function loadImageUrlToLayer(
|
||||
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -304,9 +288,7 @@ export function uploadImageAndCreateLayer(
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找背景图层以获取尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
// 设置最大宽高为背景图层的尺寸
|
||||
const maxWidth = bgLayer?.canvasWidth || canvas.width;
|
||||
@@ -361,9 +343,7 @@ export function safeLoadImage(imageSource, options = {}) {
|
||||
.then(resolve)
|
||||
.catch((error) => {
|
||||
if (attempt < retries) {
|
||||
console.warn(
|
||||
`图片加载失败,正在重试 (${attempt + 1}/${retries})...`
|
||||
);
|
||||
console.warn(`图片加载失败,正在重试 (${attempt + 1}/${retries})...`);
|
||||
setTimeout(() => attemptLoad(attempt + 1), 500);
|
||||
} else {
|
||||
reject(error);
|
||||
@@ -426,12 +406,7 @@ export function loadImageAndChangeFixedLayer({
|
||||
* @param {Object} options.imageOptions - 图片加载选项
|
||||
* @returns {Promise<string>} 新图像对象ID的Promise
|
||||
*/
|
||||
export function uploadImageAndChangeFixedLayer({
|
||||
file,
|
||||
layerManager,
|
||||
layerId,
|
||||
imageOptions = {},
|
||||
}) {
|
||||
export function uploadImageAndChangeFixedLayer({ file, layerManager, layerId, imageOptions = {} }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!file || !layerManager || !layerId) {
|
||||
reject(new Error("参数无效:需要文件、图层管理器和图层ID"));
|
||||
@@ -449,9 +424,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
reader.onload = async (e) => {
|
||||
try {
|
||||
// 查找目标固定图层以获取尺寸信息
|
||||
const targetLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.id === layerId
|
||||
);
|
||||
const targetLayer = layerManager.layers.value.find((layer) => layer.id === layerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
throw new Error(`找不到图层 ID: ${layerId}`);
|
||||
@@ -463,9 +436,7 @@ export function uploadImageAndChangeFixedLayer({
|
||||
}
|
||||
|
||||
// 查找背景图层以获取画布尺寸
|
||||
const bgLayer = layerManager.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const bgLayer = layerManager.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
const maxWidth = bgLayer?.canvasWidth || layerManager.canvas.width;
|
||||
const maxHeight = bgLayer?.canvasHeight || layerManager.canvas.height;
|
||||
@@ -490,14 +461,10 @@ export function uploadImageAndChangeFixedLayer({
|
||||
});
|
||||
|
||||
// 通过命令管理器执行
|
||||
const newImageId = await layerManager.commandManager.execute(
|
||||
changeFixedImageCmd
|
||||
);
|
||||
const newImageId = await layerManager.commandManager.execute(changeFixedImageCmd);
|
||||
|
||||
if (newImageId) {
|
||||
console.log(
|
||||
`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`
|
||||
);
|
||||
console.log(`✅ 成功更改固定图层 "${targetLayer.name}" 的图像,新图像ID: ${newImageId}`);
|
||||
resolve(newImageId);
|
||||
} else {
|
||||
throw new Error("更改固定图层图像失败");
|
||||
@@ -826,9 +793,7 @@ export class AdvancedImageManager {
|
||||
total: imageUrls.length,
|
||||
successful: results.filter((r) => r.success).length,
|
||||
failed: results.filter((r) => !r.success).length,
|
||||
cacheHitRate:
|
||||
this.performanceMetrics.cacheHits /
|
||||
this.performanceMetrics.imageLoads,
|
||||
cacheHitRate: this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads,
|
||||
averageLoadTime: this.performanceMetrics.averageLoadTime,
|
||||
},
|
||||
};
|
||||
@@ -884,13 +849,10 @@ export class AdvancedImageManager {
|
||||
// 立即执行模式
|
||||
for (const operation of operations) {
|
||||
try {
|
||||
const result = await this.canvasManager.changeFixedImage(
|
||||
operation.imageUrl,
|
||||
{
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
}
|
||||
);
|
||||
const result = await this.canvasManager.changeFixedImage(operation.imageUrl, {
|
||||
targetLayerType: operation.layerType,
|
||||
...operation.options,
|
||||
});
|
||||
operationResults.push({ success: true, ...result, operation });
|
||||
} catch (error) {
|
||||
operationResults.push({
|
||||
@@ -1038,10 +1000,7 @@ export class AdvancedImageManager {
|
||||
}
|
||||
|
||||
// 替换模板中的变量
|
||||
const operations = this.replaceTemplateVariables(
|
||||
template.operations,
|
||||
variables
|
||||
);
|
||||
const operations = this.replaceTemplateVariables(template.operations, variables);
|
||||
|
||||
// 执行操作
|
||||
const result = await this.batchAddImagesToLayers(operations);
|
||||
@@ -1141,8 +1100,7 @@ export class AdvancedImageManager {
|
||||
this.performanceMetrics.imageLoads++;
|
||||
this.performanceMetrics.totalLoadTime += loadTime;
|
||||
this.performanceMetrics.averageLoadTime =
|
||||
this.performanceMetrics.totalLoadTime /
|
||||
this.performanceMetrics.imageLoads;
|
||||
this.performanceMetrics.totalLoadTime / this.performanceMetrics.imageLoads;
|
||||
}
|
||||
|
||||
getSummary(results) {
|
||||
@@ -1170,12 +1128,9 @@ export class AdvancedImageManager {
|
||||
// 替换字符串中的变量 {{variable}}
|
||||
Object.keys(newOp).forEach((key) => {
|
||||
if (typeof newOp[key] === "string") {
|
||||
newOp[key] = newOp[key].replace(
|
||||
/\{\{(\w+)\}\}/g,
|
||||
(match, varName) => {
|
||||
return variables[varName] || match;
|
||||
}
|
||||
);
|
||||
newOp[key] = newOp[key].replace(/\{\{(\w+)\}\}/g, (match, varName) => {
|
||||
return variables[varName] || match;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1223,10 +1178,7 @@ export class AdvancedImageManager {
|
||||
generatePerformanceRecommendations() {
|
||||
const recommendations = [];
|
||||
|
||||
if (
|
||||
this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads <
|
||||
0.3
|
||||
) {
|
||||
if (this.performanceMetrics.cacheHits / this.performanceMetrics.imageLoads < 0.3) {
|
||||
recommendations.push("考虑增加缓存大小以提高缓存命中率");
|
||||
}
|
||||
|
||||
@@ -1298,12 +1250,7 @@ export const ImageUtils = {
|
||||
* @param {string} targetLayerId - 目标图层ID (可选)
|
||||
* @returns {Promise<Object>} 执行结果
|
||||
*/
|
||||
quickAddImageToLayer: (
|
||||
file,
|
||||
layerManager,
|
||||
toolManager,
|
||||
targetLayerId = null
|
||||
) => {
|
||||
quickAddImageToLayer: (file, layerManager, toolManager, targetLayerId = null) => {
|
||||
return uploadImageAndAddToLayerSimple({
|
||||
file,
|
||||
layerManager,
|
||||
@@ -1376,12 +1323,7 @@ export function rasterizeCanvasObjects(options = {}) {
|
||||
function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const {
|
||||
trimWhitespace = true,
|
||||
trimPadding = 10,
|
||||
quality = 1,
|
||||
format = "png",
|
||||
} = options;
|
||||
const { trimWhitespace = true, trimPadding = 10, quality = 1, format = "png" } = options;
|
||||
|
||||
// 保存原始状态
|
||||
const originalObjects = canvas.getObjects();
|
||||
@@ -1389,9 +1331,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const originalZoom = canvas.getZoom();
|
||||
|
||||
// 临时隐藏其他对象,只显示要栅格化的对象
|
||||
const objectsToHide = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToHide = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
|
||||
// 隐藏不需要的对象
|
||||
objectsToHide.forEach((obj) => {
|
||||
@@ -1415,9 +1355,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
const pixelRatio = canvas.getRetinaScaling();
|
||||
|
||||
// 复制画布元素(这会保持所有变换状态)
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(
|
||||
canvas.lowerCanvasEl
|
||||
);
|
||||
const copiedCanvas = fabric.util.copyCanvasElement(canvas.lowerCanvasEl);
|
||||
|
||||
let finalCanvas = copiedCanvas;
|
||||
let trimOffset = { x: 0, y: 0 };
|
||||
@@ -1496,7 +1434,7 @@ function _rasterizeUsingCanvasCopy(canvas, objects, options = {}) {
|
||||
*/
|
||||
function _restoreObjectVisibility(objects) {
|
||||
objects.forEach((obj) => {
|
||||
if (obj.hasOwnProperty("_originalVisible")) {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, "_originalVisible")) {
|
||||
obj.set("visible", obj._originalVisible);
|
||||
delete obj._originalVisible;
|
||||
}
|
||||
@@ -1521,9 +1459,7 @@ function _rasterizeUsingDataURL(canvas, objects, options = {}) {
|
||||
const originalObjects = canvas.getObjects();
|
||||
|
||||
// 临时移除其他对象
|
||||
const objectsToRemove = originalObjects.filter(
|
||||
(obj) => !objects.includes(obj)
|
||||
);
|
||||
const objectsToRemove = originalObjects.filter((obj) => !objects.includes(obj));
|
||||
objectsToRemove.forEach((obj) => {
|
||||
canvas.remove(obj);
|
||||
});
|
||||
@@ -1770,12 +1706,7 @@ function _trimCanvas(canvas, padding = 0) {
|
||||
trimmedCanvas.height = trimHeight;
|
||||
|
||||
// 复制裁剪区域(包含padding)
|
||||
const trimmedImageData = ctx.getImageData(
|
||||
paddedMinX,
|
||||
paddedMinY,
|
||||
trimWidth,
|
||||
trimHeight
|
||||
);
|
||||
const trimmedImageData = ctx.getImageData(paddedMinX, paddedMinY, trimWidth, trimHeight);
|
||||
trimmedCtx.putImageData(trimmedImageData, 0, 0);
|
||||
|
||||
return {
|
||||
@@ -1955,8 +1886,7 @@ export function smartRasterize(options = {}) {
|
||||
function _analyzeRasterizationContext(canvas, objects) {
|
||||
const zoom = canvas.getZoom();
|
||||
const viewportTransform = canvas.viewportTransform;
|
||||
const hasTransform =
|
||||
zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
const hasTransform = zoom !== 1 || viewportTransform[4] !== 0 || viewportTransform[5] !== 0;
|
||||
|
||||
// 分析对象类型分布
|
||||
const objectTypes = objects.reduce((acc, obj) => {
|
||||
@@ -1971,9 +1901,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
// 计算画布利用率
|
||||
const canvasArea = canvas.width * canvas.height;
|
||||
const objectsBounds = _calculateObjectsBounds(objects);
|
||||
const objectsArea = objectsBounds
|
||||
? objectsBounds.width * objectsBounds.height
|
||||
: 0;
|
||||
const objectsArea = objectsBounds ? objectsBounds.width * objectsBounds.height : 0;
|
||||
const utilization = objectsArea / canvasArea;
|
||||
|
||||
return {
|
||||
@@ -1985,9 +1913,7 @@ function _analyzeRasterizationContext(canvas, objects) {
|
||||
utilization,
|
||||
canvasSize: { width: canvas.width, height: canvas.height },
|
||||
objectsBounds,
|
||||
supportsCanvasCopy: !!(
|
||||
fabric.util.copyCanvasElement && canvas.lowerCanvasEl
|
||||
),
|
||||
supportsCanvasCopy: !!(fabric.util.copyCanvasElement && canvas.lowerCanvasEl),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2223,12 +2149,7 @@ function _getAlternativeMethods(analysis) {
|
||||
* @param {number} params.canvasWidth - 画布宽度
|
||||
* @param {number} params.canvasHeight - 画布高度
|
||||
*/
|
||||
export const imageModeHandler = ({
|
||||
imageMode,
|
||||
newImage,
|
||||
canvasWidth,
|
||||
canvasHeight,
|
||||
}) => {
|
||||
export const imageModeHandler = ({ imageMode, newImage, canvasWidth, canvasHeight }) => {
|
||||
switch (imageMode) {
|
||||
case "stretch":
|
||||
// 拉伸模式 - 填充整个画布
|
||||
@@ -2253,10 +2174,8 @@ export const imageModeHandler = ({
|
||||
// 这里可以添加裁剪逻辑,如果需要的话
|
||||
// 例如使用fabric.Image.clipPath来裁剪图像
|
||||
break;
|
||||
case "contains":
|
||||
// 包含模式 - 保证图像在画布内完整显示
|
||||
// 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
// 图片缩放后要保证最长边能完全显示在画布内
|
||||
case "contains": {
|
||||
// 图片缩放后要保证最长边能完全显示在画布内 // 既要考虑画布的宽高比,也要考虑图像的宽高比 // 包含模式 - 保证图像在画布内完整显示
|
||||
const canvasAspect = canvasWidth / canvasHeight;
|
||||
const imageAspect = newImage.width / newImage.height;
|
||||
// 保证图像在画布内完整显示 - 既要考虑画布的宽高比,也要考虑图像的宽高比
|
||||
@@ -2269,5 +2188,6 @@ export const imageModeHandler = ({
|
||||
newImage.scaleToHeight(canvasHeight);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -95,8 +95,7 @@ export const BlendMode = {
|
||||
export function isGroupLayer(layer) {
|
||||
if (!layer) return false;
|
||||
return (
|
||||
layer.type === LayerType.GROUP ||
|
||||
(Array.isArray(layer.children) && layer.children.length > 0)
|
||||
layer.type === LayerType.GROUP || (Array.isArray(layer.children) && layer.children.length > 0)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -107,11 +106,7 @@ export function isGroupLayer(layer) {
|
||||
* @param {Object} options 其他选项
|
||||
* @returns {Object} 创建的图层对象
|
||||
*/
|
||||
export function createLayerFromFabricObject(
|
||||
fabricObject,
|
||||
layerType = "bitmap",
|
||||
options = {}
|
||||
) {
|
||||
export function createLayerFromFabricObject(fabricObject, layerType = "bitmap", options = {}) {
|
||||
if (!fabricObject) return null;
|
||||
|
||||
// 确定图层类型
|
||||
@@ -137,9 +132,7 @@ export function createLayerFromFabricObject(
|
||||
type: type,
|
||||
name:
|
||||
options.name ||
|
||||
`${
|
||||
fabricObject.type.charAt(0).toUpperCase() + fabricObject.type.slice(1)
|
||||
} 图层`,
|
||||
`${fabricObject.type.charAt(0).toUpperCase() + fabricObject.type.slice(1)} 图层`,
|
||||
parentId: options.parentId || null,
|
||||
});
|
||||
|
||||
@@ -166,9 +159,7 @@ export function createLayerFromFabricObject(
|
||||
*/
|
||||
export function createLayer(options = {}) {
|
||||
const id =
|
||||
options.id ||
|
||||
generateId("layer_") ||
|
||||
`layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
options.id || generateId("layer_") || `layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -208,8 +199,7 @@ export function createLayer(options = {}) {
|
||||
* @returns {Object} 背景图层对象
|
||||
*/
|
||||
export function createBackgroundLayer(options = {}) {
|
||||
const id =
|
||||
options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const id = options.id || `bg_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -400,9 +390,7 @@ export function createSmartObjectLayer(options = {}) {
|
||||
* @returns {Object} 固定图层对象
|
||||
*/
|
||||
export function createFixedLayer(options = {}) {
|
||||
const id =
|
||||
options.id ||
|
||||
`fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
const id = options.id || `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
return {
|
||||
id: id,
|
||||
// 图层基本属性
|
||||
@@ -445,9 +433,7 @@ export function cloneLayer(layer) {
|
||||
// 复制 fabric 对象 (如果存在)
|
||||
if (Array.isArray(layer.fabricObjects)) {
|
||||
clonedLayer.fabricObjects = layer.fabricObjects.map((obj) => {
|
||||
return obj && typeof obj.clone === "function"
|
||||
? obj.clone()
|
||||
: JSON.parse(JSON.stringify(obj));
|
||||
return obj && typeof obj.clone === "function" ? obj.clone() : JSON.parse(JSON.stringify(obj));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -15,18 +15,13 @@ export function buildLayerAssociations(layer, canvasObjects) {
|
||||
// 处理单个fabricObject关联
|
||||
if (layer.fabricObject) {
|
||||
// 如果图层已经有关联的fabricObject,确保它的layerId和layerName正确
|
||||
layer.fabricObject =
|
||||
canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
|
||||
layer.fabricObject = canvasObjects.find((obj) => obj.id === layer.fabricObject.id) || null;
|
||||
}
|
||||
|
||||
if (layer.clippingMask) {
|
||||
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
const clippingMaskObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.clippingMask.id
|
||||
);
|
||||
layer.clippingMask = clippingMaskObj
|
||||
? clippingMaskObj?.toObject?.(["id"])
|
||||
: layer.clippingMask;
|
||||
const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
|
||||
layer.clippingMask = clippingMaskObj ? clippingMaskObj?.toObject?.(["id"]) : layer.clippingMask;
|
||||
}
|
||||
|
||||
// 处理多个fabricObjects关联
|
||||
@@ -172,10 +167,7 @@ export function simplifyLayers(layers) {
|
||||
opacity: layer.opacity,
|
||||
isBackground: layer.isBackground || false,
|
||||
isFixed: layer.isFixed || false,
|
||||
clippingMask:
|
||||
layer.clippingMask?.toObject?.(["id", "layerId"]) ||
|
||||
layer.clippingMask ||
|
||||
null, // 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
clippingMask: layer.clippingMask?.toObject?.(["id", "layerId"]) || layer.clippingMask || null, // 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
// ? {
|
||||
// id: layer.clippingMask.id,
|
||||
// type: layer.clippingMask.type,
|
||||
@@ -200,10 +192,7 @@ export function simplifyLayers(layers) {
|
||||
)
|
||||
.filter((obj) => obj !== null)
|
||||
: [],
|
||||
children:
|
||||
layer.children && isArray(layer.children)
|
||||
? simplifyLayers(layer.children)
|
||||
: [],
|
||||
children: layer.children && isArray(layer.children) ? simplifyLayers(layer.children) : [],
|
||||
};
|
||||
|
||||
return simplifiedLayer;
|
||||
@@ -240,9 +229,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
|
||||
if (layer.clippingMask) {
|
||||
// clippingMask 可能是一个fabricObject或组 也可能是一个简单对象
|
||||
const clippingMaskObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.clippingMask.id
|
||||
);
|
||||
const clippingMaskObj = canvasObjects.find((obj) => obj.id === layer.clippingMask.id);
|
||||
restoredLayer.clippingMask = clippingMaskObj
|
||||
? clippingMaskObj?.toObject?.(["id"])
|
||||
: layer.clippingMask;
|
||||
@@ -250,17 +237,11 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
|
||||
// 恢复单个fabricObject关联
|
||||
if (layer.fabricObject?.id) {
|
||||
const fabricObj = canvasObjects.find(
|
||||
(obj) => obj.id === layer.fabricObject.id
|
||||
);
|
||||
const fabricObj = canvasObjects.find((obj) => obj.id === layer.fabricObject.id);
|
||||
if (fabricObj) {
|
||||
fabricObj.layerId = layer.id;
|
||||
fabricObj.layerName = layer.name;
|
||||
restoredLayer.fabricObject = fabricObj.toObject([
|
||||
"id",
|
||||
"layerId",
|
||||
"type",
|
||||
]);
|
||||
restoredLayer.fabricObject = fabricObj.toObject(["id", "layerId", "type"]);
|
||||
} else {
|
||||
restoredLayer.fabricObject = null;
|
||||
}
|
||||
@@ -270,9 +251,7 @@ export function restoreLayers(simplifiedLayers, canvasObjects) {
|
||||
if (layer.fabricObjects && isArray(layer.fabricObjects)) {
|
||||
restoredLayer.fabricObjects = layer.fabricObjects
|
||||
.map((fabricRef) => {
|
||||
const fabricObj = canvasObjects.find(
|
||||
(obj) => obj.id === fabricRef.id
|
||||
);
|
||||
const fabricObj = canvasObjects.find((obj) => obj.id === fabricRef.id);
|
||||
if (fabricObj) {
|
||||
fabricObj.layerId = layer.id;
|
||||
fabricObj.layerName = layer.name;
|
||||
|
||||
@@ -18,10 +18,8 @@ export async function restoreFabricObject(serializedObject, canvas) {
|
||||
|
||||
// 恢复自定义属性
|
||||
if (serializedObject.id) fabricObject.id = serializedObject.id;
|
||||
if (serializedObject.layerId)
|
||||
fabricObject.layerId = serializedObject.layerId;
|
||||
if (serializedObject.layerName)
|
||||
fabricObject.layerName = serializedObject.layerName;
|
||||
if (serializedObject.layerId) fabricObject.layerId = serializedObject.layerId;
|
||||
if (serializedObject.layerName) fabricObject.layerName = serializedObject.layerName;
|
||||
|
||||
// 更新坐标
|
||||
fabricObject.setCoords();
|
||||
|
||||
@@ -22,11 +22,7 @@ export const createRasterizedImage = async ({
|
||||
isGroupWithMask = false, // 是否为带遮罩的组图层
|
||||
} = {}) => {
|
||||
try {
|
||||
console.log(
|
||||
`📊 开始栅格化 ${fabricObjects.length} 个对象${
|
||||
maskObject ? "(带遮罩)" : ""
|
||||
}`
|
||||
);
|
||||
console.log(`📊 开始栅格化 ${fabricObjects.length} 个对象${maskObject ? "(带遮罩)" : ""}`);
|
||||
|
||||
// 确保有对象需要栅格化
|
||||
if (fabricObjects.length === 0) {
|
||||
@@ -36,10 +32,7 @@ export const createRasterizedImage = async ({
|
||||
|
||||
// 高清倍数
|
||||
const currentZoom = canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
|
||||
|
||||
if (isThumbnail) scaleFactor = 0.2; // 缩略图使用较小的高清倍数
|
||||
|
||||
@@ -191,9 +184,7 @@ const createRasterizedImageWithGroup = async ({
|
||||
hasControls: false,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 调整组的位置,让它位于画布的左上角
|
||||
group.set({
|
||||
@@ -317,9 +308,7 @@ const createRasterizedImageWithMask = async ({
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 调整对象位置,相对于遮罩边界重新定位
|
||||
clonedObjects.forEach((obj) => {
|
||||
|
||||
@@ -85,18 +85,12 @@ const createClippedObjects = async ({
|
||||
console.log("🎯 使用新的图像遮罩裁剪方法创建对象");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
||||
console.log("📐 优化后的选区边界框:", optimizedBounds);
|
||||
|
||||
// 获取羽化值
|
||||
let featherAmount = 0;
|
||||
if (
|
||||
selectionManager &&
|
||||
typeof selectionManager.getFeatherAmount === "function"
|
||||
) {
|
||||
if (selectionManager && typeof selectionManager.getFeatherAmount === "function") {
|
||||
featherAmount = selectionManager.getFeatherAmount();
|
||||
console.log(`🌟 应用羽化效果: ${featherAmount}px`);
|
||||
}
|
||||
@@ -177,10 +171,7 @@ const createClippedDataURLByCanvas = async ({
|
||||
console.log("🖼️ 使用图像遮罩裁剪方法生成DataURL");
|
||||
|
||||
// 使用优化后的边界计算,确保包含描边区域
|
||||
const optimizedBounds = calculateOptimizedBounds(
|
||||
clippingObject,
|
||||
fabricObjects
|
||||
);
|
||||
const optimizedBounds = calculateOptimizedBounds(clippingObject, fabricObjects);
|
||||
|
||||
// 使用高分辨率以保证质量
|
||||
const pixelRatio = window.devicePixelRatio || 1;
|
||||
@@ -239,13 +230,7 @@ const createClippedDataURLByCanvas = async ({
|
||||
* 创建简单克隆对象
|
||||
* 当不需要裁剪时,直接克隆原对象
|
||||
*/
|
||||
const createSimpleClone = async ({
|
||||
canvas,
|
||||
fabricObjects,
|
||||
isReturenDataURL,
|
||||
quality,
|
||||
format,
|
||||
}) => {
|
||||
const createSimpleClone = async ({ canvas, fabricObjects, isReturenDataURL, quality, format }) => {
|
||||
try {
|
||||
console.log("📋 创建简单克隆对象");
|
||||
|
||||
@@ -459,10 +444,7 @@ const createLegacyRasterization = async ({
|
||||
|
||||
// 这里保留原有的离屏渲染逻辑作为备选方案
|
||||
const currentZoom = canvas.getZoom?.() || 1;
|
||||
scaleFactor = Math.max(
|
||||
scaleFactor || canvas?.getRetinaScaling?.(),
|
||||
currentZoom
|
||||
);
|
||||
scaleFactor = Math.max(scaleFactor || canvas?.getRetinaScaling?.(), currentZoom);
|
||||
scaleFactor = Math.min(scaleFactor, 3);
|
||||
|
||||
const { absoluteBounds, relativeBounds } = calculateBounds(fabricObjects);
|
||||
@@ -592,9 +574,7 @@ const createOffscreenRasterization = async ({
|
||||
height: canvasHeight,
|
||||
});
|
||||
|
||||
console.log(
|
||||
`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`
|
||||
);
|
||||
console.log(`🎨 离屏画布尺寸: ${canvasWidth}x${canvasHeight}, 缩放: ${scaleFactor}`);
|
||||
|
||||
// 克隆对象到离屏画布
|
||||
const clonedObjects = [];
|
||||
@@ -749,11 +729,7 @@ export const getObjectsBounds = (fabricObjects) => {
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 遮罩图像的DataURL
|
||||
*/
|
||||
const createMaskImageFromPath = async ({
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
const createMaskImageFromPath = async ({ clippingObject, selectionBounds, qualityMultiplier }) => {
|
||||
try {
|
||||
console.log("🎭 创建路径遮罩图像");
|
||||
|
||||
@@ -769,11 +745,7 @@ const createMaskImageFromPath = async ({
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
||||
|
||||
// 添加路径到遮罩画布
|
||||
maskCanvas.add(maskPath);
|
||||
@@ -804,11 +776,7 @@ const createMaskImageFromPath = async ({
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<String>} 内容图像的DataURL
|
||||
*/
|
||||
const renderContentToImage = async ({
|
||||
fabricObjects,
|
||||
selectionBounds,
|
||||
qualityMultiplier,
|
||||
}) => {
|
||||
const renderContentToImage = async ({ fabricObjects, selectionBounds, qualityMultiplier }) => {
|
||||
try {
|
||||
console.log("🖼️ 渲染内容图像");
|
||||
|
||||
@@ -964,11 +932,7 @@ const createAdvancedMaskImage = async ({
|
||||
});
|
||||
|
||||
// 克隆路径对象并处理描边转填充
|
||||
const maskPath = await createSolidMaskPath(
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
);
|
||||
const maskPath = await createSolidMaskPath(clippingObject, selectionBounds, qualityMultiplier);
|
||||
|
||||
// 如果有羽化值,添加模糊效果
|
||||
if (featherAmount > 0) {
|
||||
@@ -987,10 +951,7 @@ const createAdvancedMaskImage = async ({
|
||||
|
||||
// 如果有羽化,需要进行后处理
|
||||
if (featherAmount > 0) {
|
||||
return await applyCanvasBlur(
|
||||
maskCanvas,
|
||||
featherAmount * qualityMultiplier
|
||||
);
|
||||
return await applyCanvasBlur(maskCanvas, featherAmount * qualityMultiplier);
|
||||
}
|
||||
|
||||
// 生成遮罩图像
|
||||
@@ -1066,11 +1027,7 @@ const applyCanvasBlur = async (canvas, blurAmount) => {
|
||||
* @param {Number} qualityMultiplier 质量倍数
|
||||
* @returns {Promise<fabric.Object>} 处理后的遮罩路径对象
|
||||
*/
|
||||
const createSolidMaskPath = async (
|
||||
clippingObject,
|
||||
selectionBounds,
|
||||
qualityMultiplier
|
||||
) => {
|
||||
const createSolidMaskPath = async (clippingObject, selectionBounds, qualityMultiplier) => {
|
||||
try {
|
||||
console.log("🔧 创建实体遮罩路径,处理描边转填充");
|
||||
|
||||
@@ -1081,29 +1038,19 @@ const createSolidMaskPath = async (
|
||||
const hasStroke = maskPath.stroke && maskPath.strokeWidth > 0;
|
||||
|
||||
if (hasStroke) {
|
||||
console.log(
|
||||
`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`
|
||||
);
|
||||
console.log(`📏 检测到描边: ${maskPath.stroke}, 宽度: ${maskPath.strokeWidth}`);
|
||||
|
||||
// 对于有描边的路径,我们需要更精确的处理
|
||||
const strokeWidth = maskPath.strokeWidth;
|
||||
|
||||
// 方法1: 如果是简单的几何形状(矩形、圆形等),可以通过调整尺寸来补偿描边
|
||||
if (
|
||||
maskPath.type === "rect" ||
|
||||
maskPath.type === "circle" ||
|
||||
maskPath.type === "ellipse"
|
||||
) {
|
||||
if (maskPath.type === "rect" || maskPath.type === "circle" || maskPath.type === "ellipse") {
|
||||
// 对于矩形和椭圆,增加宽高来包含描边
|
||||
const strokeOffset = strokeWidth;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset / 2) *
|
||||
qualityMultiplier,
|
||||
left: (maskPath.left - selectionBounds.left - strokeOffset / 2) * qualityMultiplier,
|
||||
top: (maskPath.top - selectionBounds.top - strokeOffset / 2) * qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier,
|
||||
width: (maskPath.width || 0) + strokeOffset,
|
||||
@@ -1122,12 +1069,8 @@ const createSolidMaskPath = async (
|
||||
const strokeOffset = strokeWidth / 2;
|
||||
|
||||
maskPath.set({
|
||||
left:
|
||||
(maskPath.left - selectionBounds.left - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
top:
|
||||
(maskPath.top - selectionBounds.top - strokeOffset) *
|
||||
qualityMultiplier,
|
||||
left: (maskPath.left - selectionBounds.left - strokeOffset) * qualityMultiplier,
|
||||
top: (maskPath.top - selectionBounds.top - strokeOffset) * qualityMultiplier,
|
||||
scaleX: (maskPath.scaleX || 1) * qualityMultiplier * expandRatio,
|
||||
scaleY: (maskPath.scaleY || 1) * qualityMultiplier * expandRatio,
|
||||
fill: "#ffffff",
|
||||
|
||||
Reference in New Issue
Block a user