WebP Lossless Bitstream 的規格

Jyrki Alakuijala 博士,Google, Inc., 2023-03-09

摘要

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 位元組成的整數在顯示轉換作業行為的程式碼中,這些值會以以下位元編碼:位元 31..24 為 alpha,位元 23..16 為紅色,位元 15..8 為綠色,位元 7..0 為藍色;不過,格式的實作方式可在內部使用其他表示法。

一般來說,WebP 無損圖片包含標頭資料、轉換資訊和實際圖片資料。標頭包含圖片的寬度和高度。無損的 WebP 圖片在經過熵編碼前,可以經過四種不同類型的轉換。位元串流中的轉換資訊包含資料 這些 Pod 會 套用相應的反向轉換

2 命名法

ARGB
由 Alpha、紅色、綠色和藍色值的像素值。
ARGB 圖片
包含 ARGB 像素的 2D 陣列。
色彩快取
雜湊地址陣列,用於儲存最近使用的顏色 你可以使用較短的驗證碼,喚回聽起來。
色彩索引圖片
單維顏色圖片,可使用小整數進行索引 (在 WebP 無損壓縮格式中最多可達 256 個)。
色彩轉換圖片
二維子解析度圖片,其中包含色彩元件的相關資料。
距離對應
變更 LZ77 距離,使其採用最小的像素值 兩維距離。
熵圖片
二維次解析度圖片,指出應有熵編碼 用在圖像中的個別正方形中,也就是說,每個像素都是中繼圖片 前置字元代碼。
LZ77
以字典為基礎的滑動窗口壓縮演算法,兩者會發出 或以過去的符號連續字元描述這類行為
中繼前置字元程式碼
一個小整數 (最多 16 位元),可為中繼前置字串中的元素建立索引 。
預測圖片
二維子解析度圖片,指出圖片中特定方塊使用的空間預測器。
前置字元代碼
熵編碼的經典方法,使用較少位元來處理較常見的編碼。
前置字元編碼
一種處理大型整數的熵代碼,也就是說,將少量整數解碼 並編碼其餘位元的原始資料。這樣一來 熵代碼的說明相對較小 符號範圍相當大
掃描線順序
像素的處理順序 (由左至右和由上至下),從左上角像素開始。資料列完成後,請從 下一列左欄。

3 RIFF 標頭

標頭開頭處有 RIFF 容器。這包括 接續的 21 個位元組:

  1. 字串「RIFF」。
  2. 小端的 32 位元值,是 RIFF 標頭控制的區塊長度,也是區塊的整個大小。通常等於 酬載大小 (檔案大小減 8 位元組:「RIFF」是 4 個位元組 ID 和 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 x 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 種,在每個預測模式下 像素值是根據一或多個相鄰的像素預測,其值如下: 。

我們選擇目前像素的相鄰像素 (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 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)

針對每個 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 函式 每個 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 像素。

針對最右欄的像素處理 TR-pixel 是例外情況。系統會使用各種模式預測最右欄中的像素 [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 位元 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 位元,而在這 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 無損編碼編碼:色彩快取)。

色彩索引轉換會檢查圖片中不重複的 ARGB 值數量。如果該數字低於閾值 (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 種顏色),系統會將多個像素合併為單一像素。Pixel 套裝組合包含數種 (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 表示會合併八個像素,且每個像素的範圍為 [0..1],也就是二進位值。

值會以以下方式塞入綠色元件:

  • width_bits = 1:對於每個 x 值 (其中 x ≡ 0 (mod 2)),x 處的綠色值會置於 x / 2 處綠色值的 4 個最低有效位元,而 x + 1 處的綠色值會置於 x / 2 處綠色值的 4 個最高有效位元。
  • width_bits = 2:每個 x 值,其中 x 遷移資料 0 (mod 4),綠色 就會被定位在 綠色值為 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. 熵圖:儲存中繼前置詞代碼 (請參閱「中繼前置詞代碼解碼」)。
  3. 預測者圖片:儲存預測者轉換的中繼資料 (請參閱 "Predictor Transform")。
  4. 色彩轉換圖片:由 ColorTransformElement 值建立 (於「Color 轉換」中定義) 不同的建塊 圖片的部分
  5. 彩色索引圖片:color_table_size大小的陣列 (上限為 256) ARGB 值),用於儲存色彩索引轉換的中繼資料 (請參閱 "Color Indexing Transform")。

5.2 圖像資料編碼

圖片資料的編碼與其角色無關。

圖片會先分割成一組固定大小的區塊 (通常為 16x16 塊) 區塊)。每個區塊都會使用專屬的熵碼進行建模。另外, 多個區塊可能會共用相同的熵代碼。

