Jyrki Alakuijala 博士,Google LLC,2023 年 3 月 9 日
摘要
WebP 無損壓縮 ARGB 圖像無損壓縮的圖片格式。 無損格式會完全儲存並還原像素值,包括 填滿全透明像素的顏色值。用於處理序列的通用演算法 包括資料壓縮 (LZ77)、前置字串編碼和色彩快取 壓縮大量資料。我們已證明,解碼速度比 PNG 快,且壓縮密度比現今的 PNG 格式高出 25%。
1 簡介
本文件說明 WebP 損失的壓縮資料表示法 圖片。該範本是 WebP 無損編碼器的詳細參考資訊, 導入解碼器
在本文件中,我們大量使用 C 程式設計語言語法來描述位元串流,並假設存在用於讀取位元的函式 ReadBits(n)
。系統會按照包含
以及每個位元組的位元,皆依照最低順位進行讀取。時間
系統會同時讀取多個位元,在這個情況下,系統會從
原始資料 (以原始順序排列)傳回的
也是原始資料中最重要的位元。因此,陳述式
b = ReadBits(2);
等同於以下兩個陳述式:
b = ReadBits(1);
b |= ReadBits(1) << 1;
我們假設每個顏色元件 (Alpha、紅色、藍色及綠色) 是 以 8 位元位元組表示我們將對應類型定義為 uint8。A 罩杯 整個 ARGB 像素都是以類型 uint32 表示,後者為無正負號 由 32 位元組成的整數在程式碼中,顯示 轉換後,這些值會按照以下位元編碼:Alpha 位元 31..24、紅色代表位元 23..16、綠色以位元 15..8 表示,而在位元 7..0 中為藍色;不過 廣告格式的實作可以自由在內部使用其他表示法。
一般來說,WebP 無損圖片包含標頭資料、轉換資訊和實際圖片資料。標頭包含圖片的寬度和高度。無損的 WebP 圖片在經過熵編碼前,可以經過四種不同類型的轉換。位元串流中的轉換資訊包含資料 這些 Pod 會 套用相應的反向轉換
2 命名法
- ARGB
- 由 Alpha、紅色、綠色和藍色值的像素值。
- ARGB 圖片
- 包含 ARGB 像素的 2D 陣列。
- 色彩快取
- 雜湊地址陣列,用於儲存最近使用的顏色 你可以使用較短的驗證碼,喚回聽起來。
- 顏色索引圖片
- 可使用小整數建立索引的一維顏色圖片 (最多 256 部 WebP 無損檔案)。
- 色彩轉換圖片
- 2D 副解析度圖片,內含 顏色元件
- 距離對應
- 變更 LZ77 距離,以便在二維鄰近度中取得最小值的像素。
- 熵圖片
- 二維次解析度圖片,指出應有熵編碼 用在圖像中的個別正方形中,也就是說,每個像素都是中繼圖片 前置字元代碼。
- LZ77
- 以字典為基礎的滑動窗口壓縮演算法,會發出 或以過去的符號連續字元描述這類行為
- 中繼前置字元代碼
- 一個小整數 (最多 16 位元),可為中繼前置字串中的元素建立索引 。
- 預測者圖片
- 二維次解析度圖片,指出空間預測器 用於圖片中特定正方形的色彩
- 前置字元代碼
- 使用少量位元的資料進行熵編碼的傳統方法 ,取得更頻繁的驗證碼
- 前置字元編碼
- 一種會佔滿整數的熵程式碼,也就是說,將少量整數編碼。 並編碼其餘位元的原始資料。這樣一來,即使符號範圍很大,熵碼的說明仍可保持相對較小。
- 掃描行順序
- 像素的處理順序 (由左至右和由上至下),從左上角像素開始。資料列完成後,請從 下一列左欄。
3 RIFF 標頭
標頭開頭處有 RIFF 容器。這項資料包含下列 21 個位元組:
- 字串「RIFF」。
- 區塊長度的 32 位元小端值,即整個大小 也就是 RIFF 標頭控制的區塊區塊。通常等於 酬載大小 (檔案大小減 8 位元組:「RIFF」是 4 個位元組 ID 和 4 個位元組 (用於儲存值本身)。
- 字串「WEBP」(RIFF 容器名稱)。
- 字串「VP8L」(適用於無損編碼圖片資料使用 FourCC)。
- 這個含位元組數量的小端子值 (32 位元) 無損串流
- 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 預測者轉換
預測器轉換作業可用於利用 相鄰像素通常彼此相關。在預測者轉換中 目前的像素值是根據已解碼的像素預測 (在掃描行中) 順序),而且只有殘差值 (實際 - 預測) 會經過編碼。像素的綠色元件會定義 ARGB 圖片特定區塊中使用的 14 個預測器。預測模式會決定要使用的預測類型。我們會將圖片分割為正方形, 正方形會使用相同的預測模式
前 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 | TR |
4 | TL |
5 | 平均 2(平均 2(L, TR)、T) |
6 | 平均 2(L 號、TL 值) |
7 | 平均 2(L、T) |
8 | 平均 2(TL, T) |
9 | 平均 2(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) |
每個 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;
}
}
系統會執行 ClampAddSubtractFull
和 ClampAddSubtractHalf
函式
每個 ARGB 元件的變數如下:
// 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-pixel。
最右欄之像素的 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 值解除相關性。色彩轉換會維持原本的綠色 (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
是使用代表 3.5 固定小數的 8 位元帶符號整數和 8 位元帶符號 RGB 色彩通道 (c) [-128..127] 計算而得,定義如下:
int8 ColorTransformDelta(int8 t, int8 c) {
return (t * c) >> 5;
}
呼叫 ColorTransformDelta()
前,必須將 8 位元無符號表示法 (uint8) 轉換為 8 位元有符號表示法 (int8)。已簽署的值
應解讀為 8 位元 2 的補數 (即 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 色彩索引轉換
如果不重複像素值不多,建立不重複像素值可能更有效率。 色彩索引陣列,並以陣列的索引取代像素值。色彩 即可達到這個目的(在 WebP 無損的情況下,我們特別不稱此為色版轉換,因為 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) 像素捆綁成單一像素,進而減少圖片寬度。Pixel 的妙用 套裝組合具備更有效率的聯合分佈熵編碼 相鄰像素,並帶來一些像程式設計一樣的優勢 熵代碼,但只有在不重複值數量不超過 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 / 2,而 x + 1 放在 4 位元綠色值的部分為 x / 2。width_bits
= 2:對於每個 x 值 (其中 x ≡ 0 (mod 4)),x 處的綠色值會放置在 x/4 處綠色值的 2 個最小位元,而 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 圖像資料角色
我們會在五種不同角色中使用圖片資料:
- ARGB 圖片:儲存圖片的實際像素。
- 熵圖片:儲存中繼前置字元代碼 (請參閱 「解碼中繼前置字元代碼」)。
- 預測者圖片:儲存預測者轉換的中繼資料 (請參閱 "Predictor Transform")。
- 色彩轉換圖片:由
ColorTransformElement
值建立 (於「Color 轉換」中定義) 不同的建塊 圖片的部分 - 彩色索引圖片:
color_table_size
大小的陣列 (上限為 256) ARGB 值),用於儲存色彩索引轉換的中繼資料 (請參閱 "Color Indexing Transform")。
5.2 圖片資料編碼
圖片資料的編碼與其角色無關。
圖片會先分割成一組固定大小的區塊 (通常為 16x16 塊) 區塊)。這些區塊都會使用其專屬的熵代碼來建模。另外, 多個區塊可能會共用相同的熵代碼。
理由:儲存熵代碼會產生費用。如果統計上相似的區塊共用一個熵碼,則可以將這項成本降到最低,因此只需儲存一次該程式碼。例如,編碼器可建立分群法,找出類似的區塊 或重複彙整一組隨機數字 以減少編碼所需的總位元量 該圖片
每個像素都會使用下列三種可能方法之一進行編碼:
- 前置字碼常值:每個通道 (綠色、紅色、藍色和 Alpha) 都會個別進行熵編碼。
- LZ77 反向參照:從 該圖片
- 顏色快取程式碼:使用短的乘法雜湊碼 (色彩快取) 索引)。
以下各節將詳細說明這些不同之處。
5.2.1 前置碼字面值
像素儲存後會以前置字元編碼的綠色、紅色、藍色和 Alpha 值 ( 順序)。請參閱 6.2.3 節,瞭解相關資訊 詳細資料。
5.2.2 LZ77 回溯參考資料
反向參照是 length 和距離代碼的組合:
- 長度 代表要複製的掃描順序像素數量。
- 距離代碼是一個數字,指出先前發現 像素,做為複製像素。確切的對應值是 下文。
系統會使用 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;
距離對應
如前所述,距離代碼是指 要複製的像素本子節 會定義距離代碼與先前路徑位置之間的對應 像素。
大於 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.1 和 5.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]。相容性解碼器必須表示
毀損的 Bitstream。
顏色快取是大小 color_cache_size
的陣列。每個項目都會儲存一個 ARGB
顏色。(0x1e35a7bd * color) >> (32 -
color_cache_code_bits)
已為顏色建立索引。顏色快取中只會執行一次查詢,不會有衝突解決問題。
在圖片解碼或編碼的開頭,所有顏色的項目 快取值設為零色彩快取程式碼會在 編碼器-解碼器架構而會保留色彩快取狀態的 程式產生的像素,是透過反向參照或文字形式產生成 廣告在訊息串中出現的順序。
6 熵代碼
6.1 總覽
大部分資料都是使用標準前置字串代碼進行編碼。 因此,系統會傳送前置字串代碼長度, 而不是實際的前置字元代碼
具體來說,格式會使用空間變化版本的前置字元編碼。在其他 圖像的不同區塊 可能會運用不同的熵 代碼。
理由:圖片的不同區域可能會有不同的特性。 因此,允許使用不同的熵碼可提供更大的彈性,並可能帶來更優異的壓縮效果。
6.2 詳細資料
經過編碼的圖片資料包含以下幾個部分:
- 解碼及建構前置字串代碼。
- 中繼前置字元代碼,
- 熵編碼的圖片資料。
在任何像素 (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;
}
這兩個符號必須不同。可使用重複的符號,但 效率低落
注意:另一種特殊情況是「所有」前置字元代碼長度為「0」(
空白前置字元代碼)。舉例來說,假設您在
也沒有回溯參照同樣地,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
) 都設為
字母大小:
- Google 管道: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
程式碼長度
- Code [0..15] 表示常值代碼長度。
- 值 0 表示未經過編碼。
- 值 [1..15] 表示個別程式碼的位元長度。
- 代碼 16 會重複上一個非零值 [3..6],也就是
3 + ReadBits(2)
次。若在非 0 前使用代碼 16 值,重複值為 8。 - 程式碼 17 會發出連續零秒 [3..10],也就是
3 + ReadBits(3)
次。 - 程式碼 18 會發出連續零秒 [11..138],也就是
11 + ReadBits(7)
次。
讀取程式碼長度後,各符號類型 (A、R、G、B 和 距離) 以各自的字母大小組成。
正常程式碼長度代碼必須編碼完整的決策樹,也就是所有非零程式碼的 2 ^ (-length)
總和必須恰好為 1。
有一個例外狀況,也就是單一分葉節點樹狀結構
值標示為 1,其他值則為 0s。
6.2.2 中繼前置字元程式碼解碼
如前所述,此格式允許使用 圖像的不同區塊中繼前置字元代碼是能夠識別哪些 前置字串,以便在圖片的不同部分使用。
中繼前置字元代碼「只能」用於圖片 ARGB 圖片的 角色。
中繼前置字元代碼有 2 種可能性,以 1 位元 值:
- 如果這個位元為 0,則內部任何地方只會使用一個中繼前置字元代碼 該圖片沒有其他資料。
- 如果是這個位元,圖片就會使用多個中繼前置字元代碼。這些中繼搜尋工具 前置字串儲存為熵圖片 (如下所述)。
像素的紅色和綠色元件會定義 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_groups
是
PrefixCodeGroup
(大小為 num_prefix_groups
)。
解碼器接著會使用前置字串代碼群組 prefix_group
將像素解碼
(x, y),詳情請參閱「解碼 Entropy-Coded 圖片
資料」。
6.2.3 解碼 Entropy-Coded 影像資料
針對圖片中的目前位置 (x, y),解碼器會先識別 對應的前置字串代碼群組 (如上一節所述)。由於 前置字元代碼群組,那麼系統讀取和解碼像素會如下所示。
接著,使用前置字元代碼 #1 從位元串流讀取符號 S。請注意,S 是
介於 0
到
(256 + 24 +
color_cache_size
- 1)
。
S 的解釋取決於其值:
- 如果 S <256 人
- 使用 S 做為綠色元件。
- 使用前置字串 #2 從 Bitstream 讀取紅色。
- 使用前置字元代碼 #3 從位元串流讀取藍色。
- 使用前置字串 #4 從位元串流讀取 Alpha 版。
- 如果 S >= 256,且南256 + 24
- 請使用「S - 256」做為長度前置字元代碼。
- 從位元串流中讀取長度的額外位元。
- 根據長度前置碼和讀取的額外位元,判斷回溯參照長度 L。
- 使用前置字元代碼 #5 讀取位元流的距離前置字元代碼。
- 讀取額外的位元,瞭解與位元流的距離。
- 確定與距離前置字元代碼之間的反向參照距離 D 額外加入一些內容
- 從起始的像素序列複製 L 像素 (按照掃描順序) 把它放在目前位置減 D 像素。
- 如果 S 大於等於 256 + 24
- 使用 S - (256 + 24) 做為色彩快取的索引。
- 從該索引的色彩快取取得 ARGB 顏色。
7 影片格式整體結構
以下是擴增 Backus-Naur 形式 (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