Web 技术研究所

我一直坚信着,Web 将会成为未来应用程序的主流

Canvas2D的线宽问题

  我觉得这是每个学习Canvas2D的人都会遇到的一个问题(也许是我以小人之心度各位君子之腹了)。我第一次接触Canvas时就遇上了,还在贴吧提了个问,最终自己在谷歌上找到了答案。为什么Canvas2D中绘制出的线条是两像素的呢?其实这个问题涉及了很多东西。
  先把这个问题测试一遍 <canvas id="canvas"></canvas>
<script>
canvas.width=canvas.height=120;
canvas.style.border="1px solid #CCC";
var g=canvas.getContext("2d");
g.lineWidth=1; //线宽1px
g.strokeStyle="#000"; //颜色黑色
g.rect(10,10,100,100);
g.stroke();
</script>

  这个结果的宽度就是两像素的,但是再仔细观察会发现不仅是宽度不对,连颜色都不对。

  原来是100%的黑色,现在只有50%的黑色。宽度变成原来的两倍,而颜色变成原来的一半,这就可以推测出是线条被antialias了。为什么会这样呢?antialias的工作条件是出现单位小于像素的值,可是程序中rect的参数全是整数,而且线条是平行或垂直于坐标轴的,怎么会出现小于像素的值呢?这就涉及到另一个方面了。
  考虑一下CSS中border的两种情况。标准的CSS中,border是在width之外的,也就是说border的宽度不算入width。而在怪异模式中,border是在width中的,border增加不会使整个盒子变大,而是使内容变小。这两种模式应该很清楚吧?那么Canvas2D中,我们调用rect时绘制出的矩形是在外部还是内部呢?
  都不是!实际上rect并没有绘制矩形,而是创建一个逻辑上的矩形,它的位置以像素为单位,可以把它理解为PS中的选区。

  既然它的位置是以像素为单位,我们设置整数像素时,它的边缘就会落在两个像素之间。而选区的边缘线本身是没有宽度的,它是矢量,所以选区本身没有CSS设置边框时候的内部外部问题。这个问题出在了后面的stroke上。stroke实际上是对选区描边,这也可以参考PS中的描边。

  PS中的描边有“位置”这个选项来选择内部外部还是居中。而Canvas2D中调用stroke时就是居中的描边,这是如果描边的宽度为奇数就会有一个像素被拆成两个0.5像素分别放入内部和外部。而0.5像素显然不能正常绘制出来,这时就触发了antialias,把0.5像素变成一像素,并且把颜色降低成原来的一半。这就是我们前面看到的结果。和PS中不同的是,PS中的选区并没有线的概念,它永远是一块区域。而Canvas2D中,像lineTo这样的方法得到的就是线的选区,或者说是路径更确切。另外,PS的描边并没有自动antialias的功能,所以在PS中即使居中描边1像素也只是被放入内部而已。
  要解决Canvas2D中这个问题也没有什么特别的方法,通常是在选区的坐标上加上0.5个像素。但是要注意只有奇数线宽的描边时需要做这个调整。偶数线宽或填充时如果加上这个0.5像素的调整反而会触发antialias。当然有时候有大堆的路径需要绘制,而且都是奇数线宽的,如果每个坐标都加0.5,操作起来就非常麻烦。这时可以使用translate方法来设置操作坐标系时的默认平移变换,后面的操作就可以不用逐个添加0.5像素的调整。不过要注意这个translate方法设置的是相对坐标,看下面例子就能明白。
<canvas id="canvas"></canvas>
<script>
canvas.width=120,canvas.height=52;
canvas.style.border="1px solid #CCC";
var g=canvas.getContext("2d");
//线宽度为1像素
g.lineWidth=1;
//需要调整坐标
g.translate(0.5,0.5);
//绘制两条线
g.beginPath();
g.moveTo(10,10),g.lineTo(110,10);
g.moveTo(10,20),g.lineTo(110,20);
g.stroke();
//绘制结束后把坐标调整回0,0点
g.translate(-0.5,-0.5);
//线宽为两像素
g.lineWidth=2;
//不需要调整0.5像素
//绘制两条线
g.beginPath();
g.moveTo(10,30),g.lineTo(110,30);
g.moveTo(10,40),g.lineTo(110,40);
g.stroke();
</script>

网名:
54.144.24.*
电子邮箱:
仅用于接收通知
提交 悄悄的告诉你,Ctrl+Enter 可以提交哦
神奇海螺
[查看全部主题]
各类Web技术问题讨论区
发起新主题
本模块采用即时聊天邮件通知的模式
让钛合金F5成为历史吧!
次碳酸钴的技术博客,文章原创,转载请保留原文链接 ^_^