webp-rust 0.1.0

Pure Rust implementation of a WebP encoder and decoder
Documentation
# WebP 実装概要

この文書は、この crate の WebP 実装を読むための日本語 overview です。
目的は次の 3 つです。

- WebP のフォーマットを RFC 9649 ベースで整理する
- decoder / encoder をどの順で実装すべきかを示す
- このリポジトリのソース配置と責務を対応付ける

この crate は pure Rust の WebP 実装です。現状の大きな範囲は次のとおりです。

- decode: still lossy (`VP8`), still lossless (`VP8L`), alpha, animation compositing
- encode: still lossy (`VP8`), still lossless (`VP8L`)
- library の画像出力: RGBA
- example の出力: BMP

## 1. WebP とは

WebP は RIFF ベースの画像コンテナです。RFC 9649 は WebP container, animation, metadata, lossless bitstream (`VP8L`) を定義し、lossy payload としては VP8 bitstream を参照します。

WebP は大きく次の 3 形態に分かれます。

- Simple lossy: `RIFF` + `WEBP` + `VP8 `
- Simple lossless: `RIFF` + `WEBP` + `VP8L`
- Extended WebP: `RIFF` + `WEBP` + `VP8X` + optional chunks

optional chunks の代表は次です。

- `ALPH`: lossy image / frame 用の alpha plane
- `ICCP`: ICC profile
- `EXIF`: Exif metadata
- `XMP `: XMP metadata
- `ANIM`: animation 全体設定
- `ANMF`: animation の各 frame

## 2. RIFF / Chunk の基本

WebP file は RIFF container なので、まずここを正しく実装する必要があります。

- file header は `RIFF` + 32-bit little-endian size + `WEBP`
- 以降は chunk の列
- 各 chunk は `FourCC(4 bytes)` + `Chunk Size(32-bit little-endian)` + payload
- payload が奇数長なら 1 byte padding が入る

実装の第一段階は「chunk を安全に読むこと」です。codec 本体より先に、以下を必ずやるべきです。

- chunk size overflow の検査
- RIFF size と実 buffer 長の整合
- odd padding の吸収
- unknown chunk の skip
- mandatory / optional chunk の順序制約の確認

この crate では container 周りを次が担当します。

- `src/decoder/header.rs`
- `src/encoder/container.rs`
- `src/encoder/writer.rs`

## 3. Extended WebP (`VP8X`)

`VP8X` は extended header です。ここで feature flags と canvas size を持ちます。

- canvas width / height は 24-bit little-endian の `minus one` 表現
- alpha, EXIF, XMP, ICC, animation の有無を flag で持つ
- animation の場合は canvas 全体サイズの基準になる

実装上の注意点です。

- width / height は `stored + 1`
- `width * height <= 2^32 - 1` の制約がある
- `VP8X` がある場合は後続 chunk の feature と矛盾しないか確認する
- unknown flag は将来拡張に備えて無視できる形にする

この crate では `get_features()` と container parser がここを処理します。

- `src/decoder/header.rs`

## 4. Lossy WebP (`VP8 `)

lossy payload は `VP8 ` chunk に入ります。ここでの payload 自体は VP8 key frame bitstream です。

decode の流れは概ね次です。

1. frame header を読む
2. segmentation / filter / quantizer を読む
3. partition 0 と token partitions を読む
4. macroblock の intra mode を読む
5. residual coefficients を読む
6. inverse transform を適用する
7. intra prediction と合成して Y/U/V を復元する
8. loop filter をかける
9. YUV420 から RGBA に変換する

lossy 実装の要点です。

- VP8 bool coder が必要
- luma は `i16x16` または `i4x4`
- chroma は 8x8 の intra prediction
- residual は zig-zag 順の token 列
- dequant と inverse transform の順序を間違えると全面的に崩れる
- loop filter を省いても decode はできるが、参照実装と完全一致しにくい

この crate の対応箇所です。

- parser / low-level state:
  - `src/decoder/vp8.rs`
  - `src/decoder/quant.rs`
  - `src/decoder/tree.rs`
  - `src/decoder/vp8i.rs`
- lossy decode:
  - `src/decoder/lossy.rs`
- lossy encode:
  - `src/encoder/lossy/predict.rs`
  - `src/encoder/lossy/bitstream.rs`
  - `src/encoder/lossy/api.rs`

