Canvas在移动端绘制模糊的缘由与解决办法

因为一些移动端的兼容性缘由,咱们某个项目须要前端将pdf转换成在移动端页面可直接观看的界面。为了方便解决,咱们采用了pdf.js这个插件,该插件能够将pdf转换成canvas绘制在页面上。不过,在测试过程当中却发现,在移动端的浏览器上,绘制的内容展现十分模糊(以下图),通过分析以后发现是因为移动端高清屏幕引发的。 在解决问题以后以文字方式记述缘由和探究结果css

图片描述

在解释问题以前,首先须要了解一些移动端显示和cavans的小知识,方便后面探究。若是想直接看结果的话看能够拉到最后。前端

关于屏幕的一些基础知识

物理像素(DP)
物理像素也称设备像素,咱们常听到的手机的分辨率及为物理像素,好比 iPhone 7的物理分辨率为750 * 1334。屏幕是由像素点组成的,也就是说屏幕的水平方向有750的像素点,垂直方向上有1334个像素点canvas

设备独立像素(DIP)
也称为逻辑像素,好比Iphone4和Iphone3GS的尺寸都是3.5寸,iphone4的物理分辨率是640 980,而3gs只有320 480,假如咱们按照真实布局取绘制一个320px宽度的图像时,在iphone4上只有一半有内容,剩下的一半则是一片空白,为了不这种问题,咱们引入了逻辑像素,将两种手机的逻辑像素都设置为320px,方便绘制浏览器

设备像素比(DPR)
上面的设备独立像素说究竟是为了方便计算,咱们统一了设备的逻辑像素,可是每一个逻辑像素所表明的物理像素却不是肯定的,为了肯定在未缩放状况下,物理像素和逻辑像素的关系,咱们引入了设备像素比(DPR)这个概念微信

设备像素比 = 设备像素 / 逻辑像素
DPR = DP / DIP

上面说了不少理论,下面附个图解释一下iphone

图片描述

从上面的图能够看出,在一样大小的逻辑像素下,高清屏所具备的物理像素更多。普通屏幕下,1个逻辑像素对应1个物理像素,而在dpr = 2的高清屏幕下,1个逻辑像素由4个物理像素组成。这也是为何高清屏更加细腻的缘由。布局

关于canvas的一些基础知识

canvas绘制的是位图
这是一个全部了解过canvas的人都应该知道的知识点,也是接下来咱们将要分析问题的核心。
关于位图的解释咱们放在后面,如今咱们只要知道canvas绘制的图像是位图。测试

canvas的width和height属性
canvas的width和height属性是初学者很是容易搞错的内容。这两个属性常常会与css中的width和height属性混淆。
好比咱们有以下代码(1):spa

<canvas width="600" height="300" style="width: 300px; height: 150px"></canvas>
  • style中的width和height分别表明canvas这个元素在界面上所占据的宽高,即样式上的宽高
  • attribute中的width和height则表明canvas实际像素的宽高

若是还没法理解的话,能够想象成如下的代码(2):插件

<!-- logo.png的像素为600 * 300  -->
<img  style="width: 300px; height: 150px" src="logo.png" />

canvas默认的width和height是300 * 150,对其设置了css以后,canvas会根据设置css宽高进行缩放(注意不是裁剪),这一点和img标签同样
上述代码(1)其实还能够再换一种更通俗的解释方式,就是1个逻辑像素实际上由2个canvas像素填充。

模糊缘由的初步探讨

上面是对所需基础知识的一些简介,下面开始正式进行探究。

首先咱们提到使用canvas绘制图像的是位图,而咱们日常用的jpg,png也是位图。那么什么是位图?

位图又叫像素图或栅格图,它是经过记录图像中每个点的颜色、深度等信息来存储和显示图像。具象一点讲,能够将位图想象成一个巨大的拼图,这个拼图有无数的拼块,每一个拼块表明了一个纯色的像素点。理论上,1个位图像素对应着1个物理像素。但假如说你使用了高清屏,好比苹果的retina屏去查看一幅图画,又会是什么样子呢?

假设咱们有以下代码,该代码将展现在iphone4的retina屏上:

<canvas width="320" height="150" style="width: 320px; height: 150px"></canvas>

iphone4自己的物理像素为640 980,而设备独立像素为320 480,这表明着1个css像素实际由4个物理像素构成,canvas的像素为320 150,其css像素为320 150,则表明1个css像素将会由1个canvas元素构成,这样进行换算,在retina屏幕下,1个canvas像素(或者说是1个位图像素)将会填充4个物理像素,因为单个位图像素不能够再进一步分割,因此只能就近取色,从而致使图片模糊。

若是还有疑惑的话,如下的图片能够说明位图在retina屏幕下是如何填充的:

图片描述

上图中左侧的是在普通屏幕下的显示规则,能够看出有4个位图像素点,而右侧的高清屏幕下则有16个像素点。因为像素点不可切割的缘由,颜色产生了改变。

可是还有一点没有解释清楚,就是为何图片会就近取色而不是直接取原值,这也是致使模糊的幕后黑手。

幕后黑手---平滑处理技术

下面是个人某位大佬同窗帮我解释的,刚才咱们说了每一个位图元素实际上一个纯色的像素点。如今假设咱们须要在一个css大小为4px * 4px,dpr为1普通屏幕上绘制一个数字“0”,那么咱们绘制的样子应该以下图,其中1表明黑色像素点,0表明白色像素点。

图片描述

能够看出在dpr比较小的状况下,咱们的“0”这个图案还比较明显,如今假如咱们css大小不变,可是改为在retina屏幕下绘制图像,效果又会变成什么样呢?

图片描述

咱们已知在retina屏幕下,一个css像素表明4个物理像素,假如咱们不作任何处理,直接按照上面矩阵排列,将矩阵扩大的话,会发如今retina屏幕下,咱们的图案锯齿感很是明显,图像明显缺少了一丝顺化。

假如咱们对图像稍做改变,改为下图这样

图片描述

图像感受瞬间柔和了,可是本来应该4个0充斥的地方变成了3个1加上1个0。这其实就是所谓的图像平滑处理,为了解决锯齿感受,将本来的颜色改变,在充斥着较多颜色的图片上,为了更天然,图片的链接处变成了近似的颜色,这也解释了为何上面填充颜色的时候不是使用本色而是使用近似色。

缘由总结

经过了上述的解释,如今咱们来总结如下结论,在移动端盛行,高清屏基本上已经普及的如今,1px的css像素实际上表明了4个甚至更多的物理像素。可是因为咱们的代码问题,咱们1px的css像素和1个canvas像素画上了等号,也就致使了1个canvas像素实际须要填充4个甚至更多物理像素,为了保证图像平滑处理,在填充剩余的物理像素时采用了原先颜色的近似值,致使了图像的模糊。

解决思路

了解了问题出现的缘由,解决问题就很容易,解决该问题最重要的一点是让1个canvas像素和一个物理像素挂等号

高版本的浏览器的window对象下都挂着一个devicePixelRatio属性,该属性就是上面所说的dpr,

在canvas元素css宽高肯定的状况下,咱们能够这么作

let canvas = document.getElementById('canvas');
let ctx = canvas.getContext('2d');
let dpr = window.devicePixelRatio; // 假设dpr为2
// 获取css的宽高
let { width: cssWidth, height: cssHeight } = canvas.getBoundingClientRect();
// 根据dpr,扩大canvas画布的像素,使1个canvas像素和1个物理像素相等
canvas.width = dpr * cssWidth;
canvas.height = dpr * cssHeight;
// 因为画布扩大,canvas的坐标系也跟着扩大,若是按照原先的坐标系绘图内容会缩小
// 因此须要将绘制比例放大
ctx.scale(dpr,dpr);

经验总结

不少时候,咱们发现了问题,不能只集中于问题的解决,而是应该深刻去了解问题发生的缘由,这样才能更好的在这行走下去。

做者:carbrokers


本文首发微信公众号:qianduanshe

图片描述

欢迎扫描二维码关注公众号,天天都给你推送新鲜的前端技术文章

相关文章
相关标签/搜索