feat: 裁剪组裁剪跟随选择组移动
This commit is contained in:
@@ -97,8 +97,7 @@ export class BrushIndicator {
|
||||
if (!this.staticCanvas || !this.canvas) return;
|
||||
|
||||
// 检查是否为笔刷或橡皮擦模式,非相关模式直接返回
|
||||
const isBrushMode =
|
||||
this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
|
||||
const isBrushMode = this.canvas.isDrawingMode && this.canvas.freeDrawingBrush;
|
||||
const isEraserMode =
|
||||
this.canvas.isDrawingMode &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
@@ -136,10 +135,7 @@ export class BrushIndicator {
|
||||
const currentVpt = this.canvas.viewportTransform;
|
||||
if (
|
||||
currentVpt &&
|
||||
!this._areViewportTransformsEqual(
|
||||
currentVpt,
|
||||
this._lastCanvasState.viewportTransform
|
||||
)
|
||||
!this._areViewportTransformsEqual(currentVpt, this._lastCanvasState.viewportTransform)
|
||||
) {
|
||||
this.staticCanvas.setViewportTransform([...currentVpt]);
|
||||
this._lastCanvasState.viewportTransform = [...currentVpt];
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import { fabric } from "fabric-with-all";
|
||||
import initAligningGuidelines, {
|
||||
initCenteringGuidelines,
|
||||
} from "../utils/helperLine";
|
||||
import initAligningGuidelines, { initCenteringGuidelines } from "../utils/helperLine";
|
||||
import { ThumbnailManager } from "./ThumbnailManager";
|
||||
import { ExportManager } from "./ExportManager";
|
||||
import {
|
||||
@@ -16,11 +14,7 @@ import { CanvasEventManager } from "./events/CanvasEventManager";
|
||||
import CanvasConfig from "../config/canvasConfig";
|
||||
import { RedGreenModeManager } from "./RedGreenModeManager";
|
||||
import { EraserStateManager } from "./EraserStateManager";
|
||||
import {
|
||||
deepClone,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { deepClone, generateId, optimizeCanvasRendering } from "../utils/helper";
|
||||
import { ChangeFixedImageCommand } from "../commands/ObjectLayerCommands";
|
||||
import { isFunction } from "lodash-es";
|
||||
import {
|
||||
@@ -155,10 +149,7 @@ export class CanvasManager {
|
||||
return false;
|
||||
};
|
||||
|
||||
this.eraserStateManager = new EraserStateManager(
|
||||
this.canvas,
|
||||
this.layerManager
|
||||
);
|
||||
this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
|
||||
|
||||
// 监听擦除开始事件
|
||||
this.canvas.on("erasing:start", () => {
|
||||
@@ -179,17 +170,12 @@ export class CanvasManager {
|
||||
}
|
||||
|
||||
// 更新交互性
|
||||
command &&
|
||||
(await this.layerManager?.updateLayersObjectsInteractivity?.());
|
||||
command && (await this.layerManager?.updateLayersObjectsInteractivity?.());
|
||||
|
||||
this.thumbnailManager?.generateLayerThumbnail(
|
||||
this.layerManager?.activeLayerId?.value
|
||||
);
|
||||
this.thumbnailManager?.generateLayerThumbnail(this.layerManager?.activeLayerId?.value);
|
||||
|
||||
// 固定图层 的擦除也需要重新生成缩略图 要判断 当前固定图层是否锁定
|
||||
const fixedLayer = this.layers?.value?.find(
|
||||
(layer) => layer.isFixed && !layer.locked
|
||||
);
|
||||
const fixedLayer = this.layers?.value?.find((layer) => layer.isFixed && !layer.locked);
|
||||
// 如果有固定图层且未锁定,则生成缩略图
|
||||
fixedLayer &&
|
||||
this.isFixedErasable &&
|
||||
@@ -378,9 +364,7 @@ export class CanvasManager {
|
||||
// 设置固定图层的可擦除状态
|
||||
layer.locked = flag;
|
||||
// 更新画布对象的erasable属性
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((obj) => obj.id === layer.id);
|
||||
const fabricObject = this.canvas.getObjects().find((obj) => obj.id === layer.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.erasable = flag;
|
||||
fabricObject.set("erasable", flag);
|
||||
@@ -492,9 +476,7 @@ export class CanvasManager {
|
||||
const deltaY = backgroundObject.top - backgroundOldTop;
|
||||
|
||||
// 将相同的偏移量应用到所有其他对象上
|
||||
const otherObjects = visibleObjects.filter(
|
||||
(obj) => obj !== backgroundObject
|
||||
);
|
||||
const otherObjects = visibleObjects.filter((obj) => obj !== backgroundObject);
|
||||
|
||||
otherObjects.forEach((obj) => {
|
||||
obj.set({
|
||||
@@ -597,8 +579,7 @@ export class CanvasManager {
|
||||
if (!backgroundLayerObject) return;
|
||||
|
||||
const bgWidth = backgroundLayerObject.width * backgroundLayerObject.scaleX;
|
||||
const bgHeight =
|
||||
backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
||||
const bgHeight = backgroundLayerObject.height * backgroundLayerObject.scaleY;
|
||||
const left = backgroundLayerObject.left;
|
||||
const top = backgroundLayerObject.top;
|
||||
|
||||
@@ -659,9 +640,7 @@ export class CanvasManager {
|
||||
return obj.isBackground || obj.id === backgroundLayerId;
|
||||
});
|
||||
if (!backgroundLayerByBgLayer) {
|
||||
console.warn(
|
||||
"CanvasManager.js = >getBackgroundLayer 方法没有找到背景层"
|
||||
);
|
||||
console.warn("CanvasManager.js = >getBackgroundLayer 方法没有找到背景层");
|
||||
}
|
||||
|
||||
return backgroundLayerByBgLayer;
|
||||
@@ -671,8 +650,7 @@ export class CanvasManager {
|
||||
* @param {Object} backgroundLayerObject 背景层对象
|
||||
*/
|
||||
updateMaskPosition(backgroundLayerObject) {
|
||||
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath)
|
||||
return;
|
||||
if (!backgroundLayerObject || !this.maskLayer || !this.canvas.clipPath) return;
|
||||
|
||||
const left = backgroundLayerObject.left;
|
||||
const top = backgroundLayerObject.top;
|
||||
@@ -737,8 +715,7 @@ export class CanvasManager {
|
||||
...options,
|
||||
});
|
||||
|
||||
command.undoable =
|
||||
options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
||||
command.undoable = options.undoable !== undefined ? options.undoable : false; // 默认不可撤销 undoable = true 为可撤销
|
||||
|
||||
const result = (await command?.execute?.()) || {
|
||||
success: false,
|
||||
@@ -785,9 +762,7 @@ export class CanvasManager {
|
||||
...options,
|
||||
// 如果没有明确指定,则根据当前模式自动设置
|
||||
restoreOpacityInRedGreen:
|
||||
options.restoreOpacityInRedGreen !== undefined
|
||||
? options.restoreOpacityInRedGreen
|
||||
: true, // 默认在红绿图模式下恢复透明度
|
||||
options.restoreOpacityInRedGreen !== undefined ? options.restoreOpacityInRedGreen : true, // 默认在红绿图模式下恢复透明度
|
||||
};
|
||||
|
||||
// 如果在红绿图模式下且没有指定具体的图层,自动包含所有普通图层
|
||||
@@ -801,9 +776,7 @@ export class CanvasManager {
|
||||
// 获取所有非背景、非固定的普通图层ID
|
||||
const normalLayerIds =
|
||||
this.layers?.value
|
||||
?.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed && layer.visible
|
||||
)
|
||||
?.filter((layer) => !layer.isBackground && !layer.isFixed && layer.visible)
|
||||
?.map((layer) => layer.id) || [];
|
||||
|
||||
if (normalLayerIds.length > 0) {
|
||||
@@ -912,9 +885,11 @@ export class CanvasManager {
|
||||
// });
|
||||
// };
|
||||
try {
|
||||
const simplifyLayersData = simplifyLayers(
|
||||
JSON.parse(JSON.stringify(this.layers.value))
|
||||
);
|
||||
// 清除画布中选中状态
|
||||
this.canvas.discardActiveObject();
|
||||
this.canvas.renderAll();
|
||||
|
||||
const simplifyLayersData = simplifyLayers(JSON.parse(JSON.stringify(this.layers.value)));
|
||||
console.log("获取画布JSON数据...", simplifyLayersData);
|
||||
return JSON.stringify({
|
||||
canvas: this.canvas.toJSON([
|
||||
@@ -962,6 +937,7 @@ export class CanvasManager {
|
||||
this.canvasHeight.value = parsedJson.canvasHeight || this.height;
|
||||
this.canvasColor.value = parsedJson.canvasColor || this.backgroundColor;
|
||||
|
||||
// eslint-disable-next-line no-async-promise-executor
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const tempLayers = JSON.parse(parsedJson?.layers) || [];
|
||||
const canvasData = parsedJson?.canvas;
|
||||
@@ -1023,10 +999,7 @@ export class CanvasManager {
|
||||
// 重置画布数据
|
||||
this.setCanvasSize(this.canvas.width, this.canvas.height);
|
||||
// 重新构建对象关系
|
||||
restoreObjectLayerAssociations(
|
||||
this.layers.value,
|
||||
this.canvas.getObjects()
|
||||
);
|
||||
restoreObjectLayerAssociations(this.layers.value, this.canvas.getObjects());
|
||||
// 验证图层关联关系 - 稳定后可以注释
|
||||
const isValidate = validateLayerAssociations(
|
||||
this.layers.value,
|
||||
@@ -1038,8 +1011,7 @@ export class CanvasManager {
|
||||
// 使用LayerSort工具重新排列画布对象(如果可用)
|
||||
await this?.layerManager?.layerSort?.rearrangeObjects();
|
||||
|
||||
this.layerManager.activeLayerId.value = this.layers.value[0]
|
||||
.children?.length
|
||||
this.layerManager.activeLayerId.value = this.layers.value[0].children?.length
|
||||
? this.layers.value[0].children[0].id
|
||||
: this.layers.value[0]?.id || parsedJson?.activeLayerId || null;
|
||||
|
||||
@@ -1052,9 +1024,7 @@ export class CanvasManager {
|
||||
await calllBack?.();
|
||||
|
||||
// 确保所有对象的交互性正确设置
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(
|
||||
false
|
||||
);
|
||||
await this.layerManager?.updateLayersObjectsInteractivity?.(false);
|
||||
console.log(this.layerManager.layers.value);
|
||||
|
||||
// 更新所有缩略图
|
||||
@@ -1211,9 +1181,7 @@ export class CanvasManager {
|
||||
if (!this.layers || !this.layers.value) return [];
|
||||
|
||||
// 查找所有非背景、非固定的普通图层
|
||||
const normalLayers = this.layers.value.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = this.layers.value.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
// 收集所有普通图层中的对象
|
||||
const objects = [];
|
||||
@@ -1250,15 +1218,9 @@ export class CanvasManager {
|
||||
|
||||
// 比较尺寸(允许5%的误差)
|
||||
const sizeMatch =
|
||||
Math.abs(
|
||||
obj.width * obj.scaleX -
|
||||
fixedLayerObject.width * fixedLayerObject.scaleX
|
||||
) <
|
||||
Math.abs(obj.width * obj.scaleX - fixedLayerObject.width * fixedLayerObject.scaleX) <
|
||||
fixedLayerObject.width * fixedLayerObject.scaleX * 0.05 &&
|
||||
Math.abs(
|
||||
obj.height * obj.scaleY -
|
||||
fixedLayerObject.height * fixedLayerObject.scaleY
|
||||
) <
|
||||
Math.abs(obj.height * obj.scaleY - fixedLayerObject.height * fixedLayerObject.scaleY) <
|
||||
fixedLayerObject.height * fixedLayerObject.scaleY * 0.05;
|
||||
|
||||
// 比较位置(允许一定的偏差)
|
||||
|
||||
@@ -86,12 +86,7 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
_exportSpecificLayer(
|
||||
layerId,
|
||||
expPicType,
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
_exportSpecificLayer(layerId, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
|
||||
if (!this.layerManager) {
|
||||
throw new Error("图层管理器未初始化");
|
||||
}
|
||||
@@ -115,19 +110,11 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,19 +155,11 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -215,22 +194,14 @@ export class ExportManager {
|
||||
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
let canvasClipPath = this.canvas.clipPath;
|
||||
if (isCropByBg) {
|
||||
const cropWidth =
|
||||
this.canvasManager?.canvasWidth?.value ||
|
||||
this.canvas?.canvasWidth ||
|
||||
this.canvas.width;
|
||||
this.canvasManager?.canvasWidth?.value || this.canvas?.canvasWidth || this.canvas.width;
|
||||
const cropHeight =
|
||||
this.canvasManager?.canvasHeight?.value ||
|
||||
this.canvas?.canvasHeight ||
|
||||
this.canvas.height;
|
||||
this.canvasManager?.canvasHeight?.value || this.canvas?.canvasHeight || this.canvas.height;
|
||||
canvasClipPath = new fabric.Rect({
|
||||
left: this.canvas.width / 2,
|
||||
top: this.canvas.height / 2,
|
||||
@@ -331,27 +302,14 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportObject(
|
||||
obj,
|
||||
expPicType,
|
||||
isRedGreenMode,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
async _exportObject(obj, expPicType, isRedGreenMode, restoreOpacityInRedGreen) {
|
||||
// 红绿图模式下使用固定尺寸和裁剪
|
||||
if (isRedGreenMode) {
|
||||
return this._exportWithRedGreenMode(
|
||||
[obj],
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithRedGreenMode([obj], expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 普通模式使用画布尺寸
|
||||
return this._exportWithCanvasSize(
|
||||
[obj],
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize([obj], expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -374,8 +332,7 @@ export class ExportManager {
|
||||
if (layerIdArray && !layerIdArray.includes(layer.id)) continue;
|
||||
|
||||
// 检查图层类型过滤条件
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed))
|
||||
continue;
|
||||
if (!this._shouldIncludeLayer(layer, isContainBg, isContainFixed)) continue;
|
||||
|
||||
if (layer.visible) {
|
||||
const layerObjects = this._collectObjectsFromLayer(layer);
|
||||
@@ -514,21 +471,12 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportWithRedGreenMode(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
) {
|
||||
async _exportWithRedGreenMode(objectsToExport, expPicType, restoreOpacityInRedGreen) {
|
||||
// 获取固定图层对象(衣服底图)作为参考
|
||||
const fixedLayerObject =
|
||||
this._getFixedLayerObject() ?? this.canvas.clipPath;
|
||||
const fixedLayerObject = this._getFixedLayerObject() ?? this.canvas.clipPath;
|
||||
if (!fixedLayerObject) {
|
||||
console.warn("红绿图模式下未找到固定图层对象,使用画布尺寸");
|
||||
return this._exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen
|
||||
);
|
||||
return this._exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen);
|
||||
}
|
||||
|
||||
// 获取固定图层对象的边界矩形(包含位置、尺寸、缩放等信息)
|
||||
@@ -565,10 +513,7 @@ export class ExportManager {
|
||||
// 克隆并添加所有对象到临时画布,需要调整位置相对于固定图层
|
||||
for (let i = 0; i < objectsToExport.length; i++) {
|
||||
const obj = objectsToExport[i];
|
||||
const cloned = await this._cloneObjectForExport(
|
||||
obj,
|
||||
restoreOpacityInRedGreen && true
|
||||
);
|
||||
const cloned = await this._cloneObjectForExport(obj, restoreOpacityInRedGreen && true);
|
||||
if (cloned) {
|
||||
// 调整对象位置:将原画布坐标转换为以固定图层为原点的相对坐标
|
||||
cloned.set({
|
||||
@@ -606,12 +551,7 @@ export class ExportManager {
|
||||
* @returns {String} 图片数据URL
|
||||
* @private
|
||||
*/
|
||||
async _exportWithCanvasSize(
|
||||
objectsToExport,
|
||||
expPicType,
|
||||
restoreOpacityInRedGreen,
|
||||
maskObject
|
||||
) {
|
||||
async _exportWithCanvasSize(objectsToExport, expPicType, restoreOpacityInRedGreen, maskObject) {
|
||||
// 使用当前画布尺寸
|
||||
// const canvasWidth =
|
||||
// this.canvasManager?.canvasWidth?.value || this.canvas.width;
|
||||
@@ -634,44 +574,44 @@ export class ExportManager {
|
||||
console.log("导出图片数据URL:", dataURL);
|
||||
return dataURL;
|
||||
|
||||
// 创建与画布相同尺寸的临时画布
|
||||
const scaleFactor = 2; // 高清导出
|
||||
const tempCanvas = document.createElement("canvas");
|
||||
tempCanvas.width = canvasWidth * scaleFactor;
|
||||
tempCanvas.height = canvasHeight * scaleFactor;
|
||||
tempCanvas.style.width = canvasWidth + "px";
|
||||
tempCanvas.style.height = canvasHeight + "px";
|
||||
// // 创建与画布相同尺寸的临时画布
|
||||
// const scaleFactor = 2; // 高清导出
|
||||
// const tempCanvas = document.createElement("canvas");
|
||||
// tempCanvas.width = canvasWidth * scaleFactor;
|
||||
// tempCanvas.height = canvasHeight * scaleFactor;
|
||||
// tempCanvas.style.width = canvasWidth + "px";
|
||||
// tempCanvas.style.height = canvasHeight + "px";
|
||||
|
||||
const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||||
width: canvasWidth,
|
||||
height: canvasHeight,
|
||||
backgroundColor: null,
|
||||
});
|
||||
// const tempFabricCanvas = new fabric.StaticCanvas(tempCanvas, {
|
||||
// width: canvasWidth,
|
||||
// height: canvasHeight,
|
||||
// backgroundColor: null,
|
||||
// });
|
||||
|
||||
tempFabricCanvas.enableRetinaScaling = true;
|
||||
tempFabricCanvas.imageSmoothingEnabled = true;
|
||||
tempFabricCanvas.setZoom(1);
|
||||
// tempFabricCanvas.enableRetinaScaling = true;
|
||||
// tempFabricCanvas.imageSmoothingEnabled = true;
|
||||
// tempFabricCanvas.setZoom(1);
|
||||
|
||||
try {
|
||||
// 克隆并添加所有对象到临时画布
|
||||
for (const obj of objectsToExport) {
|
||||
const cloned = await this._cloneObjectForExport(
|
||||
obj,
|
||||
restoreOpacityInRedGreen && false // 普通模式不强制恢复透明度
|
||||
);
|
||||
if (cloned) {
|
||||
tempFabricCanvas.add(cloned);
|
||||
}
|
||||
}
|
||||
// try {
|
||||
// // 克隆并添加所有对象到临时画布
|
||||
// for (const obj of objectsToExport) {
|
||||
// const cloned = await this._cloneObjectForExport(
|
||||
// obj,
|
||||
// restoreOpacityInRedGreen && false, // 普通模式不强制恢复透明度
|
||||
// );
|
||||
// if (cloned) {
|
||||
// tempFabricCanvas.add(cloned);
|
||||
// }
|
||||
// }
|
||||
|
||||
// 渲染画布
|
||||
tempFabricCanvas.renderAll();
|
||||
// // 渲染画布
|
||||
// tempFabricCanvas.renderAll();
|
||||
|
||||
// 生成图片
|
||||
return this._generateHighQualityDataURL(tempCanvas, expPicType);
|
||||
} finally {
|
||||
this._cleanupTempCanvas(tempFabricCanvas);
|
||||
}
|
||||
// // 生成图片
|
||||
// return this._generateHighQualityDataURL(tempCanvas, expPicType);
|
||||
// } finally {
|
||||
// this._cleanupTempCanvas(tempFabricCanvas);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -703,10 +643,7 @@ export class ExportManager {
|
||||
* @returns {Promise<fabric.Object>} 克隆的对象
|
||||
* @private
|
||||
*/
|
||||
_cloneObjectAsync(
|
||||
obj,
|
||||
propertiesToInclude = ["id", "layerId", "layerName", "name"]
|
||||
) {
|
||||
_cloneObjectAsync(obj, propertiesToInclude = ["id", "layerId", "layerName", "name"]) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!obj) {
|
||||
resolve(null);
|
||||
@@ -736,11 +673,7 @@ export class ExportManager {
|
||||
* @returns {Promise<Object>} 克隆的对象
|
||||
* @private
|
||||
*/
|
||||
async _cloneObjectForExport(
|
||||
obj,
|
||||
forceRestoreOpacity = false,
|
||||
removeClipPath = true
|
||||
) {
|
||||
async _cloneObjectForExport(obj, forceRestoreOpacity = false, removeClipPath = true) {
|
||||
if (!obj) return null;
|
||||
|
||||
try {
|
||||
@@ -874,11 +807,7 @@ export class ExportManager {
|
||||
}
|
||||
|
||||
// 克隆对象作为裁剪路径
|
||||
const clonedClipPath = await this._cloneObjectForExport(
|
||||
clipObject,
|
||||
false,
|
||||
false
|
||||
);
|
||||
const clonedClipPath = await this._cloneObjectForExport(clipObject, false, false);
|
||||
|
||||
if (!clonedClipPath) {
|
||||
console.warn("无法克隆裁剪对象");
|
||||
|
||||
@@ -51,24 +51,16 @@ import {
|
||||
} from "../commands/RasterizeLayerCommand";
|
||||
|
||||
// 导入图层排序相关类和混入
|
||||
import {
|
||||
LayerSort,
|
||||
createLayerSort,
|
||||
LayerSortMixin,
|
||||
LayerSortUtils,
|
||||
} from "../utils/LayerSort";
|
||||
import { LayerSort, createLayerSort, LayerSortMixin, LayerSortUtils } from "../utils/LayerSort";
|
||||
|
||||
import CanvasConfig from "../config/canvasConfig";
|
||||
import { isBoolean, template } from "lodash-es";
|
||||
import {
|
||||
findObjectById,
|
||||
generateId,
|
||||
optimizeCanvasRendering,
|
||||
} from "../utils/helper";
|
||||
import { findObjectById, generateId, optimizeCanvasRendering } from "../utils/helper";
|
||||
import { message } from "ant-design-vue";
|
||||
import { fabric } from "fabric-with-all";
|
||||
import { getOriginObjectInfo } from "../utils/layerUtils";
|
||||
import { restoreFabricObject } from "../utils/objectHelper";
|
||||
import { UpdateGroupMaskPositionCommand } from "../commands/UpdateGroupMaskPositionCommand";
|
||||
|
||||
/**
|
||||
* 图层管理器 - 负责管理画布上的所有图层
|
||||
@@ -219,11 +211,7 @@ export class LayerManager {
|
||||
// 基于 fabric-with-erasing 库的 erasable 属性设置擦除权限
|
||||
// 只有活动图层、可见、非锁定、非背景、非固定图层的对象才可擦除
|
||||
obj.erasable =
|
||||
isInActiveLayer &&
|
||||
layer.visible &&
|
||||
!layer.locked &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed;
|
||||
isInActiveLayer && layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
||||
|
||||
// 图层状态决定交互性
|
||||
if (layer.isBackground || obj.isBackground || layer.isFixed) {
|
||||
@@ -270,11 +258,7 @@ export class LayerManager {
|
||||
|
||||
if (this.isRedGreenMode) {
|
||||
// 红绿图模式下 所有普通图层都可擦除
|
||||
obj.erasable =
|
||||
layer.visible &&
|
||||
!layer.locked &&
|
||||
!layer.isBackground &&
|
||||
!layer.isFixed;
|
||||
obj.erasable = layer.visible && !layer.locked && !layer.isBackground && !layer.isFixed;
|
||||
}
|
||||
|
||||
// 平移模式下,禁用多选和擦除
|
||||
@@ -325,9 +309,7 @@ export class LayerManager {
|
||||
|
||||
// 设置子图层对象的交互性
|
||||
layer?.childLayer?.forEach(async (childLayer) => {
|
||||
const childObj = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.layerId === childLayer.id);
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
await this._setObjectInteractivity(childObj, childLayer, editorMode);
|
||||
}
|
||||
@@ -339,10 +321,7 @@ export class LayerManager {
|
||||
let clippingMaskFabricObject = null;
|
||||
if (layer.clippingMask) {
|
||||
// 反序列化 clippingMask
|
||||
clippingMaskFabricObject = await restoreFabricObject(
|
||||
layer.clippingMask,
|
||||
this.canvas
|
||||
);
|
||||
clippingMaskFabricObject = await restoreFabricObject(layer.clippingMask, this.canvas);
|
||||
clippingMaskFabricObject.clipPath = null;
|
||||
|
||||
clippingMaskFabricObject.set({
|
||||
@@ -354,9 +333,7 @@ export class LayerManager {
|
||||
// 如果是组图层 则给所有子对象设置裁剪对象
|
||||
if (layer.type === LayerType.GROUP || layer.children?.length > 0) {
|
||||
layer.children.forEach((childLayer) => {
|
||||
const childObj = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.layerId === childLayer.id);
|
||||
const childObj = this.canvas.getObjects().find((o) => o.layerId === childLayer.id);
|
||||
if (childObj) {
|
||||
childObj.clipPath = clippingMaskFabricObject;
|
||||
childObj.dirty = true; // 标记为脏对象
|
||||
@@ -365,9 +342,7 @@ export class LayerManager {
|
||||
});
|
||||
} else {
|
||||
layer.fabricObjects?.forEach((obj) => {
|
||||
const fabricObject = this.canvas
|
||||
.getObjects()
|
||||
.find((o) => o.id === obj.id);
|
||||
const fabricObject = this.canvas.getObjects().find((o) => o.id === obj.id);
|
||||
if (fabricObject) {
|
||||
fabricObject.clipPath = clippingMaskFabricObject;
|
||||
fabricObject.dirty = true; // 标记为脏对象
|
||||
@@ -447,9 +422,7 @@ export class LayerManager {
|
||||
*/
|
||||
createBackgroundLayer(name = "背景") {
|
||||
// 检查是否已有背景图层
|
||||
const hasBackgroundLayer = this.layers.value.some(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
||||
|
||||
if (hasBackgroundLayer) {
|
||||
console.warn("已存在背景层,不再创建新的背景层");
|
||||
@@ -502,9 +475,7 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 生成唯一ID
|
||||
const layerId = `fixed_layer_${Date.now()}_${Math.floor(
|
||||
Math.random() * 1000
|
||||
)}`;
|
||||
const layerId = `fixed_layer_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
// 创建固定图层
|
||||
const fixedLayer = createFixedLayer({
|
||||
@@ -564,9 +535,7 @@ export class LayerManager {
|
||||
await this.createLayer("图层 1");
|
||||
} else {
|
||||
// 检查是否已有背景层
|
||||
const hasBackgroundLayer = this.layers.value.some(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const hasBackgroundLayer = this.layers.value.some((layer) => layer.isBackground);
|
||||
|
||||
if (!hasBackgroundLayer) {
|
||||
this.createBackgroundLayer();
|
||||
@@ -612,10 +581,7 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
// 验证目标图层是否存在
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
if (!targetLayer) {
|
||||
console.error(`目标图层 ${targetLayerId} 不存在`);
|
||||
return null;
|
||||
@@ -669,8 +635,7 @@ export class LayerManager {
|
||||
*/
|
||||
removeObjectFromLayer(objectOrId) {
|
||||
// 获取对象ID
|
||||
const objectId =
|
||||
typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
||||
const objectId = typeof objectOrId === "string" ? objectOrId : objectOrId.id;
|
||||
|
||||
if (!objectId) {
|
||||
console.error("无效的对象ID");
|
||||
@@ -729,9 +694,7 @@ export class LayerManager {
|
||||
*/
|
||||
getActiveLayer() {
|
||||
if (!this.activeLayerId.value) {
|
||||
console.warn(
|
||||
"没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层"
|
||||
);
|
||||
console.warn("没有活动图层ID,无法获取活动图层 ==== 默认设置第一个图层为活动图层");
|
||||
this.activeLayerId.value = this.layers.value[0]?.id || null;
|
||||
}
|
||||
|
||||
@@ -836,13 +799,8 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
const tempFabricObject =
|
||||
fabricObject?.toObject?.([
|
||||
"id",
|
||||
"layerId",
|
||||
"layerName",
|
||||
"isBackgroud",
|
||||
"isFixed",
|
||||
]) || fabricObject;
|
||||
fabricObject?.toObject?.(["id", "layerId", "layerName", "isBackgroud", "isFixed"]) ||
|
||||
fabricObject;
|
||||
if (layer.isFixed || layer.isBackground) {
|
||||
layer.fabricObject = tempFabricObject;
|
||||
} else {
|
||||
@@ -868,9 +826,7 @@ export class LayerManager {
|
||||
|
||||
// 如果是背景层或固定层,不允许移动
|
||||
if (layer && (layer.isBackground || layer.isFixed)) {
|
||||
console.warn(
|
||||
layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动")
|
||||
);
|
||||
console.warn(layer.isBackground ? $t("背景层不可移动") : $t("固定层不可移动"));
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1000,6 +956,9 @@ export class LayerManager {
|
||||
|
||||
// 设置活动选择组的属性
|
||||
this.canvas.setActiveObject(activeSelection);
|
||||
// 为活动选择组添加移动事件监听器,用于同步更新遮罩位置
|
||||
this._setupGroupMaskMovementSync(activeSelection, layer);
|
||||
|
||||
activeSelection = null; // 清理引用,避免内存泄漏
|
||||
// 确保选择组正确渲染
|
||||
// activeSelection.setCoords();
|
||||
@@ -1234,11 +1193,7 @@ export class LayerManager {
|
||||
async ungroupLayers(groupId) {
|
||||
// 查找组图层
|
||||
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
||||
if (
|
||||
!groupLayer ||
|
||||
!groupLayer.children ||
|
||||
groupLayer.children.length === 0
|
||||
) {
|
||||
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
||||
console.error(`${groupId} 不是有效的组图层或不包含子图层`);
|
||||
return null;
|
||||
}
|
||||
@@ -1295,9 +1250,7 @@ export class LayerManager {
|
||||
*/
|
||||
resizeCanvasWithScale(width, height, options = {}) {
|
||||
// 检查是否有除背景层外的其他元素
|
||||
const hasOtherElements = this.canvas
|
||||
.getObjects()
|
||||
.some((obj) => !obj.isBackground);
|
||||
const hasOtherElements = this.canvas.getObjects().some((obj) => !obj.isBackground);
|
||||
|
||||
if (hasOtherElements) {
|
||||
// 有其他元素时使用带缩放的命令
|
||||
@@ -1366,9 +1319,7 @@ export class LayerManager {
|
||||
if (!this.canvas) return;
|
||||
|
||||
// 获取画布上的所有对象
|
||||
const canvasObjects = [
|
||||
...this.canvas.getObjects(["id", "layerId", "layerName"]),
|
||||
];
|
||||
const canvasObjects = [...this.canvas.getObjects(["id", "layerId", "layerName"])];
|
||||
|
||||
// 清空画布
|
||||
this.canvas.clear();
|
||||
@@ -1383,19 +1334,13 @@ export class LayerManager {
|
||||
|
||||
if (layer.isBackground && layer.fabricObject) {
|
||||
// 背景图层
|
||||
const originalObj = canvasObjects.find(
|
||||
(o) => o.id === layer.fabricObject.id
|
||||
);
|
||||
const originalObj = canvasObjects.find((o) => o.id === layer.fabricObject.id);
|
||||
if (originalObj) {
|
||||
this.canvas.add(originalObj);
|
||||
} else {
|
||||
this.canvas.add(layer.fabricObject);
|
||||
}
|
||||
} else if (
|
||||
layer.isFixed &&
|
||||
layer.fabricObjects &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (layer.isFixed && layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
// 固定图层
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||||
@@ -1405,10 +1350,7 @@ export class LayerManager {
|
||||
this.canvas.add(obj);
|
||||
}
|
||||
});
|
||||
} else if (
|
||||
Array.isArray(layer.fabricObjects) &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (Array.isArray(layer.fabricObjects) && layer.fabricObjects.length > 0) {
|
||||
// 普通图层,添加所有fabricObjects
|
||||
layer.fabricObjects.forEach((obj) => {
|
||||
const originalObj = canvasObjects.find((o) => o.id === obj.id);
|
||||
@@ -1437,9 +1379,7 @@ export class LayerManager {
|
||||
if (layer.isBackground) {
|
||||
// 背景图层处理
|
||||
if (layer.fabricObject) {
|
||||
const existsOnCanvas = canvasObjects.some(
|
||||
(obj) => obj.id === layer.fabricObject.id
|
||||
);
|
||||
const existsOnCanvas = canvasObjects.some((obj) => obj.id === layer.fabricObject.id);
|
||||
if (!existsOnCanvas) {
|
||||
this.canvas.add(layer.fabricObject);
|
||||
}
|
||||
@@ -1549,9 +1489,7 @@ export class LayerManager {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layerCopy.serializedObjects = layer.fabricObjects
|
||||
.map((obj) =>
|
||||
typeof obj.toObject === "function"
|
||||
? obj.toObject(["id", "layerId", "layerName"])
|
||||
: null
|
||||
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -1596,9 +1534,7 @@ export class LayerManager {
|
||||
if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
layerCopy.serializedObjects = layer.fabricObjects
|
||||
.map((obj) =>
|
||||
typeof obj.toObject === "function"
|
||||
? obj.toObject(["id", "layerId", "layerName"])
|
||||
: null
|
||||
typeof obj.toObject === "function" ? obj.toObject(["id", "layerId", "layerName"]) : null
|
||||
)
|
||||
.filter(Boolean);
|
||||
}
|
||||
@@ -1719,8 +1655,7 @@ export class LayerManager {
|
||||
// 查找第一个非背景、非锁定的图层,排除指定的图层
|
||||
return (
|
||||
this.layers.value.find(
|
||||
(layer) =>
|
||||
layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
||||
(layer) => layer.id !== excludeLayerId && !layer.isBackground && !layer.locked
|
||||
) || null
|
||||
);
|
||||
}
|
||||
@@ -1855,9 +1790,7 @@ export class LayerManager {
|
||||
* @param {string} backgroundColor 背景颜色
|
||||
*/
|
||||
updateBackgroundColor(backgroundColor, options = {}) {
|
||||
const backgroundLayer = this.layers.value.find(
|
||||
(layer) => layer.isBackground
|
||||
);
|
||||
const backgroundLayer = this.layers.value.find((layer) => layer.isBackground);
|
||||
|
||||
if (!backgroundLayer) {
|
||||
console.warn("没有找到背景图层");
|
||||
@@ -2057,8 +1990,7 @@ export class LayerManager {
|
||||
if (!this.canvas || !textObject) return null;
|
||||
|
||||
// 确保对象有ID
|
||||
textObject.id =
|
||||
textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
textObject.id = textObject.id || `text_${Date.now()}_${Math.floor(Math.random() * 1000)}`;
|
||||
|
||||
// 创建文本图层
|
||||
const layerName = options.name || "文本图层";
|
||||
@@ -2075,9 +2007,7 @@ export class LayerManager {
|
||||
overline: options.overline || textObject.overline || false,
|
||||
fill: options.fill || textObject.fill || "#000000",
|
||||
textBackgroundColor:
|
||||
options.textBackgroundColor ||
|
||||
textObject.textBackgroundColor ||
|
||||
"transparent",
|
||||
options.textBackgroundColor || textObject.textBackgroundColor || "transparent",
|
||||
lineHeight: options.lineHeight || textObject.lineHeight || 1.16,
|
||||
charSpacing: options.charSpacing || textObject.charSpacing || 0,
|
||||
},
|
||||
@@ -2088,9 +2018,7 @@ export class LayerManager {
|
||||
textObject.layerName = layerName;
|
||||
|
||||
// 添加到画布,如果还未添加
|
||||
const isOnCanvas = this.canvas
|
||||
.getObjects()
|
||||
.some((obj) => obj.id === textObject.id);
|
||||
const isOnCanvas = this.canvas.getObjects().some((obj) => obj.id === textObject.id);
|
||||
if (!isOnCanvas) {
|
||||
this.canvas.add(textObject);
|
||||
}
|
||||
@@ -2099,9 +2027,7 @@ export class LayerManager {
|
||||
const layer = this.getLayerById(layerId);
|
||||
if (layer) {
|
||||
layer.fabricObjects = layer.fabricObjects || [];
|
||||
layer.fabricObjects.push(
|
||||
textObject.toObject(["id", "layerId", "layerName"])
|
||||
);
|
||||
layer.fabricObjects.push(textObject.toObject(["id", "layerId", "layerName"]));
|
||||
}
|
||||
|
||||
// 设置此图层为活动图层
|
||||
@@ -2134,9 +2060,7 @@ export class LayerManager {
|
||||
|
||||
// 检查普通图层
|
||||
if (layer.fabricObjects && Array.isArray(layer.fabricObjects)) {
|
||||
const foundObject = layer.fabricObjects.find(
|
||||
(obj) => obj.id === fabricObject.id
|
||||
);
|
||||
const foundObject = layer.fabricObjects.find((obj) => obj.id === fabricObject.id);
|
||||
if (foundObject) {
|
||||
return layer;
|
||||
}
|
||||
@@ -2217,12 +2141,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
reorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||||
return this.layerSort?.reorderChildLayers(
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId
|
||||
);
|
||||
return this.layerSort?.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2403,19 +2322,13 @@ export class LayerManager {
|
||||
const layersCount = this.layers?.value?.length || 0;
|
||||
|
||||
const shouldUseAsync =
|
||||
async !== null
|
||||
? async
|
||||
: LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
||||
async !== null ? async : LayerSortUtils.shouldUseAsyncProcessing(objectsCount, layersCount);
|
||||
|
||||
if (shouldUseAsync) {
|
||||
console.log(
|
||||
`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||||
);
|
||||
console.log(`使用异步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
||||
return this.layerSort.rearrangeObjectsAsync();
|
||||
} else {
|
||||
console.log(
|
||||
`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`
|
||||
);
|
||||
console.log(`使用同步排序处理 ${objectsCount} 个对象, ${layersCount} 个图层`);
|
||||
this.layerSort.rearrangeObjects();
|
||||
}
|
||||
}
|
||||
@@ -2524,10 +2437,7 @@ export class LayerManager {
|
||||
moveLayerToIndex({ parentId, oldIndex, newIndex, layerId }) {
|
||||
if (!this.layerSort) {
|
||||
console.warn("图层排序工具未初始化,使用基础移动方法");
|
||||
return this.moveLayer(
|
||||
layerId,
|
||||
newIndex > this.getLayerIndex(layerId) ? "down" : "up"
|
||||
);
|
||||
return this.moveLayer(layerId, newIndex > this.getLayerIndex(layerId) ? "down" : "up");
|
||||
}
|
||||
|
||||
const result = this.layerSort.moveLayerToIndex({
|
||||
@@ -2538,9 +2448,7 @@ export class LayerManager {
|
||||
});
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`
|
||||
);
|
||||
console.log(`图层 ${layerId} - oldIndex: ${oldIndex} 已移动到位置 ${newIndex}`);
|
||||
// 更新对象交互性
|
||||
// this.updateLayersObjectsInteractivity();
|
||||
}
|
||||
@@ -2584,9 +2492,7 @@ export class LayerManager {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let layerIndex = this.layers.value.findIndex(
|
||||
(layer) => layer.id === layerId
|
||||
);
|
||||
let layerIndex = this.layers.value.findIndex((layer) => layer.id === layerId);
|
||||
if (layerIndex >= 0) return layerIndex;
|
||||
// 如果未找到,尝试在子图层中查找
|
||||
const { parent } = findLayerRecursively(this.layers.value, layerId);
|
||||
@@ -2617,9 +2523,7 @@ export class LayerManager {
|
||||
const result = this.layerSort.reorderLayers(oldIndex, newIndex, layerId);
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`
|
||||
);
|
||||
console.log(`高级排序完成: 图层 ${layerId} 从位置 ${oldIndex} 移动到 ${newIndex}`);
|
||||
// 更新对象交互性
|
||||
this.updateLayersObjectsInteractivity();
|
||||
}
|
||||
@@ -2636,12 +2540,7 @@ export class LayerManager {
|
||||
* @returns {boolean} 是否排序成功
|
||||
*/
|
||||
advancedReorderChildLayers(parentId, oldIndex, newIndex, layerId) {
|
||||
const result = this.reorderChildLayers(
|
||||
parentId,
|
||||
oldIndex,
|
||||
newIndex,
|
||||
layerId
|
||||
);
|
||||
const result = this.reorderChildLayers(parentId, oldIndex, newIndex, layerId);
|
||||
|
||||
if (result) {
|
||||
console.log(
|
||||
@@ -2758,11 +2657,7 @@ export class LayerManager {
|
||||
async mergeGroupLayers(groupId) {
|
||||
// 查找组图层
|
||||
const groupLayer = this.layers.value.find((l) => l.id === groupId);
|
||||
if (
|
||||
!groupLayer ||
|
||||
!groupLayer.children ||
|
||||
groupLayer.children.length === 0
|
||||
) {
|
||||
if (!groupLayer || !groupLayer.children || groupLayer.children.length === 0) {
|
||||
console.warn($t("找不到有效的组图层或组图层为空"));
|
||||
return [];
|
||||
}
|
||||
@@ -2803,10 +2698,7 @@ export class LayerManager {
|
||||
|
||||
// 查找目标图层
|
||||
// const targetLayer = this.getLayerById(targetLayerId);
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
console.error($t("图层不存在", { layerId: targetLayerId }));
|
||||
@@ -2864,9 +2756,7 @@ export class LayerManager {
|
||||
return (
|
||||
layer.children &&
|
||||
layer.children.length > 0 &&
|
||||
layer.children.some(
|
||||
(child) => child.fabricObjects && child.fabricObjects.length > 0
|
||||
)
|
||||
layer.children.some((child) => child.fabricObjects && child.fabricObjects.length > 0)
|
||||
);
|
||||
} else {
|
||||
// 普通图层:检查是否有对象
|
||||
@@ -2888,10 +2778,7 @@ export class LayerManager {
|
||||
|
||||
// 查找目标图层
|
||||
// const targetLayer = this.getLayerById(targetLayerId);
|
||||
const { layer: targetLayer } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
targetLayerId
|
||||
);
|
||||
const { layer: targetLayer } = findLayerRecursively(this.layers.value, targetLayerId);
|
||||
|
||||
if (!targetLayer) {
|
||||
console.error($t("图层不存在", { layerId: targetLayerId }));
|
||||
@@ -2926,15 +2813,162 @@ export class LayerManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新图层缩略图
|
||||
* @param {string} layerId 图层ID
|
||||
* 为组图层的活动选择组设置遮罩移动同步(修复版)
|
||||
* @param {fabric.ActiveSelection} activeSelection 活动选择组
|
||||
* @param {Object} layer 组图层对象
|
||||
* @private
|
||||
*/
|
||||
_updateLayerThumbnail(layerId) {
|
||||
if (this.canvas && this.canvas.thumbnailManager) {
|
||||
setTimeout(() => {
|
||||
this.canvas.thumbnailManager.generateLayerThumbnail(layerId);
|
||||
}, 100);
|
||||
_setupGroupMaskMovementSync(activeSelection, layer) {
|
||||
if (!activeSelection || !layer || !layer.clippingMask) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 记录初始位置
|
||||
let initialLeft = activeSelection.left;
|
||||
let initialTop = activeSelection.top;
|
||||
|
||||
// 记录遮罩初始位置
|
||||
let maskInitialLeft = layer.clippingMask.left || 0;
|
||||
let maskInitialTop = layer.clippingMask.top || 0;
|
||||
|
||||
// 用于节流和状态管理的变量
|
||||
let isUpdating = false;
|
||||
let lastUpdateTime = 0;
|
||||
let hasMoved = false; // 追踪是否实际发生了移动
|
||||
const UPDATE_THRESHOLD = 16; // 约60fps
|
||||
|
||||
// 移动开始事件处理
|
||||
const handleMovingStart = (e) => {
|
||||
if (e.target === activeSelection) {
|
||||
hasMoved = false; // 重置移动状态
|
||||
console.log("🎯 开始移动组选择对象");
|
||||
// 记录遮罩初始位置
|
||||
console.log(
|
||||
"🖼️ 记录遮罩初始位置",
|
||||
`${layer.clippingMask.left || 0}, ${layer.clippingMask.top || 0}`
|
||||
);
|
||||
// 记录初始位置
|
||||
initialLeft = activeSelection.left;
|
||||
initialTop = activeSelection.top;
|
||||
|
||||
maskInitialLeft = layer.clippingMask.left || 0;
|
||||
maskInitialTop = layer.clippingMask.top || 0;
|
||||
}
|
||||
};
|
||||
// 移动中事件处理函数(带节流)
|
||||
const handleMoving = (e) => {
|
||||
const target = e.target;
|
||||
if (target === activeSelection) {
|
||||
hasMoved = true; // 标记发生了移动
|
||||
const now = Date.now();
|
||||
|
||||
// 节流处理,避免过于频繁的更新
|
||||
if (now - lastUpdateTime < UPDATE_THRESHOLD) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
isUpdating = true;
|
||||
lastUpdateTime = now;
|
||||
|
||||
// 使用 requestAnimationFrame 优化渲染
|
||||
requestAnimationFrame(() => {
|
||||
try {
|
||||
// 计算移动距离
|
||||
const deltaX = target.left - initialLeft;
|
||||
const deltaY = target.top - initialTop;
|
||||
|
||||
// 创建更新遮罩位置的命令
|
||||
const command = new UpdateGroupMaskPositionCommand({
|
||||
canvas: this.canvas,
|
||||
layerManager: this,
|
||||
layers: this.layers,
|
||||
layerId: layer.id,
|
||||
deltaX: deltaX,
|
||||
deltaY: deltaY,
|
||||
maskInitialLeft: maskInitialLeft,
|
||||
maskInitialTop: maskInitialTop,
|
||||
isExecuteRealtime: true,
|
||||
});
|
||||
|
||||
// 执行实时更新
|
||||
command.executeRealtime();
|
||||
} finally {
|
||||
isUpdating = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 修改事件处理函数 - 使用 object:modified 替代 object:moved
|
||||
const handleModified = (e) => {
|
||||
const target = e.target;
|
||||
if (target === activeSelection && hasMoved) {
|
||||
console.log("✅ 组选择对象移动完成");
|
||||
|
||||
// 计算最终移动距离
|
||||
const deltaX = target.left - initialLeft;
|
||||
const deltaY = target.top - initialTop;
|
||||
|
||||
// 如果有实际移动,创建可撤销的命令
|
||||
if (Math.abs(deltaX) > 0.1 || Math.abs(deltaY) > 0.1) {
|
||||
const command = new UpdateGroupMaskPositionCommand({
|
||||
canvas: this.canvas,
|
||||
layers: this.layers,
|
||||
layerManager: this,
|
||||
layerId: layer.id,
|
||||
deltaX: deltaX,
|
||||
deltaY: deltaY,
|
||||
maskInitialLeft: maskInitialLeft,
|
||||
maskInitialTop: maskInitialTop,
|
||||
activeSelection,
|
||||
});
|
||||
|
||||
// 执行可撤销的命令
|
||||
if (this.commandManager) {
|
||||
this.commandManager.execute(command);
|
||||
} else {
|
||||
command.execute();
|
||||
}
|
||||
}
|
||||
|
||||
hasMoved = false; // 重置移动状态
|
||||
}
|
||||
};
|
||||
|
||||
// 鼠标抬起事件处理 - 备用方案
|
||||
const handleMouseUp = (e) => {
|
||||
if (hasMoved && this.canvas.getActiveObject() === activeSelection) {
|
||||
console.log("🖱️ 鼠标抬起 - 备用移动完成处理");
|
||||
handleModified(e);
|
||||
}
|
||||
};
|
||||
|
||||
// 清理事件监听器的函数
|
||||
const cleanup = () => {
|
||||
this.canvas.off("object:moving", handleMoving);
|
||||
this.canvas.off("object:modified", handleModified);
|
||||
this.canvas.off("mouse:down", handleMovingStart);
|
||||
this.canvas.off("mouse:up", handleMouseUp);
|
||||
this.canvas.off("selection:cleared", cleanup);
|
||||
this.canvas.off("selection:updated", cleanup);
|
||||
|
||||
console.log("🧹 清理组遮罩移动同步事件监听器");
|
||||
};
|
||||
|
||||
// 绑定事件监听器
|
||||
this.canvas.on("mouse:down", handleMovingStart);
|
||||
this.canvas.on("object:moving", handleMoving);
|
||||
this.canvas.on("object:modified", handleModified); // 使用 modified 替代 moved
|
||||
this.canvas.on("mouse:up", handleMouseUp); // 备用方案
|
||||
|
||||
// 当选择被清除或更新时清理事件监听器
|
||||
this.canvas.on("selection:cleared", cleanup);
|
||||
this.canvas.on("selection:updated", cleanup);
|
||||
|
||||
console.log("🎨 已设置组遮罩移动同步 - 使用 object:modified 事件");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,9 +141,7 @@ export class LiquifyReferenceManager {
|
||||
const snapshot = this.stateSnapshots.get(snapshotId);
|
||||
|
||||
if (!fabricObject || !snapshot) {
|
||||
throw new Error(
|
||||
`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`
|
||||
);
|
||||
throw new Error(`无法恢复快照: 对象或快照不存在 (${refId}, ${snapshotId})`);
|
||||
}
|
||||
|
||||
// 恢复图像数据
|
||||
@@ -175,10 +173,7 @@ export class LiquifyReferenceManager {
|
||||
|
||||
for (const update of updates) {
|
||||
try {
|
||||
const result = await this.updateObjectImageData(
|
||||
update.refId,
|
||||
update.imageData
|
||||
);
|
||||
const result = await this.updateObjectImageData(update.refId, update.imageData);
|
||||
results.push({ refId: update.refId, success: true, result });
|
||||
} catch (error) {
|
||||
console.error(`批量更新失败 ${update.refId}:`, error);
|
||||
@@ -253,19 +248,10 @@ export class LiquifyReferenceManager {
|
||||
const listeners = {};
|
||||
|
||||
// 备份常见的事件监听器
|
||||
const eventTypes = [
|
||||
"mousedown",
|
||||
"mouseup",
|
||||
"mousemove",
|
||||
"mouseout",
|
||||
"mouseover",
|
||||
];
|
||||
const eventTypes = ["mousedown", "mouseup", "mousemove", "mouseout", "mouseover"];
|
||||
|
||||
eventTypes.forEach((eventType) => {
|
||||
if (
|
||||
fabricObject.__eventListeners &&
|
||||
fabricObject.__eventListeners[eventType]
|
||||
) {
|
||||
if (fabricObject.__eventListeners && fabricObject.__eventListeners[eventType]) {
|
||||
listeners[eventType] = [...fabricObject.__eventListeners[eventType]];
|
||||
}
|
||||
});
|
||||
@@ -378,11 +364,7 @@ export class LiquifyReferenceManager {
|
||||
*/
|
||||
_captureImageData(fabricObject) {
|
||||
try {
|
||||
if (
|
||||
fabricObject._element &&
|
||||
fabricObject._element.width &&
|
||||
fabricObject._element.height
|
||||
) {
|
||||
if (fabricObject._element && fabricObject._element.width && fabricObject._element.height) {
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.width = fabricObject._element.width;
|
||||
canvas.height = fabricObject._element.height;
|
||||
|
||||
@@ -55,14 +55,10 @@ export class RedGreenModeManager {
|
||||
if (typeof options.normalLayerOpacity === "number") {
|
||||
if (options.normalLayerOpacity > 1) {
|
||||
// 如果大于1,认为是百分比值(0-100)
|
||||
this.normalLayerOpacity =
|
||||
Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
||||
this.normalLayerOpacity = Math.max(0, Math.min(100, options.normalLayerOpacity)) / 100;
|
||||
} else {
|
||||
// 如果小于等于1,认为是小数值(0-1)
|
||||
this.normalLayerOpacity = Math.max(
|
||||
0,
|
||||
Math.min(1, options.normalLayerOpacity)
|
||||
);
|
||||
this.normalLayerOpacity = Math.max(0, Math.min(1, options.normalLayerOpacity));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,10 +93,7 @@ export class RedGreenModeManager {
|
||||
this.registerRedGreenMouseUpEvent();
|
||||
|
||||
// 启用图层管理器的红绿图模式
|
||||
if (
|
||||
this.layerManager &&
|
||||
typeof this.layerManager.enableRedGreenMode === "function"
|
||||
) {
|
||||
if (this.layerManager && typeof this.layerManager.enableRedGreenMode === "function") {
|
||||
this.layerManager.enableRedGreenMode();
|
||||
}
|
||||
|
||||
@@ -177,26 +170,24 @@ export class RedGreenModeManager {
|
||||
normalizedOpacity = Math.max(0, Math.min(1, opacity));
|
||||
}
|
||||
|
||||
// 创建透明度更新命令
|
||||
const opacityCommand = new UpdateNormalLayerOpacityCommand({
|
||||
canvas: this.canvas,
|
||||
layerManager: this.layerManager,
|
||||
opacity: normalizedOpacity,
|
||||
});
|
||||
// // 创建透明度更新命令
|
||||
// const opacityCommand = new UpdateNormalLayerOpacityCommand({
|
||||
// canvas: this.canvas,
|
||||
// layerManager: this.layerManager,
|
||||
// opacity: normalizedOpacity,
|
||||
// });
|
||||
|
||||
// 执行命令
|
||||
if (this.commandManager) {
|
||||
this.commandManager.execute(opacityCommand);
|
||||
} else {
|
||||
opacityCommand.execute();
|
||||
}
|
||||
// // 执行命令
|
||||
// if (this.commandManager) {
|
||||
// this.commandManager.execute(opacityCommand);
|
||||
// } else {
|
||||
// opacityCommand.execute();
|
||||
// }
|
||||
|
||||
// 更新内部状态
|
||||
this.normalLayerOpacity = normalizedOpacity;
|
||||
|
||||
console.log(
|
||||
`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`
|
||||
);
|
||||
console.log(`普通图层透明度已更新为: ${Math.round(normalizedOpacity * 100)}%`);
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("更新普通图层透明度失败:", error);
|
||||
@@ -280,9 +271,7 @@ export class RedGreenModeManager {
|
||||
const layers = this.layerManager.layers.value || [];
|
||||
const backgroundLayer = layers.find((layer) => layer.isBackground);
|
||||
const fixedLayer = layers.find((layer) => layer.isFixed);
|
||||
const normalLayers = layers.filter(
|
||||
(layer) => !layer.isBackground && !layer.isFixed
|
||||
);
|
||||
const normalLayers = layers.filter((layer) => !layer.isBackground && !layer.isFixed);
|
||||
|
||||
return {
|
||||
backgroundLayer:
|
||||
@@ -318,10 +307,7 @@ export class RedGreenModeManager {
|
||||
cleanup() {
|
||||
try {
|
||||
// 禁用图层管理器的红绿图模式
|
||||
if (
|
||||
this.layerManager &&
|
||||
typeof this.layerManager.disableRedGreenMode === "function"
|
||||
) {
|
||||
if (this.layerManager && typeof this.layerManager.disableRedGreenMode === "function") {
|
||||
this.layerManager.disableRedGreenMode();
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,8 @@ export class ThumbnailManager {
|
||||
this.layers = options.layers || []; // 图层管理器
|
||||
this.layerThumbSize = options.layerThumbSize || { width: 48, height: 48 };
|
||||
|
||||
this.layerThumbnails = new Map(); // 图层缩略图缓存
|
||||
// this.layerThumbnails = new Map(); // 图层缩略图缓存 - 改成使用图层对象的thumbnailUrl属性
|
||||
// 使用图层对象的thumbnailUrl属性来存储缩略图URL
|
||||
this.defaultThumbnail =
|
||||
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII="; // 1x1 透明图
|
||||
}
|
||||
@@ -23,17 +24,24 @@ export class ThumbnailManager {
|
||||
async generateLayerThumbnail(layerId) {
|
||||
const fabricObjects = this._collectLayersAndObjects(layerId);
|
||||
|
||||
if (!fabricObjects || fabricObjects.length === 0) {
|
||||
console.warn("⚠️ 无法生成缩略图:没有可栅格化的对象 返回空缩略图");
|
||||
// 如果没有对象,返回默认缩略图
|
||||
const { layer } = findLayerRecursively(this.layers.value, layerId);
|
||||
if (layer) {
|
||||
layer.thumbnailUrl = this.defaultThumbnail; // 更新图层对象的缩略图
|
||||
}
|
||||
return this.defaultThumbnail;
|
||||
}
|
||||
|
||||
// 延迟执行,避免阻塞UI
|
||||
fabricObjects.length > 0 &&
|
||||
requestIdleCallback(() => {
|
||||
setTimeout(async () => {
|
||||
const base64 = await this._generateLayerThumbnailNow(fabricObjects);
|
||||
this.layerThumbnails.set(layerId, base64);
|
||||
// this.layerThumbnails.set(layerId, base64);
|
||||
try {
|
||||
const { layer, parent } = findLayerRecursively(
|
||||
this.layers.value,
|
||||
layerId
|
||||
);
|
||||
const { layer, parent } = findLayerRecursively(this.layers.value, layerId);
|
||||
if (layer) {
|
||||
layer.thumbnailUrl = base64; // 更新图层对象的缩略图
|
||||
}
|
||||
@@ -201,7 +209,7 @@ export class ThumbnailManager {
|
||||
*/
|
||||
getLayerThumbnail(layerId) {
|
||||
if (!layerId) return null;
|
||||
return this.layerThumbnails.get(layerId) || null;
|
||||
// return this.layerThumbnails.get(layerId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -210,7 +218,7 @@ export class ThumbnailManager {
|
||||
*/
|
||||
clearLayerThumbnail(layerId) {
|
||||
if (layerId && this.layerThumbnails.has(layerId)) {
|
||||
this.layerThumbnails.delete(layerId);
|
||||
// this.layerThumbnails.delete(layerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,7 +226,7 @@ export class ThumbnailManager {
|
||||
* 清除所有缩略图
|
||||
*/
|
||||
clearAllThumbnails() {
|
||||
this.layerThumbnails.clear();
|
||||
// this.layerThumbnails.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -258,10 +258,7 @@ export class ToolManager {
|
||||
const shiftKey = event.shiftKey;
|
||||
|
||||
// 当处于输入状态时不触发快捷键
|
||||
if (
|
||||
event.target.tagName === "INPUT" ||
|
||||
event.target.tagName === "TEXTAREA"
|
||||
) {
|
||||
if (event.target.tagName === "INPUT" || event.target.tagName === "TEXTAREA") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -286,10 +283,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 在红绿图模式下检查工具可用性
|
||||
if (
|
||||
this.isRedGreenMode &&
|
||||
!this.isToolAvailableInRedGreenMode(toolId)
|
||||
) {
|
||||
if (this.isRedGreenMode && !this.isToolAvailableInRedGreenMode(toolId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -564,9 +558,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到矩形套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.LASSO_RECTANGLE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_RECTANGLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -581,9 +573,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到椭圆套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.LASSO_ELLIPSE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.LASSO_ELLIPSE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -598,9 +588,7 @@ export class ToolManager {
|
||||
|
||||
// 通知选区管理器切换到椭圆套索工具
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.AREA_CUSTOM
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_CUSTOM);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -618,9 +606,7 @@ export class ToolManager {
|
||||
console.log("矩形选区工具已激活");
|
||||
|
||||
if (this.canvasManager && this.canvasManager.selectionManager) {
|
||||
this.canvasManager.selectionManager.setCurrentTool(
|
||||
OperationType.AREA_RECTANGLE
|
||||
);
|
||||
this.canvasManager.selectionManager.setCurrentTool(OperationType.AREA_RECTANGLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -677,10 +663,7 @@ export class ToolManager {
|
||||
if (layer) {
|
||||
if (layer.isBackground || layer.type === "background") {
|
||||
panelDetail.targetObject = layer.fabricObject;
|
||||
} else if (
|
||||
layer.fabricObjects &&
|
||||
layer.fabricObjects.length > 0
|
||||
) {
|
||||
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
|
||||
panelDetail.targetObject = layer.fabricObjects[0];
|
||||
}
|
||||
|
||||
@@ -906,9 +889,7 @@ export class ToolManager {
|
||||
});
|
||||
|
||||
// 准备液化操作,获取原始图像数据
|
||||
const prepareResult = await liquifyManager.prepareForLiquify(
|
||||
targetObject
|
||||
);
|
||||
const prepareResult = await liquifyManager.prepareForLiquify(targetObject);
|
||||
|
||||
// 创建和初始化命令
|
||||
const initCommand = new InitLiquifyToolCommand({
|
||||
@@ -1110,9 +1091,7 @@ export class ToolManager {
|
||||
const target = e.target;
|
||||
if (
|
||||
target &&
|
||||
(target.type === "text" ||
|
||||
target.type === "i-text" ||
|
||||
target.type === "textbox")
|
||||
(target.type === "text" || target.type === "i-text" || target.type === "textbox")
|
||||
) {
|
||||
// 获取对应的图层
|
||||
const layer = this.layerManager.getLayerById(target.layerId);
|
||||
@@ -1380,11 +1359,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 从画布笔刷获取
|
||||
if (
|
||||
this.canvas &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
this.canvas.freeDrawingBrush.width
|
||||
) {
|
||||
if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.width) {
|
||||
return this.canvas.freeDrawingBrush.width;
|
||||
}
|
||||
|
||||
@@ -1409,11 +1384,7 @@ export class ToolManager {
|
||||
}
|
||||
|
||||
// 从画布笔刷获取
|
||||
if (
|
||||
this.canvas &&
|
||||
this.canvas.freeDrawingBrush &&
|
||||
this.canvas.freeDrawingBrush.color
|
||||
) {
|
||||
if (this.canvas && this.canvas.freeDrawingBrush && this.canvas.freeDrawingBrush.color) {
|
||||
return this.canvas.freeDrawingBrush.color;
|
||||
}
|
||||
|
||||
|
||||
@@ -87,8 +87,7 @@ export class AnimationManager {
|
||||
|
||||
// 计算过渡动画持续时间 - 根据当前值到目标值的距离比例
|
||||
const progressRatio =
|
||||
Math.abs(targetZoom - currentZoomValue) /
|
||||
Math.abs(targetZoom - currentZoom);
|
||||
Math.abs(targetZoom - currentZoomValue) / Math.abs(targetZoom - currentZoom);
|
||||
const duration = options.duration || 0.3 * progressRatio;
|
||||
|
||||
// 计算缩放后目标位置需要的修正,保持缩放点不变
|
||||
@@ -425,17 +424,17 @@ export class AnimationManager {
|
||||
const now = Date.now();
|
||||
|
||||
// Mac设备的轻量防抖检查 - 进一步减少冷却时间,确保响应性
|
||||
if (
|
||||
this._isMac &&
|
||||
now - this._lastMacAnimationTime < this._macAnimationCooldown
|
||||
) {
|
||||
if (this._isMac && now - this._lastMacAnimationTime < this._macAnimationCooldown) {
|
||||
// 如果距离上次动画时间太短,只延迟很短时间,不阻塞太久
|
||||
if (this._wheelAccumulationTimeout) {
|
||||
clearTimeout(this._wheelAccumulationTimeout);
|
||||
}
|
||||
this._wheelAccumulationTimeout = setTimeout(() => {
|
||||
this._processAccumulatedWheel(lastOpt);
|
||||
}, Math.min(this._macAnimationCooldown, 3)); // 最多延迟3ms
|
||||
this._wheelAccumulationTimeout = setTimeout(
|
||||
() => {
|
||||
this._processAccumulatedWheel(lastOpt);
|
||||
},
|
||||
Math.min(this._macAnimationCooldown, 3)
|
||||
); // 最多延迟3ms
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -513,28 +512,16 @@ export class AnimationManager {
|
||||
// Mac设备使用平衡的动画时间控制
|
||||
if (speedFactor > 2) {
|
||||
// 快速操作:快速但平滑
|
||||
duration = Math.min(
|
||||
0.18,
|
||||
Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.18, Math.max(0.08, (zoomRatio * 0.3) / Math.sqrt(speedFactor)));
|
||||
} else if (speedFactor > 1.2) {
|
||||
// 中等速度:标准响应
|
||||
duration = Math.min(
|
||||
0.25,
|
||||
Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.25, Math.max(0.1, (zoomRatio * 0.4) / Math.sqrt(speedFactor)));
|
||||
} else {
|
||||
// 慢速精确操作:确保平滑
|
||||
duration = Math.min(
|
||||
0.3,
|
||||
Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.3, Math.max(0.12, (zoomRatio * 0.5) / Math.sqrt(speedFactor)));
|
||||
}
|
||||
} else {
|
||||
duration = Math.min(
|
||||
0.5,
|
||||
Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor))
|
||||
);
|
||||
duration = Math.min(0.5, Math.max(0.15, (zoomRatio * 0.8) / Math.sqrt(speedFactor)));
|
||||
}
|
||||
|
||||
// 根据滚动速度选择不同的缓动效果
|
||||
|
||||
@@ -125,9 +125,7 @@ export class BaseBrush {
|
||||
_createRGBAColor(color, opacity) {
|
||||
// 如果已经是rgba颜色,先提取RGB部分
|
||||
if (color.startsWith("rgba")) {
|
||||
const rgbaMatch = color.match(
|
||||
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
|
||||
);
|
||||
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
||||
if (rgbaMatch) {
|
||||
const [, r, g, b] = rgbaMatch;
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
@@ -258,10 +256,7 @@ export class BaseBrush {
|
||||
|
||||
// 过滤掉同名属性(子类优先)
|
||||
const basePropsFiltered = baseProperties.filter(
|
||||
(baseProp) =>
|
||||
!specificProperties.some(
|
||||
(specificProp) => specificProp.id === baseProp.id
|
||||
)
|
||||
(baseProp) => !specificProperties.some((specificProp) => specificProp.id === baseProp.id)
|
||||
);
|
||||
|
||||
return [...basePropsFiltered, ...specificProperties];
|
||||
@@ -334,10 +329,7 @@ export class BaseBrush {
|
||||
* @returns {Array} 选项数组
|
||||
*/
|
||||
getDynamicOptions(property, currentValues) {
|
||||
if (
|
||||
property.dynamicOptions &&
|
||||
typeof property.dynamicOptions === "function"
|
||||
) {
|
||||
if (property.dynamicOptions && typeof property.dynamicOptions === "function") {
|
||||
return property.dynamicOptions(currentValues);
|
||||
}
|
||||
return property.options || [];
|
||||
|
||||
@@ -206,9 +206,7 @@ export class TexturePresetManager {
|
||||
* @returns {Object|null} 材质对象
|
||||
*/
|
||||
getTextureById(textureId) {
|
||||
return (
|
||||
this.getAllTextures().find((texture) => texture.id === textureId) || null
|
||||
);
|
||||
return this.getAllTextures().find((texture) => texture.id === textureId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -217,9 +215,7 @@ export class TexturePresetManager {
|
||||
* @returns {Array} 材质数组
|
||||
*/
|
||||
getTexturesByCategory(category) {
|
||||
return this.getAllTextures().filter(
|
||||
(texture) => texture.category === category
|
||||
);
|
||||
return this.getAllTextures().filter((texture) => texture.category === category);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,8 +237,7 @@ export class TexturePresetManager {
|
||||
*/
|
||||
addCustomTexture(textureData) {
|
||||
const textureId =
|
||||
textureData.id ||
|
||||
`custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
textureData.id || `custom_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const texture = {
|
||||
id: textureId,
|
||||
@@ -286,9 +281,7 @@ export class TexturePresetManager {
|
||||
* @returns {Boolean} 是否删除成功
|
||||
*/
|
||||
removeCustomTexture(textureId) {
|
||||
const index = this.customTextures.findIndex(
|
||||
(texture) => texture.id === textureId
|
||||
);
|
||||
const index = this.customTextures.findIndex((texture) => texture.id === textureId);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
}
|
||||
@@ -430,10 +423,7 @@ export class TexturePresetManager {
|
||||
file: null,
|
||||
}));
|
||||
|
||||
localStorage.setItem(
|
||||
"canvasEditor_customTextures",
|
||||
JSON.stringify(customTexturesData)
|
||||
);
|
||||
localStorage.setItem("canvasEditor_customTextures", JSON.stringify(customTexturesData));
|
||||
} catch (error) {
|
||||
console.error("保存自定义材质失败:", error);
|
||||
}
|
||||
@@ -472,9 +462,7 @@ export class TexturePresetManager {
|
||||
* @returns {String} 预设ID
|
||||
*/
|
||||
createTexturePreset(name, settings) {
|
||||
const presetId = `preset_${Date.now()}_${Math.random()
|
||||
.toString(36)
|
||||
.substr(2, 9)}`;
|
||||
const presetId = `preset_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
||||
|
||||
const preset = {
|
||||
id: presetId,
|
||||
|
||||
@@ -17,6 +17,7 @@ import { MarkerBrush } from "./types/MarkerBrush";
|
||||
import { CustomPenBrush } from "./types/CustomPenBrush";
|
||||
import { RibbonBrush } from "./types/RibbonBrush";
|
||||
import { ShadedBrush } from "./types/ShadedBrush";
|
||||
import { EraserStateManager } from "../EraserStateManager";
|
||||
// import { SketchyBrush } from "./types/SketchyBrush";
|
||||
// import { SpraypaintBrush } from "./types/SpraypaintBrush";
|
||||
|
||||
@@ -379,27 +380,23 @@ export class BrushManager {
|
||||
}
|
||||
// 创建新笔刷实例
|
||||
try {
|
||||
const brushInstance = brushRegistry.createBrushInstance(
|
||||
brushId,
|
||||
this.canvas,
|
||||
{
|
||||
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
const brushInstance = brushRegistry.createBrushInstance(brushId, this.canvas, {
|
||||
color: brushId === "eraser" ? this.brushStore.state.color : undefined,
|
||||
width: this.brushStore.state.size,
|
||||
opacity: this.brushStore.state.opacity,
|
||||
|
||||
// 阴影相关配置
|
||||
shadowEnabled: this.brushStore.state.shadowEnabled,
|
||||
shadowColor: this.brushStore.state.shadowColor,
|
||||
shadowWidth: this.brushStore.state.shadowWidth,
|
||||
shadowOffsetX: this.brushStore.state.shadowOffsetX,
|
||||
shadowOffsetY: this.brushStore.state.shadowOffsetY,
|
||||
// 阴影相关配置
|
||||
shadowEnabled: this.brushStore.state.shadowEnabled,
|
||||
shadowColor: this.brushStore.state.shadowColor,
|
||||
shadowWidth: this.brushStore.state.shadowWidth,
|
||||
shadowOffsetX: this.brushStore.state.shadowOffsetX,
|
||||
shadowOffsetY: this.brushStore.state.shadowOffsetY,
|
||||
|
||||
// 材质笔刷特有配置
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
}
|
||||
);
|
||||
// 材质笔刷特有配置
|
||||
textureEnabled: this.brushStore.state.textureEnabled,
|
||||
texturePath: this.brushStore.state.texturePath,
|
||||
textureScale: this.brushStore.state.textureScale,
|
||||
});
|
||||
|
||||
if (brushInstance) {
|
||||
// 创建笔刷
|
||||
@@ -648,48 +645,7 @@ export class BrushManager {
|
||||
|
||||
// 初始化橡皮擦状态管理器
|
||||
if (this.canvas && this.layerManager) {
|
||||
this.eraserStateManager = new EraserStateManager(
|
||||
this.canvas,
|
||||
this.layerManager
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建橡皮擦
|
||||
* @param {Object} options 橡皮擦选项
|
||||
*/
|
||||
createEraser(options = {}) {
|
||||
if (!this.canvas) {
|
||||
console.error("画布未初始化");
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
// 直接使用 fabric-with-erasing 库提供的 EraserBrush
|
||||
this.brush = new fabric.EraserBrush(this.canvas);
|
||||
|
||||
// 应用配置
|
||||
this.configure(this.brush, {
|
||||
width: this.brushSize.value,
|
||||
color: this.brushColor.value,
|
||||
opacity: this.brushOpacity.value,
|
||||
inverted: options.inverted || false,
|
||||
...options,
|
||||
});
|
||||
|
||||
// 设置画布为绘图模式
|
||||
this.canvas.isDrawingMode = true;
|
||||
this.canvas.freeDrawingBrush = this.brush;
|
||||
|
||||
// 绑定橡皮擦事件处理器
|
||||
// this._bindEraserEvents();
|
||||
|
||||
console.log("橡皮擦创建成功");
|
||||
return this.brush;
|
||||
} catch (error) {
|
||||
console.error("创建橡皮擦失败:", error);
|
||||
return null;
|
||||
this.eraserStateManager = new EraserStateManager(this.canvas, this.layerManager);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -866,6 +822,44 @@ export class BrushManager {
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 创建橡皮擦
|
||||
// * @param {Object} options 橡皮擦选项
|
||||
// */
|
||||
// createEraser(options = {}) {
|
||||
// if (!this.canvas) {
|
||||
// console.error("画布未初始化");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
// try {
|
||||
// // 直接使用 fabric-with-erasing 库提供的 EraserBrush
|
||||
// this.brush = new fabric.EraserBrush(this.canvas);
|
||||
|
||||
// // 应用配置
|
||||
// this.configure(this.brush, {
|
||||
// width: this.brushSize.value,
|
||||
// color: this.brushColor.value,
|
||||
// opacity: this.brushOpacity.value,
|
||||
// inverted: options.inverted || false,
|
||||
// ...options,
|
||||
// });
|
||||
|
||||
// // 设置画布为绘图模式
|
||||
// this.canvas.isDrawingMode = true;
|
||||
// this.canvas.freeDrawingBrush = this.brush;
|
||||
|
||||
// // 绑定橡皮擦事件处理器
|
||||
// // this._bindEraserEvents();
|
||||
|
||||
// console.log("橡皮擦创建成功");
|
||||
// return this.brush;
|
||||
// } catch (error) {
|
||||
// console.error("创建橡皮擦失败:", error);
|
||||
// return null;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 创建橡皮擦
|
||||
* @returns {Object} 橡皮擦笔刷
|
||||
@@ -891,12 +885,7 @@ export class BrushManager {
|
||||
const imageData = ctx.getImageData(pointer.x, pointer.y, 1, 1).data;
|
||||
|
||||
// 将RGB转换为十六进制颜色
|
||||
const color = `#${(
|
||||
(1 << 24) +
|
||||
(imageData[0] << 16) +
|
||||
(imageData[1] << 8) +
|
||||
imageData[2]
|
||||
)
|
||||
const color = `#${((1 << 24) + (imageData[0] << 16) + (imageData[1] << 8) + imageData[2])
|
||||
.toString(16)
|
||||
.slice(1)}`;
|
||||
|
||||
|
||||
@@ -97,9 +97,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
}
|
||||
if (color.indexOf("rgb") === -1) {
|
||||
// convert named colors
|
||||
var tempElem = document.body.appendChild(
|
||||
document.createElement("fictum")
|
||||
); // intentionally use unknown tag to lower chances of css rule override with !important
|
||||
var tempElem = document.body.appendChild(document.createElement("fictum")); // intentionally use unknown tag to lower chances of css rule override with !important
|
||||
var flag = "rgb(1, 2, 3)"; // this flag tested on chrome 59, ff 53, ie9, ie10, ie11, edge 14
|
||||
tempElem.style.color = flag;
|
||||
if (tempElem.style.color !== flag) {
|
||||
@@ -116,7 +114,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
if (color.indexOf("rgba") === -1) {
|
||||
color += ",1"; // convert 'rgb(R,G,B)' to 'rgb(R,G,B)A' which looks awful but will pass the regxep below
|
||||
}
|
||||
return color.match(/[\.\d]+/g).map(function (a) {
|
||||
return color.match(/[.\d]+/g).map(function (a) {
|
||||
return +a;
|
||||
});
|
||||
}
|
||||
@@ -269,13 +267,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
draw: function () {
|
||||
var ctx = this.ctx;
|
||||
ctx.save();
|
||||
this.line(
|
||||
ctx,
|
||||
this._lastPoint,
|
||||
this._point,
|
||||
this.color,
|
||||
this._currentLineWidth
|
||||
);
|
||||
this.line(ctx, this._lastPoint, this._point, this.color, this._currentLineWidth);
|
||||
ctx.restore();
|
||||
},
|
||||
|
||||
@@ -319,10 +311,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
this._size = this.width / 2 + this._baseWidth;
|
||||
this._drawn = false;
|
||||
@@ -330,10 +320,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this.update(pointer);
|
||||
this.draw(this.canvas.contextTop);
|
||||
},
|
||||
@@ -358,9 +346,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
update: function (p) {
|
||||
this.set(p);
|
||||
this._latestStrokeLength = this._point
|
||||
.subtract(this._latest)
|
||||
.distanceFrom({ x: 0, y: 0 });
|
||||
this._latestStrokeLength = this._point.subtract(this._latest).distanceFrom({ x: 0, y: 0 });
|
||||
},
|
||||
|
||||
draw: function (ctx) {
|
||||
@@ -372,12 +358,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
v.normalize(s);
|
||||
|
||||
dotSize =
|
||||
this._sep *
|
||||
fabric.util.clamp(
|
||||
(this._inkAmount / this._latestStrokeLength) * 3,
|
||||
1,
|
||||
0.5
|
||||
);
|
||||
this._sep * fabric.util.clamp((this._inkAmount / this._latestStrokeLength) * 3, 1, 0.5);
|
||||
dotNum = Math.ceil(this._size * this._sep);
|
||||
|
||||
range = this._size / 2;
|
||||
@@ -428,10 +409,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
@@ -440,15 +419,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
color = fabric.util.colorValues(this.color);
|
||||
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.1 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.1 * this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
|
||||
this._points.push(pointer);
|
||||
@@ -456,10 +427,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points.push(pointer);
|
||||
|
||||
@@ -528,10 +497,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
_render: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var len,
|
||||
i,
|
||||
point = this.setPointer(pointer),
|
||||
@@ -579,21 +546,11 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
for (i = 0; i < num; i++) {
|
||||
r = fabric.util.getRandom(range, 1);
|
||||
c = fabric.util.getRandom(Math.PI * 2);
|
||||
point = new fabric.Point(
|
||||
pointer.x + r * Math.sin(c),
|
||||
pointer.y + r * Math.cos(c)
|
||||
);
|
||||
point = new fabric.Point(pointer.x + r * Math.sin(c), pointer.y + r * Math.cos(c));
|
||||
|
||||
ctx.fillStyle = color;
|
||||
ctx.beginPath();
|
||||
ctx.arc(
|
||||
point.x,
|
||||
point.y,
|
||||
fabric.util.getRandom(maxSize) / 2,
|
||||
0,
|
||||
Math.PI * 2,
|
||||
false
|
||||
);
|
||||
ctx.arc(point.x, point.y, fabric.util.getRandom(maxSize) / 2, 0, Math.PI * 2, false);
|
||||
ctx.fill();
|
||||
}
|
||||
ctx.restore();
|
||||
@@ -609,10 +566,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
_resetTip: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var len,
|
||||
i,
|
||||
point = this.setPointer(pointer);
|
||||
@@ -655,10 +610,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
|
||||
@@ -667,23 +620,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.05 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var i,
|
||||
@@ -772,10 +715,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this._lastPoint.x - lineWidthDiff + num,
|
||||
this._lastPoint.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(
|
||||
pointer.x - lineWidthDiff + num,
|
||||
pointer.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(pointer.x - lineWidthDiff + num, pointer.y + lineWidthDiff - num);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
@@ -783,10 +723,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
@@ -795,10 +733,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -852,10 +788,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this._lastPoint.x + lineWidthDiff - num,
|
||||
this._lastPoint.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(
|
||||
pointer.x + lineWidthDiff - num,
|
||||
pointer.y + lineWidthDiff - num
|
||||
);
|
||||
ctx.lineTo(pointer.x + lineWidthDiff - num, pointer.y + lineWidthDiff - num);
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
@@ -863,10 +796,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
@@ -875,10 +806,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -923,10 +852,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [];
|
||||
this._points.push(["M", pointer.x, pointer.y]);
|
||||
|
||||
@@ -938,10 +865,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -1010,10 +935,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.strokeStyle = this.color;
|
||||
@@ -1024,10 +947,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
// 添加坐标转换处理画布缩放和偏移
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._render(pointer);
|
||||
}
|
||||
@@ -1093,10 +1014,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this.canvas.contextTop.lineWidth = this._lineWidth;
|
||||
this._size = this.width + this._baseWidth;
|
||||
@@ -1104,10 +1023,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
if (this.canvas._isCurrentlyDrawing) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._render(pointer);
|
||||
}
|
||||
},
|
||||
@@ -1158,12 +1075,10 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(painters[i].dx, painters[i].dy);
|
||||
painters[i].dx -= painters[i].ax =
|
||||
(painters[i].ax +
|
||||
(painters[i].dx - this._lastPoint.x) * painters[i].div) *
|
||||
(painters[i].ax + (painters[i].dx - this._lastPoint.x) * painters[i].div) *
|
||||
painters[i].ease;
|
||||
painters[i].dy -= painters[i].ay =
|
||||
(painters[i].ay +
|
||||
(painters[i].dy - this._lastPoint.y) * painters[i].div) *
|
||||
(painters[i].ay + (painters[i].dy - this._lastPoint.y) * painters[i].div) *
|
||||
painters[i].ease;
|
||||
ctx.lineTo(painters[i].dx, painters[i].dy);
|
||||
ctx.stroke();
|
||||
@@ -1185,26 +1100,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
ease: Math.random() * 0.2 + 0.6,
|
||||
});
|
||||
}
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.05 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.05 * this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
|
||||
for (var i = 0; i < this._nrPainters; i++) {
|
||||
for (let i = 0; i < this._nrPainters; i++) {
|
||||
this._painters[i].dx = pointer.x;
|
||||
this._painters[i].dy = pointer.y;
|
||||
}
|
||||
@@ -1216,10 +1121,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
},
|
||||
|
||||
@@ -1254,34 +1157,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
color = fabric.util.colorValues(this.color);
|
||||
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + this.opacity + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
ctx.lineJoin = ctx.lineCap = "round";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
@@ -1346,10 +1237,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
|
||||
// 坐标转换
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
@@ -1361,23 +1250,13 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
ctx.lineWidth = this.width;
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.3 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.3 * this.opacity + ")";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
// 坐标转换
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
|
||||
this._points.push(pointer);
|
||||
|
||||
@@ -1396,15 +1275,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
// 增加透明度设置
|
||||
var color = fabric.util.colorValues(this.color);
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
0.2 * this.opacity +
|
||||
")";
|
||||
"rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + 0.2 * this.opacity + ")";
|
||||
|
||||
// 修改循环逻辑,确保在有点时能画出效果
|
||||
if (this._count > 0) {
|
||||
@@ -1416,10 +1287,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
if (d < 4000 && Math.random() > d / 2000) {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(
|
||||
points[this._count].x + dx * factor,
|
||||
points[this._count].y + dy * factor
|
||||
);
|
||||
ctx.moveTo(points[this._count].x + dx * factor, points[this._count].y + dy * factor);
|
||||
ctx.lineTo(points[i].x - dx * factor, points[i].y - dy * factor);
|
||||
ctx.stroke();
|
||||
this._drawn = true;
|
||||
@@ -1494,10 +1362,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._point = new fabric.Point(pointer.x, pointer.y);
|
||||
this._lastPoint = this._point;
|
||||
|
||||
@@ -1510,10 +1376,8 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = this._point;
|
||||
this._point = new fabric.Point(pointer.x, pointer.y);
|
||||
},
|
||||
@@ -1542,13 +1406,7 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
|
||||
x = self._lastPoint.x + Math.sin(angle) - self.size / 2;
|
||||
y = self._lastPoint.y + Math.cos(angle) - self.size / 2;
|
||||
self.canvas.contextTop.drawImage(
|
||||
self.brush._element,
|
||||
x,
|
||||
y,
|
||||
self.size,
|
||||
self.size
|
||||
);
|
||||
self.canvas.contextTop.drawImage(self.brush._element, x, y, self.size, self.size);
|
||||
|
||||
if (self.canvas._isCurrentlyDrawing) {
|
||||
setTimeout(draw, self._interval);
|
||||
@@ -1594,43 +1452,22 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
var ctx = this.canvas.contextTop,
|
||||
color = fabric.util.colorValues(this.color),
|
||||
bgColor = fabric.util.colorValues(this.bgColor);
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._lastPoint = pointer;
|
||||
this._drawn = false;
|
||||
|
||||
//ctx.globalCompositeOperation = 'source-over';
|
||||
this.canvas.contextTop.globalAlpha = this.opacity;
|
||||
ctx.fillStyle =
|
||||
"rgba(" +
|
||||
bgColor[0] +
|
||||
"," +
|
||||
bgColor[1] +
|
||||
"," +
|
||||
bgColor[2] +
|
||||
"," +
|
||||
bgColor[3] +
|
||||
")";
|
||||
ctx.strokeStyle =
|
||||
"rgba(" +
|
||||
color[0] +
|
||||
"," +
|
||||
color[1] +
|
||||
"," +
|
||||
color[2] +
|
||||
"," +
|
||||
color[3] +
|
||||
")";
|
||||
"rgba(" + bgColor[0] + "," + bgColor[1] + "," + bgColor[2] + "," + bgColor[3] + ")";
|
||||
ctx.strokeStyle = "rgba(" + color[0] + "," + color[1] + "," + color[2] + "," + color[3] + ")";
|
||||
ctx.lineWidth = this.width;
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
var ctx = this.canvas.contextTop,
|
||||
dx = pointer.x - this._lastPoint.x,
|
||||
dy = pointer.y - this._lastPoint.y,
|
||||
@@ -1683,20 +1520,16 @@ import { sprayBrushDataUrl } from "./data/sprayBrushData.js";
|
||||
},
|
||||
|
||||
onMouseDown: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points = [pointer];
|
||||
this._count = 0;
|
||||
this._colorValues = fabric.util.colorValues(this.color);
|
||||
},
|
||||
|
||||
onMouseMove: function (pointer) {
|
||||
pointer.x =
|
||||
pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y =
|
||||
pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
pointer.x = pointer.x * this.canvas.getZoom() + this.canvas.viewportTransform[4];
|
||||
pointer.y = pointer.y * this.canvas.getZoom() + this.canvas.viewportTransform[5];
|
||||
this._points.push(pointer);
|
||||
|
||||
var ctx = this.canvas.contextTop,
|
||||
|
||||
@@ -350,9 +350,7 @@ export class RainbowBrush extends BaseBrush {
|
||||
];
|
||||
|
||||
// 移除基础属性中的颜色选择器,因为彩虹笔不需要颜色选择
|
||||
const filteredBaseProps = baseProperties.filter(
|
||||
(prop) => prop.id !== "color"
|
||||
);
|
||||
const filteredBaseProps = baseProperties.filter((prop) => prop.id !== "color");
|
||||
|
||||
// 合并并返回所有属性
|
||||
return [...filteredBaseProps, ...rainbowProperties];
|
||||
@@ -432,11 +430,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
this.dashPattern = options.dashPattern || [0, 0]; // 实线
|
||||
this.noiseAmount = options.noiseAmount || 0;
|
||||
this.gradientEnabled = options.gradientEnabled || false;
|
||||
this.gradientColors = options.gradientColors || [
|
||||
"#ff0000",
|
||||
"#ffff00",
|
||||
"#00ff00",
|
||||
];
|
||||
this.gradientColors = options.gradientColors || ["#ff0000", "#ffff00", "#00ff00"];
|
||||
this.angle = options.angle || 0;
|
||||
|
||||
// 材质相关属性
|
||||
@@ -885,8 +879,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
description: "选择图案类型",
|
||||
category: "特效设置",
|
||||
order: 130,
|
||||
visibleWhen: (values) =>
|
||||
values.effectType === "pattern" || values.patternType === "dashed",
|
||||
visibleWhen: (values) => values.effectType === "pattern" || values.patternType === "dashed",
|
||||
},
|
||||
|
||||
// 虚线特有属性
|
||||
@@ -1184,7 +1177,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
return true;
|
||||
|
||||
case "dashPattern1":
|
||||
case "dashPattern2":
|
||||
case "dashPattern2": {
|
||||
// 更新虚线模式
|
||||
const idx = propId === "dashPattern1" ? 0 : 1;
|
||||
this.dashPattern[idx] = value;
|
||||
@@ -1192,6 +1185,7 @@ export class CustomBrushExample extends BaseBrush {
|
||||
this.brush.strokeDashArray = this.dashPattern;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "noiseAmount":
|
||||
this.noiseAmount = value;
|
||||
@@ -1206,13 +1200,14 @@ export class CustomBrushExample extends BaseBrush {
|
||||
|
||||
case "gradientColor1":
|
||||
case "gradientColor2":
|
||||
case "gradientColor3":
|
||||
case "gradientColor3": {
|
||||
const colorIdx = parseInt(propId.slice(-1)) - 1;
|
||||
this.gradientColors[colorIdx] = value;
|
||||
if (this.gradientEnabled && this.brush) {
|
||||
this._configureGradient(this.brush);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
case "angle":
|
||||
this.angle = value;
|
||||
|
||||
@@ -24,8 +24,7 @@ export class InkBrush extends BaseBrush {
|
||||
this._baseWidth = options._baseWidth || 15;
|
||||
this._inkAmount = options._inkAmount || 7;
|
||||
this._range = options._range || 10;
|
||||
this.splashEnabled =
|
||||
options.splashEnabled !== undefined ? options.splashEnabled : true;
|
||||
this.splashEnabled = options.splashEnabled !== undefined ? options.splashEnabled : true;
|
||||
this.splashSize = options.splashSize || 5;
|
||||
this.splashDistance = options.splashDistance || 30;
|
||||
}
|
||||
|
||||
@@ -26,9 +26,7 @@ export class LongfurBrush extends BaseBrush {
|
||||
this.furFlowFactor = options.furFlowFactor || 0.5;
|
||||
this.furCurvature = options.furCurvature || 0.3;
|
||||
this.randomizeDirection =
|
||||
options.randomizeDirection !== undefined
|
||||
? options.randomizeDirection
|
||||
: true;
|
||||
options.randomizeDirection !== undefined ? options.randomizeDirection : true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -39,9 +39,7 @@ export class PencilBrush extends BaseBrush {
|
||||
this.brush = new fabric.PencilBrush(this.canvas);
|
||||
|
||||
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
|
||||
this.brush
|
||||
);
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
|
||||
const self = this; // 保存外部this引用
|
||||
|
||||
this.brush._finalizeAndAddPath = function () {
|
||||
@@ -54,10 +52,7 @@ export class PencilBrush extends BaseBrush {
|
||||
this._points = this.decimatePoints(this._points, this.decimate);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"PencilBrush: points count =",
|
||||
this._points ? this._points.length : 0
|
||||
);
|
||||
console.log("PencilBrush: points count =", this._points ? this._points.length : 0);
|
||||
|
||||
// 检查是否有有效的路径数据
|
||||
if (!this._points || this._points.length < 2) {
|
||||
@@ -97,9 +92,7 @@ export class PencilBrush extends BaseBrush {
|
||||
// 恢复透明度
|
||||
ctx.globalAlpha = currentAlpha;
|
||||
} else {
|
||||
console.warn(
|
||||
"convertToImg method not found, falling back to original behavior"
|
||||
);
|
||||
console.warn("convertToImg method not found, falling back to original behavior");
|
||||
// 如果没有convertToImg方法,回退到原始行为
|
||||
this.canvas.add(path);
|
||||
this.canvas.fire("path:created", { path: path });
|
||||
@@ -135,11 +128,7 @@ export class PencilBrush extends BaseBrush {
|
||||
const command = pathData[i];
|
||||
if (command[0] === "M") {
|
||||
moveCount++;
|
||||
} else if (
|
||||
command[0] === "L" ||
|
||||
command[0] === "Q" ||
|
||||
command[0] === "C"
|
||||
) {
|
||||
} else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
|
||||
hasDrawing = true;
|
||||
break;
|
||||
}
|
||||
@@ -210,9 +199,7 @@ export class PencilBrush extends BaseBrush {
|
||||
_createRGBAColor(color, opacity) {
|
||||
// 如果已经是rgba颜色,先提取RGB部分
|
||||
if (color.startsWith("rgba")) {
|
||||
const rgbaMatch = color.match(
|
||||
/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/
|
||||
);
|
||||
const rgbaMatch = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)/);
|
||||
if (rgbaMatch) {
|
||||
const [, r, g, b] = rgbaMatch;
|
||||
return `rgba(${r}, ${g}, ${b}, ${opacity})`;
|
||||
|
||||
@@ -75,10 +75,7 @@ export class RibbonBrush extends BaseBrush {
|
||||
brush.opacity = options.opacity;
|
||||
// 如果启用渐变,更新渐变的第一个颜色
|
||||
if (this.gradient && this.gradientColors.length > 0) {
|
||||
this.gradientColors[0] = this._createRGBAColor(
|
||||
brush.color,
|
||||
options.opacity
|
||||
);
|
||||
this.gradientColors[0] = this._createRGBAColor(brush.color, options.opacity);
|
||||
this.updateGradient();
|
||||
}
|
||||
brush.canvas.freeDrawingBrush.opacity = options.opacity;
|
||||
|
||||
@@ -23,8 +23,7 @@ export class SpraypaintBrush extends BaseBrush {
|
||||
// 喷漆笔刷特有属性
|
||||
this.density = options.density || 20;
|
||||
this.sprayRadius = options.sprayRadius || 10;
|
||||
this.randomOpacity =
|
||||
options.randomOpacity !== undefined ? options.randomOpacity : true;
|
||||
this.randomOpacity = options.randomOpacity !== undefined ? options.randomOpacity : true;
|
||||
this.dotSize = options.dotSize || 1;
|
||||
this.dotShape = options.dotShape || "circle";
|
||||
}
|
||||
|
||||
@@ -71,9 +71,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this.brush = new fabric.PatternBrush(this.canvas);
|
||||
|
||||
// 重写 _finalizeAndAddPath 方法,使其调用 convertToImg 而不是创建 Path 对象
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(
|
||||
this.brush
|
||||
);
|
||||
const originalFinalizeAndAddPath = this.brush._finalizeAndAddPath.bind(this.brush);
|
||||
const self = this; // 保存外部this引用
|
||||
|
||||
this.brush._finalizeAndAddPath = function () {
|
||||
@@ -86,10 +84,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this._points = this.decimatePoints(this._points, this.decimate);
|
||||
}
|
||||
|
||||
console.log(
|
||||
"TextureBrush: points count =",
|
||||
this._points ? this._points.length : 0
|
||||
);
|
||||
console.log("TextureBrush: points count =", this._points ? this._points.length : 0);
|
||||
|
||||
// 检查是否有有效的路径数据
|
||||
if (!this._points || this._points.length < 2) {
|
||||
@@ -122,9 +117,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this.convertToImg();
|
||||
console.log("TextureBrush: convertToImg called successfully");
|
||||
} else {
|
||||
console.warn(
|
||||
"convertToImg method not found, falling back to original behavior"
|
||||
);
|
||||
console.warn("convertToImg method not found, falling back to original behavior");
|
||||
// 如果没有convertToImg方法,回退到原始行为
|
||||
this.canvas.add(path);
|
||||
this.canvas.fire("path:created", { path: path });
|
||||
@@ -254,10 +247,7 @@ export class TextureBrush extends BaseBrush {
|
||||
this._applyTextureToPatternBrush(img);
|
||||
resolve(img);
|
||||
});
|
||||
} else if (
|
||||
source instanceof Image ||
|
||||
source instanceof HTMLCanvasElement
|
||||
) {
|
||||
} else if (source instanceof Image || source instanceof HTMLCanvasElement) {
|
||||
// 如果已经是Image或Canvas对象,直接使用
|
||||
this._applyTextureToPatternBrush(source);
|
||||
resolve(source);
|
||||
@@ -425,9 +415,7 @@ export class TextureBrush extends BaseBrush {
|
||||
if (textures.length === 0) return Promise.resolve();
|
||||
|
||||
this.currentTextureIndex =
|
||||
this.currentTextureIndex === 0
|
||||
? textures.length - 1
|
||||
: this.currentTextureIndex - 1;
|
||||
this.currentTextureIndex === 0 ? textures.length - 1 : this.currentTextureIndex - 1;
|
||||
const prevTexture = textures[this.currentTextureIndex];
|
||||
|
||||
return this.setTextureById(prevTexture.id);
|
||||
@@ -943,11 +931,7 @@ export class TextureBrush extends BaseBrush {
|
||||
const command = pathData[i];
|
||||
if (command[0] === "M") {
|
||||
moveCount++;
|
||||
} else if (
|
||||
command[0] === "L" ||
|
||||
command[0] === "Q" ||
|
||||
command[0] === "C"
|
||||
) {
|
||||
} else if (command[0] === "L" || command[0] === "Q" || command[0] === "C") {
|
||||
hasDrawing = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -25,9 +25,7 @@ export class WritingBrush extends BaseBrush {
|
||||
this.inkAmount = options.inkAmount || 20;
|
||||
this.brushTaperFactor = options.brushTaperFactor || 0.6;
|
||||
this.enableInkDripping =
|
||||
options.enableInkDripping !== undefined
|
||||
? options.enableInkDripping
|
||||
: true;
|
||||
options.enableInkDripping !== undefined ? options.enableInkDripping : true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,14 +61,10 @@ export class PerformanceManager {
|
||||
: 0;
|
||||
|
||||
const avgUndoTime =
|
||||
this.stats.totalUndos > 0
|
||||
? this.stats.totalUndoTime / this.stats.totalUndos
|
||||
: 0;
|
||||
this.stats.totalUndos > 0 ? this.stats.totalUndoTime / this.stats.totalUndos : 0;
|
||||
|
||||
const avgRedoTime =
|
||||
this.stats.totalRedos > 0
|
||||
? this.stats.totalRedoTime / this.stats.totalRedos
|
||||
: 0;
|
||||
this.stats.totalRedos > 0 ? this.stats.totalRedoTime / this.stats.totalRedos : 0;
|
||||
|
||||
return {
|
||||
overview: {
|
||||
@@ -79,26 +75,18 @@ export class PerformanceManager {
|
||||
avgUndoTime: Number(avgUndoTime.toFixed(2)),
|
||||
avgRedoTime: Number(avgRedoTime.toFixed(2)),
|
||||
},
|
||||
commandBreakdown: Array.from(this.stats.commandStats.entries()).map(
|
||||
([name, stats]) => ({
|
||||
commandName: name,
|
||||
executions: stats.executions,
|
||||
undos: stats.undos,
|
||||
redos: stats.redos,
|
||||
avgExecutionTime:
|
||||
stats.executions > 0
|
||||
? Number((stats.totalExecutionTime / stats.executions).toFixed(2))
|
||||
: 0,
|
||||
avgUndoTime:
|
||||
stats.undos > 0
|
||||
? Number((stats.totalUndoTime / stats.undos).toFixed(2))
|
||||
: 0,
|
||||
avgRedoTime:
|
||||
stats.redos > 0
|
||||
? Number((stats.totalRedoTime / stats.redos).toFixed(2))
|
||||
: 0,
|
||||
})
|
||||
),
|
||||
commandBreakdown: Array.from(this.stats.commandStats.entries()).map(([name, stats]) => ({
|
||||
commandName: name,
|
||||
executions: stats.executions,
|
||||
undos: stats.undos,
|
||||
redos: stats.redos,
|
||||
avgExecutionTime:
|
||||
stats.executions > 0
|
||||
? Number((stats.totalExecutionTime / stats.executions).toFixed(2))
|
||||
: 0,
|
||||
avgUndoTime: stats.undos > 0 ? Number((stats.totalUndoTime / stats.undos).toFixed(2)) : 0,
|
||||
avgRedoTime: stats.redos > 0 ? Number((stats.totalRedoTime / stats.redos).toFixed(2)) : 0,
|
||||
})),
|
||||
recentOperations: this.stats.recentOperations.slice(-20), // 最近20个操作
|
||||
};
|
||||
}
|
||||
@@ -110,10 +98,8 @@ export class PerformanceManager {
|
||||
const slowCommands = [];
|
||||
|
||||
for (const [name, stats] of this.stats.commandStats.entries()) {
|
||||
const avgExecTime =
|
||||
stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0;
|
||||
const avgUndoTime =
|
||||
stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
|
||||
const avgExecTime = stats.executions > 0 ? stats.totalExecutionTime / stats.executions : 0;
|
||||
const avgUndoTime = stats.undos > 0 ? stats.totalUndoTime / stats.undos : 0;
|
||||
|
||||
if (avgExecTime > threshold || avgUndoTime > threshold) {
|
||||
slowCommands.push({
|
||||
@@ -128,8 +114,7 @@ export class PerformanceManager {
|
||||
|
||||
return slowCommands.sort(
|
||||
(a, b) =>
|
||||
Math.max(b.avgExecutionTime, b.avgUndoTime) -
|
||||
Math.max(a.avgExecutionTime, a.avgUndoTime)
|
||||
Math.max(b.avgExecutionTime, b.avgUndoTime) - Math.max(a.avgExecutionTime, a.avgUndoTime)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -68,14 +68,9 @@ export class CanvasEventManager {
|
||||
// 让动画管理器自行处理冲突,避免过度干预
|
||||
} else {
|
||||
// 非 Mac 设备的标准处理
|
||||
if (
|
||||
this.animationManager._panAnimation ||
|
||||
this.animationManager._zoomAnimation
|
||||
) {
|
||||
this.animationManager._wasPanning =
|
||||
!!this.animationManager._panAnimation;
|
||||
this.animationManager._wasZooming =
|
||||
!!this.animationManager._zoomAnimation;
|
||||
if (this.animationManager._panAnimation || this.animationManager._zoomAnimation) {
|
||||
this.animationManager._wasPanning = !!this.animationManager._panAnimation;
|
||||
this.animationManager._wasZooming = !!this.animationManager._zoomAnimation;
|
||||
this.animationManager.smoothStopAnimations({ duration: 0.1 });
|
||||
}
|
||||
}
|
||||
@@ -127,8 +122,7 @@ export class CanvasEventManager {
|
||||
|
||||
// Mac 设备优化:触控板滚动通常比鼠标滚轮更敏感
|
||||
if (this.deviceInfo.isMac) {
|
||||
const isMacTrackpadScroll =
|
||||
Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
|
||||
const isMacTrackpadScroll = Math.abs(opt.e.deltaY) < 100 && opt.e.deltaMode === 0;
|
||||
if (isMacTrackpadScroll) {
|
||||
// Mac 触控板滚动更细腻,需要调整滚动因子
|
||||
scrollFactor *= 0.8; // 降低滚动敏感度
|
||||
@@ -183,11 +177,7 @@ export class CanvasEventManager {
|
||||
// 平滑停止任何正在进行的惯性动画
|
||||
this.stopInertiaAnimation(true);
|
||||
|
||||
if (
|
||||
opt.e.altKey ||
|
||||
opt.e.which === 2 ||
|
||||
this.editorMode === OperationType.PAN
|
||||
) {
|
||||
if (opt.e.altKey || opt.e.which === 2 || this.editorMode === OperationType.PAN) {
|
||||
this.canvas.isDragging = true;
|
||||
this.canvas.lastPosX = opt.e.clientX;
|
||||
this.canvas.lastPosY = opt.e.clientY;
|
||||
@@ -247,10 +237,8 @@ export class CanvasEventManager {
|
||||
|
||||
if (opt.e.touches && opt.e.touches.length === 2) {
|
||||
this.canvas.isDragging = true;
|
||||
this.canvas.lastPosX =
|
||||
(opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
|
||||
this.canvas.lastPosY =
|
||||
(opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
|
||||
this.canvas.lastPosX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
|
||||
this.canvas.lastPosY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
|
||||
|
||||
// 重置触摸位置历史
|
||||
this.dragStartTime = Date.now();
|
||||
@@ -290,10 +278,8 @@ export class CanvasEventManager {
|
||||
if (!this.canvas.isDragging) return;
|
||||
|
||||
if (opt.e.touches && opt.e.touches.length === 2) {
|
||||
const currentX =
|
||||
(opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
|
||||
const currentY =
|
||||
(opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
|
||||
const currentX = (opt.e.touches[0].clientX + opt.e.touches[1].clientX) / 2;
|
||||
const currentY = (opt.e.touches[0].clientY + opt.e.touches[1].clientY) / 2;
|
||||
|
||||
const vpt = this.canvas.viewportTransform;
|
||||
vpt[4] += currentX - this.canvas.lastPosX;
|
||||
@@ -321,8 +307,7 @@ export class CanvasEventManager {
|
||||
|
||||
// 单指拖动更新
|
||||
this.canvas.on("touch:drag:update", (opt) => {
|
||||
if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN)
|
||||
return;
|
||||
if (!this.canvas.isDragging || this.editorMode !== OperationType.PAN) return;
|
||||
|
||||
const currentX = opt.e.touches[0].clientX;
|
||||
const currentY = opt.e.touches[0].clientY;
|
||||
@@ -367,10 +352,7 @@ export class CanvasEventManager {
|
||||
if (this.canvas.isDragging) {
|
||||
// 使用动画管理器处理惯性效果
|
||||
if (this.lastMousePositions.length > 1 && opt && opt.e) {
|
||||
this.animationManager.applyInertiaEffect(
|
||||
this.lastMousePositions,
|
||||
isTouch
|
||||
);
|
||||
this.animationManager.applyInertiaEffect(this.lastMousePositions, isTouch);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -405,22 +387,10 @@ export class CanvasEventManager {
|
||||
});
|
||||
|
||||
// 添加对象开始变换时的状态捕获
|
||||
this.canvas.on(
|
||||
"object:moving",
|
||||
this._captureInitialTransformState.bind(this)
|
||||
);
|
||||
this.canvas.on(
|
||||
"object:scaling",
|
||||
this._captureInitialTransformState.bind(this)
|
||||
);
|
||||
this.canvas.on(
|
||||
"object:rotating",
|
||||
this._captureInitialTransformState.bind(this)
|
||||
);
|
||||
this.canvas.on(
|
||||
"object:skewing",
|
||||
this._captureInitialTransformState.bind(this)
|
||||
);
|
||||
this.canvas.on("object:moving", this._captureInitialTransformState.bind(this));
|
||||
this.canvas.on("object:scaling", this._captureInitialTransformState.bind(this));
|
||||
this.canvas.on("object:rotating", this._captureInitialTransformState.bind(this));
|
||||
this.canvas.on("object:skewing", this._captureInitialTransformState.bind(this));
|
||||
|
||||
this.canvas.on("object:modified", (e) => {
|
||||
// 移除调试日志
|
||||
@@ -592,8 +562,7 @@ export class CanvasEventManager {
|
||||
}
|
||||
// 验证是否需要合并
|
||||
const hasExistingObjects =
|
||||
Array.isArray(activeLayer.fabricObjects) &&
|
||||
activeLayer.fabricObjects.length > 0;
|
||||
Array.isArray(activeLayer.fabricObjects) && activeLayer.fabricObjects.length > 0;
|
||||
const hasNewImage = !!fabricImage;
|
||||
|
||||
if (!hasExistingObjects && !hasNewImage) {
|
||||
@@ -611,10 +580,7 @@ export class CanvasEventManager {
|
||||
try {
|
||||
console.log(`开始合并图层 ${activeLayer.name} 中的对象为组...`);
|
||||
|
||||
const command = await this.layerManager.LayerObjectsToGroup(
|
||||
activeLayer,
|
||||
fabricImage
|
||||
);
|
||||
const command = await this.layerManager.LayerObjectsToGroup(activeLayer, fabricImage);
|
||||
|
||||
// 设置命令的撤销状态
|
||||
if (isBoolean(options.undoable)) command.undoable = options.undoable; // 是否撤销
|
||||
@@ -660,9 +626,7 @@ export class CanvasEventManager {
|
||||
|
||||
// 查找对应的图层(现在元素就是图层)
|
||||
const layer = this.layers.value.find(
|
||||
(l) =>
|
||||
l.id === elementId ||
|
||||
(l.fabricObjects && l.fabricObjects?.[0]?.id === layerId)
|
||||
(l) => l.fabricObjects && l.fabricObjects?.[0]?.id === layerId
|
||||
);
|
||||
|
||||
if (layer) {
|
||||
@@ -745,8 +709,7 @@ export class CanvasEventManager {
|
||||
_detectDeviceType() {
|
||||
const userAgent = navigator.userAgent.toLowerCase();
|
||||
const platform = navigator.platform.toLowerCase();
|
||||
const hasTouchSupport =
|
||||
"ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
const hasTouchSupport = "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
// 检测操作系统
|
||||
const isMac = /mac|darwin/.test(platform) || /macintosh/.test(userAgent);
|
||||
|
||||
@@ -92,9 +92,7 @@ export class KeyboardManager {
|
||||
*/
|
||||
detectTouchDevice() {
|
||||
return (
|
||||
"ontouchstart" in window ||
|
||||
navigator.maxTouchPoints > 0 ||
|
||||
navigator.msMaxTouchPoints > 0
|
||||
"ontouchstart" in window || navigator.maxTouchPoints > 0 || navigator.msMaxTouchPoints > 0
|
||||
);
|
||||
}
|
||||
|
||||
@@ -214,9 +212,7 @@ export class KeyboardManager {
|
||||
this.container.addEventListener("touchcancel", this._handleTouchEnd);
|
||||
}
|
||||
|
||||
console.log(
|
||||
`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`
|
||||
);
|
||||
console.log(`键盘管理器已初始化,平台: ${this.platform}, 触摸设备: ${this.isTouchDevice}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -262,10 +258,7 @@ export class KeyboardManager {
|
||||
|
||||
// 调用自定义处理程序
|
||||
const key = event.key.toLowerCase();
|
||||
if (
|
||||
this.customHandlers[key] &&
|
||||
typeof this.customHandlers[key].onKeyUp === "function"
|
||||
) {
|
||||
if (this.customHandlers[key] && typeof this.customHandlers[key].onKeyUp === "function") {
|
||||
this.customHandlers[key].onKeyUp(event);
|
||||
}
|
||||
}
|
||||
@@ -281,15 +274,11 @@ export class KeyboardManager {
|
||||
if (touches.length === 2) {
|
||||
// 双指触摸 - 可用于缩放或调整画笔大小
|
||||
this.touchState.isTwoFingerTouch = true;
|
||||
this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(
|
||||
touches[0],
|
||||
touches[1]
|
||||
);
|
||||
this.touchState.pinchStartDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
|
||||
|
||||
// 如果有画笔管理器,记录起始画笔大小
|
||||
if (this.toolManager && this.toolManager.brushManager) {
|
||||
this.touchState.pinchStartBrushSize =
|
||||
this.toolManager.brushManager.brushSize.value;
|
||||
this.touchState.pinchStartBrushSize = this.toolManager.brushManager.brushSize.value;
|
||||
}
|
||||
} else if (touches.length === 3) {
|
||||
// 三指触摸 - 可用于撤销/重做
|
||||
@@ -311,10 +300,7 @@ export class KeyboardManager {
|
||||
|
||||
// 双指缩放处理 - 调整画笔大小
|
||||
if (touches.length === 2 && this.touchState.isTwoFingerTouch) {
|
||||
const currentDistance = this.getDistanceBetweenTouches(
|
||||
touches[0],
|
||||
touches[1]
|
||||
);
|
||||
const currentDistance = this.getDistanceBetweenTouches(touches[0], touches[1]);
|
||||
const scale = currentDistance / this.touchState.pinchStartDistance;
|
||||
|
||||
// 调整画笔大小
|
||||
@@ -587,10 +573,7 @@ export class KeyboardManager {
|
||||
let shortcutKey = "";
|
||||
|
||||
// 统一处理Mac和PC的修饰键
|
||||
if (
|
||||
(this.platform === "mac" && event.metaKey) ||
|
||||
(this.platform !== "mac" && event.ctrlKey)
|
||||
) {
|
||||
if ((this.platform === "mac" && event.metaKey) || (this.platform !== "mac" && event.ctrlKey)) {
|
||||
shortcutKey += `${this.modifierKeys.cmdOrCtrl}+`;
|
||||
} else if (event.ctrlKey) {
|
||||
shortcutKey += "ctrl+";
|
||||
|
||||
@@ -216,9 +216,7 @@ export class EnhancedLiquifyManager {
|
||||
// 计算图像大小
|
||||
const pixelCount = imageData.width * imageData.height;
|
||||
|
||||
console.log(
|
||||
`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`
|
||||
);
|
||||
console.log(`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`);
|
||||
|
||||
// 默认使用CPU渲染器
|
||||
this.activeRenderer = this.cpuRenderer;
|
||||
@@ -249,11 +247,7 @@ export class EnhancedLiquifyManager {
|
||||
this.activeRenderer = this.webglRenderer;
|
||||
this.renderMode = "webgl";
|
||||
} else {
|
||||
console.log(
|
||||
`液化功能: 使用CPU渲染模式${
|
||||
!this.isWebGLAvailable ? " (WebGL不可用)" : ""
|
||||
}`
|
||||
);
|
||||
console.log(`液化功能: 使用CPU渲染模式${!this.isWebGLAvailable ? " (WebGL不可用)" : ""}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -318,16 +312,11 @@ export class EnhancedLiquifyManager {
|
||||
this.params[param] = value;
|
||||
|
||||
// 同步更新当前渲染器 - 关键修复:确保参数正确传递
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.setParam === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.setParam === "function") {
|
||||
console.log(`EnhancedLiquifyManager 设置参数: ${param}=${value}`);
|
||||
this.activeRenderer.setParam(param, value);
|
||||
} else {
|
||||
console.warn(
|
||||
`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`
|
||||
);
|
||||
console.warn(`EnhancedLiquifyManager: 无法设置参数 ${param},渲染器未就绪`);
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -382,25 +371,17 @@ export class EnhancedLiquifyManager {
|
||||
* @param {Number} y 初始Y坐标
|
||||
*/
|
||||
startLiquifyOperation(x, y) {
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.startDeformation === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.startDeformation === "function") {
|
||||
this.activeRenderer.startDeformation(x, y);
|
||||
}
|
||||
console.log(
|
||||
`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`
|
||||
);
|
||||
console.log(`开始液化操作,渲染模式=${this.renderMode}, 初始点: (${x}, ${y})`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束液化操作
|
||||
*/
|
||||
endLiquifyOperation() {
|
||||
if (
|
||||
this.activeRenderer &&
|
||||
typeof this.activeRenderer.endDeformation === "function"
|
||||
) {
|
||||
if (this.activeRenderer && typeof this.activeRenderer.endDeformation === "function") {
|
||||
this.activeRenderer.endDeformation();
|
||||
}
|
||||
console.log(`结束液化操作,渲染模式=${this.renderMode}`);
|
||||
@@ -443,9 +424,7 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
// 坐标边界检查
|
||||
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) {
|
||||
console.warn(
|
||||
`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`
|
||||
);
|
||||
console.warn(`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -510,9 +489,7 @@ export class EnhancedLiquifyManager {
|
||||
console.log(
|
||||
`液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed(
|
||||
2
|
||||
)}ms, 图像尺寸=${this.originalImageData?.width}x${
|
||||
this.originalImageData?.height
|
||||
}`
|
||||
)}ms, 图像尺寸=${this.originalImageData?.width}x${this.originalImageData?.height}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -604,8 +581,7 @@ export class EnhancedLiquifyManager {
|
||||
const singleObject = objectsToCheck.length === 1;
|
||||
const isImage =
|
||||
singleObject &&
|
||||
(objectsToCheck[0].type === "image" ||
|
||||
objectsToCheck[0].type === "rasterized-layer");
|
||||
(objectsToCheck[0].type === "image" || objectsToCheck[0].type === "rasterized-layer");
|
||||
|
||||
// 检查是否为组
|
||||
const isGroup =
|
||||
@@ -655,19 +631,14 @@ export class EnhancedLiquifyManager {
|
||||
tempCanvas.height = fabricObject.height;
|
||||
const tempCtx = tempCanvas.getContext("2d");
|
||||
|
||||
console.log(
|
||||
`创建临时Canvas,尺寸: ${tempCanvas.width}x${tempCanvas.height}`
|
||||
);
|
||||
console.log(`创建临时Canvas,尺寸: ${tempCanvas.width}x${tempCanvas.height}`);
|
||||
|
||||
// 处理不同的图像源
|
||||
if (fabricObject._element) {
|
||||
console.log("使用 _element 绘制图像");
|
||||
|
||||
// 检查_element是否有效
|
||||
if (
|
||||
!fabricObject._element.complete &&
|
||||
fabricObject._element.tagName === "IMG"
|
||||
) {
|
||||
if (!fabricObject._element.complete && fabricObject._element.tagName === "IMG") {
|
||||
console.log("图像未加载完成,等待加载...");
|
||||
fabricObject._element.onload = () => {
|
||||
try {
|
||||
@@ -678,12 +649,7 @@ export class EnhancedLiquifyManager {
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
console.log("✅ 图像加载完成后获取数据成功");
|
||||
resolve(imageData);
|
||||
} catch (error) {
|
||||
@@ -698,17 +664,8 @@ export class EnhancedLiquifyManager {
|
||||
}
|
||||
|
||||
// 直接绘制已加载的图像
|
||||
tempCtx.drawImage(
|
||||
fabricObject._element,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
} else if (
|
||||
fabricObject.getSrc &&
|
||||
typeof fabricObject.getSrc === "function"
|
||||
) {
|
||||
tempCtx.drawImage(fabricObject._element, 0, 0, fabricObject.width, fabricObject.height);
|
||||
} else if (fabricObject.getSrc && typeof fabricObject.getSrc === "function") {
|
||||
console.log("使用 getSrc() 方法获取图像源");
|
||||
|
||||
// 通过URL创建图像
|
||||
@@ -717,24 +674,11 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
console.log(
|
||||
`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`
|
||||
);
|
||||
console.log(`图像加载成功,原始尺寸: ${img.naturalWidth}x${img.naturalHeight}`);
|
||||
|
||||
tempCtx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
|
||||
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log("✅ 通过URL获取图像数据成功");
|
||||
resolve(imageData);
|
||||
@@ -762,20 +706,9 @@ export class EnhancedLiquifyManager {
|
||||
|
||||
img.onload = () => {
|
||||
try {
|
||||
tempCtx.drawImage(
|
||||
img,
|
||||
0,
|
||||
0,
|
||||
fabricObject.width,
|
||||
fabricObject.height
|
||||
);
|
||||
tempCtx.drawImage(img, 0, 0, fabricObject.width, fabricObject.height);
|
||||
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log("✅ 通过src属性获取图像数据成功");
|
||||
resolve(imageData);
|
||||
@@ -795,20 +728,13 @@ export class EnhancedLiquifyManager {
|
||||
return;
|
||||
} else {
|
||||
console.error("无法找到有效的图像源");
|
||||
reject(
|
||||
new Error("图像对象缺少有效的图像源(_element, getSrc, 或 src)")
|
||||
);
|
||||
reject(new Error("图像对象缺少有效的图像源(_element, getSrc, 或 src)"));
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果走到这里,说明使用了_element直接绘制
|
||||
try {
|
||||
const imageData = tempCtx.getImageData(
|
||||
0,
|
||||
0,
|
||||
tempCanvas.width,
|
||||
tempCanvas.height
|
||||
);
|
||||
const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
|
||||
console.log(
|
||||
`✅ 获取图像数据成功: 对象尺寸=${fabricObject.width}x${fabricObject.height}, ` +
|
||||
|
||||
@@ -178,10 +178,7 @@ export class HybridLiquifyManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
|
||||
const baseRotationSpeed = 0.015;
|
||||
const rotationAngle =
|
||||
(clockwise ? 1 : -1) *
|
||||
baseRotationSpeed *
|
||||
strength *
|
||||
(1.0 + timeFactor * 0.3);
|
||||
(clockwise ? 1 : -1) * baseRotationSpeed * strength * (1.0 + timeFactor * 0.3);
|
||||
|
||||
this.accumulatedRotation += rotationAngle;
|
||||
|
||||
@@ -209,13 +206,7 @@ export class HybridLiquifyManager {
|
||||
const sourceY = centerY + Math.sin(newAngle) * distance;
|
||||
|
||||
// 双线性插值采样
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -232,16 +223,7 @@ export class HybridLiquifyManager {
|
||||
/**
|
||||
* 像素级水晶效果
|
||||
*/
|
||||
_applyPixelCrystal(
|
||||
srcData,
|
||||
dstData,
|
||||
width,
|
||||
height,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength
|
||||
) {
|
||||
_applyPixelCrystal(srcData, dstData, width, height, centerX, centerY, radius, strength) {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
|
||||
const distortionStrength = strength * (1.0 + timeFactor * 0.5);
|
||||
|
||||
@@ -266,25 +248,16 @@ export class HybridLiquifyManager {
|
||||
// 多层波浪扭曲
|
||||
const wave1 = Math.sin(angle * 8 + this.pressDuration * 0.005) * 0.6;
|
||||
const wave2 = Math.cos(angle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle =
|
||||
angle + (wave1 + wave2) * distortionStrength * falloff;
|
||||
const waveAngle = angle + (wave1 + wave2) * distortionStrength * falloff;
|
||||
|
||||
const radialMod =
|
||||
1 +
|
||||
Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) *
|
||||
0.3;
|
||||
1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
|
||||
const modDistance = distance * radialMod;
|
||||
|
||||
const sourceX = centerX + Math.cos(waveAngle) * modDistance;
|
||||
const sourceY = centerY + Math.sin(waveAngle) * modDistance;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -311,16 +284,7 @@ export class HybridLiquifyManager {
|
||||
/**
|
||||
* 像素级边缘效果
|
||||
*/
|
||||
_applyPixelEdge(
|
||||
srcData,
|
||||
dstData,
|
||||
width,
|
||||
height,
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength
|
||||
) {
|
||||
_applyPixelEdge(srcData, dstData, width, height, centerX, centerY, radius, strength) {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
|
||||
const edgeStrength = strength * (1.0 + timeFactor * 0.4);
|
||||
|
||||
@@ -354,13 +318,7 @@ export class HybridLiquifyManager {
|
||||
const sourceX = x + offsetX;
|
||||
const sourceY = y + offsetY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(srcData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
|
||||
@@ -79,12 +79,7 @@ export class LiquifyCPUManager {
|
||||
this.canvas.width = imageSource.width;
|
||||
this.canvas.height = imageSource.height;
|
||||
this.ctx.drawImage(imageSource, 0, 0);
|
||||
this.originalImageData = this.ctx.getImageData(
|
||||
0,
|
||||
0,
|
||||
imageSource.width,
|
||||
imageSource.height
|
||||
);
|
||||
this.originalImageData = this.ctx.getImageData(0, 0, imageSource.width, imageSource.height);
|
||||
} else {
|
||||
throw new Error("不支持的图像类型");
|
||||
}
|
||||
@@ -95,10 +90,7 @@ export class LiquifyCPUManager {
|
||||
this.originalImageData.height
|
||||
);
|
||||
|
||||
this._initMesh(
|
||||
this.originalImageData.width,
|
||||
this.originalImageData.height
|
||||
);
|
||||
this._initMesh(this.originalImageData.width, this.originalImageData.height);
|
||||
this.initialized = true;
|
||||
return true;
|
||||
} catch (error) {
|
||||
@@ -268,13 +260,7 @@ export class LiquifyCPUManager {
|
||||
* @param {number} strength 强度
|
||||
* @param {boolean} isClockwise 是否顺时针旋转
|
||||
*/
|
||||
_applyEnhancedRotationDeformation(
|
||||
centerX,
|
||||
centerY,
|
||||
radius,
|
||||
strength,
|
||||
isClockwise
|
||||
) {
|
||||
_applyEnhancedRotationDeformation(centerX, centerY, radius, strength, isClockwise) {
|
||||
if (!this.currentImageData) return;
|
||||
|
||||
const data = this.currentImageData.data;
|
||||
@@ -287,11 +273,7 @@ export class LiquifyCPUManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 5.0);
|
||||
const baseRotationSpeed = 0.02; // 使用与测试文件相同的速度
|
||||
const rotationAngle =
|
||||
(isClockwise ? 1 : -1) *
|
||||
baseRotationSpeed *
|
||||
pressure *
|
||||
power *
|
||||
(1.0 + timeFactor * 0.5);
|
||||
(isClockwise ? 1 : -1) * baseRotationSpeed * pressure * power * (1.0 + timeFactor * 0.5);
|
||||
|
||||
// 累积旋转角度 - 关键:这确保了持续旋转效果
|
||||
this.accumulatedRotation += rotationAngle;
|
||||
@@ -322,13 +304,7 @@ export class LiquifyCPUManager {
|
||||
const sourceY = centerY + Math.sin(newAngle) * distance;
|
||||
|
||||
// 双线性插值采样 - 确保像素连续性
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -389,13 +365,7 @@ export class LiquifyCPUManager {
|
||||
const sourceY = centerY + dy * scale;
|
||||
|
||||
// 双线性插值采样
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -461,13 +431,7 @@ export class LiquifyCPUManager {
|
||||
const sourceX = x - pushX;
|
||||
const sourceY = y - pushY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -504,13 +468,7 @@ export class LiquifyCPUManager {
|
||||
const sourceX = x - offsetX;
|
||||
const sourceY = y - offsetY;
|
||||
|
||||
const color = this._bilinearSample(
|
||||
tempData,
|
||||
width,
|
||||
height,
|
||||
sourceX,
|
||||
sourceY
|
||||
);
|
||||
const color = this._bilinearSample(tempData, width, height, sourceX, sourceY);
|
||||
|
||||
if (color) {
|
||||
const targetIdx = (y * width + x) * 4;
|
||||
@@ -561,7 +519,7 @@ export class LiquifyCPUManager {
|
||||
this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
break;
|
||||
|
||||
default:
|
||||
default: {
|
||||
// 对于其他模式,使用原有的网格算法
|
||||
if (!this.mesh) return;
|
||||
|
||||
@@ -569,20 +527,14 @@ export class LiquifyCPUManager {
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 4.0);
|
||||
const finalStrength = baseStrength * (1.0 + timeFactor * 0.5);
|
||||
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
|
||||
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
this._lightSmoothing();
|
||||
}
|
||||
|
||||
return this._applyMeshToImage();
|
||||
}
|
||||
}
|
||||
|
||||
// 对于像素算法,直接返回当前图像数据
|
||||
@@ -592,96 +544,96 @@ export class LiquifyCPUManager {
|
||||
/**
|
||||
* 应用液化变形 - 主要入口,集成增强算法
|
||||
*/
|
||||
applyDeformation(x, y) {
|
||||
if (!this.initialized || !this.originalImageData) {
|
||||
console.warn("液化管理器未初始化或缺少必要数据");
|
||||
return this.currentImageData;
|
||||
}
|
||||
// applyDeformation(x, y) {
|
||||
// if (!this.initialized || !this.originalImageData) {
|
||||
// console.warn("液化管理器未初始化或缺少必要数据");
|
||||
// return this.currentImageData;
|
||||
// }
|
||||
|
||||
// 更新鼠标位置
|
||||
this.currentMouseX = x;
|
||||
this.currentMouseY = y;
|
||||
// // 更新鼠标位置
|
||||
// this.currentMouseX = x;
|
||||
// this.currentMouseY = y;
|
||||
|
||||
// 计算拖拽参数
|
||||
const deltaX = this.currentMouseX - this.initialMouseX;
|
||||
const deltaY = this.currentMouseY - this.initialMouseY;
|
||||
this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
this.dragAngle = Math.atan2(deltaY, deltaX);
|
||||
// // 计算拖拽参数
|
||||
// const deltaX = this.currentMouseX - this.initialMouseX;
|
||||
// const deltaY = this.currentMouseY - this.initialMouseY;
|
||||
// this.dragDistance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
// this.dragAngle = Math.atan2(deltaY, deltaX);
|
||||
|
||||
// 获取当前参数
|
||||
const { size, pressure, power } = this.params;
|
||||
const mode = this.currentMode;
|
||||
const radius = size;
|
||||
const strength = pressure * power;
|
||||
// // 获取当前参数
|
||||
// const { size, pressure, power } = this.params;
|
||||
// const mode = this.currentMode;
|
||||
// const radius = size;
|
||||
// const strength = pressure * power;
|
||||
|
||||
// 根据模式选择算法
|
||||
const pixelModes = [
|
||||
this.modes.CLOCKWISE,
|
||||
this.modes.COUNTERCLOCKWISE,
|
||||
this.modes.PINCH,
|
||||
this.modes.EXPAND,
|
||||
this.modes.PUSH,
|
||||
];
|
||||
// // 根据模式选择算法
|
||||
// const pixelModes = [
|
||||
// this.modes.CLOCKWISE,
|
||||
// this.modes.COUNTERCLOCKWISE,
|
||||
// this.modes.PINCH,
|
||||
// this.modes.EXPAND,
|
||||
// this.modes.PUSH,
|
||||
// ];
|
||||
|
||||
if (pixelModes.includes(mode)) {
|
||||
// 使用增强的像素算法
|
||||
switch (mode) {
|
||||
case this.modes.CLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.COUNTERCLOCKWISE:
|
||||
this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.PINCH:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
break;
|
||||
case this.modes.EXPAND:
|
||||
this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
|
||||
break;
|
||||
case this.modes.PUSH:
|
||||
this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
break;
|
||||
}
|
||||
// if (pixelModes.includes(mode)) {
|
||||
// // 使用增强的像素算法
|
||||
// switch (mode) {
|
||||
// case this.modes.CLOCKWISE:
|
||||
// this._applyEnhancedRotationDeformation(x, y, radius, strength, false);
|
||||
// break;
|
||||
// case this.modes.COUNTERCLOCKWISE:
|
||||
// this._applyEnhancedRotationDeformation(x, y, radius, strength, true);
|
||||
// break;
|
||||
// case this.modes.PINCH:
|
||||
// this._applyEnhancedPinchDeformation(x, y, radius, strength, true);
|
||||
// break;
|
||||
// case this.modes.EXPAND:
|
||||
// this._applyEnhancedPinchDeformation(x, y, radius, strength, false);
|
||||
// break;
|
||||
// case this.modes.PUSH:
|
||||
// this._applyEnhancedPushDeformation(x, y, radius, strength);
|
||||
// break;
|
||||
// }
|
||||
|
||||
// 更新最后应用时间
|
||||
this.lastApplyTime = Date.now();
|
||||
this.isFirstApply = false;
|
||||
// // 更新最后应用时间
|
||||
// this.lastApplyTime = Date.now();
|
||||
// this.isFirstApply = false;
|
||||
|
||||
return this.currentImageData;
|
||||
} else {
|
||||
// 使用原有的网格算法处理其他模式
|
||||
if (!this.mesh) {
|
||||
console.warn("网格未初始化");
|
||||
return this.currentImageData;
|
||||
}
|
||||
// return this.currentImageData;
|
||||
// } else {
|
||||
// // 使用原有的网格算法处理其他模式
|
||||
// if (!this.mesh) {
|
||||
// console.warn("网格未初始化");
|
||||
// return this.currentImageData;
|
||||
// }
|
||||
|
||||
const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
// const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
|
||||
// 应用变形
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
// // 应用变形
|
||||
// this._applyDeformation(
|
||||
// x,
|
||||
// y,
|
||||
// radius,
|
||||
// finalStrength,
|
||||
// mode,
|
||||
// this.params.distortion,
|
||||
// );
|
||||
|
||||
// 平滑处理
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
this._smoothMesh();
|
||||
}
|
||||
// // 平滑处理
|
||||
// if (this.config.smoothingIterations > 0) {
|
||||
// this._smoothMesh();
|
||||
// }
|
||||
|
||||
// 更新图像数据
|
||||
const result = this._applyMeshToImage();
|
||||
// // 更新图像数据
|
||||
// const result = this._applyMeshToImage();
|
||||
|
||||
// 更新最后应用时间
|
||||
this.lastApplyTime = Date.now();
|
||||
this.isFirstApply = false;
|
||||
// // 更新最后应用时间
|
||||
// this.lastApplyTime = Date.now();
|
||||
// this.isFirstApply = false;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
// return result;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 双线性插值采样 - 用于像素级算法
|
||||
@@ -773,22 +725,14 @@ export class LiquifyCPUManager {
|
||||
|
||||
const baseDistortion = Math.max(distortion, 0.3);
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.0);
|
||||
const timeEnhancedDistortion =
|
||||
baseDistortion * (1.0 + timeFactor * 0.3);
|
||||
const timeEnhancedDistortion = baseDistortion * (1.0 + timeFactor * 0.3);
|
||||
|
||||
const wave1 =
|
||||
Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
|
||||
const wave2 =
|
||||
Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle =
|
||||
crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
|
||||
const wave1 = Math.sin(crystalAngle * 8 + this.pressDuration * 0.005) * 0.6;
|
||||
const wave2 = Math.cos(crystalAngle * 12 + this.pressDuration * 0.003) * 0.4;
|
||||
const waveAngle = crystalAngle + (wave1 + wave2) * timeEnhancedDistortion;
|
||||
|
||||
const radialMod =
|
||||
1 +
|
||||
Math.sin(
|
||||
crystalRadius * Math.PI * 2 + this.pressDuration * 0.002
|
||||
) *
|
||||
0.3;
|
||||
1 + Math.sin(crystalRadius * Math.PI * 2 + this.pressDuration * 0.002) * 0.3;
|
||||
const modDistance = distance * radialMod;
|
||||
|
||||
const crystalX = x + Math.cos(waveAngle) * modDistance;
|
||||
@@ -809,8 +753,7 @@ export class LiquifyCPUManager {
|
||||
|
||||
const baseEdgeDistortion = Math.max(distortion, 0.5);
|
||||
const timeFactor = Math.min(this.pressDuration / 1000, 2.5);
|
||||
const timeEnhancedDistortion =
|
||||
baseEdgeDistortion * (1.0 + timeFactor * 0.4);
|
||||
const timeEnhancedDistortion = baseEdgeDistortion * (1.0 + timeFactor * 0.4);
|
||||
|
||||
const edgeWave =
|
||||
Math.sin(edgeRadius * Math.PI * 4 + this.pressDuration * 0.004) *
|
||||
@@ -887,11 +830,7 @@ export class LiquifyCPUManager {
|
||||
const points = this.mesh.deformedPoints;
|
||||
const tempPoints = points.map((p) => ({ x: p.x, y: p.y }));
|
||||
|
||||
for (
|
||||
let iteration = 0;
|
||||
iteration < this.config.smoothingIterations;
|
||||
iteration++
|
||||
) {
|
||||
for (let iteration = 0; iteration < this.config.smoothingIterations; iteration++) {
|
||||
for (let y = 1; y < rows; y++) {
|
||||
for (let x = 1; x < cols; x++) {
|
||||
const idx = y * (cols + 1) + x;
|
||||
@@ -996,19 +935,8 @@ export class LiquifyCPUManager {
|
||||
for (let x = 0; x < width; x += step) {
|
||||
const srcPos = this._mapPointBack(x, y);
|
||||
|
||||
if (
|
||||
srcPos.x >= 0 &&
|
||||
srcPos.x < width &&
|
||||
srcPos.y >= 0 &&
|
||||
srcPos.y < height
|
||||
) {
|
||||
const color = this._bilinearInterpolate(
|
||||
srcData,
|
||||
width,
|
||||
height,
|
||||
srcPos.x,
|
||||
srcPos.y
|
||||
);
|
||||
if (srcPos.x >= 0 && srcPos.x < width && srcPos.y >= 0 && srcPos.y < height) {
|
||||
const color = this._bilinearInterpolate(srcData, width, height, srcPos.x, srcPos.y);
|
||||
|
||||
// 如果使用步长采样,需要填充相邻像素
|
||||
for (let dy = 0; dy < step && y + dy < height; dy++) {
|
||||
@@ -1070,22 +998,14 @@ export class LiquifyCPUManager {
|
||||
// 根据推拉模式和拖拽距离动态调整强度
|
||||
let strength;
|
||||
if (mode === this.modes.PUSH) {
|
||||
const baseStrength =
|
||||
(pressure * power * this.config.maxStrength) / 100;
|
||||
const baseStrength = (pressure * power * this.config.maxStrength) / 100;
|
||||
const distanceFactor = Math.min(this.dragDistance / radius, 2.0);
|
||||
strength = baseStrength * distanceFactor * 0.3; // 批量处理时降低强度
|
||||
} else {
|
||||
strength = (pressure * power * this.config.maxStrength) / 100;
|
||||
}
|
||||
|
||||
this._applyDeformation(
|
||||
pos.x,
|
||||
pos.y,
|
||||
radius,
|
||||
strength,
|
||||
mode,
|
||||
distortion
|
||||
);
|
||||
this._applyDeformation(pos.x, pos.y, radius, strength, mode, distortion);
|
||||
});
|
||||
|
||||
// 结束拖拽操作
|
||||
@@ -1331,14 +1251,7 @@ export class LiquifyCPUManager {
|
||||
const finalStrength = (strength * this.config.maxStrength) / 100;
|
||||
|
||||
// 应用变形
|
||||
this._applyDeformation(
|
||||
x,
|
||||
y,
|
||||
radius,
|
||||
finalStrength,
|
||||
mode,
|
||||
this.params.distortion
|
||||
);
|
||||
this._applyDeformation(x, y, radius, finalStrength, mode, this.params.distortion);
|
||||
|
||||
// 平滑处理
|
||||
if (this.config.smoothingIterations > 0) {
|
||||
|
||||
@@ -130,32 +130,18 @@ export class LiquifyManager {
|
||||
return null;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`,
|
||||
params
|
||||
);
|
||||
console.log(`LiquifyManager.applyLiquify: 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
|
||||
|
||||
try {
|
||||
// 直接调用EnhancedLiquifyManager的applyLiquify方法
|
||||
// 避免重复设置参数,让EnhancedLiquifyManager处理参数设置
|
||||
const resultData = await this.enhancedManager.applyLiquify(
|
||||
targetObject,
|
||||
mode,
|
||||
params,
|
||||
x,
|
||||
y
|
||||
);
|
||||
const resultData = await this.enhancedManager.applyLiquify(targetObject, mode, params, x, y);
|
||||
|
||||
// 确保返回结果数据
|
||||
if (!resultData) {
|
||||
console.warn("液化变形没有返回结果数据");
|
||||
} else {
|
||||
console.log(
|
||||
"✅ 液化变形成功,返回图像数据尺寸:",
|
||||
resultData.width,
|
||||
"x",
|
||||
resultData.height
|
||||
);
|
||||
console.log("✅ 液化变形成功,返回图像数据尺寸:", resultData.width, "x", resultData.height);
|
||||
}
|
||||
|
||||
return resultData;
|
||||
|
||||
@@ -159,9 +159,7 @@ export class LiquifyRealTimeUpdater {
|
||||
// });
|
||||
// }
|
||||
} else {
|
||||
console.warn(
|
||||
"=================快速更新液化效果时,图像数据未变化,跳过更新"
|
||||
);
|
||||
console.warn("=================快速更新液化效果时,图像数据未变化,跳过更新");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("快速更新液化效果失败:", error);
|
||||
@@ -184,6 +182,8 @@ export class LiquifyRealTimeUpdater {
|
||||
*/
|
||||
async _fullUpdate(imageData) {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 临时禁用画布自动渲染
|
||||
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
||||
try {
|
||||
// 使用高质量canvas进行最终渲染
|
||||
this.highQualityCtx.putImageData(imageData, 0, 0);
|
||||
@@ -233,9 +233,6 @@ export class LiquifyRealTimeUpdater {
|
||||
selected: false,
|
||||
evented: originalObj.evented,
|
||||
});
|
||||
|
||||
// 临时禁用画布自动渲染
|
||||
const oldRenderOnAddRemove = this.canvas.renderOnAddRemove;
|
||||
this.canvas.renderOnAddRemove = false;
|
||||
|
||||
// 智能查找和替换canvas上的对象
|
||||
@@ -244,9 +241,7 @@ export class LiquifyRealTimeUpdater {
|
||||
|
||||
// 如果直接查找失败,尝试通过ID查找
|
||||
if (targetIndex === -1 && originalObjId) {
|
||||
targetIndex = allObjects.findIndex(
|
||||
(obj) => obj.id === originalObjId
|
||||
);
|
||||
targetIndex = allObjects.findIndex((obj) => obj.id === originalObjId);
|
||||
if (targetIndex !== -1) {
|
||||
console.log(`通过ID找到目标对象: ${originalObjId}`);
|
||||
// 更新目标对象引用
|
||||
@@ -256,9 +251,7 @@ export class LiquifyRealTimeUpdater {
|
||||
|
||||
// 如果通过ID查找仍然失败,尝试通过图层ID查找
|
||||
if (targetIndex === -1 && originalObjLayerId) {
|
||||
targetIndex = allObjects.findIndex(
|
||||
(obj) => obj.layerId === originalObjLayerId
|
||||
);
|
||||
targetIndex = allObjects.findIndex((obj) => obj.layerId === originalObjLayerId);
|
||||
if (targetIndex !== -1) {
|
||||
console.log(`通过图层ID找到目标对象: ${originalObjLayerId}`);
|
||||
// 更新目标对象引用
|
||||
@@ -284,9 +277,7 @@ export class LiquifyRealTimeUpdater {
|
||||
resolve(newImg);
|
||||
} else {
|
||||
// 如果在画布中找不到对象,可能对象已被移除或引用已更新
|
||||
console.warn(
|
||||
"在画布中找不到目标对象,可能已被其他操作移除或替换"
|
||||
);
|
||||
console.warn("在画布中找不到目标对象,可能已被其他操作移除或替换");
|
||||
|
||||
// 恢复自动渲染设置
|
||||
this.canvas.renderOnAddRemove = oldRenderOnAddRemove;
|
||||
@@ -333,24 +324,6 @@ export class LiquifyRealTimeUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
dispose() {
|
||||
this.targetObject = null;
|
||||
this.cachedDataURL = null;
|
||||
this.pendingImageData = null;
|
||||
this.updateQueue.length = 0;
|
||||
|
||||
// 清理临时canvas
|
||||
if (this.tempCanvas) {
|
||||
this.tempCanvas.width = 0;
|
||||
this.tempCanvas.height = 0;
|
||||
this.tempCanvas = null;
|
||||
this.tempCtx = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前目标对象
|
||||
* @returns {Object} 当前的fabric对象
|
||||
@@ -388,13 +361,6 @@ export class LiquifyRealTimeUpdater {
|
||||
console.log("✅ 恢复正常渲染模式");
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前目标对象
|
||||
*/
|
||||
getTargetObject() {
|
||||
return this.targetObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置图像质量
|
||||
* @param {Number} quality 质量值 (0.1-1.0)
|
||||
@@ -416,6 +382,24 @@ export class LiquifyRealTimeUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
// /**
|
||||
// * 清理资源
|
||||
// */
|
||||
// dispose() {
|
||||
// this.targetObject = null;
|
||||
// this.cachedDataURL = null;
|
||||
// this.pendingImageData = null;
|
||||
// this.updateQueue.length = 0;
|
||||
|
||||
// // 清理临时canvas
|
||||
// if (this.tempCanvas) {
|
||||
// this.tempCanvas.width = 0;
|
||||
// this.tempCanvas.height = 0;
|
||||
// this.tempCanvas = null;
|
||||
// this.tempCtx = null;
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 清理资源
|
||||
*/
|
||||
|
||||
@@ -30,10 +30,7 @@ export class LiquifyStateManager {
|
||||
// 设备性能检测
|
||||
this.devicePerformance = this._detectDevicePerformance();
|
||||
|
||||
console.log(
|
||||
"🎯 液化状态管理器已初始化,设备性能等级:",
|
||||
this.devicePerformance
|
||||
);
|
||||
console.log("🎯 液化状态管理器已初始化,设备性能等级:", this.devicePerformance);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -129,17 +126,10 @@ export class LiquifyStateManager {
|
||||
this.performanceMetrics.totalOperations++;
|
||||
this.performanceMetrics.totalTime += operationTime;
|
||||
this.performanceMetrics.averageTime =
|
||||
this.performanceMetrics.totalTime /
|
||||
this.performanceMetrics.totalOperations;
|
||||
this.performanceMetrics.totalTime / this.performanceMetrics.totalOperations;
|
||||
|
||||
this.performanceMetrics.maxTime = Math.max(
|
||||
this.performanceMetrics.maxTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.minTime = Math.min(
|
||||
this.performanceMetrics.minTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
|
||||
this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
|
||||
this.performanceMetrics.lastOperationTime = operationTime;
|
||||
}
|
||||
|
||||
@@ -148,15 +138,8 @@ export class LiquifyStateManager {
|
||||
* @param {Object} metrics 性能指标对象
|
||||
*/
|
||||
recordOperationMetrics(metrics) {
|
||||
const {
|
||||
operationTime,
|
||||
operationType,
|
||||
mode,
|
||||
coordinates,
|
||||
imageSize,
|
||||
renderMode,
|
||||
isRealTime,
|
||||
} = metrics;
|
||||
const { operationTime, operationType, mode, coordinates, imageSize, renderMode, isRealTime } =
|
||||
metrics;
|
||||
|
||||
// 记录基础性能数据
|
||||
this.recordDeformation(operationTime);
|
||||
@@ -195,9 +178,7 @@ export class LiquifyStateManager {
|
||||
// 降低图像质量
|
||||
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
|
||||
if (currentQuality > 0.7) {
|
||||
this.realtimeUpdater.setImageQuality(
|
||||
Math.max(0.7, currentQuality - 0.1)
|
||||
);
|
||||
this.realtimeUpdater.setImageQuality(Math.max(0.7, currentQuality - 0.1));
|
||||
console.log("⚡ 自动降低图像质量以提升性能");
|
||||
}
|
||||
|
||||
@@ -215,9 +196,7 @@ export class LiquifyStateManager {
|
||||
if (operationTime < 20 && this.devicePerformance === "high") {
|
||||
const currentQuality = this.realtimeUpdater.config.imageQuality || 1.0;
|
||||
if (currentQuality < 1.0) {
|
||||
this.realtimeUpdater.setImageQuality(
|
||||
Math.min(1.0, currentQuality + 0.05)
|
||||
);
|
||||
this.realtimeUpdater.setImageQuality(Math.min(1.0, currentQuality + 0.05));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -375,8 +354,7 @@ export class LiquifyStateManager {
|
||||
// 恢复原始设置
|
||||
this.canvas.renderOnAddRemove = this._originalSettings.renderOnAddRemove;
|
||||
this.canvas.skipOffscreen = this._originalSettings.skipOffscreen;
|
||||
this.canvas.enableRetinaScaling =
|
||||
this._originalSettings.enableRetinaScaling;
|
||||
this.canvas.enableRetinaScaling = this._originalSettings.enableRetinaScaling;
|
||||
|
||||
this._originalSettings = null;
|
||||
}
|
||||
@@ -389,14 +367,8 @@ export class LiquifyStateManager {
|
||||
this.performanceMetrics.averageTime =
|
||||
this.performanceMetrics.totalTime / (this.operationCount + 1);
|
||||
|
||||
this.performanceMetrics.maxTime = Math.max(
|
||||
this.performanceMetrics.maxTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.minTime = Math.min(
|
||||
this.performanceMetrics.minTime,
|
||||
operationTime
|
||||
);
|
||||
this.performanceMetrics.maxTime = Math.max(this.performanceMetrics.maxTime, operationTime);
|
||||
this.performanceMetrics.minTime = Math.min(this.performanceMetrics.minTime, operationTime);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,8 +61,7 @@ export class LiquifyWebGLManager {
|
||||
static isSupported() {
|
||||
try {
|
||||
const canvas = document.createElement("canvas");
|
||||
const gl =
|
||||
canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
return !!gl;
|
||||
} catch (e) {
|
||||
return false;
|
||||
@@ -76,9 +75,7 @@ export class LiquifyWebGLManager {
|
||||
try {
|
||||
// 创建WebGL画布
|
||||
this.canvas = document.createElement("canvas");
|
||||
this.gl =
|
||||
this.canvas.getContext("webgl") ||
|
||||
this.canvas.getContext("experimental-webgl");
|
||||
this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
|
||||
|
||||
if (!this.gl) {
|
||||
throw new Error("WebGL不可用");
|
||||
@@ -200,10 +197,7 @@ export class LiquifyWebGLManager {
|
||||
}
|
||||
`;
|
||||
|
||||
this.program = this._createProgram(
|
||||
vertexShaderSource,
|
||||
fragmentShaderSource
|
||||
);
|
||||
this.program = this._createProgram(vertexShaderSource, fragmentShaderSource);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -228,10 +222,7 @@ export class LiquifyWebGLManager {
|
||||
_createProgram(vertexSource, fragmentSource) {
|
||||
const gl = this.gl;
|
||||
const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource);
|
||||
const fragmentShader = this._createShader(
|
||||
gl.FRAGMENT_SHADER,
|
||||
fragmentSource
|
||||
);
|
||||
const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource);
|
||||
|
||||
const program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
@@ -267,9 +258,7 @@ export class LiquifyWebGLManager {
|
||||
*/
|
||||
_createMeshBuffer() {
|
||||
const gl = this.gl;
|
||||
const vertices = new Float32Array([
|
||||
-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1,
|
||||
]);
|
||||
const vertices = new Float32Array([-1, -1, 0, 0, 1, -1, 1, 0, -1, 1, 0, 1, 1, 1, 1, 1]);
|
||||
|
||||
this.meshBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
|
||||
@@ -427,12 +416,8 @@ export class LiquifyWebGLManager {
|
||||
}
|
||||
|
||||
// 计算拖拽向量(用于推拉模式)
|
||||
const dragX = this.isDragging
|
||||
? (x - this.initialMouseX) / this.canvas.width
|
||||
: 0;
|
||||
const dragY = this.isDragging
|
||||
? -(y - this.initialMouseY) / this.canvas.height
|
||||
: 0;
|
||||
const dragX = this.isDragging ? (x - this.initialMouseX) / this.canvas.width : 0;
|
||||
const dragY = this.isDragging ? -(y - this.initialMouseY) / this.canvas.height : 0;
|
||||
|
||||
// 获取模式索引
|
||||
const modeIndex = Object.values(this.modes).indexOf(this.currentMode);
|
||||
@@ -466,22 +451,13 @@ export class LiquifyWebGLManager {
|
||||
|
||||
// 读取结果并转换为ImageData
|
||||
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
|
||||
gl.readPixels(
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixels
|
||||
);
|
||||
gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
|
||||
// 翻转Y轴以匹配ImageData格式
|
||||
const flippedPixels = new Uint8ClampedArray(pixels.length);
|
||||
for (let y = 0; y < this.canvas.height; y++) {
|
||||
for (let x = 0; x < this.canvas.width; x++) {
|
||||
const srcIndex =
|
||||
((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
|
||||
const srcIndex = ((this.canvas.height - 1 - y) * this.canvas.width + x) * 4;
|
||||
const dstIndex = (y * this.canvas.width + x) * 4;
|
||||
flippedPixels[dstIndex] = pixels[srcIndex];
|
||||
flippedPixels[dstIndex + 1] = pixels[srcIndex + 1];
|
||||
@@ -503,16 +479,7 @@ export class LiquifyWebGLManager {
|
||||
|
||||
// 将原始纹理复制到当前纹理
|
||||
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
|
||||
gl.copyTexImage2D(
|
||||
gl.TEXTURE_2D,
|
||||
0,
|
||||
gl.RGBA,
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
0
|
||||
);
|
||||
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.canvas.width, this.canvas.height, 0);
|
||||
|
||||
// 读取原始纹理数据
|
||||
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
|
||||
@@ -525,21 +492,9 @@ export class LiquifyWebGLManager {
|
||||
);
|
||||
|
||||
const pixels = new Uint8Array(this.canvas.width * this.canvas.height * 4);
|
||||
gl.readPixels(
|
||||
0,
|
||||
0,
|
||||
this.canvas.width,
|
||||
this.canvas.height,
|
||||
gl.RGBA,
|
||||
gl.UNSIGNED_BYTE,
|
||||
pixels
|
||||
);
|
||||
gl.readPixels(0, 0, this.canvas.width, this.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
|
||||
|
||||
return new ImageData(
|
||||
new Uint8ClampedArray(pixels),
|
||||
this.canvas.width,
|
||||
this.canvas.height
|
||||
);
|
||||
return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -144,10 +144,7 @@ export class MinimapManager {
|
||||
this.eventHandlers.mouseup = this.handleMinimapMouseUp;
|
||||
// 移除mouseout事件处理,允许拖动操作持续到鼠标释放
|
||||
|
||||
this.minimapCanvas.addEventListener(
|
||||
"mousedown",
|
||||
this.eventHandlers.mousedown
|
||||
);
|
||||
this.minimapCanvas.addEventListener("mousedown", this.eventHandlers.mousedown);
|
||||
document.addEventListener("mousemove", this.eventHandlers.mousemove);
|
||||
document.addEventListener("mouseup", this.eventHandlers.mouseup);
|
||||
// 移除mouseout事件监听
|
||||
@@ -177,10 +174,7 @@ export class MinimapManager {
|
||||
|
||||
this.eventHandlers.touchend = this.handleMinimapMouseUp;
|
||||
|
||||
this.minimapCanvas.addEventListener(
|
||||
"touchstart",
|
||||
this.eventHandlers.touchstart
|
||||
);
|
||||
this.minimapCanvas.addEventListener("touchstart", this.eventHandlers.touchstart);
|
||||
document.addEventListener("touchmove", this.eventHandlers.touchmove, {
|
||||
passive: false,
|
||||
});
|
||||
@@ -204,18 +198,12 @@ export class MinimapManager {
|
||||
this.mainCanvas.off("object:rotating", this.handleMainCanvasChange);
|
||||
|
||||
// 移除鼠标事件监听
|
||||
this.minimapCanvas.removeEventListener(
|
||||
"mousedown",
|
||||
this.eventHandlers.mousedown
|
||||
);
|
||||
this.minimapCanvas.removeEventListener("mousedown", this.eventHandlers.mousedown);
|
||||
document.removeEventListener("mousemove", this.eventHandlers.mousemove);
|
||||
document.removeEventListener("mouseup", this.eventHandlers.mouseup);
|
||||
|
||||
// 移除触摸事件监听
|
||||
this.minimapCanvas.removeEventListener(
|
||||
"touchstart",
|
||||
this.eventHandlers.touchstart
|
||||
);
|
||||
this.minimapCanvas.removeEventListener("touchstart", this.eventHandlers.touchstart);
|
||||
document.removeEventListener("touchmove", this.eventHandlers.touchmove);
|
||||
document.removeEventListener("touchend", this.eventHandlers.touchend);
|
||||
}
|
||||
@@ -281,8 +269,7 @@ export class MinimapManager {
|
||||
}
|
||||
|
||||
// 添加边距
|
||||
const padding =
|
||||
Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
|
||||
const padding = Math.max(this.mainCanvas.getWidth(), this.mainCanvas.getHeight()) * 0.1;
|
||||
this.contentBounds = {
|
||||
minX: minX - padding,
|
||||
minY: minY - padding,
|
||||
@@ -347,11 +334,7 @@ export class MinimapManager {
|
||||
this.dragStart = { x, y };
|
||||
|
||||
// 移动画布视口
|
||||
this.moveViewport(
|
||||
this.dragStartViewport.x + deltaX,
|
||||
this.dragStartViewport.y + deltaY,
|
||||
false
|
||||
);
|
||||
this.moveViewport(this.dragStartViewport.x + deltaX, this.dragStartViewport.y + deltaY, false);
|
||||
|
||||
// 更新拖拽起始视口位置
|
||||
this.dragStartViewport = this.calculateViewportRect();
|
||||
@@ -391,9 +374,7 @@ export class MinimapManager {
|
||||
viewportHeight = this.lastViewportSize.height;
|
||||
} else {
|
||||
viewportWidth = Math.round((this.mainCanvas.getWidth() / zoom) * scaleX);
|
||||
viewportHeight = Math.round(
|
||||
(this.mainCanvas.getHeight() / zoom) * scaleY
|
||||
);
|
||||
viewportHeight = Math.round((this.mainCanvas.getHeight() / zoom) * scaleY);
|
||||
}
|
||||
|
||||
// 添加边界限制,确保视口不会超出小地图
|
||||
@@ -476,21 +457,11 @@ export class MinimapManager {
|
||||
|
||||
try {
|
||||
// 清空小地图
|
||||
this.minimapCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
this.minimapSize.width,
|
||||
this.minimapSize.height
|
||||
);
|
||||
this.minimapCtx.clearRect(0, 0, this.minimapSize.width, this.minimapSize.height);
|
||||
|
||||
// 绘制小地图背景
|
||||
this.minimapCtx.fillStyle = this.mainCanvas.backgroundColor || "#f0f0f0";
|
||||
this.minimapCtx.fillRect(
|
||||
0,
|
||||
0,
|
||||
this.minimapSize.width,
|
||||
this.minimapSize.height
|
||||
);
|
||||
this.minimapCtx.fillRect(0, 0, this.minimapSize.width, this.minimapSize.height);
|
||||
|
||||
// 计算内容边界尺寸
|
||||
const contentWidth = this.contentBounds.maxX - this.contentBounds.minX;
|
||||
@@ -654,11 +625,7 @@ export class MinimapManager {
|
||||
this.removeEventListeners();
|
||||
|
||||
// 从DOM中移除canvas
|
||||
if (
|
||||
this.container &&
|
||||
this.minimapCanvas &&
|
||||
this.minimapCanvas.parentNode === this.container
|
||||
) {
|
||||
if (this.container && this.minimapCanvas && this.minimapCanvas.parentNode === this.container) {
|
||||
this.container.removeChild(this.minimapCanvas);
|
||||
}
|
||||
|
||||
@@ -709,12 +676,7 @@ export class MinimapManager {
|
||||
}
|
||||
|
||||
const offCtx = this._offscreenCanvas.getContext("2d");
|
||||
offCtx.clearRect(
|
||||
0,
|
||||
0,
|
||||
this._offscreenCanvas.width,
|
||||
this._offscreenCanvas.height
|
||||
);
|
||||
offCtx.clearRect(0, 0, this._offscreenCanvas.width, this._offscreenCanvas.height);
|
||||
|
||||
// 绘制图层内容到离屏画布
|
||||
this._renderLayersToMinimap(offCtx, ratio);
|
||||
@@ -755,8 +717,8 @@ export class MinimapManager {
|
||||
typeof this.canvas.layers.value !== "undefined"
|
||||
? this.canvas.layers.value
|
||||
: Array.isArray(this.canvas.layers)
|
||||
? this.canvas.layers
|
||||
: [];
|
||||
? this.canvas.layers
|
||||
: [];
|
||||
|
||||
// 过滤出可见图层
|
||||
layersArray.forEach((layer) => {
|
||||
@@ -800,10 +762,8 @@ export class MinimapManager {
|
||||
|
||||
const left = fabricObj.left * ratio;
|
||||
const top = fabricObj.top * ratio;
|
||||
const width =
|
||||
(fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio;
|
||||
const height =
|
||||
(fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
|
||||
const width = (fabricObj.width || 20) * (fabricObj.scaleX || 1) * ratio;
|
||||
const height = (fabricObj.height || 20) * (fabricObj.scaleY || 1) * ratio;
|
||||
|
||||
ctx.fillRect(left, top, width, height);
|
||||
}
|
||||
|
||||
@@ -319,10 +319,7 @@ export class SelectionManager {
|
||||
}
|
||||
|
||||
// 触发选区变化回调
|
||||
if (
|
||||
this.onSelectionChanged &&
|
||||
typeof this.onSelectionChanged === "function"
|
||||
) {
|
||||
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
|
||||
@@ -397,10 +394,7 @@ export class SelectionManager {
|
||||
this.featherAmount = 0;
|
||||
|
||||
// 触发选区变化回调
|
||||
if (
|
||||
this.onSelectionChanged &&
|
||||
typeof this.onSelectionChanged === "function"
|
||||
) {
|
||||
if (this.onSelectionChanged && typeof this.onSelectionChanged === "function") {
|
||||
this.onSelectionChanged();
|
||||
}
|
||||
|
||||
@@ -600,8 +594,7 @@ export class SelectionManager {
|
||||
// 添加新的点,但避免添加过于密集的点
|
||||
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
|
||||
const distance = Math.sqrt(
|
||||
Math.pow(pointer.x - lastPoint.x, 2) +
|
||||
Math.pow(pointer.y - lastPoint.y, 2)
|
||||
Math.pow(pointer.x - lastPoint.x, 2) + Math.pow(pointer.y - lastPoint.y, 2)
|
||||
);
|
||||
|
||||
// 只有当距离大于2像素时才添加新点,避免路径过于复杂
|
||||
@@ -653,8 +646,7 @@ export class SelectionManager {
|
||||
const firstPoint = this.drawingPoints[0];
|
||||
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1];
|
||||
const closingDistance = Math.sqrt(
|
||||
Math.pow(firstPoint.x - lastPoint.x, 2) +
|
||||
Math.pow(firstPoint.y - lastPoint.y, 2)
|
||||
Math.pow(firstPoint.x - lastPoint.x, 2) + Math.pow(firstPoint.y - lastPoint.y, 2)
|
||||
);
|
||||
|
||||
// 如果首尾距离较大,自动添加闭合线段
|
||||
|
||||
Reference in New Issue
Block a user