线宽

0 阅读3分钟

为什么 1px 线条要 +0.5 才清晰?这个问题问到了 Canvas 渲染最本质的像素原理,我用最简单、最直观的方式给你讲明白:

为什么 1px 线条要 +0.5 才清晰?

一句话结论: 因为 Canvas 的坐标点,落在像素格子的「缝隙」上,而不是格子中心。


1. 先看屏幕像素是怎么排列的

你可以把屏幕想象成棋盘格

image.png

(0,0)      (1,0)      (2,0)
   ┌─────────┬─────────┬───
   │         │         │
   │ 像素0,0  │ 像素1,0  │
   │         │         │
(0,1)├────────┼─────────┼───
   │         │         │
   │ 像素0,1  │ 像素1,1

重点:

  • 坐标 (x,y) 是像素格子的角点
  • 不是像素中心!

2. 画 1px 线条时发生了什么?

Canvas 画线规则: 线条以坐标为中心,向左右各延伸 0.5px

比如你画:

ctx.lineWidth = 1
ctx.strokeRect(10, 10, 50, 50)

边框线会从:

  • 左:10.0 向左 0.5px → 9.5
  • 右:10.0 向右 0.5px → 10.5

结果: 线条跨在两个像素中间!

  • 一半在 9 号像素
  • 一半在 10 号像素

浏览器只能做一件事: 👉 两个像素都半透明模糊显示 于是你看到的就是:发虚、模糊、灰线


3. 为什么 +0.5 就清晰了?

当你写:

ctx.strokeRect(10.5, 10.5, 50, 50)

线条中心在 10.5

  • 向左 0.5px → 10.0
  • 向右 0.5px → 11.0

刚好完美占满第 10 号像素格子

没有跨像素,没有半透明 → 显示为清晰锐利的 1px 实线


4. 超直观总结

  • 坐标是整数 → 线落在像素缝隙 → 模糊
  • 坐标是 x.5 → 线落在像素正中心 → 清晰
写法线条位置效果
strokeRect(10,10,w,h)跨两个像素模糊、发灰
strokeRect(10.5,10.5,w,h)刚好填满一个像素清晰、锐利

5. 只有 1px 线需要这样吗?

是的!

  • lineWidth = 1 → 必须 +0.5
  • lineWidth ≥ 2 → 不需要 因为偶数宽度可以刚好对齐整数坐标,不会跨像素。

在Canvas中:

  • 如果不偏移0.5,线会跨在两个像素之间,导致抗锯齿模糊
  • 如果偏移0.5(从整数坐标移到 x.5),线会正好在一个像素中心,显示清晰

分成两种情况:

  • 水平线(左右延伸):y 坐标加 0.5,x 保持整数
  • 垂直线(上下延伸):x 坐标加 0.5,y 保持整数
    • 因为:
      • 水平线跨越多个 x 像素,但只占 1 个 y 像素高度。为了让这 1px 高度正好落在单个像素行内,y 需要是 x.5。
      • 垂直线跨越多个 y 像素,但只占 1 个 x 像素宽度。为了让这 1px 宽度正好落在单个像素列内,x 需要是 x.5。

斜线模糊是几何决定的:它斜着穿过像素网格,无论如何偏移(哪怕是 x+0.5, y+0.5),都必然跨过多个像素的边界,抗锯齿不可避免。

  • 横/竖线:只需在垂直于线的方向对齐像素中心即可锐利
  • 斜线:在长度方向上跨越无数像素,整体平移 +0.5 只是换一组像素被跨越,不会更清晰 想要斜线相对锐利,通常做法是:
  1. lineWidth 设为 2(牺牲细度换清晰度)
  2. 或者干脆接受 1px 斜线的轻微抗锯齿,这是正常的

最终一句话记忆

Canvas 坐标在像素角,1px 线要放在像素中心,所以 +0.5。

需要我给你写一段对比代码,一眼看清模糊 vs 清晰吗?