Files
aida_front/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyWebGLManager.js
2025-07-14 01:00:23 +08:00

532 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* WebGL版本的液化管理器 - 暂时不兼容WebGL的版本 后续看情况支持
* 使用GPU加速进行高性能液化变形处理支持持续按压效果
*/
export class LiquifyWebGLManager {
constructor(options = {}) {
this.config = {
meshResolution: options.meshResolution || 64, // WebGL网格精度
maxStrength: options.maxStrength || 100,
useFloatTextures: options.useFloatTextures !== false, // 默认启用浮点纹理
};
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
this.currentMode = this.modes.PUSH;
this.initialized = false;
// WebGL相关
this.canvas = null;
this.gl = null;
this.program = null;
this.meshBuffer = null;
this.originalTexture = null;
this.currentTexture = null;
this.framebuffer = null;
// 持续按压相关状态
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.isHolding = false;
this.continuousTimer = null;
this.continuousApplyInterval = 50; // 50ms间隔
// 初始点坐标
this.initialMouseX = 0;
this.initialMouseY = 0;
this.isDragging = false;
}
/**
* 检查WebGL支持
*/
static isSupported() {
try {
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
return !!gl;
} catch (e) {
return false;
}
}
/**
* 初始化WebGL液化管理器
*/
initialize(imageSource) {
try {
// 创建WebGL画布
this.canvas = document.createElement("canvas");
this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
if (!this.gl) {
throw new Error("WebGL不可用");
}
// 处理图像源
if (imageSource instanceof HTMLImageElement) {
this.canvas.width = imageSource.width;
this.canvas.height = imageSource.height;
this._initWebGL(imageSource);
} else {
throw new Error("WebGL版本目前只支持HTMLImageElement");
}
this.initialized = true;
return true;
} catch (error) {
console.error("WebGL液化管理器初始化失败:", error);
return false;
}
}
/**
* 初始化WebGL环境
*/
_initWebGL(image) {
const gl = this.gl;
// 创建着色器程序
this._createShaderProgram();
// 创建纹理
this.originalTexture = this._createTexture(image);
this.currentTexture = this._createTexture(image);
// 创建帧缓冲
this.framebuffer = gl.createFramebuffer();
// 创建网格缓冲
this._createMeshBuffer();
console.log("WebGL液化环境初始化完成");
}
/**
* 创建着色器程序
*/
_createShaderProgram() {
const gl = this.gl;
// 顶点着色器
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
// 片段着色器 - 支持多种液化模式
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
uniform vec2 u_center;
uniform float u_radius;
uniform float u_strength;
uniform int u_mode;
uniform float u_time;
uniform float u_distortion;
uniform vec2 u_dragVector;
varying vec2 v_texCoord;
void main() {
vec2 coord = v_texCoord;
vec2 center = u_center;
float dist = distance(coord, center);
if (dist < u_radius && dist > 0.0) {
float factor = 1.0 - (dist / u_radius);
factor = factor * factor * factor * u_strength;
if (u_mode == 0) { // PUSH
vec2 direction = normalize(u_dragVector);
coord += direction * factor * 0.1;
}
else if (u_mode == 1 || u_mode == 2) { // CLOCKWISE/COUNTERCLOCKWISE
float angle = atan(coord.y - center.y, coord.x - center.x);
float rotationSpeed = u_mode == 1 ? 0.1 : -0.1;
angle += rotationSpeed * factor * (1.0 + u_time * 0.001);
coord = center + dist * vec2(cos(angle), sin(angle));
}
else if (u_mode == 3) { // PINCH
vec2 direction = center - coord;
coord += direction * factor * (0.5 + u_time * 0.0005);
}
else if (u_mode == 4) { // EXPAND
vec2 direction = coord - center;
coord += direction * factor * (0.5 + u_time * 0.0005);
}
else if (u_mode == 5) { // CRYSTAL
float crystalAngle = atan(coord.y - center.y, coord.x - center.x);
float wave1 = sin(crystalAngle * 8.0 + u_time * 0.005) * 0.6;
float wave2 = cos(crystalAngle * 12.0 + u_time * 0.003) * 0.4;
crystalAngle += (wave1 + wave2) * u_distortion * factor;
coord = center + dist * vec2(cos(crystalAngle), sin(crystalAngle));
}
else if (u_mode == 6) { // EDGE
float edgeAngle = atan(coord.y - center.y, coord.x - center.x);
float edgeWave = sin(dist * 3.14159 * 4.0 + u_time * 0.004) *
cos(edgeAngle * 6.0 + u_time * 0.002);
float perpAngle = edgeAngle + 3.14159 * 0.5;
coord += vec2(cos(perpAngle), sin(perpAngle)) * edgeWave * factor * u_distortion * 0.5;
}
}
gl_FragColor = texture2D(u_texture, coord);
}
`;
this.program = this._createProgram(vertexShaderSource, fragmentShaderSource);
}
/**
* 创建着色器
*/
_createShader(type, source) {
const gl = this.gl;
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
throw new Error("着色器编译失败: " + gl.getShaderInfoLog(shader));
}
return shader;
}
/**
* 创建着色器程序
*/
_createProgram(vertexSource, fragmentSource) {
const gl = this.gl;
const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource);
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
throw new Error("程序链接失败: " + gl.getProgramInfoLog(program));
}
return program;
}
/**
* 创建纹理
*/
_createTexture(image) {
const gl = this.gl;
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
return texture;
}
/**
* 创建网格缓冲
*/
_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]);
this.meshBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
}
/**
* 设置液化模式
*/
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 };
}
/**
* 开始液化操作
*/
startDeformation(x, y) {
this.initialMouseX = x;
this.initialMouseY = y;
this.isDragging = true;
// 初始化持续按压状态
this.pressStartTime = Date.now();
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
this.isHolding = true;
// 启动持续效果定时器
this.startContinuousEffect();
console.log(`WebGL液化开始初始点: (${x}, ${y})`);
}
/**
* 结束液化操作
*/
endDeformation() {
this.isDragging = false;
this.isHolding = false;
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
// 停止持续效果定时器
this.stopContinuousEffect();
console.log("WebGL液化结束");
}
/**
* 启动持续效果
*/
startContinuousEffect() {
this.stopContinuousEffect();
this.continuousTimer = setInterval(() => {
if (this.isHolding && this.initialized) {
this.pressDuration = Date.now() - this.pressStartTime;
this.applyContinuousDeformation();
}
}, this.continuousApplyInterval);
}
/**
* 停止持续效果
*/
stopContinuousEffect() {
if (this.continuousTimer) {
clearInterval(this.continuousTimer);
this.continuousTimer = null;
}
}
/**
* 应用持续变形效果
*/
applyContinuousDeformation() {
if (!this.isHolding || !this.initialized) return;
return this.applyDeformation(this.initialMouseX, this.initialMouseY);
}
/**
* 应用液化变形
*/
applyDeformation(x, y) {
if (!this.initialized || !this.gl) return null;
const gl = this.gl;
const { size, pressure, power, distortion } = this.params;
// 设置视口
gl.viewport(0, 0, this.canvas.width, this.canvas.height);
// 使用着色器程序
gl.useProgram(this.program);
// 绑定属性
const positionLoc = gl.getAttribLocation(this.program, "a_position");
const texCoordLoc = gl.getAttribLocation(this.program, "a_texCoord");
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 16, 0);
gl.enableVertexAttribArray(texCoordLoc);
gl.vertexAttribPointer(texCoordLoc, 2, gl.FLOAT, false, 16, 8);
// 设置uniform变量
const centerLoc = gl.getUniformLocation(this.program, "u_center");
const radiusLoc = gl.getUniformLocation(this.program, "u_radius");
const strengthLoc = gl.getUniformLocation(this.program, "u_strength");
const modeLoc = gl.getUniformLocation(this.program, "u_mode");
const timeLoc = gl.getUniformLocation(this.program, "u_time");
const distortionLoc = gl.getUniformLocation(this.program, "u_distortion");
const dragVectorLoc = gl.getUniformLocation(this.program, "u_dragVector");
// 计算参数
const centerX = x / this.canvas.width;
const centerY = 1.0 - y / this.canvas.height; // WebGL坐标系Y轴翻转
const radius = (size / Math.max(this.canvas.width, this.canvas.height)) * 2;
// 计算强度,考虑持续按压效果
let strength = pressure * power;
if (this.isHolding) {
const timeFactor = Math.min(this.pressDuration / 1000, 3.0);
if (this.currentMode === this.modes.PUSH) {
strength *= 0.2 + timeFactor * 0.3;
} else {
strength *= 0.5 + timeFactor * 0.5;
}
}
// 计算拖拽向量(用于推拉模式)
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);
// 设置uniform值
gl.uniform2f(centerLoc, centerX, centerY);
gl.uniform1f(radiusLoc, radius);
gl.uniform1f(strengthLoc, strength);
gl.uniform1i(modeLoc, modeIndex);
gl.uniform1f(timeLoc, this.pressDuration);
gl.uniform1f(distortionLoc, distortion);
gl.uniform2f(dragVectorLoc, dragX, dragY);
// 绑定纹理
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
gl.uniform1i(gl.getUniformLocation(this.program, "u_texture"), 0);
// 渲染到帧缓冲
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.currentTexture,
0
);
// 绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
// 读取结果并转换为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);
// 翻转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 dstIndex = (y * this.canvas.width + x) * 4;
flippedPixels[dstIndex] = pixels[srcIndex];
flippedPixels[dstIndex + 1] = pixels[srcIndex + 1];
flippedPixels[dstIndex + 2] = pixels[srcIndex + 2];
flippedPixels[dstIndex + 3] = pixels[srcIndex + 3];
}
}
return new ImageData(flippedPixels, this.canvas.width, this.canvas.height);
}
/**
* 重置到原始状态
*/
reset() {
if (!this.initialized) return null;
const gl = this.gl;
// 将原始纹理复制到当前纹理
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.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.originalTexture,
0
);
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);
return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height);
}
/**
* 重置参数为默认值
*/
resetParams() {
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
}
/**
* 释放资源
*/
dispose() {
if (this.gl) {
if (this.program) this.gl.deleteProgram(this.program);
if (this.meshBuffer) this.gl.deleteBuffer(this.meshBuffer);
if (this.originalTexture) this.gl.deleteTexture(this.originalTexture);
if (this.currentTexture) this.gl.deleteTexture(this.currentTexture);
if (this.framebuffer) this.gl.deleteFramebuffer(this.framebuffer);
}
this.stopContinuousEffect();
this.initialized = false;
this.canvas = null;
this.gl = null;
console.log("WebGL液化管理器资源已释放");
}
}