接入画布

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