首页» 前端 » HTML5 » canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决方案

canvas中画线条,线条效果比预期宽1像素且模糊问题分析及解决方案

日期:2019年12月18日 阅读次数:4495 分类:HTML5javascript前端

【问题】 canvas中画线条,线条效果比预期宽1像素且模糊。
【出现条件】 这种情况一般是垂直或者水平的线,且坐标为整数,宽度不是偶数。
【解决方法】 坐标偏移0.5像素。

实际情况中可能并没有这么简单,下面我们通过实例分析更多情况。

查看源码对比下面的分析会更好理解哦。 canvas画线条源码

效果如下图(在PS中放大后效果)
canvas画线实例

【事例解析】

  1. 图中上面第1、2条都是【1像素】线,但是第一条看着像2px。而第二条(X坐标偏移了0.5px)才真正实现了1像素宽的效果。

  2. 图中第3-6条上下并列的是【非整数宽】的线,从左到右宽分别是1.3px、0.8px、0.5px、0.1px。下面4条X坐标都偏移了0.5px。效果更接近预期的宽度。上面以整数为X坐标反而像是颜色淡点的2px宽的线。0.1px的更是看不到了。

  3. 从图中几根斜线发现,canvas画斜线毛边比较明显。但是越靠近45度角毛边会越少。(这个问题暂时没有找到好的解决方案,即使偏移0.5px也不行。只能尽量避免画斜线。)

  4. 图中下面第1、2条,分别是整数坐标和偏移0.5px坐标的5像素宽线。第一条实际效果看着像是6px。

  5. 图中下面第3、4条,分别是整数坐标和偏移0.5px坐标的宽为4像素的线。而这次反而是偏移0.5px的线宽度大了1px。这是为什么呢?下面“canvas画线的原理”会解释其中原因。

  6. 从图中下面的两个矩形(第一个是X,Y坐标均为整数,第二个是X,Y坐标均偏移了0.5)可以看出,垂直或者水平的线都会有这种问题。

canvas画线的原理:以指定坐标为中心向两侧画线(两侧各画宽的一半)。

下面我们看个例子

var dom = document.querySelector("#canvas1");
var ctx = dom.getContext('2d');

ctx.strokeStyle = '#000';

// 正常画线(坐标为整数,线宽为1px),1像素画出的效果像2像素。
ctx.lineWidth = 1;
ctx.moveTo(30, 50);
ctx.lineTo(30, 200);
ctx.stroke();

// 处理之后(坐标偏移0.5像素),线条宽度正常。
ctx.lineWidth = 1;
ctx.moveTo(50.5, 50);
ctx.lineTo(50.5, 200);
ctx.stroke();

效果如下图(在PS中放大后效果)
canvas画线的原理

【实例解析】
1. 指定坐标为30px时,实际是以30px为中心向两边各画一半(0.5px),会画在30px前后的两个像素格子中。又因为像素是最小单位,所以30px前后的两个像素都被画了1px的线,但是颜色要比实际的谈一些。

  1. 而指定坐标为50.5px时,线是以50.5为中心向两边各画一半(0.5px),这样子刚好只占用了一个像素的宽,就实现了1px的宽了。

当线的宽度为非整数时,同样会出现“宽度大1px”的情况

ctx.strokeStyle = '#000';
ctx.lineWidth = 1;

// 默认从整数坐标画起时
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.3;
ctx.moveTo(65, 50);
ctx.lineTo(65, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.8;
ctx.moveTo(70, 50);
ctx.lineTo(70, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.moveTo(75, 50);
ctx.lineTo(75, 120);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.1;
ctx.moveTo(80, 50);
ctx.lineTo(80, 120);
ctx.stroke();

// 坐标偏移0.5px后
ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 1.3;
ctx.moveTo(65.5, 130);
ctx.lineTo(65.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.8;
ctx.moveTo(70.5, 130);
ctx.lineTo(70.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.5;
ctx.moveTo(75.5, 130);
ctx.lineTo(75.5, 200);
ctx.stroke();

ctx.beginPath();
ctx.strokeStyle = '#000';
ctx.lineWidth = 0.1;
ctx.moveTo(80.5, 130);
ctx.lineTo(80.5, 200);
ctx.stroke();

效果如下图(在PS中放大后效果)
canvas画非整数宽的线

上图中,上面几条线是以整数为坐标的线,下面几条是坐标偏移了0.5px的线。

从该例子中看出,即使是非整数宽的线,坐标偏移0.5也能解决这种问题。当宽小于1px时,实际画的线还是1px宽,但是颜色要淡一些,视觉上就也达到了细一些的效果了(请看第一张图中的效果)。

canvas画线问题总结

以上所说的偏移0.5px,其实并不准确。因为上面例子中,坐标都是整数。
更准确的说法应该是:当线宽为偶数时,坐标应指定为整数。否则坐标应指定为整数+0.5px。

下面奉上我总结的最终解决方案

这里以竖线为例,横线同理


// 封装一个画线的方法 function drawLine (ctx, x, y1, y2, width) { // 当线宽为偶数时,坐标应指定为整数。否则坐标应指定为整数+0.5px。 let newx = width % 2 === 0 ? Math.floor(x) : Math.floor(x) + 0.5; ctx.lineWidth = width; ctx.moveTo(newx, y1); ctx.lineTo(newx, y2); } ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 350, 250, 380, 1); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 360, 250, 380, 2); ctx.stroke(); ctx.beginPath(); ctx.strokeStyle = '#000'; drawLine (ctx, 370.4, 250, 380, 1.3); ctx.stroke();

具体效果请看canvas画线条源码中,右下角的三根线。

文章标签: