WebP 无损比特流规范

Jyrki Alakuijala 博士,Google, Inc.2023 年 3 月 9 日

摘要

WebP 无损是一种对 ARGB 图片进行无损压缩的图片格式。通过 无损格式精确地存储和恢复像素值,包括 颜色值。适用于序列的通用算法 数据压缩 (LZ77)、前缀编码和颜色缓存用于 压缩大量数据。解码速度比 PNG 更快 而且压缩率要比使用这两种技术所能达到的 最新的 PNG 格式。

1 简介

本文档介绍了 WebP 无损图像的压缩数据表示法 图片。本文档旨在作为 WebP 无损编码器的详细参考, 解码器实现。

在本文档中,我们广泛使用 C 编程语言语法来描述 比特流,并假设存在用于读取比特的函数, ReadBits(n)。按照流的自然顺序读取字节,其中包含 并且每个字节的位按最小有效位优先顺序读取。时间 有多个位被同时读取时,整数由 原始顺序显示原始数据。所返回数据的最高有效位 整数也是原始数据的最高有效位。因此, 对账单

b = ReadBits(2);

等效于以下两个语句:

b = ReadBits(1);
b |= ReadBits(1) << 1;

我们假设每个颜色分量(即 alpha、红色、蓝色和绿色) 以 8 位字节表示。我们将相应的类型定义为 uint8。答 整个 ARGB 像素由名为 uint32 的类型表示,该类型是无符号的 由 32 位组成的整数。在显示 转换,这些值将编码为以下位:alpha(位) 31..24,位 23..16 为红色,位 15..8 为绿色,位 7..0 为蓝色;不过, 该格式的实现方式可以在内部自由使用其他表示法。

一般来说,WebP 无损图像包含标头数据、转换信息以及 真实图像数据。标题包含图片的宽度和高度。WebP 无损图像经过四种不同类型的转换, 。比特流中的转换信息包含数据 应用相应的反转换所需的操作。

2 个术语

ARGB
一个像素值,由 Alpha、红色、绿色和蓝色值组成。
ARGB 图片
包含 ARGB 像素的二维数组。
颜色缓存
一个采用哈希寻址的小型数组,用于存储最近使用的颜色,以便 使用较短的代码就能轻松召回。
颜色索引图片
可使用小整数编入索引的一维颜色图片 (在 WebP 无损中,最大为 256 个字符)。
颜色转换图片
一张二维子分辨率图像,包含有关 颜色成分。
距离地图
更改 LZ77 距离,使 二维邻近性。
熵图片
一张二维子分辨率图片,指示应该使用哪种熵编码方式 在图片中的相应方形中使用,也就是说,每个像素都是一个 前缀代码。
LZ77
一种基于字典的滑动窗口压缩算法, 或者将这些符号描述为一连串过去的符号。
元前缀代码
一个小整数(最多 16 位),用于将元前缀中的元素编入索引 表格。
预测器图片
一个二维子分辨率图像,指示哪个空间预测器是 用于图片中的特定方形。
前缀代码
一种经典的熵编码方式,使用的位数较少 获取更频繁的代码。
前缀编码
一种对较大整数进行熵编码的方法,即对整数的几位进行编码 并使用熵码将剩余位编码为原始数据。这样, 使熵代码的说明保持相对较小, 符号的范围很大。
扫描行顺序
像素的处理顺序(从左到右以及从上到下),从 与左上角像素之间的距离完成一行后,从 左列。

3 RIFF 标题

标题的开头包含 RIFF 容器。其中包括 以下 21 个字节:

  1. 字符串“RIFF”。
  2. 分块长度的 32 位小端字节序值,即整个大小 数据块的 RIFF 标头进行控制。通常,该值等于 有效负载大小(文件大小减去 8 个字节:对于“RIFF”为 4 个字节 标识符和用于存储值本身的 4 个字节)。
  3. 字符串“WEBP”(RIFF 容器名称)。
  4. 字符串 'VP8L'(FourCC 适用于无损编码图片数据)。
  5. 一个采用小端字节序的 32 位值,表示 无损流。
  6. 1 字节签名 0x2f。

比特流的前 28 位指定图片的宽度和高度。 宽度和高度被解码为 14 位整数,如下所示:

int image_width = ReadBits(14) + 1;
int image_height = ReadBits(14) + 1;

图片宽度和高度的 14 位精度限制了 WebP 无损图像,分辨率为 16384 倍 16384 像素。

alpha_is_used 位只是一个提示,应该不会影响解码。它应该 如果图片中的所有 alpha 值为 255,则设为 0,否则设为 1。

int alpha_is_used = ReadBits(1);

version_number 是 3 位代码,必须设为 0。任何其他值都应 将被视为错误。

int version_number = ReadBits(3);

4 个转换

这些转换是对图片数据的可逆操作,可减少 通过对空间和颜色相关性进行建模来预测剩余的符号熵。他们 可以增加最终压缩的密度。

一张图片可以进行四种类型的转换。1 位表示 转换的存在。每个转换只能使用一次。通过 转换仅用于主级 ARGB 图像;超分辨率图像 (颜色转换图像、熵图像和预测器图像)都没有转换, 甚至包括指示转换结束的 0 位。

通常,编码器会使用这些转换来降低 Shannon 熵 生成大量图像。此外,转换数据也可以基于熵来确定 最小化。

while (ReadBits(1)) {  // Transform present.
  // Decode transform type.
  enum TransformType transform_type = ReadBits(2);
  // Decode transform data.
  ...
}

// Decode actual image data (Section 5).

如果存在转换,则后两位指定转换类型。 转换有四种类型。

enum TransformType {
  PREDICTOR_TRANSFORM             = 0,
  COLOR_TRANSFORM                 = 1,
  SUBTRACT_GREEN_TRANSFORM        = 2,
  COLOR_INDEXING_TRANSFORM        = 3,
};

转换类型后跟转换数据。转换数据包含 应用逆向转换所需的信息,并依赖于 转换类型。逆向转换以相反的顺序应用, 系统会从比特流读取它们,也就是说,最后一个比特流先读取。

接下来,我们描述不同类型的转换数据。

4.1 Predictor 转换

预测器转换可用于减少熵,方法是利用 相邻像素往往是相关的。在预测器转换中, 当前像素值是根据已解码的像素预测的(在扫描线上 顺序),并且只对残差值(实际值 - 预测值)进行编码。绿色的 组件定义了 14 个预测项中的哪一个用于 ARGB 图像的特定块。预测模式决定了 预测使用。我们将图片分成正方形,将所有像素分成 使用相同的预测模式。

预测数据的前 3 位定义区块宽度和高度(以数字表示) 位。

int size_bits = ReadBits(3) + 2;
int block_width = (1 << size_bits);
int block_height = (1 << size_bits);
#define DIV_ROUND_UP(num, den) (((num) + (den) - 1) / (den))
int transform_width = DIV_ROUND_UP(image_width, 1 << size_bits);

转换数据包含图片每个块的预测模式。它 是次分辨率图片,其中像素的绿色部分定义了 这 14 个预测项用于其中的所有 block_width * block_height 像素 ARGB 图像的特定块。此次分辨率图片是使用 与第 5 章中介绍的方法相同。

块列的数量 transform_width 用于二维 编入索引。对于像素 (x, y),可以计算相应的滤波器块 地址提交者:

int block_index = (y >> size_bits) * transform_width +
                  (x >> size_bits);

预测模式有 14 种。在每种预测模式下,当前的 像素值是根据一个或多个相邻像素预测的,这些像素的值 已知数据。

我们选择当前像素 (P) 的相邻像素(TL、T、TR 和 L), 如下:

O    O    O    O    O    O    O    O    O    O    O
O    O    O    O    O    O    O    O    O    O    O
O    O    O    O    TL   T    TR   O    O    O    O
O    O    O    O    L    P    X    X    X    X    X
X    X    X    X    X    X    X    X    X    X    X
X    X    X    X    X    X    X    X    X    X    X

其中 TL 表示左上角,T 表示顶部,TR 表示右上角,L 表示左侧。在 预测 P 值的时间,所有 O、TL、T、TR 和 L 像素都已 且 P 像素和所有 X 像素是未知的。

根据前面的相邻像素,不同的预测模式为 定义如下。

模式 当前像素每个通道的预测值
0 0xff000000(表示 ARGB 中的纯黑色)
1 L
2 T
3 土耳其
4 团队负责人 (TL)
5 平均值 2(Average2(L, TR), T)
6 平均值 2(L、TL)
7 平均值 2(L, T)
8 平均值 2(TL, T)
9 平均值 2(T, TR)
10 平均值 2(Average2(L, TL), Average2(T, TR))
11 选择(L、T、TL)
12 ClampAddSubtractFull(L, T, TL)
13 ClampAddSubtractHalf(Average2(L, T), TL)

对于每个 ARGB 组成部分,Average2 的定义如下:

uint8 Average2(uint8 a, uint8 b) {
  return (a + b) / 2;
}

“Select 预测器”的定义如下:

uint32 Select(uint32 L, uint32 T, uint32 TL) {
  // L = left pixel, T = top pixel, TL = top-left pixel.

  // ARGB component estimates for prediction.
  int pAlpha = ALPHA(L) + ALPHA(T) - ALPHA(TL);
  int pRed = RED(L) + RED(T) - RED(TL);
  int pGreen = GREEN(L) + GREEN(T) - GREEN(TL);
  int pBlue = BLUE(L) + BLUE(T) - BLUE(TL);

  // Manhattan distances to estimates for left and top pixels.
  int pL = abs(pAlpha - ALPHA(L)) + abs(pRed - RED(L)) +
           abs(pGreen - GREEN(L)) + abs(pBlue - BLUE(L));
  int pT = abs(pAlpha - ALPHA(T)) + abs(pRed - RED(T)) +
           abs(pGreen - GREEN(T)) + abs(pBlue - BLUE(T));

  // Return either left or top, the one closer to the prediction.
  if (pL < pT) {
    return L;
  } else {
    return T;
  }
}

函数 ClampAddSubtractFullClampAddSubtractHalf 会执行, 如下所示:

// Clamp the input value between 0 and 255.
int Clamp(int a) {
  return (a < 0) ? 0 : (a > 255) ? 255 : a;
}
int ClampAddSubtractFull(int a, int b, int c) {
  return Clamp(a + b - c);
}
int ClampAddSubtractHalf(int a, int b) {
  return Clamp(a + (a - b) / 2);
}

部分边框像素有特殊的处理规则。如果有 无论这些像素采用何种模式 [0..13], 图片最左上方像素的预测值为 0xff000000, 顶行上的像素为 L 像素,最左列的所有像素均为 T 级像素。

寻址最右列中像素的 TR 像素 非同凡响。最右列中的像素是使用模式预测的 [0..13],就像像素不在边框上,而是位于 当前像素所在的行将用作 TR 像素。

最终的像素值是通过将预测值的每个通道相加而得到的 编码残差值。

void PredictorTransformOutput(uint32 residual, uint32 pred,
                              uint8* alpha, uint8* red,
                              uint8* green, uint8* blue) {
  *alpha = ALPHA(residual) + ALPHA(pred);
  *red = RED(residual) + RED(pred);
  *green = GREEN(residual) + GREEN(pred);
  *blue = BLUE(residual) + BLUE(pred);
}

4.2 颜色转换

颜色转换的目标是修饰每个颜色的 R、G 和 B 值。 Pixel。颜色转换将绿色 (G) 值保持原样, 根据绿色值转换红色 (R) 值,根据绿色值转换蓝色 (B) 值 先对绿色值进行标记,然后再对红色值进行标记。

与预测器转换一样,首先将图像分成 块,并且对块中的所有像素使用相同的转换模式。对于 每个块均有三种类型的颜色转换元素。

typedef struct {
  uint8 green_to_red;
  uint8 green_to_blue;
  uint8 red_to_blue;
} ColorTransformElement;

实际的颜色转换是通过定义颜色转换增量来完成的。通过 颜色转换增量取决于 ColorTransformElement, 特定块中的所有像素。在预测值期间,系统会 颜色转换。然后,反向颜色转换仅添加这些增量。

颜色转换函数定义如下:

void ColorTransform(uint8 red, uint8 blue, uint8 green,
                    ColorTransformElement *trans,
                    uint8 *new_red, uint8 *new_blue) {
  // Transformed values of red and blue components
  int tmp_red = red;
  int tmp_blue = blue;

  // Applying the transform is just subtracting the transform deltas
  tmp_red  -= ColorTransformDelta(trans->green_to_red,  green);
  tmp_blue -= ColorTransformDelta(trans->green_to_blue, green);
  tmp_blue -= ColorTransformDelta(trans->red_to_blue, red);

  *new_red = tmp_red & 0xff;
  *new_blue = tmp_blue & 0xff;
}

ColorTransformDelta 使用一个带符号的 8 位整数计算得出,该整数表示 3.5 定点数和 8 位 RGB 颜色通道 (c) [-128..127] 其定义如下:

int8 ColorTransformDelta(int8 t, int8 c) {
  return (t * c) >> 5;
}

从 8 位无符号表示法 (uint8) 到 8 位有符号的转换 在调用 ColorTransformDelta() 之前需要一 (int8)。带符号的值 应解读为 8 位二补码(即 uint8 范围) [128..255] 映射到其转换后的 int8 值的 [-128..-1] 范围)。

应使用更高的精度(至少为 16 位)进行乘法运算 精确率)。移位操作的符号扩展属性无关紧要 此处;结果中只使用最低 8 位,而符号 分位移位和无符号移位彼此一致。

现在,我们介绍颜色转换数据的内容,以便解码 逆向颜色转换并恢复原始的红色和蓝色值。通过 颜色转换数据的前 3 位包含 图片块的位数,就像预测器转换一样:

int size_bits = ReadBits(3) + 2;
int block_width = 1 << size_bits;
int block_height = 1 << size_bits;

颜色转换数据的其余部分包含 ColorTransformElement 分别对应映像的每个块。每个 ColorTransformElement 'cte' 被视为子分辨率图片中的像素 其 Alpha 分量为 255,红色分量为 cte.red_to_blue,绿色分量 组件为 cte.green_to_blue,蓝色组件为 cte.green_to_red

解码期间,系统会解码 ColorTransformElement 个块实例, 会对像素的 ARGB 值应用反向颜色转换。如 逆向颜色转换只是 ColorTransformElement 值映射到红色和蓝色通道。Alpha 和绿色 它们会保持不变

