接入画布

This commit is contained in:
X1627315083
2025-06-09 10:25:54 +08:00
parent 87a08f5f8f
commit c266967f16
157 changed files with 43833 additions and 1571 deletions

View File

@@ -0,0 +1,702 @@
/**
* 增强版液化管理器
* 整合WebGL和CPU实现智能选择最佳渲染方式
*/
import { LiquifyWebGLManager } from "./LiquifyWebGLManager";
import { LiquifyCPUManager } from "./LiquifyCPUManager";
export class EnhancedLiquifyManager {
/**
* 创建增强版液化管理器
* @param {Object} options 配置选项
*/
constructor(options = {}) {
this.config = {
// 性能阈值图像超过此尺寸会尝试使用WebGL
webglSizeThreshold: options.webglSizeThreshold || 1000 * 1000, // 默认100万像素
// 是否强制使用CPU模式
forceCPU: options.forceCPU || false,
// 是否强制使用WebGL模式
forceWebGL: options.forceWebGL || false,
// 网格大小
gridSize: options.gridSize || 15,
// 最大变形强度
maxStrength: options.maxStrength || 100,
// 平滑迭代次数
smoothingIterations: options.smoothingIterations || 2,
// 网格弹性因子
relaxFactor: options.relaxFactor || 0.25,
// WebGL网格精度
meshResolution: options.meshResolution || 64,
};
// 性能监控
this.performance = {
lastOperationTime: 0,
renderTimes: [], // 最近的渲染时间记录
isPerformanceIssue: false, // 是否存在性能问题
operationCount: 0, // 操作计数
};
// 初始化标志
this.initialized = false;
// 当前参数
this.params = {
size: 50, // 工具尺寸
pressure: 0.5, // 压力大小 (0-1)
distortion: 0, // 失真程度 (0-1)
power: 0.5, // 动力/强度 (0-1)
};
// 液化工具模式
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
// 当前模式
this.currentMode = this.modes.PUSH;
// 图像数据和目标对象
this.originalImageData = null;
this.currentImageData = null;
this.targetObject = null;
this.targetLayerId = null;
// 创建渲染器实例
this.webglRenderer = null;
this.cpuRenderer = null;
// 当前激活的渲染器
this.activeRenderer = null;
this.renderMode = "unknown"; // 'webgl', 'cpu', 'unknown'
// 画布和管理器引用
this.canvas = options.canvas || null;
this.layerManager = options.layerManager || null;
// 渲染器状态
this.isWebGLAvailable = LiquifyWebGLManager.isSupported();
}
/**
* 初始化液化管理器
* @param {Object} options 配置选项
* @returns {Boolean} 是否初始化成功
*/
initialize(options = {}) {
if (options.canvas) this.canvas = options.canvas;
if (options.layerManager) this.layerManager = options.layerManager;
if (!this.canvas || !this.layerManager) {
console.error("液化管理器初始化失败缺少canvas或layerManager");
return false;
}
// 记录初始化时间,用于性能监控
this.performance.lastInitTime = Date.now();
// 创建CPU渲染器 (始终创建作为备选)
this.cpuRenderer = new LiquifyCPUManager({
gridSize: this.config.gridSize,
maxStrength: this.config.maxStrength,
smoothingIterations: this.config.smoothingIterations,
relaxFactor: this.config.relaxFactor,
});
// 检查是否应创建WebGL渲染器
if (this.isWebGLAvailable && !this.config.forceCPU) {
this.webglRenderer = new LiquifyWebGLManager({
gridSize: this.config.gridSize,
maxStrength: this.config.maxStrength,
meshResolution: this.config.meshResolution,
});
}
this.initialized = true;
return true;
}
/**
* 为液化操作准备图像
* @param {Object|String} target 目标对象或图层ID
* @returns {Promise<Object>} 准备结果
*/
async prepareForLiquify(target) {
if (!this.initialized) {
throw new Error("液化管理器未初始化");
}
let targetObject, targetLayerId;
// 处理传入的是图层ID的情况
if (typeof target === "string") {
targetLayerId = target;
const layer = this.layerManager.getLayerById(targetLayerId);
// 检查图层是否存在和是否有对象
let hasObjects = false;
if (layer) {
if (layer.type === "background" && layer.fabricObject) {
hasObjects = true;
targetObject = layer.fabricObject;
} else if (layer.fabricObjects && layer.fabricObjects.length > 0) {
hasObjects = true;
targetObject = layer.fabricObjects[0];
}
}
if (!hasObjects) {
throw new Error("目标图层为空或不存在");
}
} else if (typeof target === "object") {
// 传入的是对象
targetObject = target;
const layer = this.layerManager.findLayerByObject(targetObject);
if (layer) {
targetLayerId = layer.id;
} else {
throw new Error("无法找到目标对象所属图层");
}
} else {
throw new Error("无效的目标参数");
}
// 检查是否为图像对象
if (!targetObject || targetObject.type !== "image") {
throw new Error("目标对象不是图像,无法进行液化操作");
}
// 保存目标对象引用
this.targetObject = targetObject;
this.targetLayerId = targetLayerId;
// 获取图像数据
const imageData = await this._getImageData(targetObject);
if (!imageData) {
throw new Error("无法获取图像数据");
}
// 保存原始图像数据
this.originalImageData = imageData;
this.currentImageData = this._cloneImageData(imageData);
// 检查图像大小,选择适合的渲染器
await this._selectRenderer(imageData);
// 预热选定的渲染器
await this._warmupRenderer(imageData);
return {
targetObject: this.targetObject,
targetLayerId: this.targetLayerId,
imageData: this.currentImageData,
originalImageData: this.originalImageData,
renderMode: this.renderMode,
};
}
/**
* 根据图像大小和设备性能选择渲染器
* @param {ImageData} imageData 图像数据
* @private
*/
async _selectRenderer(imageData) {
// 计算图像大小
const pixelCount = imageData.width * imageData.height;
console.log(
`液化选择渲染器: 图像大小=${pixelCount}像素, WebGL可用=${this.isWebGLAvailable}`
);
// 默认使用CPU渲染器
this.activeRenderer = this.cpuRenderer;
this.renderMode = "cpu";
// 如果配置强制使用WebGL
if (this.config.forceWebGL && this.isWebGLAvailable && this.webglRenderer) {
console.log("液化功能: 强制使用WebGL渲染模式");
this.activeRenderer = this.webglRenderer;
this.renderMode = "webgl";
return;
}
// 如果配置强制使用CPU
if (this.config.forceCPU) {
console.log("液化功能: 强制使用CPU渲染模式");
return;
}
// 根据图像大小和WebGL可用性决定
if (
pixelCount > this.config.webglSizeThreshold / 2 && // 降低阈值让更多尺寸的图像使用WebGL
this.isWebGLAvailable &&
this.webglRenderer
) {
// 切换到WebGL渲染器
console.log("液化功能: 自动选择WebGL渲染模式(基于图像尺寸)");
this.activeRenderer = this.webglRenderer;
this.renderMode = "webgl";
} else {
console.log(
`液化功能: 使用CPU渲染模式${
!this.isWebGLAvailable ? " (WebGL不可用)" : ""
}`
);
}
}
/**
* 预热渲染器
* @param {ImageData} imageData 图像数据
* @private
*/
async _warmupRenderer(imageData) {
// 创建图像元素
const img = document.createElement("img");
// 将ImageData转换为URL
const canvas = document.createElement("canvas");
canvas.width = imageData.width;
canvas.height = imageData.height;
const ctx = canvas.getContext("2d");
ctx.putImageData(imageData, 0, 0);
// 使用Promise等待图像加载
await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = canvas.toDataURL();
});
// 初始化当前渲染器
if (this.activeRenderer) {
if (this.renderMode === "webgl") {
this.activeRenderer.initialize(img);
} else {
this.activeRenderer.initialize(imageData);
}
}
}
/**
* 设置液化模式
* @param {String} mode 模式名称
*/
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
// 同步更新当前渲染器
if (this.activeRenderer) {
this.activeRenderer.setMode(mode);
}
return true;
}
return false;
}
/**
* 设置液化参数
* @param {String} param 参数名称
* @param {Number} value 参数值
*/
setParam(param, value) {
if (param in this.params) {
this.params[param] = value;
// 同步更新当前渲染器
if (this.activeRenderer) {
this.activeRenderer.setParam(param, value);
}
return true;
}
return false;
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
*/
getParams() {
return { ...this.params };
}
/**
* 重置参数为默认值
*/
resetParams() {
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
// 同步更新当前渲染器
if (this.activeRenderer) {
this.activeRenderer.resetParams();
}
}
/**
* 应用液化变形
* @param {Object} target 目标对象
* @param {String} mode 液化模式
* @param {Object} params 液化参数
* @param {Number} x 操作中心点X坐标 (图像像素坐标)
* @param {Number} y 操作中心点Y坐标 (图像像素坐标)
* @returns {Promise<ImageData>} 处理后的图像数据
*/
async applyLiquify(target, mode, params, x, y) {
// 性能追踪开始
const startTime = performance.now();
// 如果首次调用,先准备环境
if (!this.targetObject || this.targetObject !== target) {
await this.prepareForLiquify(target);
}
// 更新模式和参数
if (mode) this.setMode(mode);
if (params) {
for (const [key, value] of Object.entries(params)) {
this.setParam(key, value);
}
}
// 验证坐标是否在图像范围内
if (!this.originalImageData) {
console.error("缺少原始图像数据");
return null;
}
const imageWidth = this.originalImageData.width;
const imageHeight = this.originalImageData.height;
// 坐标边界检查
if (x < 0 || x >= imageWidth || y < 0 || y >= imageHeight) {
console.warn(
`液化坐标超出图像范围: (${x}, ${y}), 图像尺寸: ${imageWidth}x${imageHeight}`
);
return null;
}
console.log(
`应用液化变形: 模式=${mode}, 图像坐标=(${x}, ${y}), 图像尺寸=${imageWidth}x${imageHeight}`
);
// 检查并应用变形
if (this.activeRenderer && typeof x === "number" && typeof y === "number") {
// 应用变形
let result;
if (this.renderMode === "webgl") {
// WebGL渲染器传入图像像素坐标
result = this.activeRenderer.applyDeformation(x, y);
} else {
// CPU渲染器传入图像像素坐标
result = this.activeRenderer.applyDeformation(x, y);
}
// 更新当前图像数据
if (result) {
this.currentImageData = result;
}
// 性能追踪结束
const endTime = performance.now();
this._trackPerformance(endTime - startTime);
return result;
}
console.error("无法应用液化变形:渲染器未初始化或坐标无效");
return null;
}
/**
* 追踪性能数据
* @param {Number} time 操作耗时(毫秒)
* @private
*/
_trackPerformance(time) {
this.performance.lastOperationTime = time;
this.performance.operationCount++;
// 维护最近10次操作的耗时记录
this.performance.renderTimes.push(time);
if (this.performance.renderTimes.length > 10) {
this.performance.renderTimes.shift();
}
// 计算平均耗时
const avgTime =
this.performance.renderTimes.reduce((sum, t) => sum + t, 0) /
this.performance.renderTimes.length;
// 检测性能问题
this.performance.isPerformanceIssue = avgTime > 100; // 如果平均耗时超过100毫秒
// 输出性能信息(调试用)
if (this.performance.operationCount % 10 === 0) {
console.log(
`液化性能数据: 模式=${this.renderMode}, 平均耗时=${avgTime.toFixed(
2
)}ms, 图像尺寸=${this.originalImageData?.width}x${
this.originalImageData?.height
}`
);
}
// 如果使用WebGL但性能差可以考虑切换到优化的CPU实现
if (
this.renderMode === "webgl" &&
this.performance.isPerformanceIssue &&
this.performance.operationCount > 5
) {
console.warn("WebGL液化性能不佳考虑切换到CPU模式");
// 注意:这里不自动切换,因为可能会导致中途渲染结果不一致
}
}
/**
* 重置液化操作
* @returns {ImageData} 重置后的图像数据
*/
reset() {
if (!this.activeRenderer) return null;
// 使用当前渲染器重置
const result = this.activeRenderer.reset();
// 更新当前图像数据
if (result) {
this.currentImageData = result;
}
return result;
}
/**
* 检查图层是否可以液化
* @param {String} layerId 图层ID
* @returns {Object} 检查结果
*/
checkLayerForLiquify(layerId) {
if (!this.layerManager) {
return {
valid: false,
message: "图层管理器未初始化",
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 获取图层
const layer = this.layerManager.getLayerById(layerId);
if (!layer) {
return {
valid: false,
message: "图层不存在",
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 检查图层是否为空
let objectsToCheck = [];
if (layer.isBackground || layer.type === "background") {
// 背景图层使用 fabricObject (单数)
if (layer.fabricObject) {
objectsToCheck = [layer.fabricObject];
}
} else {
// 普通图层使用 fabricObjects (复数)
objectsToCheck = layer.fabricObjects || [];
}
if (objectsToCheck.length === 0) {
return {
valid: false,
message: "图层为空,无法进行液化操作",
needsRasterization: false,
isImage: false,
isEmpty: true,
isGroup: false,
};
}
// 检查是否为单一图像
const singleObject = objectsToCheck.length === 1;
const isImage =
singleObject &&
(objectsToCheck[0].type === "image" ||
objectsToCheck[0].type === "rasterized-layer");
// 检查是否为组
const isGroup = objectsToCheck.some((obj) => obj.type === "group");
// 如果不是单一图像,需要栅格化
const needsRasterization = !isImage || isGroup;
return {
valid: isImage && !isGroup,
message: isImage ? "图层可以进行液化操作" : "需要先将图层栅格化",
needsRasterization: needsRasterization,
isImage: isImage,
isEmpty: false,
isGroup: isGroup,
};
}
/**
* 获取图像数据
* @param {Object} fabricObject Fabric图像对象
* @returns {Promise<ImageData>} 图像数据
* @private
*/
async _getImageData(fabricObject) {
return new Promise((resolve, reject) => {
try {
// 创建临时canvas
const tempCanvas = document.createElement("canvas");
tempCanvas.width = fabricObject.width * fabricObject.scaleX;
tempCanvas.height = fabricObject.height * fabricObject.scaleY;
const tempCtx = tempCanvas.getContext("2d");
// 如果对象有图像元素
if (fabricObject._element) {
tempCtx.drawImage(
fabricObject._element,
0,
0,
tempCanvas.width,
tempCanvas.height
);
} else if (fabricObject.getSrc) {
// 通过URL创建图像
const img = new Image();
img.onload = () => {
tempCtx.drawImage(img, 0, 0, tempCanvas.width, tempCanvas.height);
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
resolve(imageData);
};
img.onerror = reject;
img.src = fabricObject.getSrc();
return;
} else {
reject(new Error("无法获取图像数据"));
return;
}
// 获取图像数据
const imageData = tempCtx.getImageData(
0,
0,
tempCanvas.width,
tempCanvas.height
);
resolve(imageData);
} catch (error) {
reject(error);
}
});
}
/**
* 克隆图像数据
* @param {ImageData} imageData 原始图像数据
* @returns {ImageData} 克隆的图像数据
* @private
*/
_cloneImageData(imageData) {
if (!imageData) return null;
// 使用新的浏览器API直接复制
if (typeof ImageData.prototype.constructor === "function") {
try {
return new ImageData(
new Uint8ClampedArray(imageData.data),
imageData.width,
imageData.height
);
} catch (e) {
console.warn("使用备选方法克隆ImageData");
}
}
// 备选方法
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = imageData.width;
canvas.height = imageData.height;
ctx.putImageData(imageData, 0, 0);
return ctx.getImageData(0, 0, imageData.width, imageData.height);
}
/**
* 释放资源
*/
dispose() {
// 释放渲染器资源
if (this.webglRenderer) {
this.webglRenderer.dispose();
this.webglRenderer = null;
}
if (this.cpuRenderer) {
this.cpuRenderer.dispose();
this.cpuRenderer = null;
}
// 清除引用
this.activeRenderer = null;
this.canvas = null;
this.layerManager = null;
this.targetObject = null;
this.originalImageData = null;
this.currentImageData = null;
this.initialized = false;
this.renderMode = "unknown";
}
/**
* 获取当前状态信息
* @returns {Object} 状态信息
*/
getStatus() {
return {
initialized: this.initialized,
renderMode: this.renderMode,
isWebGLAvailable: this.isWebGLAvailable,
currentMode: this.currentMode,
params: { ...this.params },
performance: { ...this.performance },
imageSize: this.originalImageData
? `${this.originalImageData.width}x${this.originalImageData.height}`
: "N/A",
};
}
}

View File

@@ -0,0 +1,594 @@
/**
* CPU版本的液化管理器
* 修复版本 - 解决三角形网格失真问题
*/
export class LiquifyCPUManager {
constructor(options = {}) {
this.config = {
gridSize: options.gridSize || 16, // 稍微增大网格提高性能
maxStrength: options.maxStrength || 200, // 适度降低最大强度
smoothingIterations: options.smoothingIterations || 1, // 增加平滑处理
relaxFactor: options.relaxFactor || 0.1, // 适度松弛
};
this.params = {
size: 80, // 增大默认尺寸
pressure: 0.8, // 增大默认压力
distortion: 0,
power: 0.8, // 增大默认动力
};
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
this.currentMode = this.modes.PUSH;
this.originalImageData = null;
this.currentImageData = null;
this.mesh = null;
this.initialized = false;
this.canvas = document.createElement("canvas");
this.ctx = this.canvas.getContext("2d");
this.deformHistory = [];
// 性能优化相关
this.lastUpdateTime = 0;
this.updateThrottle = 16; // 限制更新频率约60fps
this.isProcessing = false;
// 鼠标位置跟踪(用于推拉模式)
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = true; // 标记是否是首次应用
}
initialize(imageSource) {
try {
if (imageSource instanceof ImageData) {
this.originalImageData = new ImageData(
new Uint8ClampedArray(imageSource.data),
imageSource.width,
imageSource.height
);
} else if (imageSource instanceof HTMLImageElement) {
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
);
} else {
throw new Error("不支持的图像类型");
}
this.currentImageData = new ImageData(
new Uint8ClampedArray(this.originalImageData.data),
this.originalImageData.width,
this.originalImageData.height
);
this._initMesh(
this.originalImageData.width,
this.originalImageData.height
);
this.initialized = true;
return true;
} catch (error) {
console.error("液化管理器初始化失败:", error);
return false;
}
}
_initMesh(width, height) {
const gridSize = this.config.gridSize;
const cols = Math.ceil(width / gridSize);
const rows = Math.ceil(height / gridSize);
this.mesh = {
cols,
rows,
gridSize,
width,
height,
originalPoints: [],
deformedPoints: [],
};
for (let y = 0; y <= rows; y++) {
for (let x = 0; x <= cols; x++) {
const point = { x: x * gridSize, y: y * gridSize };
this.mesh.originalPoints.push({ ...point });
this.mesh.deformedPoints.push({ ...point });
}
}
}
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
return true;
}
return false;
}
setParam(param, value) {
if (param in this.params) {
this.params[param] = value;
return true;
}
return false;
}
getParams() {
return { ...this.params };
}
resetParams() {
this.params = {
size: 80, // 增大默认尺寸
pressure: 0.8, // 增大默认压力
distortion: 0,
power: 0.8, // 增大默认动力
};
}
applyDeformation(x, y) {
// 计算鼠标移动方向
if (!this.isFirstApply) {
this.mouseMovementX = x - this.lastMouseX;
this.mouseMovementY = y - this.lastMouseY;
} else {
// 首次应用时不计算移动,避免初始变形
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = false;
}
this.lastMouseX = x;
this.lastMouseY = y;
// 性能优化:限制更新频率
const now = Date.now();
if (now - this.lastUpdateTime < this.updateThrottle || this.isProcessing) {
return this.currentImageData;
}
this.isProcessing = true;
this.lastUpdateTime = now;
if (!this.initialized || !this.mesh) {
this.isProcessing = false;
return this.currentImageData;
}
const { size, pressure, distortion, power } = this.params;
const mode = this.currentMode;
const radius = size * 1.2; // 稍微增大影响半径
const strength = (pressure * power * this.config.maxStrength) / 20; // 调整基础强度
this._applyDeformation(x, y, radius, strength, mode, distortion);
if (this.config.smoothingIterations > 0) {
this._smoothMesh();
}
const result = this._applyMeshToImage();
this.isProcessing = false;
return result;
}
_applyDeformation(x, y, radius, strength, mode, distortion) {
if (!this.mesh) return;
const points = this.mesh.deformedPoints;
const originalPoints = this.mesh.originalPoints;
for (let i = 0; i < points.length; i++) {
const point = points[i];
const originalPoint = originalPoints[i];
const dx = point.x - x;
const dy = point.y - y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < radius && distance > 0) {
// 使用平方衰减函数
const factor = Math.pow(1 - distance / radius, 2) * strength * 0.1; // 大幅降低基础系数
switch (mode) {
case this.modes.PUSH: {
// 推拉模式 - 真正的拖拽效果
// 计算实际移动距离
const movementLength = Math.sqrt(
this.mouseMovementX * this.mouseMovementX +
this.mouseMovementY * this.mouseMovementY
);
// 只有在有足够移动距离时才应用效果
if (movementLength > 1.0) {
// 提高阈值,确保有明显移动
// 归一化移动方向
const moveX = this.mouseMovementX / movementLength;
const moveY = this.mouseMovementY / movementLength;
// 计算衰减(距离中心越近,效果越强)
const radiusRatio = distance / radius;
const falloff = Math.pow(1 - radiusRatio, 2.0); // 使用更强的衰减
// 基于实际移动距离计算强度
const { pressure, power } = this.params;
const moveStrength = pressure * power * movementLength * 0.3; // 降低移动强度系数
// 计算最终拖拽强度
const dragStrength = moveStrength * falloff * factor;
// 向鼠标移动方向拖拽
const dragX = moveX * dragStrength;
const dragY = moveY * dragStrength;
// 应用变形,但限制最大变形量
const maxDeform = 2.0; // 限制单次最大变形量
point.x += Math.max(-maxDeform, Math.min(maxDeform, dragX));
point.y += Math.max(-maxDeform, Math.min(maxDeform, dragY));
}
break;
}
case this.modes.CLOCKWISE:
case this.modes.COUNTERCLOCKWISE: {
// 旋转模式 - 保持原有效果
const angle = Math.atan2(dy, dx);
const direction = mode === this.modes.CLOCKWISE ? 1 : -1;
const rotationAngle = angle + direction * factor;
const newX = x + Math.cos(rotationAngle) * distance;
const newY = y + Math.sin(rotationAngle) * distance;
point.x += (newX - point.x) * 0.8;
point.y += (newY - point.y) * 0.8;
break;
}
case this.modes.PINCH: {
// 捏合模式 - 保持原有效果
const pinchStrength = factor * 1.2;
point.x -= dx * pinchStrength;
point.y -= dy * pinchStrength;
break;
}
case this.modes.EXPAND: {
// 展开模式 - 参考捏合的反向操作
const expandFactor = factor * 1.5;
point.x += dx * expandFactor;
point.y += dy * expandFactor;
break;
}
case this.modes.CRYSTAL: {
// 水晶模式 - 参考旋转算法创建多重波形
const crystalAngle = Math.atan2(dy, dx);
const crystalRadius = distance / radius;
// 确保有基础效果
const baseDistortion = Math.max(distortion, 0.3);
// 多重波形 - 类似旋转的角度调制
const wave1 = Math.sin(crystalAngle * 8) * 0.6;
const wave2 = Math.cos(crystalAngle * 12) * 0.4;
const waveAngle = crystalAngle + (wave1 + wave2) * baseDistortion;
// 径向调制 - 类似旋转的距离调制
const radialMod = 1 + Math.sin(crystalRadius * Math.PI * 2) * 0.3;
const modDistance = distance * radialMod;
const crystalX = x + Math.cos(waveAngle) * modDistance;
const crystalY = y + Math.sin(waveAngle) * modDistance;
const crystalFactor = factor * baseDistortion;
point.x += (crystalX - point.x) * crystalFactor;
point.y += (crystalY - point.y) * crystalFactor;
break;
}
case this.modes.EDGE: {
// 边缘模式 - 参考旋转算法创建垂直波纹
const edgeAngle = Math.atan2(dy, dx);
const edgeRadius = distance / radius;
// 确保有基础效果
const baseEdgeDistortion = Math.max(distortion, 0.5);
// 边缘波纹 - 垂直于径向的调制
const edgeWave =
Math.sin(edgeRadius * Math.PI * 4) * Math.cos(edgeAngle * 6);
const perpAngle = edgeAngle + Math.PI / 2; // 垂直角度
const edgeFactor = edgeWave * factor * baseEdgeDistortion;
const edgeOffsetX = Math.cos(perpAngle) * edgeFactor;
const edgeOffsetY = Math.sin(perpAngle) * edgeFactor;
point.x += edgeOffsetX;
point.y += edgeOffsetY;
break;
}
case this.modes.RECONSTRUCT: {
// 重建模式 - 向原始位置恢复
const restoreFactor = factor * 0.15;
point.x += (originalPoint.x - point.x) * restoreFactor;
point.y += (originalPoint.y - point.y) * restoreFactor;
break;
}
}
}
}
}
// 优化衰减函数,使过渡更平滑
_smoothFalloff(t) {
if (t >= 1) return 0;
// 使用更平滑的衰减曲线
const smoothT = 1 - t;
return smoothT * smoothT * smoothT * (3 - 2 * smoothT);
}
_smoothMesh() {
const { rows, cols } = this.mesh;
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 y = 1; y < rows; y++) {
for (let x = 1; x < cols; x++) {
const idx = y * (cols + 1) + x;
const left = points[y * (cols + 1) + (x - 1)];
const right = points[y * (cols + 1) + (x + 1)];
const top = points[(y - 1) * (cols + 1) + x];
const bottom = points[(y + 1) * (cols + 1) + x];
const centerX = (left.x + right.x + top.x + bottom.x) / 4;
const centerY = (left.y + right.y + top.y + bottom.y) / 4;
const relaxFactor = this.config.relaxFactor;
tempPoints[idx].x += (centerX - points[idx].x) * relaxFactor;
tempPoints[idx].y += (centerY - points[idx].y) * relaxFactor;
}
}
for (let i = 0; i < points.length; i++) {
points[i].x = tempPoints[i].x;
points[i].y = tempPoints[i].y;
}
}
}
_applyMeshToImage() {
if (!this.mesh || !this.originalImageData) {
return this.currentImageData;
}
const width = this.originalImageData.width;
const height = this.originalImageData.height;
const result = new ImageData(width, height);
const srcData = this.originalImageData.data;
const dstData = result.data;
// 性能优化:使用步长采样减少计算量
const step = width > 1000 || height > 1000 ? 2 : 1;
for (let y = 0; y < height; y += step) {
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
);
// 如果使用步长采样,需要填充相邻像素
for (let dy = 0; dy < step && y + dy < height; dy++) {
for (let dx = 0; dx < step && x + dx < width; dx++) {
const dstIdx = ((y + dy) * width + (x + dx)) * 4;
dstData[dstIdx] = color[0];
dstData[dstIdx + 1] = color[1];
dstData[dstIdx + 2] = color[2];
dstData[dstIdx + 3] = color[3];
}
}
}
}
}
this.currentImageData = result;
return result;
}
// 添加异步处理方法用于大图像
async applyDeformationAsync(x, y) {
return new Promise((resolve) => {
setTimeout(() => {
const result = this.applyDeformation(x, y);
resolve(result);
}, 0);
});
}
// 批量处理方法
applyDeformationBatch(positions) {
if (!this.initialized || !this.mesh || positions.length === 0) {
return this.currentImageData;
}
const { size, pressure, distortion, power } = this.params;
const mode = this.currentMode;
const radius = size * 1.0;
const strength = (pressure * power * this.config.maxStrength) / 60;
// 批量应用所有变形
positions.forEach((pos) => {
this._applyDeformation(
pos.x,
pos.y,
radius * 0.5,
strength * 0.3,
mode,
distortion
);
});
if (this.config.smoothingIterations > 0) {
this._smoothMesh();
}
return this._applyMeshToImage();
}
_mapPointBack(x, y) {
const { cols, rows, gridSize } = this.mesh;
const gridX = x / gridSize;
const gridY = y / gridSize;
const x1 = Math.floor(gridX);
const y1 = Math.floor(gridY);
const x2 = Math.min(x1 + 1, cols);
const y2 = Math.min(y1 + 1, rows);
const fx = gridX - x1;
const fy = gridY - y1;
// 获取四个网格点的变形和原始坐标
const deformed = [
this.mesh.deformedPoints[y1 * (cols + 1) + x1],
this.mesh.deformedPoints[y1 * (cols + 1) + x2],
this.mesh.deformedPoints[y2 * (cols + 1) + x1],
this.mesh.deformedPoints[y2 * (cols + 1) + x2],
];
const original = [
this.mesh.originalPoints[y1 * (cols + 1) + x1],
this.mesh.originalPoints[y1 * (cols + 1) + x2],
this.mesh.originalPoints[y2 * (cols + 1) + x1],
this.mesh.originalPoints[y2 * (cols + 1) + x2],
];
// 双线性插值计算变形后的位置
const deformedX =
(1 - fx) * (1 - fy) * deformed[0].x +
fx * (1 - fy) * deformed[1].x +
(1 - fx) * fy * deformed[2].x +
fx * fy * deformed[3].x;
const deformedY =
(1 - fx) * (1 - fy) * deformed[0].y +
fx * (1 - fy) * deformed[1].y +
(1 - fx) * fy * deformed[2].y +
fx * fy * deformed[3].y;
// 计算原始网格位置
const originalX = x1 * gridSize + fx * gridSize;
const originalY = y1 * gridSize + fy * gridSize;
// 计算偏移量并应用反向映射
const offsetX = deformedX - originalX;
const offsetY = deformedY - originalY;
return {
x: x - offsetX,
y: y - offsetY,
};
}
_bilinearInterpolate(data, width, height, x, y) {
const x1 = Math.floor(x);
const y1 = Math.floor(y);
const x2 = Math.min(x1 + 1, width - 1);
const y2 = Math.min(y1 + 1, height - 1);
const fx = x - x1;
const fy = y - y1;
const getPixel = (px, py) => {
const idx = (py * width + px) * 4;
return [data[idx], data[idx + 1], data[idx + 2], data[idx + 3]];
};
const p1 = getPixel(x1, y1);
const p2 = getPixel(x2, y1);
const p3 = getPixel(x1, y2);
const p4 = getPixel(x2, y2);
return [
Math.round(
(1 - fx) * (1 - fy) * p1[0] +
fx * (1 - fy) * p2[0] +
(1 - fx) * fy * p3[0] +
fx * fy * p4[0]
),
Math.round(
(1 - fx) * (1 - fy) * p1[1] +
fx * (1 - fy) * p2[1] +
(1 - fx) * fy * p3[1] +
fx * fy * p4[1]
),
Math.round(
(1 - fx) * (1 - fy) * p1[2] +
fx * (1 - fy) * p2[2] +
(1 - fx) * fy * p3[2] +
fx * fy * p4[2]
),
Math.round(
(1 - fx) * (1 - fy) * p1[3] +
fx * (1 - fy) * p2[3] +
(1 - fx) * fy * p3[3] +
fx * fy * p4[3]
),
];
}
reset() {
if (!this.mesh || !this.originalImageData) return false;
for (let i = 0; i < this.mesh.deformedPoints.length; i++) {
this.mesh.deformedPoints[i].x = this.mesh.originalPoints[i].x;
this.mesh.deformedPoints[i].y = this.mesh.originalPoints[i].y;
}
this.currentImageData = new ImageData(
new Uint8ClampedArray(this.originalImageData.data),
this.originalImageData.width,
this.originalImageData.height
);
this.deformHistory = [];
return true;
}
getCurrentImageData() {
return this.currentImageData;
}
destroy() {
this.originalImageData = null;
this.currentImageData = null;
this.mesh = null;
this.deformHistory = [];
this.initialized = false;
}
}