lossy encoder の実装順は、一般には次が安全です。

1. RGBA -> YUV420
2. intra prediction
3. forward transform / quantize
4. token encode
5. partition 0 encode
6. frame assemble
7. mode search
8. filter / segmentation 最適化

この crate でも `predict.rs` と `bitstream.rs` を分離して、この順序が追える構造にしています。

## 5. Alpha (`ALPH`)

`ALPH` は lossy still image または animation frame に付く alpha chunk です。

- `ALPH` は color payload とは別に alpha plane を持つ
- alpha plane には filtering がある
- alpha payload は raw または lossless-compressed の場合がある
- 実合成では color decode 後に alpha を RGBA の A channel に適用する

実装上は、`VP8 + ALPH` を `VP8L` と混同しないことが重要です。container 上は別 feature です。

この crate では次を使います。

- `src/decoder/alpha.rs`
- `src/decoder/lossy.rs`
- `src/decoder/animation.rs`

## 6. Lossless WebP (`VP8L`)

`VP8L` は lossy の `VP8 ` と完全に別の codec です。bitstream は 1-byte signature `0x2f` から始まり、その後に width / height などが続きます。

decode の高レベルな流れは次です。

1. 14-bit width / height を読む
2. transform chain を読む
3. color cache の有無を読む
4. Huffman / meta-Huffman を読む
5. token stream を decode する
6. backward references を展開する
7. transform を逆順に戻す
8. RGBA を得る

`VP8L` の重要要素は次です。

- subtract-green transform
- predictor transform
- cross-color transform
- color indexing transform (palette)
- color cache
- backward references
- Huffman / meta-Huffman

実装では「transform chain」「token stream」「entropy coding」を分けると保守しやすくなります。

この crate の対応は次です。

- decode:
  - `src/decoder/lossless.rs`
- encode:
  - `src/encoder/lossless/plans.rs`
  - `src/encoder/lossless/tokens.rs`
  - `src/encoder/lossless/entropy.rs`
  - `src/encoder/lossless/api.rs`

### 6.1 transform

lossless encode/decode で最初に理解すべきなのは transform です。

- subtract-green: R/B から G を差し引く
- predictor: 近傍画素から予測して residual を持つ
- cross-color: channel 間相関を減らす
- color indexing: palette 化して index を詰める

encoder では「どの transform を使うか」の探索が圧縮率に効きます。decode では「reverse order で正確に戻す」ことが重要です。

この crate では `plans.rs` がそれを担当します。

### 6.2 backward references / LZ77

lossless のサイズ効率を大きく左右するのは backward references です。

- literal を出すか
- color cache hit を出すか
- copy(distance, length) を出すか

この選択は単純 greedy でも動きますが、圧縮率を詰めるには cost model と traceback が必要になります。

この crate では `tokens.rs` に以下を集めています。

- match search
- window offsets
- lazy matching
- traceback
- cache-aware token build

### 6.3 Huffman / meta-Huffman

`VP8L` は単純な 1 本の Huffman tree だけではなく、tile ごとに histogram group を切る meta-Huffman を使えます。

ここで必要になるのは次です。

- token histogram の構築
- group ごとの tree 生成
- tile -> group assignment
- group 数や huffman bits の探索

この crate では `entropy.rs` にまとめています。

## 7. Animation (`ANIM` / `ANMF`)

animation は extended WebP の上に載ります。

- `ANIM` は background color と loop count
- `ANMF` は frame rectangle, duration, blend, dispose を持つ
- `Frame X`, `Frame Y` は half-pixel 単位で保存され、実座標は `2 * X`, `2 * Y`
- frame payload は `ALPH` + `VP8 ` または `VP8L` の組み合わせになれる

decode で重要なのは「frame を decode すること」より「canvas に正しく合成すること」です。

- blend: 上書きか alpha blend か
- dispose: 次 frame 前に背景へ戻すか
- frame rectangle が canvas 内に収まるか
- loop count の扱い

この crate では animation decode を次が扱います。

- `src/decoder/animation.rs`
- `src/decoder/header.rs`

現状の encoder は still image のみで、animation encode は未実装です。

## 8. Metadata (`EXIF`, `XMP `, `ICCP`)

