canvas 抗锯齿|图片模糊

文章讲述了在HTML5Canvas中处理图像时遇到的锯齿和模糊问题的原因及解决方案,包括调整线条坐标、使用高清画布、修改画布物理像素、缩放画布、配置图像平滑属性以及处理线条宽度和图像锐化的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

锯齿和模糊产生原因

  • 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);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值