void InverseTransform(uint8 red, uint8 green, uint8 blue,
                      ColorTransformElement *trans,
                      uint8 *new_red, uint8 *new_blue) {
  // Transformed values of red and blue components
  int tmp_red = red;
  int tmp_blue = blue;

  // Applying the inverse transform is just adding the
  // color transform deltas
  tmp_red  += ColorTransformDelta(trans->green_to_red, green);
  tmp_blue += ColorTransformDelta(trans->green_to_blue, green);
  tmp_blue +=
      ColorTransformDelta(trans->red_to_blue, tmp_red & 0xff);

  *new_red = tmp_red & 0xff;
  *new_blue = tmp_blue & 0xff;
}

4.3 减绿色转换

减绿色转换会从 的红色和蓝色值中减去绿色值, 每个像素。如果存在此转换,则解码器需要将绿色 设置为红色和蓝色值。没有与此内容相关联的数据 转换。解码器按如下方式应用反向转换:

void AddGreenToBlueAndRed(uint8 green, uint8 *red, uint8 *blue) {
  *red  = (*red  + green) & 0xff;
  *blue = (*blue + green) & 0xff;
}

此转换是多余的,因为它可以使用颜色转换进行建模, 由于这里没有额外的数据,因此减去绿色转换 使用比全面颜色转换更少的位数进行编码。

4.4 颜色索引转换

如果没有很多唯一像素值,那么创建 颜色索引数组,并使用数组的索引替换像素值。颜色 Indexing 转换就可以做到这一点。(在 WebP 无损的情况下, 具体而言,不要将其称为 Palette 转换,因为 WebP 无损编码中存在动态概念:颜色缓存。)

颜色索引转换会检查 图片。如果该数字低于阈值 (256),则会创建一个由这些数字组成的数组 ARGB 值,之后会将像素值替换为 对应的索引:像素的绿色通道替换为 所有 alpha 值均设为 255,所有红色和蓝色值均设为 0。

转换数据包含颜色表大小和颜色中的条目 表格。解码器按如下方式读取颜色索引转换数据:

// 8-bit value for the color table size
int color_table_size = ReadBits(8) + 1;

颜色表使用图片存储格式本身进行存储。颜色表 无需 RIFF 标头、图片大小和 会进行转换,并假设高度为 1 像素,宽度为 color_table_size。 颜色表始终进行减码编码以减少图片熵。增量 的调色板颜色包含的熵通常远远小于 这样可以为较小的图片节省大量内存。在解码阶段, 颜色表中的每种最终颜色都可以通过将前一种颜色相加 根据每个 ARGB 成分单独分配颜色分量值,并存储 结果的有效 8 位。

图片的反向转换只是将像素值( 是颜色表的索引)。索引编制 根据 ARGB 颜色的绿色分量确定。

// Inverse transform
argb = color_table[GREEN(argb)];

如果索引大于或等于 color_table_size,则使用 argb 颜色值 应设置为 0x00000000(透明黑色)。

如果颜色表较小(等于或少于 16 种颜色),则会发生数个像素 捆绑到单个像素中像素捆绑包中包含几个(2 个、4 个或 8 个) 像素转换为单个像素,从而可分别缩小图片宽度。像素 通过捆绑,可以对数据进行更高效的联合分布熵编码, 相邻像素并给模型提供一些类似于算术编码的好处, 熵代码,但只能在唯一值数不超过 16 个时使用。

color_table_size 指定组合的像素数:

int width_bits;
if (color_table_size <= 2) {
  width_bits = 3;
} else if (color_table_size <= 4) {
  width_bits = 2;
} else if (color_table_size <= 16) {
  width_bits = 1;
} else {
  width_bits = 0;
}

width_bits 的值为 0、1、2 或 3。值 0 表示没有像素 图像捆绑。值为 1 表示两个像素 每个像素的范围是 [0..15]。值为 2 表示 这四个像素合并在一起,每个像素的范围为 [0..3]。值为 3 表示合并了 8 个像素,每个像素的范围为 [0..1], 也就是二进制值。

这些值会打包到绿色组件中,如下所示:

  • width_bits = 1:对于每个 x 值,其中 x ☰ 0 (mod 2),则会显示绿色的 位于 x 处的值的 4 个最低有效位 x / 2 处的绿色值,并将 x + 1 处的绿色值置于 x / 2 处绿色值的 4 个最高有效位。
  • width_bits = 2:对于每个 x 值,其中 x ☰ 0 (mod 4),则会显示绿色的 位于 x 处的 2 个最低有效位 x / 4 处的绿色值以及 x + 1 到 x + 3 处的绿色值定位在 设置为 x / 4 处绿色值的更高有效位。
  • width_bits = 3:对于每个 x 值,其中 x ☰ 0 (mod 8),则会显示绿色的 x 处的值位于绿色部分的最低有效位 x / 8 处的绿色值以及 x + 1 至 x + 7 处的绿色值按顺序排列 在 x / 8 处映射到绿色值的更有效位。

读取此转换后,image_width 将按 width_bits 下采样。这个 会影响后续转换的大小。新大小的计算公式如下: DIV_ROUND_UP(如之前定义)。

image_width = DIV_ROUND_UP(image_width, 1 << width_bits);

5 图片数据

图像数据是按扫描行顺序排列的像素值数组。

5.1 图片数据的作用

我们以五种不同的角色使用图片数据:

  1. ARGB 图片:存储图片的实际像素。
  2. 熵图片:存储元前缀代码(请参阅 “Decoding of 元前缀代码”)。
  3. 预测器图片:存储预测器转换的元数据(请参阅 "Predictor Transform")。
  4. 颜色转换图片:由 ColorTransformElement 值创建 (在“Color Transform”中定义)针对不同的块 图片。
  5. 颜色索引编制图片:大小为 color_table_size 的数组(最多 256 ARGB) 值)存储颜色索引转换的元数据(请参阅 “Color Indexing Transform”)。

5.2 图像数据编码

图片数据的编码与其角色无关。

首先将图片分割为一组固定尺寸的块(通常为 16x16 块)。其中每个块都使用自己的熵码进行建模。此外, 可能有多个块共用相同的熵码。

说明:存储熵代码会产生费用。可以最大限度地降低 如果统计类似的块共用熵代码,从而存储该代码, 仅一次。例如,编码器可以将相似块进行聚类, 使用它们的统计属性,或反复将一对随机 编码所需的总比特数 图片。

每个像素都采用以下三种可能的方法之一进行编码:

  1. 前缀编码的文字:每个通道(绿色、红色、蓝色和 alpha)都是 进行单独熵编码。
  2. LZ77 反向引用:从 图片。
  3. 颜色缓存代码:使用较短的乘法哈希代码(颜色缓存) 索引)。

以下各小节将详细介绍上述各项。

5.2.1 前缀编码的字面量

像素存储为绿色、红色、蓝色和 alpha 的前缀编码值(在 该订单)。请参阅第 6.2.3 节,了解 。

5.2.2 LZ77 向后参考

后向引用是长度和距离代码的元组:

  • 长度表示要按扫描行顺序复制的像素数。
  • 距离代码是一个数字,表示以前看过的 要从中复制像素的像素。确切的对应关系是 下文所述。

长度和距离值使用 LZ77 前缀编码进行存储。

LZ77 前缀编码将大型整数值分为两部分:前缀 代码额外位。前缀代码使用熵代码进行存储, 而额外位则按原样存储(没有熵代码)。

说明:此方法可降低对熵所需的存储要求 代码。此外,较大的值通常很少见,因此额外的位将用于非常 图片中的几个值。因此,这种方法可以提高压缩率, 。

下表显示了前缀代码和额外位,用于存储 不同的值范围。

值范围 前缀代码 额外位
1 0 0
2 1 0
3 2 0
4 3 0
5..6 4 1
7..8 5 1
9 月 12 日 6 2
13 月 16 日 7 2
3072..4096 23 10
524289..786432 38 18
786433..1048576 39 18

从前缀代码获取(长度或距离)值的伪代码为 如下:

if (prefix_code < 4) {
  return prefix_code + 1;
}
int extra_bits = (prefix_code - 2) >> 1;
int offset = (2 + (prefix_code & 1)) << extra_bits;
return offset + ReadBits(extra_bits) + 1;
距离映射

如前所述,距离代码是一个数字,表示 要从中复制像素。该小节 定义了距离代码和上一个 Pixel。

大于 120 的距离代码以扫描行顺序表示像素距离, 偏移 120。

最小的距离代码 [1..120] 是特殊代码,专门用于近距离 当前像素的邻近区域。此社区包含 120 像素:

  • 当前像素上方第 1 至 7 行且最多 8 列的像素 在当前像素左侧或右侧最多 7 列。[总计 此类像素 = 7 * (8 + 1 + 7) = 112]。
  • 与当前像素位于同一行的像素数,最多 8 个 列。[8 个此类像素]。

距离代码 distance_code 与相邻像素之间的映射 偏移 (xi, yi) 如下所示:

(0, 1),  (1, 0),  (1, 1),  (-1, 1), (0, 2),  (2, 0),  (1, 2),
(-1, 2), (2, 1),  (-2, 1), (2, 2),  (-2, 2), (0, 3),  (3, 0),
(1, 3),  (-1, 3), (3, 1),  (-3, 1), (2, 3),  (-2, 3), (3, 2),
(-3, 2), (0, 4),  (4, 0),  (1, 4),  (-1, 4), (4, 1),  (-4, 1),
(3, 3),  (-3, 3), (2, 4),  (-2, 4), (4, 2),  (-4, 2), (0, 5),
(3, 4),  (-3, 4), (4, 3),  (-4, 3), (5, 0),  (1, 5),  (-1, 5),
(5, 1),  (-5, 1), (2, 5),  (-2, 5), (5, 2),  (-5, 2), (4, 4),
(-4, 4), (3, 5),  (-3, 5), (5, 3),  (-5, 3), (0, 6),  (6, 0),
(1, 6),  (-1, 6), (6, 1),  (-6, 1), (2, 6),  (-2, 6), (6, 2),
(-6, 2), (4, 5),  (-4, 5), (5, 4),  (-5, 4), (3, 6),  (-3, 6),
(6, 3),  (-6, 3), (0, 7),  (7, 0),  (1, 7),  (-1, 7), (5, 5),
(-5, 5), (7, 1),  (-7, 1), (4, 6),  (-4, 6), (6, 4),  (-6, 4),
(2, 7),  (-2, 7), (7, 2),  (-7, 2), (3, 7),  (-3, 7), (7, 3),
(-7, 3), (5, 6),  (-5, 6), (6, 5),  (-6, 5), (8, 0),  (4, 7),
(-4, 7), (7, 4),  (-7, 4), (8, 1),  (8, 2),  (6, 6),  (-6, 6),
(8, 3),  (5, 7),  (-5, 7), (7, 5),  (-7, 5), (8, 4),  (6, 7),
(-6, 7), (7, 6),  (-7, 6), (8, 5),  (7, 7),  (-7, 7), (8, 6),
(8, 7)

例如,距离代码 1 表示(0, 1) 相邻像素,即当前像素上方的像素(0 像素 X 方向上的差异和 Y 方向上的 1 像素差异)。 同样,距离代码 3 表示左上角像素。

解码器可以将距离代码 distance_code 转换为扫描线顺序 距离 dist 如下所示:

(xi, yi) = distance_map[distance_code - 1]
dist = xi + yi * image_width
if (dist < 1) {
  dist = 1
}

其中 distance_map 是上述映射,image_width 是宽度 图片尺寸(以像素为单位)。

5.2.3 颜色缓存编码

颜色缓存会存储图片中最近使用过的一组颜色。

说明:这样,系统有时就可以将最近使用的颜色引用 比使用其他两种方法(如 5.2.15.2.2)。

颜色缓存代码的存储方式如下。第一个,有一个 1 位值, 表示是否使用了颜色缓存。如果该位为 0,则没有颜色缓存代码 并且不会传输在对绿色字体进行解码的前缀代码中。 符号和长度前缀代码。不过,如果该位为 1,则颜色缓存 尺寸:

int color_cache_code_bits = ReadBits(4);
int color_cache_size = 1 << color_cache_code_bits;

color_cache_code_bits 定义了颜色缓存的大小 (1 << color_cache_code_bits)。允许值的范围 color_cache_code_bits 为 [1..11]。符合要求的解码器必须指示 损坏的比特流。

颜色缓存是大小为 color_cache_size 的数组。每个条目存储一个 ARGB color 决定。查询颜色时,系统会按 (0x1e35a7bd * color) >> (32 - color_cache_code_bits) 将其编入索引。颜色缓存中只执行一次查询;没有 冲突解决。

在图像解码或编码开始时,所有颜色的条目 设置为零。颜色缓存代码会在以下位置转换为此颜色: 解码时间。将每个 像素,无论是通过反向引用生成还是作为字面量, 它们在信息流中的显示顺序。

6 熵代码

6.1 概览

大部分数据都使用规范前缀代码进行编码。 因此,系统会通过发送前缀代码长度来传输代码,如 而不是实际的前缀代码

具体而言,该格式使用空间变体前缀编码。在其他 那么图片的不同块可能会使用不同的熵 代码。

说明:图片的不同区域可能具有不同的特征。 因此,允许它们使用不同的熵代码可以提高灵活性, 可能获得更好的压缩效果。

6.2 详细信息

编码的图片数据由几个部分组成:

  1. 解码和构建前缀代码。
  2. 元前缀代码。
  3. 经过熵编码的图片数据。

对于任何给定的像素 (x, y),都存在一组与五个相关联的前缀代码。 。这些代码是(按比特流顺序排列):

  • 前缀代码 1:用于绿色通道、反向引用长度和 颜色缓存。
  • 前缀代码 #2、#3 和 #4:用于红色、蓝色和 alpha 通道。 。
  • 前缀代码 #5:用于向后引用距离。

在下文中,我们将这组代码称为前缀代码组

6.2.1 解码和构建前缀代码

本部分介绍如何从比特流中读取前缀码长度。

可以通过两种方式对前缀代码长度进行编码。所使用的方法由 一个 1 位值。

  • 如果该位为 1,则是一个简单代码长度码
  • 如果该位为 0,则是一个普通的代码长度代码

在这两种情况下,可能仍有未使用的代码长度, 。这可能效率低下,但这种格式是允许的。 所描述的树必须是一个完整的二元树。单个叶节点 被视为完整的二元树,可以使用简单的 代码长度代码或常规代码长度代码。对单个叶项进行编码时 使用标准代码长度代码的节点,但除了一个代码长度,所有其他代码长度均为零, 并且单叶节点值标有长度 1,即便是 在使用该单叶节点树时,会消耗比特数。

简单代码长度代码

此变体用于只有 1 个或 2 个前缀符号的特殊情况中 范围 [0..255],代码长度为 1。其他所有前缀码长度为 隐式为零。

第一位表示符号数量:

int num_symbols = ReadBits(1) + 1;

以下是符号值。

第一个符号使用 1 位或 8 位进行编码,具体取决于 is_first_8bits。范围分别为 [0..1] 或 [0..255]。第二个 符号(如果存在)始终假定在 [0..255] 范围内并编码 使用 8 位

int is_first_8bits = ReadBits(1);
symbol0 = ReadBits(1 + 7 * is_first_8bits);
code_lengths[symbol0] = 1;
if (num_symbols == 2) {
  symbol1 = ReadBits(8);
  code_lengths[symbol1] = 1;
}

这两个符号应不同。允许出现重复的符号,但 效率低下。

注意:另一种特殊情况是所有前缀代码长度均为零(即 前缀代码为空)。例如,如果出现以下情况,表示距离的前缀代码可以为空 即没有后向引用同样,如果是 alpha、red 和 如果生成了同一元前缀代码内的所有像素,则蓝色可能为空 使用颜色缓存不过,这种情况不需要进行特殊处理,因为 空前缀代码可编码为包含单个符号 0 的代码。

一般代码长度代码

前缀码的代码长度为 8 位,如下所述。 首先,num_code_lengths 指定代码长度的数量。

int num_code_lengths = 4 + ReadBits(4);

代码长度本身使用前缀代码进行编码;较低级别的代码 长度 code_length_code_lengths,必须先读取。其余 code_length_code_lengths(根据kCodeLengthCodeOrder中的命令) 为零。

int kCodeLengthCodes = 19;
int kCodeLengthCodeOrder[kCodeLengthCodes] = {
  17, 18, 0, 1, 2, 3, 4, 5, 16, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15
};
int code_length_code_lengths[kCodeLengthCodes] = { 0 };  // All zeros
for (i = 0; i < num_code_lengths; ++i) {
  code_length_code_lengths[kCodeLengthCodeOrder[i]] = ReadBits(3);
}

接下来,如果 ReadBits(1) == 0,则输入不同读取符号的 每个符号类型(A、R、G、B 和距离)的 (max_symbol) 都设置为 字母大小:

  • G 渠道:256 + 24 + color_cache_size
  • 其他字面量(A、R 和 B):256
  • 距离代码:40

否则,它定义为:

int length_nbits = 2 + 2 * ReadBits(3);
int max_symbol = 2 + ReadBits(length_nbits);

如果 max_symbol 大于符号类型的字母大小,则 无效。

然后,根据 code_length_code_lengths 构建前缀表,用于读取 至 max_symbol 个代码长度。

  • 代码 [0..15] 表示字面量代码长度。
    • 值 0 表示未对任何符号进行编码。
    • 值 [1..15] 表示相应代码的位长度。
  • 代码 16 会重复上一个非零值 [3..6] 次,即 3 + ReadBits(2) 次。如果代码 16 用在非零值之前 值,系统会重复值 8。
  • 代码 17 会发出长度为 [3..10] 的零条纹,即 3 + ReadBits(3) 次。
  • 代码 18 会发出一条长度为 [11..138] 的零条纹,即 11 + ReadBits(7) 次。

系统读取代码长度后,将为每个符号类型(A、R、G、B 和 距离)使用各自的字母大小组成。

正常代码长度码必须编码一个完整的决策树,即 所有非零代码的 2 ^ (-length) 必须正好是 1。不过, 单叶节点树是此规则的一个例外情况,叶节点 值标记为 1,其他值为 0。

6.2.2 元前缀代码解码

如前所述,此格式允许对 图片的不同块。元前缀代码是用于标识 在映像的不同部分使用的前缀代码。

只有当图片用在 ARGB 映像角色

有两种可能的元前缀代码,以 1 位 值:

  • 如果该位为零,则在 图片。系统不会再存储任何数据。
  • 如果该位为 1,则图像使用多个元前缀代码。这些元层 前缀代码以熵图像的形式存储(如下所述)。

像素的红色和绿色部分定义了一个 16 位元前缀代码, ARGB 图像的特定块。

熵图片

熵图像定义了将哪些前缀代码用于 图片。

前 3 位包含 prefix_bits 值。熵的维度 图片派生自 prefix_bits

int prefix_bits = ReadBits(3) + 2;
int prefix_image_width =
    DIV_ROUND_UP(image_width, 1 << prefix_bits);
int prefix_image_height =
    DIV_ROUND_UP(image_height, 1 << prefix_bits);

其中 DIV_ROUND_UP之前所定义。

接下来的位包含宽度 prefix_image_width 和高度的熵图像 prefix_image_height

元前缀代码的解释

ARGB 图片中前缀码组的数量可通过查找 熵图片中的最大元前缀代码

int num_prefix_groups = max(entropy image) + 1;

其中 max(entropy image) 表示存储在 图像的熵。

