feat: 裁剪组裁剪跟随选择组移动

This commit is contained in:
bighuixiang
2025-07-14 01:00:23 +08:00
parent 96e13cb22a
commit 24e9ba8ae5
80 changed files with 2052 additions and 4292 deletions

View File

@@ -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];

View File

@@ -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;
// 比较位置(允许一定的偏差)

View File

@@ -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("无法克隆裁剪对象");

View File

@@ -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 事件");
}
}

View File

@@ -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;

View File

@@ -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();
}

View File

@@ -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();
}
/**

View File

@@ -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;
}

View File

@@ -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)));
}
// 根据滚动速度选择不同的缓动效果

View File

@@ -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 || [];

View File

@@ -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,

View File

@@ -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)}`;

View File

@@ -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,

View File

@@ -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;

View File

@@ -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;
}

View File

@@ -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;
}
/**

View File

@@ -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})`;

View File

@@ -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;

View File

@@ -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";
}

View File

@@ -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;
}

View File

@@ -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;
}
/**

View File

@@ -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)
);
}

View File

@@ -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);

View File

@@ -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+";

View File

@@ -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}, ` +

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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;

View File

@@ -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;
// }
// }
/**
* 清理资源
*/

View File

@@ -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);
}
/**

View File

@@ -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);
}
/**

View File

@@ -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);
}

View File

@@ -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)
);
// 如果首尾距离较大,自动添加闭合线段