原因:儲存熵碼會產生成本。如果統計上相似的區塊共用一個熵碼,則可以將這項成本降到最低,這樣只需儲存一次該程式碼。例如,編碼器可建立分群法,找出類似的區塊 或重複彙整一組隨機數字 以減少編碼所需的總位元量 該圖片

每個像素都會使用下列三種可能方法之一進行編碼:

  1. 前置字元編碼常值:每個管道 (綠色、紅色、藍色與 Alpha) 都是 經過獨立編碼的 entropy。
  2. LZ77 回溯參照:從圖片的其他位置複製一連串像素。
  3. 顏色快取程式碼:使用短的乘法雜湊碼 (色彩快取) 索引)。

以下各節將詳細說明這些不同之處。

5.2.1 前置碼字面值

像素儲存後會以前置字元編碼的綠色、紅色、藍色和 Alpha 值 ( 順序)。詳情請參閱第 6.2.3 節

5.2.2 LZ77 回溯參照

回溯參照是長度距離代碼的元組:

  • 長度會指出要以掃描線順序複製多少像素。
  • 距離代碼是一個數字,指出先前發現 像素,做為複製像素。確切的對應值是 下文

長度和距離值會使用 LZ77 前置字元編碼進行儲存。

LZ77 前置字元編碼會將大型整數值分成兩個部分:前置字元 prefix 程式碼額外位元。前置字串代碼是採用熵代碼 而額外位元則依原樣儲存 (沒有熵程式碼)。

原因:這種做法可減少熵碼程式碼的儲存空間需求。此外,較大的值通常很少見,因此額外的位元會用於 在圖中呈現幾個不同的值因此,這種做法可以提高壓縮品質 。

下表表示用於儲存資料的前置字元代碼和額外位元 值的範圍不同

值範圍 前置碼 額外位元
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) 偏移量,也就是位於目前像素上方的像素 (X 方向的像素差異為 0,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]。相容性解碼器必須表示 毀損的 Bitstream。

顏色快取是大小 color_cache_size 的陣列。每個項目都會儲存一個 ARGB 顏色。系統會透過 (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;
}

這兩個符號必須不同。重複的符號是允許的,但效率不佳。

注意:另一種特殊情況是「所有」前置字元代碼長度為「0」( 空白前置字元代碼)。舉例來說,假設您在 也沒有回溯參照同樣地,如果同一個元資料前置字元代碼中的所有像素都是使用顏色快取產生,則 Alpha、紅色和藍色的前置字元代碼可以為空白。不過,本例不需要特殊處理 空白前置字元代碼可以像含有單一符號 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 個程式碼長度。

  • 程式碼 [0..15] 表示文字程式碼長度。
    • 值 0 表示未經過編碼。
    • 值 [1..15] 表示各個代碼的位元長度。
  • 程式碼 16 會重複前一個非零值 [3..6] 次,也就是 3 + ReadBits(2) 次。如果在非零值傳出之前使用代碼 16,系統會重複 8 的值。
  • 程式碼 17 會輸出長度為 [3..10] 的零串,也就是 3 + ReadBits(3) 次。
  • 程式碼 18 會發出連續 0s 的零 [11..138],也就是 11 + ReadBits(7) 次。

程式碼長度讀取完畢後,各符號類型 (A、R、G、B 和 距離) 以各自的字母大小組成。

「一般代碼長度代碼」必須編寫完整決策樹的程式碼,也就是 所有非零代碼的 2 ^ (-length) 都必須只有一個。不過,這項規則有一個例外狀況,就是單一葉節點樹狀結構,其中葉節點值標示為值 1,其他值則為 0。

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_groupsPrefixCodeGroup (大小為 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 的解釋取決於其值:

  1. 如果 S <256 人
    1. 使用 S 做為綠色元件。
    2. 使用前置字串 #2 從 Bitstream 讀取紅色。
    3. 使用前置字元代碼 #3 從位元串流讀取藍色。
    4. 使用前置字元代碼 #4 從位元串流讀取 alpha。
  2. 如果 S >= 256 & S < 256 + 24
    1. 使用 S - 256 做為長度前置字串代碼。
    2. 從位元串流讀取額外的位元。
    3. 根據長度前置碼和讀取的額外位元,判斷回溯參照長度 L。
    4. 使用前置字元代碼 #5 讀取位元流的距離前置字元代碼。
    5. 讀取額外的位元,瞭解與位元流的距離。
    6. 根據距離前置字元程式碼和讀取的額外位元,判斷回溯參照距離 D。
    7. 從從目前位置減去 D 像素開始的像素序列,以掃描線順序複製 L 像素。
  3. 如果 S >= 256 + 24
    1. 使用 S - (256 + 24) 做為色彩快取的索引。
    2. 從該索引的色彩快取取得 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