接入画布
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user