一张图片引发的探索

avatar
前端开发工程师 @bigo

file

本文首发于:github.com/bigo-fronte… 欢迎关注、转载。

问题起因

    公司的设计小姐姐突然找我说我做的一个图片合成工具透明的区域变成黑色了。 原图转化后的图片

    第一步想法是背景初始化存在问题,导致透明区域黑化。但试了下通过浏览器选中canvas元素下载图片透明区域是生成正常的,那就是在绘制完成后到下载这一步中间发生了问题。检索了一下整个步骤,再绘制完成到下载过程中有

  1. 将canvas转成blob对象
  2. 调用压缩库压缩blob对象
  3. 将blob对象写到本地

    通过浏览器选中canvas元素下载跟执行过程1,3是类似的,所以问题应该是出现再过程2将blob对象压缩过程中的操作导致的黑底,压缩用的库是使用的image-conversion,其中进行压缩的核心代码是 压缩代码     上图是压缩库的核心方法,利用的是canvas转图片时通过对第2个参数设置进行压缩,但仅限于图片格式为 image/jpeg 或 image/webp,因为调用的时候是没有指定图片格式,默认为image/jpeg的格式,所以再进行压缩的过程中,透明区域的颜色会默认为黑色渲染,所以生成的图片在透明区域就会呈现为黑色区域。也就是image-conversion的图片压缩只适用于不存在透明通道的图片,对需要透明通道的图片并不适用。

png图片格式的压缩

    那如何保留透明通道的图片压缩呢,先了解下png图片的一些基础。

    png,全称为流式网络图形格式(Portable Network Graphic Format),是一种位图文件(bitmap file)存储格式。主要有3种格式:png8,png24, png32     png本身是一种数据压缩文件,是由很多种数据块,分为两种类型:关键数据块以及辅助数据块。图片的内容集中在关键数据块上,数据类型如下:

数据块符号数据块名称多数据块位置限制
IHDR文件头数据块第一块
PLTE调色板数据块在IDAT之前
IDAT图像数据块与其他IDAT连续
IEND图像结束数据最后一个数据块

    每个数据块都由以下4个域组成:

名称字节数说明
Length(长度)4字节指定数据块中数据域的长度,其长度不超过(2311)(2^{31}-1)字节
Chunk Type Code(数据块类型码)4字节数据块类型码由ASCII字母(A-Z和a-z)组成
Chunk Data(数据块实际内容可变长度存储按照Chunk Type Code指定的数据
CRC(循环冗余检测)4字节存储用来检测是否有错误的循环冗余码

    IHDR存放图片的基本信息,大小是固定的25字节,IEND是标记PNG文件或者数据流已经结束,并且必须要放在文件的尾部,大小也是固定的12字节。PLTE存储得是定义图像信息得调色板信息,包含1~256个调色板信息,每一个调色板信息由3个字节组成,所包含的最大字节数为780。IDAT存储图片得真正数据,在数据流中存在多个数据块,所以想要对png图片进行压缩得从IDAT入手。 IDAT存在多个数据块,其压缩内容由全部数据块数据内容组成,会先经过过滤处理。

    对图片数据过滤主要是因为大多数情况下,图像的相邻像素点的色值时很相近的,而且很容易呈现线性变化(相邻数据的值是相似或有某种规律变化的)。利用这个特性对图片的数据进行一定程度的压缩,而使用的方式主要是差分编码的方式(对数字数据流,除第一个元素外,将其中各元素都表示为各该元素与其前一元素的差的编码)。 png 的 过滤类型有5种:

    None: 即不做任何过滤。

    Sub(X-A): 记录当前像素与左边第一个像素的差值,左边第一个像素是标准值。

    Up(X-B): 记录当前像素与上边像素的差值,第一行像素数值为标准数值。

    Average(X - (A+B)/2): 记录当前像素与左边像素和上边像素的平均值的差值,第一行按Sub方式进行处理。

    Paeth(X - Pr): 记录代码如下 转化后的图片

    图片数据经过过滤处理后的数据会出现很多重复出现的数据,对处理后的数据再进行 deflate 压缩,就得到IDAT的全部数据块的chunk数据了。

    IDAT本身就是一种压缩数据,那怎么再保证图片的呈现效果的同时对图片进行压缩呢?目前主流是存在着两种压缩方式:有损压缩与无损压缩

  1. 有损压缩:现在主流的png图片指的是png24,可以展示2^24种颜色,颜色相对png8的会更丰富清晰度也会更高,质量也会更高,但图片尺寸大小也会更大。png8最多可以展示2^8种颜色,有alpha透明通道,适合颜色比较单一的图片,图片的尺寸大小也比较小。有损压缩是将图片由png24格式转换成png8格式。目前常用的方式是使用Product Quantization算法将相近的颜色进行合并,将PNG24 压缩成 PNG8,现有的库有tinypng、pngquant、ImageAlpha、pngnq。

  2. 无损压缩:主要对IDAT的chunk数据进行处理,采用的都是基于LZ/Huffman的DEFLATE算法,减少IDAT的chunk数据从而实现无所压缩。一般有损压缩的压缩率会大大高于无损压缩。

解决方案

    生成的图片主要用于banner,启动页相关,对颜色要求较低,故采用有损压缩的方式进行压缩,借助tinypng提供的api接口进行压缩,成功完成设计小姐姐的要求 转化后的图片

欢迎大家留言讨论,祝工作顺利、生活愉快!

我是bigo前端,下期见。