View File

@@ -0,0 +1,191 @@
/**
* 液化管理器
* 负责管理液化操作的核心算法和变形处理
*
* 此版本使用增强的液化算法支持GPU加速和优化的CPU处理
*/
import { EnhancedLiquifyManager } from "./EnhancedLiquifyManager";
export class LiquifyManager {
/**
* 创建液化管理器
* @param {Object} options 配置选项
*/
constructor(options = {}) {
// 将核心属性暴露给外部保持API兼容性
this.canvas = options.canvas || null;
this.layerManager = options.layerManager || null;
// 配置参数
this.config = {
gridSize: options.gridSize || 20,
maxStrength: options.maxStrength || 100,
defaultParams: {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
},
};
// 创建增强版液化管理器实例
this.enhancedManager = new EnhancedLiquifyManager({
// 配置选项
gridSize: options.gridSize || 15,
maxStrength: options.maxStrength || 100,
smoothingIterations: options.smoothingIterations || 2,
relaxFactor: options.relaxFactor || 0.25,
meshResolution: options.meshResolution || 64,
// 根据环境选择合适的渲染模式
forceCPU: true, // 默认不强制使用CPU
forceWebGL: false, // 优先使用WebGL模式
webglSizeThreshold: options.webglSizeThreshold || 500 * 500, // 降低阈值以更倾向使用WebGL
layerManager: options.layerManager || null,
canvas: options.canvas || null,
});
// 初始化液化管理器
this.initialize();
}
/**
* 初始化液化管理器
* @param {Object} options 配置选项
*/
initialize(options = {}) {
// 更新基础属性
if (options.canvas) this.canvas = options.canvas;
if (options.layerManager) this.layerManager = options.layerManager;
// 初始化增强液化管理器
return this.enhancedManager.initialize({
canvas: this.canvas,
layerManager: this.layerManager,
});
}
/**
* 为液化操作准备图像
* @param {Object|String} target 目标对象或图层ID
* @returns {Promise<Object>} 准备结果
*/
async prepareForLiquify(target) {
return this.enhancedManager.prepareForLiquify(target);
}
/**
* 设置液化模式
* @param {String} mode 液化模式
*/
setMode(mode) {
return this.enhancedManager.setMode(mode);
}
/**
* 设置液化参数
* @param {String} param 参数名称
* @param {Number} value 参数值
*/
setParam(param, value) {
return this.enhancedManager.setParam(param, value);
}
/**
* 获取当前参数
* @returns {Object} 当前参数对象
*/
getParams() {
return this.enhancedManager.getParams();
}
/**
* 重置参数为默认值
*/
resetParams() {
return this.enhancedManager.resetParams();
}
/**
* 应用液化效果
* @param {fabric.Object} targetObject 目标对象
* @param {String} mode 液化模式
* @param {Object} params 参数
* @param {Number} x X坐标
* @param {Number} y Y坐标
* @returns {ImageData} 处理后的图像数据
*/
async applyLiquify(targetObject, mode, params, x, y) {
if (!this.enhancedManager || !targetObject) {
console.error("液化管理器未正确初始化");
return null;
}
// 确保设置正确的模式和参数
if (mode) {
this.enhancedManager.setMode(mode);
}
if (params) {
Object.entries(params).forEach(([key, value]) => {
this.enhancedManager.setParam(key, value);
});
}
// 应用液化变形
console.log(`应用液化变形, 模式=${mode}, 坐标=(${x}, ${y}), 参数=`, params);
try {
// 直接调用EnhancedLiquifyManager的applyLiquify方法
const resultData = await this.enhancedManager.applyLiquify(
targetObject,
mode,
params,
x,
y
);
// 确保返回结果数据
if (!resultData) {
console.warn("液化变形没有返回结果数据");
}
return resultData;
} catch (error) {
console.error("液化变形应用失败:", error);
return null;
}
}
/**
* 重置液化操作
* @returns {ImageData} 重置后的图像数据
*/
reset() {
return this.enhancedManager.reset();
}
/**
* 检查图层是否可以液化
* @param {String} layerId 图层ID
* @returns {Object} 检查结果
*/
checkLayerForLiquify(layerId) {
return this.enhancedManager.checkLayerForLiquify(layerId);
}
/**
* 获取当前状态信息
* @returns {Object} 状态信息
*/
getStatus() {
return this.enhancedManager.getStatus();
}
/**
* 释放资源
*/
dispose() {
if (this.enhancedManager) {
this.enhancedManager.dispose();
}
}
}

