canvas详解(3)-模糊问题

canvas详解(3)-模糊问题

1.模糊的原因

canvas绘图理论上应该是清晰的,但是有些屏幕上看到的确是模糊的,究其原因有3点:

css样式影响

canvas绘图是基于自身的宽高设置,必须明确指定宽高,不能使用百分比。如果出现线条锯齿状原因,一般都是因为,canvascss样式放大了原本的canvas图形导致的。例如:

<canvas id="test" width="200" height="100" style={{width:400,height:200}}></canvas>

css将canvas自身放大了一倍,导致图像不清晰

设备像素比

不同的设备,其物理像素/屏幕宽度是不一样,从而就导致了相同的屏幕宽度下,1px的物理像素有可能不再是1px。而设备像素比的不同是导致图像模糊的主要原因。

为了解决这一问题,我们需要完成如下步骤:

  • 获取设备像素比;
  • 将canvas宽高和图形放大相同的像素比倍数。(即所有的canvas图形都放大设备像素比倍数,包括文字);
  • 通过css将canvas缩小对应的倍数。

将图形整体放大是为了适应当前实际物理像素,这样绘制的图形才是最清晰的,但是因为放大后的图形不是我们想要的,所以必须用css缩小到相应的倍数,才能呈现出正常的图形。

需注意:canvas的scale只是对内部图形进行放大缩小,并不能改变画布大小。即是说放大后部分图形将可能不能显示,因为超出了画布范围。

线条绘制原理

canvas的线条绘制是以当前线条坐标为中心,左右各绘制指定lineWidth一半的宽度lineWidth默认是1px),而由于canvas无法在整个像素宽内只绘制半个像素,所以实际默认绘制的线条是2px的图形,只是因为默认被填充的半像素导致整体视觉颜色变浅了。

例如:

ctx.lineWidth = 1;
ctx.moveTo(50,50)
ctx.lineTo(150,50);
ctx.stroke();

绘制该线条的时候,canvas会在y轴为50的线上下各绘制0.5px,所以需要绘制的区域就是[49.5~50,50~50.5]这2个区域都是半像素区域canvas无法绘制,所以实际绘制的区域变成了[49~50,50~50],因为[49~49.5,50.5~51]是被额外填充区域,所以整体颜色变淡了。

既然找到了问题的原因,那么解决办法就相对好找了,我们只需让绘制的上下2部分都变成整像素的就OK了。

ctx.lineWidth = 1;
ctx.moveTo(50,50.5)
ctx.lineTo(150,50.5);
ctx.stroke();

我们将y轴增加0.5,那么绘制的时候就是以50.5轴线上下绘制0.5px,实际绘制区域变成了[50~50.5,50.5~51]

而50~51正好构成了一整个像素,canvas能够直接绘制,所以绘制的效果就是1px清晰效果。

当然绘制整数倍宽度的线条一般不会出现这种现象

ctx.lineWidth = 2;
ctx.moveTo(50,50)
ctx.lineTo(150,50);
ctx.stroke();

绘制结果为2px清晰线条

2.模糊解决方案

获取设备像素比
 //设备像素比
 getPixelRatio(context) {
        var backingStore = context.backingStorePixelRatio ||
            context.webkitBackingStorePixelRatio ||
            context.mozBackingStorePixelRatio ||
            context.msBackingStorePixelRatio ||
            context.oBackingStorePixelRatio ||
            context.backingStorePixelRatio || 1;
        return (window.devicePixelRatio || 1) / backingStore;
};
放大画布
 const ctx = canvas.getContext('2d');
 let ratio = this.getPixelRatio(ctx);
 let scale = ratio;
 this.setState({ ratio: 1 / ratio });//缓存缩小比例

当然这里还可以根据屏幕大小设置画布的缩放比例,但是无论画布怎么缩放,都必须让画布放大到屏幕的真实像素比,所有的图形、文字都应放大。

canvas提供了scale()方法用于缩放画布图形,但是该方法只是将画布中的图形在当前画布大小下缩放,实际画布还是原来的大小,只是内部图形被缩放了,这根本不能满足我们的业务场景,我们希望的效果是画布和其内部的图形等比缩放。所以我们只能在当前画布宽高下,对所有的图形进行等比重绘,这样就能达到想要的效果了。

将画布缩小到实际大小

因为我们按设备像素比将整个画布放大了,那么我们就需要通过css来还原它的显示大小。通过zoom属性,缩放整个画布。

<canvas id={this.props.canvasId} width={this.props.width} height={this.props.height} 
                style={{ zoom: this.state.ratio}}></canvas>

style中的内容才是重点。网上有很多说在父级进行缩放纯属扯淡。只能在canvas自身上缩放。

当然不用zoom,直接使用css定义width,height为对应比例的宽高效果也是一样的。

例如:

<canvas id={this.props.canvasId} width={this.props.width} height={this.props.height} 
                style={{ width: this.props.width*this.state.ratio,
                       height:this.props.height*this.state.ratio}}></canvas>

完整代码请参考:canvas详解(1)-原理

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值