html canvas 绘制1px 出现模糊的原因及解决方法

h5 canvas绘制一像素曲线时,可能导致2px曲线的结果。本质是画笔线宽不能被2整除,导致像素不能正好填充,有不满1px像素的绘制区域,从而导致模糊、多出1px的问题。

请参考mdn上关于画笔线宽一节的描述:
https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors


1px变2px的成因分析

这是由于绘制时,将画笔中线对齐坐标轴线,必然有一半线宽在坐标轴线之外,这一半的线宽,如果不是整数,会被自动加满,使之正好对齐一个像素点,由于本身是不满这1个像素的,所以用了更浅的颜色来表示(改变透明度),导致模糊。(因为1像素就是最小单位了,0.x的像素不能显示)

当绘制方向上的坐标是整数时

  • 若线宽为奇数,那么无论如何,线条的矩形区域都坐标都多出0.5像素,于是补满,整体多出了1像素。
  • 若线宽为偶数,那么无论如何,画笔中线总是对齐坐标的,线条宽度不会产生偏差。
  • 若当线宽为小数,那么无论如何,线条的矩形区域都坐标都存在小数,于是补满。整体也会多出1像素。

如:
当画笔线宽为1px,那么绘制直线L:(0,0),(100,0)时,画笔对齐 y 坐标 = 0。由于线宽的存在,实际绘制线条有一半线宽在y的负半轴。导致不可见。
那么线条矩形区域为:

Rect(left:0,top:-0.5,right:100;bottom:0.5)

0.5像素不可见,加满1像素,实际线条区域变为:

Rect(left:0,top:-1,right:100;bottom:1)

就变2px那么大了。

解决这个问题,我们只需要将线条的y坐标偏移0.5就行:
L:(0,0.5),(100,0.5)
当画笔中线对齐y坐标=0.5,时,线条矩形区域没有多余小数:

Rect(left:0,top:0,right:100,1)

这样,自然就是真的1px线条了。

综上,线宽为偶数不会产生误差,而线宽为小数,本来就无法准确绘制,不用处理了。

所以绘制时,我们只需要修正线宽为奇数的情况就行。甚至,只修复线宽1px的情况就可以,因为当线宽很大时,肉眼感知不到啊。

var fix1px = (cxt.lineWidth % 2 === 0) ?0: 0.5;

纵向线条,x坐标偏移:
x = x+fix1px ;

横向线条,y坐标偏移:
y = y+fix1px ;

有些场景下,或者直接偏移一半线宽。

1px线条变2px线条图示:

这里写图片描述

不满1像素时,像素加满1px,颜色透明度调整,导致线条变模糊图示:

这里写图片描述

此图是由画布保存下来的png图片,加上一个红色背景,放大到像素级的结果。

可以看到,内外边加满1px,且变得半透明。

更多清楚的描述在mdn上:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7t5YgzgB-1595341557917)(https://developer.mozilla.org/@api/deki/files/601/=Canvas-grid.png)]

链接:https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API/Tutorial/Applying_styles_and_colors

手动找到线宽一节。

实际测试

对比1px矩形,线段、3px矩形,3px矩形,可以发现1px、3px绘制时可能产生模糊、像素+1的问题。


<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=1024, height=768,initial-scale=1.0,maximum-scale=1.0,user-scalable=0">
    <title>线宽引起的误差</title>
</head>
<body>
<canvas width="600" height="600" style="background-color: #fafafa"></canvas>
<script>
    var canvas = document.getElementsByTagName('canvas')[0];
    var cxt = canvas.getContext('2d');

    cxt.font = '14px 宋体'
    cxt.lineWidth = 2;
    cxt.beginPath();
    cxt.moveTo(10,10)
    cxt.lineTo(100,10);
    cxt.closePath();
    cxt.stroke();
    cxt.fillText('2px,外边框也是2px,但因为线宽的一半在画布之外,被吃掉了,仅有1px可见',110,16);

    cxt.lineWidth = 1;
    cxt.strokeRect(0,0,canvas.width,canvas.height);//吃掉0.5px,1px已经是最小,采用颜色变浅来表示

    cxt.strokeRect(30,30,100,100);//产生0.5像素溢出,追加满1px,导致边框==2px
    cxt.fillText('边框==2px,且颜色变浅,模糊',50,65)

    cxt.strokeRect(150.5,150.5,100,100)//对齐线宽中线,边框===1px
    cxt.fillText('边框==1px,颜色正常',160,200)

    cxt.lineWidth = 2;
    cxt.strokeRect(300,300,100,100)
    cxt.fillText('正常的2px边框',305,350)

    cxt.lineWidth = 3;
    cxt.strokeRect(450,450,100,100)
    cxt.fillText('3px变4px,略微模糊',455,500)

    cxt.lineWidth = 4;
    cxt.strokeRect(300,450,100,100)
    cxt.fillText('正常的4px',305,500)


    cxt.lineWidth = 9;
    cxt.strokeRect(20,450,100,100)
    cxt.fillText('9px变10px,  只有内外边有一点点模糊',35,500)

    cxt.lineWidth = 10;
    cxt.strokeRect(20.5,300.5,100,100)
    cxt.fillText('正常10px',35,355)

</script>
</body>
</html>

结果预览:
这里写图片描述

可以看到3px变4px的矩形、9px变10px的矩形只有略微模糊,这是因为中间2像素是被实心填满颜色的,只有内外边各自补足到1px,所以比起1px变成2px来说,它模糊程度没有那么大。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值