画板的抗锯齿

抗锯齿

折线问题

通过 lineTo 实现自由画笔,本质是点与点连接的线段,无论点如何密集,都无法避免线段间的折线,快速移动下爆点率更低,线段更长,折线还会更明显。

1
2
ctx.moveTo(beginPoint.x, beginPoint.y)
ctx.lineTo(endPoint.x, endPoint.y)

Demo

  • 使用二次贝塞尔曲线处理折线
1
2
ctx.moveTo(beginPoint.x, beginPoint.y)
ctx.quadraticCurveTo(ctrlPoint.x, ctrlPoint.y, endPoint.x, endPoint.y)

二次贝塞尔曲线需要三个点,起点、终点和控制点,控制点确定了曲线的方向和形状。以 [A, B, C, D, E, F] 六点为例,其关键点算法为,以 A 为起点,B 为控制点,B, C 的中间点 B1 为终点,绘制二次贝塞尔曲线线段,接下来以 B1 为起点,C 为控制点,C, D 的中间点 C1 为终点,依此类推,直到最后。

注:使用最后两个点的中间点作为终点,而不是最后一个点作为终点,可以使得曲线的转折更加平滑,更自然地过渡,而不会出现突然的转折。除了首尾点外,整个线条串过的均为中间点。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const points = []
function handleDrawing(ev) {
const curPoint = getPosition(ev)
points.push(curPoint)
if (points.length > 3) {
const lastTwoPoints = points.slice(-2)
// 控制点
const ctrlPoint = lastTwoPoints[0]
// 终点
const endPoint = {
x: (lastTwoPoints[0].x + lastTwoPoints[1].x) / 2,
y: (lastTwoPoints[0].y + lastTwoPoints[1].y) / 2
}
drawLine(ctrlPoint, beginPoint, endPoint)
// 重置起点
beginPoint = endPoint
}
}

Demo

高分屏显示虚化

在 Retina 屏中,一个逻辑像素对应多个物理像素,如果逻辑像素不足,必然会导致显示虚化。在 Canvas 中,可以先按物理像素放大,再按逻辑像素缩小解决。

1
2
3
4
5
6
7
8
const dpr = window.devicePixelRatio
const retinaWidth = canvas.width * dpr
const retinaHeight = canvas.height * dpr
canvas.width = retinaWidth
canvas.height = retinaHeight
canvas.style.width = `${retinaWidth / dpr}px`
canvas.style.height = `${retinaHeight / dpr}px`
ctx.scale(dpr, dpr)

Demo