WebP Lossless Bitstream 的規格

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 個位元組:

  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×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;
  }
}

系統會執行 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-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 圖像資料角色

我們會在五種不同角色中使用圖片資料:

  1. ARGB 圖片:儲存圖片的實際像素。
  2. 熵圖片:儲存中繼前置字元代碼 (請參閱 「解碼中繼前置字元代碼」)。
  3. 預測者圖片:儲存預測者轉換的中繼資料 (請參閱 "Predictor Transform")。
  4. 色彩轉換圖片:由 ColorTransformElement 值建立 (於「Color 轉換」中定義) 不同的建塊 圖片的部分
  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 回溯參考資料

反向參照是 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.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、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_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,且南256 + 24
    1. 請使用「S - 256」做為長度前置字元代碼。
    2. 從位元串流中讀取長度的額外位元。
    3. 根據長度前置碼和讀取的額外位元,判斷回溯參照長度 L。
    4. 使用前置字元代碼 #5 讀取位元流的距離前置字元代碼。
    5. 讀取額外的位元,瞭解與位元流的距離。
    6. 確定與距離前置字元代碼之間的反向參照距離 D 額外加入一些內容
    7. 從起始的像素序列複製 L 像素 (按照掃描順序) 把它放在目前位置減 D 像素。
  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