浅析HTML5 Canvas的几种中文字体缩小方案

Canvas留下的坑远比我想象中的要多, 最近碰上一个很少见的需求——在Canvas上绘制大小小于12px的文字.

如果只是简单的去设定context的font属性, 来绘制点阵字体, 比如7px的宋体, 出来的效果是这样的(Chrome):

正如你看到的, 这显然不是7px, 如果你再次访问font属性, 就会发现font被强制改回了12px大小. 我们知道, Chrome早在2年前就限制了中文字体的最小尺寸为12px, 但令我没想到的是, Canvas竟然也享受同等待遇.

在Photoshop CS6里, 7px的宋体是这样的:

虽然从辨识度的角度来说很差, 但实际上这才是我想要的结果.

Google查了一圈回来, 也只能找到像"-webkit-text-size-adjust"(文字字体大小限制, 该CSS属性在Chrome 27版本中已被取消)、"image-rendering"(调整浏览器渲染缩放图像质量的CSS属性, 与文字不相干)、"Context.imageSmoothingEnabled"(决定进行Context.drawImage等操作时是否启用抗锯齿的属性)之类的东西, 然而它们都不能解决我们的需求.

我尝试的第一种方案是使用drawImage把文字作为图像绘制, 缩放所使用的算法取决于浏览器, 也就是说会受到imageSmoothingEnabled的影响.

默认情况下, imageSmoothingEnabled的值为true, 绘制出的图像会受到抗锯齿算法的影响:

文字整体不可避免的有点发虚, 但辨识度得到了一定程度的提升.

这是imageSmoothingEnabled值为false时, 绘制出的图像:

事实上比较接近PhotoShop的结果, 但由于缩放算法的关系, 文字整体没有很好的保留下来. 同时, 这种缩放结果不受CSS属性"image-rendering"的影响, 对于缩放使用的算法, 开发者无法控制.

之后我自己实现了最近邻插值算法(Nearest-neighbor interpolation)对图像进行缩放, 效果如下:

由于是使用fillRect进行像素的填充, 所以在缩放倍数低于1时, 绘制出的图像会明显发虚.

改用putImageData合成图像后, 可以得到只有黑白两种颜色的二值图像:

文字整体丢失得很严重, 不知道是不是我在这个算法的理解上有偏差. 看来最简单的最近邻插值是没法完成我们的需求了(PS: 最近邻插值法在我尝试放大文字时表现很好).

于是我又尝试实现了复杂度稍高一点的双线性插值算法(Bilinear interpolation):

效果算是很不错的, 但是由于使用了fillRect, 所以颜色不对.

用putImageData合成图像的效果如下:

这个算法的效果和imageSmoothingEnabled=true时使用drawImage得出的结果十分接近, 除了边缘由于算法的问题虚化了一部分像素外, 整体是要好于浏览器的缩放的.

接下来打算再实现Photoshop CS6缩小文字使用(个人猜测)的双三次插值算法(Bicubic interpolation), 应该会得到比较满意的效果.

文中的相关代码实现, 将在之后的几篇文章中放出.