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

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

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

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

效果如下图(在PS中放大后效果)

【事例解析】

  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画线的原理:以指定坐标为中心向两侧画线(两侧各画宽的一半)。

下面我们看个例子

javascript 复制代码
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中放大后效果)

【实例解析】

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

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

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

javascript 复制代码
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中放大后效果)

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

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

canvas画线问题总结

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

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

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

javascript 复制代码
// 封装一个画线的方法
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画线条源码中,右下角的三根线。

相关推荐
拉不动的猪14 小时前
浏览器之内置四大多线程API
前端·javascript·浏览器
林太白14 小时前
5大排序算法&2大搜索&4大算法思想
前端
摇滚侠15 小时前
浏览器的打印功能,如果通过HTML5,控制样式
前端·html·html5
喵喵侠w15 小时前
uni-app微信小程序相机组件二次拍照白屏问题的排查与解决
前端·数码相机·微信小程序·小程序·uni-app
超大只番薯15 小时前
在Next.js中实现页面级别KeepAlive
前端
快递鸟15 小时前
第三方物流接口优选:快递鸟物流 API,打破单一快递对接壁垒
前端
Mapmost15 小时前
【高斯泼溅】从一张好照片开始:Mapmost 3DGS建模之图像采集指南
前端
李少兄15 小时前
解决 Chrome 下载 `.crx` 文件被自动删除及“无法安装扩展程序,因为它使用了不受支持的清单版本”问题
前端·chrome
孟祥_成都15 小时前
最好的组件库教程又回来了,升级为 headless 组件库!
前端·面试·架构
Man16 小时前
当我们执行 npm run xxx 的时候实际执行逻辑和流程
前端·javascript·前端框架