由于每个前缀代码组包含五个前缀代码,因此,前缀的总数 代码为:

int num_prefix_codes = 5 * num_prefix_groups;

根据 ARGB 图片中的像素 (x, y),我们可以获取相应的前缀 按如下方式使用:

int position =
    (y >> prefix_bits) * prefix_image_width + (x >> prefix_bits);
int meta_prefix_code = (entropy_image[position] >> 8) & 0xffff;
PrefixCodeGroup prefix_group = prefix_code_groups[meta_prefix_code];

其中,我们假设存在 PrefixCodeGroup 结构, 表示一组五个前缀代码。此外,prefix_code_groupsPrefixCodeGroup(大小为 num_prefix_groups)。

然后,解码器使用前缀代码组 prefix_group 解码像素 (x, y),如“解码编码图像 数据”

6.2.3 解码熵编码的图片数据

对于图像中的当前位置 (x, y),解码器首先识别 对应的前缀代码组(如上一部分所述)。由于存在 前缀代码组,那么像素的读取和解码方式如下。

接下来,使用前缀代码 #1 读取比特流中的符号 S。请注意, S 是 0 到 之间的任何整数 (256 + 24 + color_cache_size- 1)

S 的解释取决于其值:

  1. 如果 S <256 <ph type="x-smartling-placeholder">
      </ph>
    1. 使用 S 作为绿色分量。
    2. 使用前缀代码 2 从比特流中读取红色。
    3. 使用前缀代码 3 从比特流中读取蓝色。
    4. 使用前缀代码 #4 从比特流中读取 alpha 值。
  2. 如果 S >= 256 且搜索 <256 + 24 <ph type="x-smartling-placeholder">
      </ph>
    1. 使用 S - 256 作为长度前缀代码。
    2. 从比特流中读取额外的位数。
    3. 根据长度前缀码和 读取额外的位。
    4. 使用前缀代码 #5 从比特流读取距离前缀代码。
    5. 读取与比特流之间的距离的额外位。
    6. 根据距离前缀代码确定后向引用距离 D 和读取的额外位。
    7. 从开始的像素序列中复制 L 像素(按扫描行顺序) 减去 D 像素。
  3. 如果 S >= 256 + 24 <ph type="x-smartling-placeholder">
      </ph>
    1. 使用 S - (256 + 24) 作为颜色缓存的索引。
    2. 从该索引处的颜色缓存中获取 ARGB 颜色。

7 形式的总体结构

下图显示了 Augmented Backus-Naur Form (ABNF) 中的格式 RFC 5234 RFC 7405。其中并未涵盖所有细节。图片结束 (EOI) 仅隐式编码为像素数 (image_width * image_height)。

请注意,*element 表示 element 可以重复 0 次或多次。5element 表示 element 正好重复 5 次。%b 表示二进制值。

7.1 基本结构

format        = RIFF-header image-header image-stream
RIFF-header   = %s"RIFF" 4OCTET %s"WEBPVP8L" 4OCTET
image-header  = %x2F image-size alpha-is-used version
image-size    = 14BIT 14BIT ; width - 1, height - 1
alpha-is-used = 1BIT
version       = 3BIT ; 0
image-stream  = optional-transform spatially-coded-image

7.2 转换的结构

optional-transform   =  (%b1 transform optional-transform) / %b0
transform            =  predictor-tx / color-tx / subtract-green-tx
transform            =/ color-indexing-tx

predictor-tx         =  %b00 predictor-image
predictor-image      =  3BIT ; sub-pixel code
                        entropy-coded-image

color-tx             =  %b01 color-image
color-image          =  3BIT ; sub-pixel code
                        entropy-coded-image

subtract-green-tx    =  %b10

color-indexing-tx    =  %b11 color-indexing-image
color-indexing-image =  8BIT ; color count
                        entropy-coded-image

7.3 图片数据的结构

spatially-coded-image =  color-cache-info meta-prefix data
entropy-coded-image   =  color-cache-info data

color-cache-info      =  %b0
color-cache-info      =/ (%b1 4BIT) ; 1 followed by color cache size

meta-prefix           =  %b0 / (%b1 entropy-image)

data                  =  prefix-codes lz77-coded-image
entropy-image         =  3BIT ; subsample value
                         entropy-coded-image

prefix-codes          =  prefix-code-group *prefix-codes
prefix-code-group     =
    5prefix-code ; See "Interpretation of Meta Prefix Codes" to
                 ; understand what each of these five prefix
                 ; codes are for.

prefix-code           =  simple-prefix-code / normal-prefix-code
simple-prefix-code    =  ; see "Simple Code Length Code" for details
normal-prefix-code    =  ; see "Normal Code Length Code" for details

lz77-coded-image      =
    *((argb-pixel / lz77-copy / color-cache-code) lz77-coded-image)

下面是一个可能的示例序列:

RIFF-header image-size %b1 subtract-green-tx
%b1 predictor-tx %b0 color-cache-info
%b0 prefix-codes lz77-coded-image