Files
aida_front/src/component/Canvas/CanvasEditor/managers/liquify/LiquifyWebGLManager.js

532 lines
15 KiB
JavaScript
Raw Normal View History

2025-06-09 10:25:54 +08:00
/**
2025-06-18 11:05:23 +08:00
* WebGL版本的液化管理器 - 暂时不兼容WebGL的版本 后续看情况支持
* 使用GPU加速进行高性能液化变形处理支持持续按压效果
2025-06-09 10:25:54 +08:00
*/
export class LiquifyWebGLManager {
constructor(options = {}) {
this.config = {
2025-06-18 11:05:23 +08:00
meshResolution: options.meshResolution || 64, // WebGL网格精度
2025-06-09 10:25:54 +08:00
maxStrength: options.maxStrength || 100,
2025-06-18 11:05:23 +08:00
useFloatTextures: options.useFloatTextures !== false, // 默认启用浮点纹理
2025-06-09 10:25:54 +08:00
};
this.params = {
2025-06-18 11:05:23 +08:00
size: 50,
pressure: 0.5,
2025-06-09 10:25:54 +08:00
distortion: 0,
2025-06-18 11:05:23 +08:00
power: 0.5,
2025-06-09 10:25:54 +08:00
};
this.modes = {
PUSH: "push",
CLOCKWISE: "clockwise",
COUNTERCLOCKWISE: "counterclockwise",
PINCH: "pinch",
EXPAND: "expand",
CRYSTAL: "crystal",
EDGE: "edge",
RECONSTRUCT: "reconstruct",
};
2025-06-18 11:05:23 +08:00
this.currentMode = this.modes.PUSH;
this.initialized = false;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 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;
2025-06-09 10:25:54 +08:00
}
/**
2025-06-18 11:05:23 +08:00
* 检查WebGL支持
2025-06-09 10:25:54 +08:00
*/
2025-06-18 11:05:23 +08:00
static isSupported() {
try {
const canvas = document.createElement("canvas");
const gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
2025-06-18 11:05:23 +08:00
return !!gl;
} catch (e) {
return false;
}
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 初始化WebGL液化管理器
*/
initialize(imageSource) {
2025-06-09 10:25:54 +08:00
try {
2025-06-18 11:05:23 +08:00
// 创建WebGL画布
this.canvas = document.createElement("canvas");
this.gl = this.canvas.getContext("webgl") || this.canvas.getContext("experimental-webgl");
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
if (!this.gl) {
throw new Error("WebGL不可用");
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 处理图像源
if (imageSource instanceof HTMLImageElement) {
this.canvas.width = imageSource.width;
this.canvas.height = imageSource.height;
this._initWebGL(imageSource);
} else {
throw new Error("WebGL版本目前只支持HTMLImageElement");
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
this.initialized = true;
return true;
} catch (error) {
console.error("WebGL液化管理器初始化失败:", error);
2025-06-09 10:25:54 +08:00
return false;
}
2025-06-18 11:05:23 +08:00
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 初始化WebGL环境
*/
_initWebGL(image) {
const gl = this.gl;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 创建着色器程序
this._createShaderProgram();
// 创建纹理
this.originalTexture = this._createTexture(image);
this.currentTexture = this._createTexture(image);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 创建帧缓冲
this.framebuffer = gl.createFramebuffer();
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 创建网格缓冲
this._createMeshBuffer();
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
console.log("WebGL液化环境初始化完成");
2025-06-09 10:25:54 +08:00
}
/**
* 创建着色器程序
*/
_createShaderProgram() {
2025-06-18 11:05:23 +08:00
const gl = this.gl;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 顶点着色器
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;
}
`;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 片段着色器 - 支持多种液化模式
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;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
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);
}
`;
2025-06-09 10:25:54 +08:00
this.program = this._createProgram(vertexShaderSource, fragmentShaderSource);
2025-06-09 10:25:54 +08:00
}
/**
2025-06-18 11:05:23 +08:00
* 创建着色器
2025-06-09 10:25:54 +08:00
*/
2025-06-18 11:05:23 +08:00
_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));
2025-06-09 10:25:54 +08:00
}
return shader;
}
/**
2025-06-18 11:05:23 +08:00
* 创建着色器程序
2025-06-09 10:25:54 +08:00
*/
2025-06-18 11:05:23 +08:00
_createProgram(vertexSource, fragmentSource) {
const gl = this.gl;
const vertexShader = this._createShader(gl.VERTEX_SHADER, vertexSource);
const fragmentShader = this._createShader(gl.FRAGMENT_SHADER, fragmentSource);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
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));
2025-06-09 10:25:54 +08:00
}
2025-06-18 11:05:23 +08:00
return program;
2025-06-09 10:25:54 +08:00
}
/**
2025-06-18 11:05:23 +08:00
* 创建纹理
2025-06-09 10:25:54 +08:00
*/
2025-06-18 11:05:23 +08:00
_createTexture(image) {
const gl = this.gl;
const texture = gl.createTexture();
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
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);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
return texture;
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 创建网格缓冲
*/
_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]);
2025-06-18 11:05:23 +08:00
this.meshBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, this.meshBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 设置液化模式
*/
setMode(mode) {
if (Object.values(this.modes).includes(mode)) {
this.currentMode = mode;
return true;
}
return false;
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 设置参数
*/
setParam(param, value) {
if (param in this.params) {
this.params[param] = value;
return true;
2025-06-09 10:25:54 +08:00
}
2025-06-18 11:05:23 +08:00
return false;
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 获取参数
*/
getParams() {
return { ...this.params };
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 开始液化操作
*/
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;
// 启动持续效果定时器
2026-01-02 11:24:11 +08:00
// this.startContinuousEffect();
2025-06-18 11:05:23 +08:00
console.log(`WebGL液化开始初始点: (${x}, ${y})`);
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 结束液化操作
*/
endDeformation() {
this.isDragging = false;
this.isHolding = false;
this.pressStartTime = 0;
this.pressDuration = 0;
this.accumulatedRotation = 0;
this.accumulatedScale = 0;
// 停止持续效果定时器
this.stopContinuousEffect();
console.log("WebGL液化结束");
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 启动持续效果
*/
startContinuousEffect() {
this.stopContinuousEffect();
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
this.continuousTimer = setInterval(() => {
if (this.isHolding && this.initialized) {
this.pressDuration = Date.now() - this.pressStartTime;
this.applyContinuousDeformation();
}
}, this.continuousApplyInterval);
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 停止持续效果
*/
stopContinuousEffect() {
if (this.continuousTimer) {
clearInterval(this.continuousTimer);
this.continuousTimer = null;
}
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
/**
* 应用持续变形效果
*/
applyContinuousDeformation() {
if (!this.isHolding || !this.initialized) return;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
return this.applyDeformation(this.initialMouseX, this.initialMouseY);
2025-06-09 10:25:54 +08:00
}
/**
* 应用液化变形
*/
applyDeformation(x, y) {
2025-06-18 11:05:23 +08:00
if (!this.initialized || !this.gl) return null;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
const gl = this.gl;
const { size, pressure, power, distortion } = this.params;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 设置视口
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;
2025-06-09 10:25:54 +08:00
}
}
2025-06-18 11:05:23 +08:00
// 计算拖拽向量(用于推拉模式)
const dragX = this.isDragging ? (x - this.initialMouseX) / this.canvas.width : 0;
const dragY = this.isDragging ? -(y - this.initialMouseY) / this.canvas.height : 0;
2025-06-18 11:05:23 +08:00
// 获取模式索引
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,
2025-06-09 10:25:54 +08:00
0
);
2025-06-18 11:05:23 +08:00
// 绘制
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 读取结果并转换为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);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 翻转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;
2025-06-18 11:05:23 +08:00
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];
}
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
return new ImageData(flippedPixels, this.canvas.width, this.canvas.height);
2025-06-09 10:25:54 +08:00
}
/**
2025-06-18 11:05:23 +08:00
* 重置到原始状态
2025-06-09 10:25:54 +08:00
*/
reset() {
if (!this.initialized) return null;
2025-06-18 11:05:23 +08:00
const gl = this.gl;
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 将原始纹理复制到当前纹理
gl.bindTexture(gl.TEXTURE_2D, this.currentTexture);
gl.copyTexImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 0, 0, this.canvas.width, this.canvas.height, 0);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
// 读取原始纹理数据
gl.bindFramebuffer(gl.FRAMEBUFFER, this.framebuffer);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0,
gl.TEXTURE_2D,
this.originalTexture,
0
);
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
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);
2025-06-09 10:25:54 +08:00
return new ImageData(new Uint8ClampedArray(pixels), this.canvas.width, this.canvas.height);
2025-06-09 10:25:54 +08:00
}
/**
* 重置参数为默认值
*/
resetParams() {
this.params = {
size: 50,
pressure: 0.5,
distortion: 0,
power: 0.5,
};
}
/**
* 释放资源
*/
dispose() {
2025-06-18 11:05:23 +08:00
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);
}
2025-06-09 10:25:54 +08:00
2025-06-18 11:05:23 +08:00
this.stopContinuousEffect();
this.initialized = false;
2025-06-09 10:25:54 +08:00
this.canvas = null;
this.gl = null;
2025-06-18 11:05:23 +08:00
console.log("WebGL液化管理器资源已释放");
2025-06-09 10:25:54 +08:00
}
}