extended WebP では metadata chunk を直接埋め込めます。

- `EXIF`
- `XMP `
- `ICCP`

この crate の top-level API は EXIF raw payload をそのまま受け取る設計です。

```rust
let webp = webp_rust::encode_lossless(&image, 6, Some(exif_bytes))?;
```

この設計にしている理由は単純です。

- metadata parser / serializer を encoder 本体から切り離せる
- caller 側が既に持っている Exif blob をそのまま流せる
- WebP container 実装は chunk を埋め込むだけで済む

対応箇所:

- `src/lib.rs`
- `src/encoder/container.rs`

## 9. この crate の読み方

実装を追う順番は次が分かりやすいです。

### 9.1 API 入口

- `src/lib.rs`
- `src/decoder/mod.rs`
- `src/encoder/mod.rs`

### 9.2 Container と feature 判定

- `src/decoder/header.rs`
- `src/encoder/container.rs`

### 9.3 Lossy decode / encode

- decode:
  - `src/decoder/vp8.rs`
  - `src/decoder/lossy.rs`
- encode:
  - `src/encoder/lossy/mod.rs`
  - `src/encoder/lossy/predict.rs`
  - `src/encoder/lossy/bitstream.rs`
  - `src/encoder/lossy/api.rs`

### 9.4 Lossless decode / encode

- decode:
  - `src/decoder/lossless.rs`
- encode:
  - `src/encoder/lossless/mod.rs`
  - `src/encoder/lossless/plans.rs`
  - `src/encoder/lossless/tokens.rs`
  - `src/encoder/lossless/entropy.rs`
  - `src/encoder/lossless/api.rs`

### 9.5 Animation / alpha

- `src/decoder/alpha.rs`
- `src/decoder/animation.rs`

## 10. 実装時の設計指針

WebP は container と codec の境界が比較的明確なので、以下の分割が保守しやすいです。

### 10.1 parser と codec を分ける

- parser は chunk 順序と feature flags を扱う
- codec は payload の bitstream を扱う

### 10.2 decode と encode を対称にしすぎない

decode は spec 準拠が最優先ですが、encode は探索と heuristic が大きいです。decode と encode を 1 対 1 に揃えようとすると設計が窮屈になります。

この crate でも次のように分けています。

- decode: codec ごとの module
- encode: search / token / entropy / frame assembly を分離

### 10.3 bit-level writer と byte-level writer を分ける

WebP 実装では両方必要です。

- byte-level:
  - RIFF
  - chunk header
  - frame header
- bit-level:
  - VP8 bool coder
  - VP8L bit writer

この crate では byte-oriented な部分を `bin-rs` ベースの `ByteWriter` に寄せています。

- `src/encoder/writer.rs`
- `src/encoder/container.rs`

一方で bit coder は codec 固有なので自前実装です。

- `src/encoder/bit_writer.rs`
- `src/encoder/vp8_bool_writer.rs`

### 10.4 先に正しさ、その後に最適化

特に encoder は、次の順で進めると安全です。

1. まず decode できる bitstream を出す
2. 参照 decoder と画素一致を取る
3. その後に mode search / transform search / traceback を入れる

lossless encode は最適化の影響範囲が広いので、以下の分割が有効です。

- transform 候補探索
- token build
- entropy coding
- candidate 比較

この構造はこの crate でもそのまま採っています。

## 11. 現状の制限

現状の主な制限です。

- library の decode 出力は RGBA のみ
- encoder は still image のみ
- lossy encoder は opaque RGBA 前提
- animation encode は未実装
- BMP は example でのみ扱う

この範囲でも container / lossy / lossless / alpha / animation compositing の主要部は読めます。

## 12. 参考資料

- RFC 9649: WebP Image Format
  - https://www.rfc-editor.org/rfc/rfc9649.html
- IETF Datatracker: RFC 9649
  - https://datatracker.ietf.org/doc/html/rfc9649
- VP8 bitstream reference mentioned by RFC 9649
  - RFC 6386: https://www.rfc-editor.org/rfc/rfc6386

## 13. 付記

WebP 実装を壊しやすい箇所は、lossy では prediction と transform、lossless では transform 順序と entropy coding、animation では canvas compositing です。バグが出たときは、まず container ではなくこの 3 層のどこでズレたかを切ると追いやすくなります。