View File

@@ -0,0 +1,878 @@
/**
* WebGL加速的液化管理器
* 使用WebGL技术进行加速液化变形处理
*/
export class LiquifyWebGLManager {
/**
* 创建WebGL液化管理器
* @param {Object} options 配置选项
*/
constructor(options = {}) {
this.canvas = null;
this.gl = null;
this.program = null;
this.texture = null;
this.mesh = null;
this.initialized = false;
this.originalImageData = null;
this.currentImageData = null;
// 变形配置
this.config = {
gridSize: options.gridSize || 20,
maxStrength: options.maxStrength || 100,
textureSize: 0,
meshResolution: options.meshResolution || 64,
};
// 当前参数
this.params = {
size: 80, // 增大默认尺寸
pressure: 0.8, // 增大默认压力
distortion: 0,
power: 0.8, // 增大默认动力
};
// 鼠标位置跟踪(用于推拉模式)
this.lastMouseX = 0;
this.lastMouseY = 0;
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = true; // 标记是否是首次应用
// 液化工具模式
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
this.currentMode = this.modes.PUSH;
// 变形点历史记录
this.deformHistory = [];
// WebGL着色器程序
this.vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform mat3 u_matrix;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4((u_matrix * vec3(a_position, 1)).xy, 0, 1);
v_texCoord = a_texCoord;
}
`;
this.fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_image;
uniform vec2 u_textureSize;
varying vec2 v_texCoord;
void main() {
vec2 onePixel = vec2(1.0, 1.0) / u_textureSize;
vec4 color = texture2D(u_image, v_texCoord);
// 简单的边缘检查,保证边缘渲染正确
if(v_texCoord.x < 0.0 || v_texCoord.x > 1.0 ||
v_texCoord.y < 0.0 || v_texCoord.y > 1.0) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
} else {
gl_FragColor = color;
}
}
`;
// 变形网格着色器程序
this.deformVertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
attribute vec2 a_deformation;
varying vec2 v_texCoord;
void main() {
vec2 position = a_position + a_deformation;
gl_Position = vec4(position * 2.0 - 1.0, 0, 1);
v_texCoord = a_texCoord;
}
`;
this.deformFragmentShaderSource = `
precision mediump float;
uniform sampler2D u_image;
varying vec2 v_texCoord;
void main() {
vec4 color = texture2D(u_image, v_texCoord);
gl_FragColor = color;
}
`;
}
/**
* 初始化WebGL环境
* @param {HTMLImageElement} image 图像元素
* @returns {Boolean} 是否初始化成功
*/
initialize(image) {
// 创建WebGL Canvas
this.canvas = document.createElement("canvas");
// 设置canvas大小与图像相同
this.canvas.width = image.width;
this.canvas.height = image.height;
// 尝试获取WebGL上下文
try {
this.gl =
this.canvas.getContext("webgl") ||
this.canvas.getContext("experimental-webgl");
} catch (e) {
console.error("WebGL初始化失败:", e);
return false;
}
if (!this.gl) {
console.error("WebGL不可用");
return false;
}
// 设置视口
this.gl.viewport(0, 0, this.canvas.width, this.canvas.height);
// 编译着色器程序
if (!this._createShaderProgram()) {
console.error("着色器程序创建失败");
return false;
}
// 创建纹理
this.texture = this._createTexture(image);
if (!this.texture) {
console.error("纹理创建失败");
return false;
}
// 记录原始图像数据
const tempCanvas = document.createElement("canvas");
tempCanvas.width = image.width;
tempCanvas.height = image.height;
const tempCtx = tempCanvas.getContext("2d");
tempCtx.drawImage(image, 0, 0);
this.originalImageData = tempCtx.getImageData(
0,
0,
image.width,
image.height
);
this.currentImageData = new ImageData(
new Uint8ClampedArray(this.originalImageData.data),
this.originalImageData.width,
this.originalImageData.height
);
// 创建变形网格
this._createDeformMesh();
this.config.textureSize = [image.width, image.height];
this.initialized = true;
return true;
}
/**
* 创建着色器程序
* @returns {Boolean} 是否创建成功
* @private
*/
_createShaderProgram() {
// 创建标准渲染程序
const vertexShader = this._compileShader(
this.vertexShaderSource,
this.gl.VERTEX_SHADER
);
const fragmentShader = this._compileShader(
this.fragmentShaderSource,
this.gl.FRAGMENT_SHADER
);
if (!vertexShader || !fragmentShader) return false;
// 创建程序
this.program = this.gl.createProgram();
this.gl.attachShader(this.program, vertexShader);
this.gl.attachShader(this.program, fragmentShader);
this.gl.linkProgram(this.program);
if (!this.gl.getProgramParameter(this.program, this.gl.LINK_STATUS)) {
console.error(
"着色器程序链接失败:",
this.gl.getProgramInfoLog(this.program)
);
return false;
}
// 创建变形渲染程序
const deformVertexShader = this._compileShader(
this.deformVertexShaderSource,
this.gl.VERTEX_SHADER
);
const deformFragmentShader = this._compileShader(
this.deformFragmentShaderSource,
this.gl.FRAGMENT_SHADER
);
if (!deformVertexShader || !deformFragmentShader) return false;
// 创建变形程序
this.deformProgram = this.gl.createProgram();
this.gl.attachShader(this.deformProgram, deformVertexShader);
this.gl.attachShader(this.deformProgram, deformFragmentShader);
this.gl.linkProgram(this.deformProgram);
if (!this.gl.getProgramParameter(this.deformProgram, this.gl.LINK_STATUS)) {
console.error(
"变形着色器程序链接失败:",
this.gl.getProgramInfoLog(this.deformProgram)
);
return false;
}
return true;
}
/**
* 编译着色器
* @param {String} source 着色器源码
* @param {Number} type 着色器类型
* @returns {WebGLShader} 编译后的着色器
* @private
*/
_compileShader(source, type) {
const shader = this.gl.createShader(type);
this.gl.shaderSource(shader, source);
this.gl.compileShader(shader);
if (!this.gl.getShaderParameter(shader, this.gl.COMPILE_STATUS)) {
console.error(
"着色器编译失败:",
this.gl.getShaderInfoLog(shader),
"shader type:",
type === this.gl.VERTEX_SHADER ? "VERTEX_SHADER" : "FRAGMENT_SHADER",
"source:",
source
);
this.gl.deleteShader(shader);
return null;
}
return shader;
}
/**
* 创建WebGL纹理
* @param {HTMLImageElement} image 图像元素
* @returns {WebGLTexture} WebGL纹理
* @private
*/
_createTexture(image) {
const texture = this.gl.createTexture();
this.gl.bindTexture(this.gl.TEXTURE_2D, texture);
// 设置参数,使我们可以渲染任何尺寸的图像
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_WRAP_S,
this.gl.CLAMP_TO_EDGE
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_WRAP_T,
this.gl.CLAMP_TO_EDGE
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MIN_FILTER,
this.gl.LINEAR
);
this.gl.texParameteri(
this.gl.TEXTURE_2D,
this.gl.TEXTURE_MAG_FILTER,
this.gl.LINEAR
);
// 上传图像到纹理
try {
this.gl.texImage2D(
this.gl.TEXTURE_2D,
0,
this.gl.RGBA,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
image
);
} catch (e) {
console.error("纹理上传失败:", e);
return null;
}
return texture;
}
/**
* 创建变形网格
* @private
*/
_createDeformMesh() {
const { meshResolution } = this.config;
// 创建网格顶点
const vertices = [];
const texCoords = [];
const indices = [];
const deformations = [];
// 创建顶点和纹理坐标
for (let y = 0; y <= meshResolution; y++) {
for (let x = 0; x <= meshResolution; x++) {
const xPos = x / meshResolution;
const yPos = y / meshResolution;
// 顶点位置
vertices.push(xPos, yPos);
// 纹理坐标
texCoords.push(xPos, yPos);
// 初始无变形
deformations.push(0, 0);
}
}
// 创建索引(三角形)
for (let y = 0; y < meshResolution; y++) {
for (let x = 0; x < meshResolution; x++) {
const i0 = y * (meshResolution + 1) + x;
const i1 = i0 + 1;
const i2 = i0 + meshResolution + 1;
const i3 = i2 + 1;
// 三角形1
indices.push(i0, i2, i1);
// 三角形2
indices.push(i1, i2, i3);
}
}
this.mesh = {
vertices: new Float32Array(vertices),
texCoords: new Float32Array(texCoords),
indices: new Uint16Array(indices),
deformations: new Float32Array(deformations),
resolution: meshResolution,
};
// 创建顶点缓冲区
this.vertexBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
this.mesh.vertices,
this.gl.STATIC_DRAW
);
// 创建纹理坐标缓冲区
this.texCoordBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
this.mesh.texCoords,
this.gl.STATIC_DRAW
);
// 创建变形缓冲区
this.deformBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
this.mesh.deformations,
this.gl.DYNAMIC_DRAW
);
// 创建索引缓冲区
this.indexBuffer = this.gl.createBuffer();
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
this.gl.bufferData(
this.gl.ELEMENT_ARRAY_BUFFER,
this.mesh.indices,
this.gl.STATIC_DRAW
);
}
/**
* 应用液化变形
* @param {Number} x 变形中心X坐标 (图像像素坐标)
* @param {Number} y 变形中心Y坐标 (图像像素坐标)
*/
applyDeformation(x, y) {
if (!this.initialized || !this.mesh) return;
// 计算鼠标移动方向
if (!this.isFirstApply) {
this.mouseMovementX = x - this.lastMouseX;
this.mouseMovementY = y - this.lastMouseY;
} else {
// 首次应用时不计算移动,避免初始变形
this.mouseMovementX = 0;
this.mouseMovementY = 0;
this.isFirstApply = false;
}
this.lastMouseX = x;
this.lastMouseY = y;
// 将图像像素坐标转换为纹理坐标 (0-1范围)
// 使用原始图像数据的尺寸进行归一化而不是WebGL canvas的尺寸
const imageWidth = this.originalImageData
? this.originalImageData.width
: this.canvas.width;
const imageHeight = this.originalImageData
? this.originalImageData.height
: this.canvas.height;
const tx = x / imageWidth;
const ty = y / imageHeight;
console.log(
`WebGL变形: 像素坐标(${x}, ${y}) -> 纹理坐标(${tx.toFixed(
3
)}, ${ty.toFixed(3)}), 图像尺寸(${imageWidth}x${imageHeight})`
);
// 获取当前参数
const { size, pressure, distortion, power } = this.params;
const mode = this.currentMode;
// 计算影响半径 (纹理坐标空间)
const radius = (size / 100) * 0.2; // 调整半径计算,使效果更自然
const strength = (pressure * power * this.config.maxStrength) / 800; // 进一步降低基础强度
// 保存当前变形点,用于重建功能
this.deformHistory.push({
x: tx,
y: ty,
radius,
strength,
mode,
distortion,
});
// 对网格顶点应用变形
const { resolution } = this.mesh;
const deformations = this.mesh.deformations;
for (let i = 0; i <= resolution; i++) {
for (let j = 0; j <= resolution; j++) {
const idx = (i * (resolution + 1) + j) * 2;
// 顶点在纹理空间中的位置
const vx = j / resolution;
const vy = i / resolution;
// 计算到变形中心的距离
const dx = vx - tx;
const dy = vy - ty;
const distance = Math.sqrt(dx * dx + dy * dy);
// 只影响半径内的点
if (distance < radius) {
// 计算影响因子
const factor = Math.pow(1 - distance / radius, 2) * strength;
// 根据不同模式应用变形
switch (mode) {
case this.modes.PUSH:
// 推拉模式 - 真正的拖拽效果
// 计算鼠标移动距离(转换为纹理坐标空间)
const movementX = this.mouseMovementX / imageWidth;
const movementY = this.mouseMovementY / imageHeight;
const movementLength = Math.sqrt(
movementX * movementX + movementY * movementY
);
// 只有在有足够移动距离时才应用效果
if (movementLength > 0.002) {
// 提高阈值,确保有明显移动
// 归一化移动方向
const moveX = movementX / movementLength;
const moveY = movementY / movementLength;
// 计算衰减(距离中心越近,效果越强)
const radiusRatio = distance / radius;
const falloff = Math.pow(1 - radiusRatio, 2.0); // 使用更强的衰减
// 基于实际移动距离计算强度
const moveStrength = pressure * power * movementLength * 0.5; // 降低移动强度系数
// 计算最终拖拽强度
const dragStrength = moveStrength * falloff * factor;
// 向鼠标移动方向拖拽
const dragX = moveX * dragStrength;
const dragY = moveY * dragStrength;
// 应用变形,但限制最大变形量
const maxDeform = 0.01; // 限制单次最大变形量(纹理坐标空间)
deformations[idx] += Math.max(
-maxDeform,
Math.min(maxDeform, dragX)
);
deformations[idx + 1] += Math.max(
-maxDeform,
Math.min(maxDeform, dragY)
);
}
break;
case this.modes.CLOCKWISE:
// 顺时针旋转
const angle = Math.atan2(dy, dx) + factor;
const len = distance;
deformations[idx] += Math.cos(angle) * len - dx;
deformations[idx + 1] += Math.sin(angle) * len - dy;
break;
case this.modes.COUNTERCLOCKWISE:
// 逆时针旋转
const angle2 = Math.atan2(dy, dx) - factor;
const len2 = distance;
deformations[idx] += Math.cos(angle2) * len2 - dx;
deformations[idx + 1] += Math.sin(angle2) * len2 - dy;
break;
case this.modes.PINCH:
// 捏合效果 - 向中心收缩
deformations[idx] -= dx * factor;
deformations[idx + 1] -= dy * factor;
break;
case this.modes.EXPAND:
// 展开效果 - 参考捏合算法的反向操作
const expandFactor = factor * 1.5;
deformations[idx] += dx * expandFactor;
deformations[idx + 1] += dy * expandFactor;
break;
case this.modes.CRYSTAL:
// 水晶效果 - 参考旋转算法创建多重角度变形
const crystalAngle = Math.atan2(dy, dx);
const crystalRadius = distance / radius;
// 确保有基础效果
const baseDistortion = Math.max(distortion, 0.3);
// 创建多重波形 - 类似旋转但加入波形调制
const wave1 = Math.sin(crystalAngle * 8) * 0.6;
const wave2 = Math.cos(crystalAngle * 12) * 0.4;
const waveAngle = crystalAngle + (wave1 + wave2) * baseDistortion;
// 径向扭曲 - 类似旋转的距离调制
const radialMod = 1 + Math.sin(crystalRadius * Math.PI * 2) * 0.3;
const modDistance = distance * radialMod;
const crystalX = Math.cos(waveAngle) * modDistance;
const crystalY = Math.sin(waveAngle) * modDistance;
deformations[idx] += (crystalX - (tx + dx)) * factor;
deformations[idx + 1] += (crystalY - (ty + dy)) * factor;
break;
case this.modes.EDGE:
// 边缘效果 - 参考旋转算法创建垂直于径向的波纹
const edgeAngle = Math.atan2(dy, dx);
const edgeRadius = distance / radius;
// 确保有基础效果
const baseEdgeDistortion = Math.max(distortion, 0.5);
// 创建边缘波纹 - 垂直于径向方向的调制
const edgeWave =
Math.sin(edgeRadius * Math.PI * 4) * Math.cos(edgeAngle * 6);
const perpAngle = edgeAngle + Math.PI / 2; // 垂直角度
const edgeFactor = edgeWave * factor * baseEdgeDistortion;
const edgeX = Math.cos(perpAngle) * edgeFactor;
const edgeY = Math.sin(perpAngle) * edgeFactor;
deformations[idx] += edgeX;
deformations[idx + 1] += edgeY;
break;
case this.modes.RECONSTRUCT:
// 重建 - 向原始位置恢复
deformations[idx] *= 0.9;
deformations[idx + 1] *= 0.9;
break;
}
}
}
}
// 更新变形缓冲区
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
deformations,
this.gl.DYNAMIC_DRAW
);
// 重新渲染
this._render();
// 更新当前图像数据
this.currentImageData = this._getImageData();
return this.currentImageData;
}
/**
* 渲染变形后的图像
* @private
*/
_render() {
if (!this.initialized) return;
// 清除画布
this.gl.clearColor(0, 0, 0, 0);
this.gl.clear(this.gl.COLOR_BUFFER_BIT);
// 使用变形程序
this.gl.useProgram(this.deformProgram);
// 设置纹理
this.gl.activeTexture(this.gl.TEXTURE0);
this.gl.bindTexture(this.gl.TEXTURE_2D, this.texture);
const u_image = this.gl.getUniformLocation(this.deformProgram, "u_image");
this.gl.uniform1i(u_image, 0);
// 设置顶点位置属性
const a_position = this.gl.getAttribLocation(
this.deformProgram,
"a_position"
);
this.gl.enableVertexAttribArray(a_position);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.vertexBuffer);
this.gl.vertexAttribPointer(a_position, 2, this.gl.FLOAT, false, 0, 0);
// 设置纹理坐标属性
const a_texCoord = this.gl.getAttribLocation(
this.deformProgram,
"a_texCoord"
);
this.gl.enableVertexAttribArray(a_texCoord);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.texCoordBuffer);
this.gl.vertexAttribPointer(a_texCoord, 2, this.gl.FLOAT, false, 0, 0);
// 设置变形属性
const a_deformation = this.gl.getAttribLocation(
this.deformProgram,
"a_deformation"
);
this.gl.enableVertexAttribArray(a_deformation);
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer);
this.gl.vertexAttribPointer(a_deformation, 2, this.gl.FLOAT, false, 0, 0);
// 绘制三角形
this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, this.indexBuffer);
this.gl.drawElements(
this.gl.TRIANGLES,
this.mesh.indices.length,
this.gl.UNSIGNED_SHORT,
0
);
}
/**
* 获取当前图像数据
* @returns {ImageData} 当前图像数据
* @private
*/
_getImageData() {
const width = this.canvas.width;
const height = this.canvas.height;
// 读取WebGL画布像素
const pixels = new Uint8Array(width * height * 4);
this.gl.readPixels(
0,
0,
width,
height,
this.gl.RGBA,
this.gl.UNSIGNED_BYTE,
pixels
);
// 直接创建ImageData不进行翻转
// WebGL和Canvas2D的坐标系不同但这里我们保持WebGL的原始输出
const imageData = new ImageData(
new Uint8ClampedArray(pixels),
width,
height
);
return imageData;
}
/**
* 重置所有变形
* @returns {ImageData} 重置后的图像数据
*/
reset() {
if (!this.initialized) return null;
// 清除变形历史
this.deformHistory = [];
// 重置所有变形
const deformations = new Float32Array(this.mesh.deformations.length);
this.mesh.deformations = deformations;
// 更新变形缓冲区
this.gl.bindBuffer(this.gl.ARRAY_BUFFER, this.deformBuffer);
this.gl.bufferData(
this.gl.ARRAY_BUFFER,
deformations,
this.gl.DYNAMIC_DRAW
);
// 重新渲染
this._render();
// 更新当前图像数据
this.currentImageData = this._getImageData();
return this.currentImageData;
}
/**
* 设置液化模式
* @param {String} mode 液化模式
*/
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
return true;
}
return false;
}
/**
* 设置液化参数
* @param {String} param 参数名
* @param {Number} value 参数值
*/
setParam(param, value) {
if (param in this.params) {
this.params[param] = value;
return true;
}
return false;
}
/**
* 获取当前参数
* @returns {Object} 当前参数
*/
getParams() {
return { ...this.params };
}
/**
* 重置参数为默认值
*/
resetParams() {
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
}
/**
* 获取原始图像数据
* @returns {ImageData} 原始图像数据
*/
getOriginalImageData() {
return this.originalImageData;
}
/**
* 获取当前图像数据
* @returns {ImageData} 当前图像数据
*/
getCurrentImageData() {
return this.currentImageData;
}
/**
* 释放资源
*/
dispose() {
if (!this.gl) return;
// 删除缓冲区
if (this.vertexBuffer) this.gl.deleteBuffer(this.vertexBuffer);
if (this.texCoordBuffer) this.gl.deleteBuffer(this.texCoordBuffer);
if (this.deformBuffer) this.gl.deleteBuffer(this.deformBuffer);
if (this.indexBuffer) this.gl.deleteBuffer(this.indexBuffer);
// 删除纹理
if (this.texture) this.gl.deleteTexture(this.texture);
// 删除着色器程序
if (this.program) this.gl.deleteProgram(this.program);
if (this.deformProgram) this.gl.deleteProgram(this.deformProgram);
// 重置属性
this.canvas = null;
this.gl = null;
this.program = null;
this.deformProgram = null;
this.texture = null;
this.mesh = null;
this.initialized = false;
this.deformHistory = [];
}
/**
* 检查是否支持WebGL
* @returns {Boolean} 是否支持WebGL
*/
static isSupported() {
try {
const canvas = document.createElement("canvas");
return !!(
window.WebGLRenderingContext &&
(canvas.getContext("webgl") || canvas.getContext("experimental-webgl"))
);
} catch (e) {
return false;
}
}
}