四种转换类型应用于对图像数据熵编码之前,通过对空间和颜色相关性进行建模来降低图像数据的熵。
一张图片可以进行四种类型的转换,每个转换最多只能使用一次。
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,};
转换数据紧接着跟在转换类型之后。
四种转换分别为:预测转换、颜色转换、减绿色转换、颜色索引转换;
下面将一一介绍。
1. 预测转换
预测转换的转换数据在WEBP中被称为“预测器图片”。其实可以视为将实际的图像数据按照区块(区块为正方形,并且所有区块的宽高相同)划分,并将区块视为一个像素点,这些像素点组成的图像即为预测器图,但是图片的颜色通道(绿色通道)存放的是该像素点对应区块所使用的预测模式。预测模式共有14种。
预测转换的转换数据的前3位定义区块宽度和高度。
若当前待预测转换的像素为P,如下所示:
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 值的时候,所有 0、TL、T、TR 和 L 像素都已知, P 像素和 X 像素未知。
14种预测模式的定义如下.
Mode | Predicted value of each channel of the current pixel |
---|---|
0 | 0xff000000 (表示 ARGB 中的纯黑色) |
1 | L |
2 | T |
3 | TR |
4 | TL |
5 | Average2(Average2(L, TR), T) |
6 | Average2(L, TL) |
7 | Average2(L, T) |
8 | Average2(TL, T) |
9 | Average2(T, TR) |
10 | Average2(Average2(L, TL), Average2(T, TR)) |
11 | Select(L, T, TL) |
12 | ClampAddSubtractFull(L, T, TL) |
13 | ClampAddSubtractHalf(Average2(L, T), TL) |
uint8 Average2(uint8 a, uint8 b) {
return (a + b) / 2;
}
// 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);
}
图片左上方像素预测为0xff000000,最上一行像素预测值均为L,最左列的像素预测值为T。
最右列的像素按照设定的预测模式进行预测,但是将当前行最左侧的像素视为TR像素的替代。
2. 颜色转换
关于分块等思路与预测器转换相同,对块中的所有像素使用相同的转换模式,对于 每个块均有三种类型的颜色转换元素。每个块对应颜色转换图中一个像素点,该像素点的三个通道分别储存三种颜色转换元素的值。
typedef struct {
uint8 green_to_red;
uint8 green_to_blue;
uint8 red_to_blue;
} 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;
}
int8 ColorTransformDelta(int8 t, int8 c) {
return (t * c) >> 5;
}
3. 减绿色转换
减绿色转换没有跟随着的转换数据,它仅仅是从每个像素的红色和蓝色值中减去绿色值。
减绿色转换可以使用2中提到的颜色转换来实现,但是由于减绿色转换较为简单,单独使用时比以特殊的颜色转换来替代可以使用更少的位数来对转换模式及数据进行编码。
void AddGreenToBlueAndRed(uint8 green, uint8 *red, uint8 *blue) {
*red = (*red + green) & 0xff;
*blue = (*blue + green) & 0xff;
}
4. 颜色索引转换
如果图片像素值有限,那么创建颜色索引数组,使用数组的索引替换像素值将对编码十分有效。
颜色索引转换的工作原理:
-
唯一值检测:
- 颜色索引转换首先检查图像中唯一的 ARGB 值的数量。
- 如果该数量低于阈值(256),就会创建一个包含这些 ARGB 值的数组。
-
颜色表的构建和使用:
- 将图像中的像素值替换为颜色表中的对应索引:(为什么还保留其他通道?)
- 替换后的像素的绿色通道保存索引值。
- 所有的 Alpha 通道值设为 255。
- 所有的红色和蓝色通道值设为 0。
- 将图像中的像素值替换为颜色表中的对应索引:(为什么还保留其他通道?)
-
传输的数据内容:
-
转换数据包含颜色表的大小和条目。
-
解码时,通过以下方式读取颜色表:
- 颜色表大小:存储为 8 位值。
int color_table_size = ReadBits(8) + 1;
- 颜色表内容:
- 颜色表使用图像的存储格式存储(省略 RIFF 头、图像大小和其他转换)。
- 颜色表的高度设为 1 像素,宽度为
color_table_size
。 - 使用"减法编码" (subtraction coding) 存储,以降低表的熵(信息复杂性)。
-
-
减法编码(Subtraction Coding):
- 减少颜色表中的熵,通过对每个 ARGB 分量的当前值与前一个值的差进行存储。
- 解码时,通过加回前一个颜色分量的值重建最终的颜色表。
-
逆变换(Inverse Transform):
- 在解码时,将像素值(即索引)替换为颜色表中的实际颜色值:
argb = color_table[GREEN(argb)];
- 如果索引值大于或等于颜色表的大小,则像素值设为透明黑色 (
0x00000000
)。
像素捆绑(Pixel Bundling):
当颜色表很小(16 个颜色或更少)时,可以将多个像素打包成一个像素,进一步提高编码效率。
-
捆绑规则:
- 如果颜色表的大小
color_table_size
≤ 16,可以打包多个像素:- 2 像素打包为 1 像素(
width_bits = 1
)。 - 4 像素打包为 1 像素(
width_bits = 2
)。 - 8 像素打包为 1 像素(
width_bits = 3
)。
- 2 像素打包为 1 像素(
- 捆绑后,图像宽度会相应减少。
- 如果颜色表的大小
-
数据打包方式:
- 绿色通道存储索引值,根据
width_bits
的值,将多个像素的索引打包到一个绿色通道中。例如:width_bits = 1
:每个绿色值的低 4 位存储第一个索引,高 4 位存储第二个索引。width_bits = 2
:每个绿色值的低 2 位存储第一个索引,其余位依次存储后续索引。
- 绿色通道存储索引值,根据
-
图像宽度的更新:
- 捆绑完成后,图像宽度按以下公式更新:
image_width = DIV_ROUND_UP(image_width, 1 << width_bits);