借助JavaScript操作canvas元素,我们可以通过对图像像素进行逐层处理,实现将普通照片转换为素描风格的手绘图像。整个过程不需要引入额外的图像处理库,核心是通过四种不同的滤镜操作组合完成效果转换。

基础准备:获取图像像素数据
首先需要在页面中准备一个canvas元素和一个用于上传图片的input标签,先获取图像的原始像素数据,这是后续所有滤镜处理的基础。
// 获取页面元素
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const uploadInput = document.getElementById('upload');
// 监听图片上传事件
uploadInput.addEventListener('change', (e) => {
const file = e.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
const img = new Image();
img.onload = () => {
// 设置canvas尺寸和图片一致
canvas.width = img.width;
canvas.height = img.height;
// 绘制图片到canvas
ctx.drawImage(img, 0, 0);
// 获取图像像素数据
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
// 后续滤镜处理从这里开始
processSketch(imageData);
};
img.src = event.target.result;
};
reader.readAsDataURL(file);
});
第一步:灰度转换滤镜
素描效果的基础是灰度图像,需要将彩色图像的每个像素的RGB值转换为对应的灰度值,常用的转换公式是灰度值 = 0.299*R + 0.587*G + 0.114*B,这个权重符合人眼对不同颜色敏感度的差异。
function toGray(imageData) {
const data = imageData.data;
// 遍历每个像素的RGBA值
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// 计算灰度值
const gray = 0.299 * r + 0.587 * g + 0.114 * b;
// 将RGB都设置为灰度值
data[i] = gray;
data[i + 1] = gray;
data[i + 2] = gray;
// alpha通道保持不变
}
return imageData;
}
第二步:反色处理滤镜
灰度图像完成后,需要进行反色处理,让亮部变暗、暗部变亮,这一步是为了后续叠加出素描的线条效果。反色的计算方式是新值 = 255 - 原灰度值。
function reverseColor(imageData) {
const data = imageData.data;
for (let i = 0; i < data.length; i += 4) {
// 对RGB通道做反色处理
data[i] = 255 - data[i];
data[i + 1] = 255 - data[i + 1];
data[i + 2] = 255 - data[i + 2];
}
return imageData;
}
第三步:高斯模糊滤镜
反色后的图像需要添加高斯模糊效果,模拟素描中铅笔涂抹的质感,模糊程度可以根据需求调整。这里实现一个简单的高斯模糊算法,通过周边像素加权平均计算新的像素值。
function gaussianBlur(imageData, radius) {
const data = imageData.data;
const width = imageData.width;
const height = imageData.height;
const copyData = new Uint8ClampedArray(data);
// 简单高斯模糊核,半径radius控制模糊程度
const kernel = [];
let sum = 0;
for (let i = -radius; i <= radius; i++) {
const val = Math.exp(-i * i / (2 * radius * radius));
kernel.push(val);
sum += val;
}
// 归一化核
for (let i = 0; i < kernel.length; i++) {
kernel[i] /= sum;
}
// 水平方向模糊
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0;
for (let k = -radius; k <= radius; k++) {
const px = Math.min(width - 1, Math.max(0, x + k));
const idx = (y * width + px) * 4;
r += copyData[idx] * kernel[k + radius];
g += copyData[idx + 1] * kernel[k + radius];
b += copyData[idx + 2] * kernel[k + radius];
}
const idx = (y * width + x) * 4;
data[idx] = r;
data[idx + 1] = g;
data[idx + 2] = b;
}
}
// 垂直方向模糊
const tempData = new Uint8ClampedArray(data);
for (let x = 0; x < width; x++) {
for (let y = 0; y < height; y++) {
let r = 0, g = 0, b = 0;
for (let k = -radius; k <= radius; k++) {
const py = Math.min(height - 1, Math.max(0, y + k));
const idx = (py * width + x) * 4;
r += tempData[idx] * kernel[k + radius];
g += tempData[idx + 1] * kernel[k + radius];
b += tempData[idx + 2] * kernel[k + radius];
}
const idx = (y * width + x) * 4;
data[idx] = r;
data[idx + 1] = g;
data[idx + 2] = b;
}
}
return imageData;
}
第四步:颜色叠加滤镜
最后将模糊后的反色图像和最初的灰度图像做颜色叠加,就能得到最终的素描效果。叠加的逻辑是最终像素值 = 灰度值 + 反色模糊值 - 灰度值 * 反色模糊值 / 255,这个公式是模拟正片叠底的效果。
function colorDodge(grayData, blurData) {
const grayPixels = grayData.data;
const blurPixels = blurData.data;
for (let i = 0; i < grayPixels.length; i += 4) {
// 对每个RGB通道做叠加计算
for (let j = 0; j < 3; j++) {
const grayVal = grayPixels[i + j];
const blurVal = blurPixels[i + j];
// 避免除零错误
if (blurVal === 255) {
grayPixels[i + j] = 255;
} else {
grayPixels[i + j] = Math.min(255, grayVal * 255 / (255 - blurVal));
}
}
}
return grayData;
}
完整流程整合
把四个步骤串联起来,就完成了整个素描效果的转换流程,最后将处理后的像素数据放回canvas显示即可。
function processSketch(originalImageData) {
// 第一步:灰度转换
const grayData = toGray(originalImageData);
// 保存灰度数据用于后续叠加
const grayCopy = new ImageData(
new Uint8ClampedArray(grayData.data),
grayData.width,
grayData.height
);
// 第二步:反色处理
const reverseData = reverseColor(grayData);
// 第三步:高斯模糊,半径设为3效果适中
const blurData = gaussianBlur(reverseData, 3);
// 第四步:颜色叠加
const resultData = colorDodge(grayCopy, blurData);
// 将结果绘制到canvas
ctx.putImageData(resultData, 0, 0);
}
注意事项
- 处理大尺寸图片时,像素遍历操作可能会阻塞主线程,建议使用Web Worker处理或者先缩小图片再处理。
- 高斯模糊的半径越大,模糊效果越明显,素描的线条会越柔和,可以根据实际需求调整参数。
- 如果图片本身对比度较低,得到的素描效果会偏淡,可以在灰度转换前先调整图片的对比度。
JavaScriptcanvas图像滤镜素描效果手绘风格修改时间:2026-06-28 02:27:47