文章目录
锯齿和模糊产生原因
- canvas 绘制的时候会从某个点向两边扩散 ,如果位置在某个像素块的顶点,1px/2=0.5px,但是无法绘制出半个像素,最小也得是 1px,所以就会自动补足成 1px,这样一来两边各扩散 1px 总共就变成了 2px
- 自动补足的这一部分的颜色已经不是之前那个颜色了,而是根据算法(比如取周围的近似色)生成的
线条坐标增加 0.5
- 根据产生原因得出的结论,但单纯增加 0.5 无法满足所有场景
使用高清画布
- 对于大多数 DOM 元素,浏览器确实会自动将 CSS 像素(设备独立像素)映射到设备物理像素。这意味着在高分辨率显示器(如 Retina 显示器)上,文本、矢量图形等内容会在呈现时保持清晰。但是 canvas 元素与其他 DOM 元素不同,因为它是一个基于像素的位图容器。
- 当将 canvas 元素的宽度和高度仅通过 CSS 设置时,实际上仅设置了对应的设备独立像素尺寸。在处理像素图形时,浏览器无法自动确定如何将设备独立像素映射到物理像素。即使在高清屏上,默认情况下,canvas 的物理像素宽度和高度将与 CSS 像素相同。
修改画布物理像素
canvas.width 和 canvas.clientWidth
- canvas.width 表示画布的物理宽度,单位为物理像素。它定义了画布内的实际像素数
- canvas.clientWidth 是画布的显示宽度,单位为设备独立像素(CSS 像素)
- 通过 canvas.width = canvas.clientWidth * devicePixelRatio 会增加画布的物理像素数,因为这里将画布的显示尺寸(客户端尺寸,即 CSS 像素)乘以设备像素比率。
- 因此在高清屏幕上,我们得到的实际物理像素尺寸会大于 CSS 像素尺寸。
const canvas = document.getElementById('your-canvas');
const context = canvas.getContext('2d');
const devicePixelRatio = window.devicePixelRatio || 1;
// 设置高清画布
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
缩放画布进行图像修正
- 画布内部的像素分辨率增加了。在这种情况下,如果不进行缩放绘制图形,那么图形将看起来更小
- 因此将绘制的所有元素按照设备像素比例进行缩放。缩放结果使得图形在设备独立像素(CSS 像素)和物理像素之间保持一致。这意味着即使在高分辨率屏幕上,我们仍然能够以预期的尺寸绘制图形,且在保留清晰度的同时防止模糊。
const canvas = document.getElementById('your-canvas');
const context = canvas.getContext('2d');
const devicePixelRatio = window.devicePixelRatio || 1;
// 设置高清画布
canvas.width = canvas.clientWidth * devicePixelRatio;
canvas.height = canvas.clientHeight * devicePixelRatio;
// 修改缩放比,在高分辨率屏幕上仍然能够以预期的尺寸绘制图形,且在保留清晰度的同时防止模糊
context.scale(devicePixelRatio, devicePixelRatio);
配置图像平滑属性
const canvas = document.getElementById('your-canvas');
const context = canvas.getContext('2d');
context.imageSmoothingEnabled = true;
context.imageSmoothingQuality = 'high'; // 可设置为'low', 'medium', 或 'high' ,注意此属性有兼容性问题
更高线宽为奇数
- 当绘制线条时,若线条宽度设置为奇数,则可能出现更少的锯齿
const canvas = document.getElementById('your-canvas');
const context = canvas.getContext('2d');
context.lineWidth = 3; // 将线条宽度设置为奇数
图像模糊和锐化
- 模糊:先对图像进行轻度模糊,以平滑边缘
- 锐化:然后再进行锐化处理,以增强边缘和纹理的清晰度
- 具体流程:先将图像绘制到一个称为"临时画布"的辅助画布上,然后按以下顺序对图像进行操作
- 将图像模糊
- 将模糊后的图像锐化
- 将处理过的图像绘制回原画布
const mainCanvas = document.getElementById('your-canvas');
const mainContext = mainCanvas.getContext('2d');
const tempCanvas = document.createElement('canvas');
tempCanvas.width = mainCanvas.width;
tempCanvas.height = mainCanvas.height;
const tempContext = tempCanvas.getContext('2d');
// 在临时画布上绘制您的图形或图像
// ...
// 应用模糊和锐化处理
// 这里可以使用一些库来实现图像处理功能,如 glfx.js 或 StackBlur 等。
// 实现细节可能会稍有不同,因为您需要根据实际情况选择合适的库。
tempContext.filter = 'blur(1px)'; // 使用 CSS filter 属性实现模糊处理(如果支持)
tempContext.drawImage(tempCanvas, 0, 0);
// 锐化过程
// ...
tempContext.drawImage(tempCanvas, 0, 0);
// 将处理后的图像绘制回主画布
mainContext.drawImage(tempCanvas, 0, 0);
锐化
- 卷积滤波器对图像进行锐化处理。卷积滤波器通过在原始图像的每个像素上应用一个权重核(kernel)来改变图像的像素值。在锐化处理中,可以使用一个锐化核(laplacian 锐化核或其他自定义核)。
const canvas = document.getElementById('your-canvas');
const context = canvas.getContext('2d');
// 在这里绘制您的图形或图像(例如,使用 context.drawImage)
// ...
function applySharpen(context, width, height) {
// 获取原始图像数据
let originalImageData = context.getImageData(0, 0, width, height);
let originalPixels = originalImageData.data;
// 创建一个用于存放处理后的图像数据的 ImageData 对象
let outputImageData = context.createImageData(width, height);
let outputPixels = outputImageData.data;
const kernel = [
0, -1, 0,
-1, 5, -1,
0, -1, 0,
];
const kernelSize = Math.sqrt(kernel.length);
const halfKernelSize = Math.floor(kernelSize / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
let r = 0, g = 0, b = 0;
for (let ky = 0; ky < kernelSize; ky++) {
for (let kx = 0; kx < kernelSize; kx++) {
// 考虑边缘像素
let pixelY = y + ky - halfKernelSize;
let pixelX = x + kx - halfKernelSize;
if (pixelY < 0 || pixelY >= height || pixelX < 0 || pixelX >= width) continue;
// 卷积计算
let offset = (pixelY * width + pixelX) * 4;
let weight = kernel[ky * kernelSize + kx];
r += originalPixels[offset] * weight;
g += originalPixels[offset + 1] * weight;
b += originalPixels[offset + 2] * weight;
}
}
let destOffset = (y * width + x) * 4;
outputPixels[destOffset] = r;
outputPixels[destOffset + 1] = g;
outputPixels[destOffset + 2] = b;
outputPixels[destOffset + 3] = originalPixels[destOffset + 3]; // 保持相同的 alpha 值
}
}
// 将处理后的图像数据绘制回画布
context.putImageData(outputImageData, 0, 0);
}
// 应用锐化处理
applySharpen(context, canvas.width, canvas.height);