# MNGデコードライブラリ実装メモ（`png` crate外部依存）

`png` crateをフォークせず依存として使い、MNGのデコードライブラリを作るための参考資料。

## 1. 仕様書・参照先

| 用途 | URL |
|---|---|
| MNG 1.0 仕様（決定版） | https://libpng.org/pub/mng/spec/mng-1.0-20010209-pdg.html |
| MNG-LC（低複雑度サブセット） | https://libpng.org/pub/mng/spec/mng-lc.html |
| MNG-VLC（超低複雑度サブセット） | https://libpng.org/pub/mng/spec/mng-vlc-1.0-20010209-pdg-h20.html |
| MNGドキュメント索引 | https://libpng.org/pub/mng/mngdocs.html |
| PNG仕様（チャンク構造・CRC・フィルタ） | http://www.libpng.org/pub/png/ / https://www.w3.org/TR/png/ |
| libmng（リファレンス実装, C） | http://www.libmng.com/ |
| libmng マニュアル | https://www.mankier.com/3/libmng |
| `png` crate | https://docs.rs/png |

- 本体仕様が決定版。MNG-LC / MNG-VLC / JNG は本体から派生した、実装者向けの便宜的なサブセット。
- 書籍「PNG: The Definitive Guide」の第12章にもMNGの解説がある。
- 仕様は1.0（2001年1月確定）で、安定版として扱われている。

## 2. 実装の段階（公式ドキュメントの推奨経路）

1. **MNG-VLC**（JNGなし）から始める
2. **MNG-LC** に拡張
3. **JNG** 対応を追加
4. 本体仕様の残りの機能（Delta-PNG等）

VLC/LCの範囲なら、フレーム内の画像はすべて通常のPNGなので `png` crateだけで完結する。

## 3. ファイル構造

- シグネチャ: `8A 4D 4E 47 0D 0A 1A 0A`（8バイト）
- 以降はPNGと同じチャンク列: `長さ(4B) | 型(4B) | データ | CRC(4B)`
- `MHDR` で始まり `MEND` で終わる
- MNGは宣言的な記述。各フレームを構成する要素が並ぶだけで、画面更新の手順は復号側に任される。単純な実装は、現れた順にフレームバッファへ合成していく手続き的な扱いでよい。
- `MHDR` の `ticks_per_second` が0の場合、MNG-VLCではデータストリーム全体が1フレームになる（他のプロファイルの扱いは仕様書参照）。
- 復号側は、MNGの中に埋め込まれたPNG（およびJNG）を復号できなければならない。

### 主なチャンク（概要。正確な定義は仕様書）

| チャンク | 役割 |
|---|---|
| `MHDR` / `MEND` | ヘッダ / 終端 |
| `DEFI` | オブジェクト定義（ID・位置・可視性） |
| `FRAM` | フレーム境界・遅延 |
| `BACK` | 背景 |
| `MOVE` / `CLIP` / `SHOW` | 移動 / クリップ / 表示制御 |
| `LOOP` / `ENDL` / `TERM` | ループ / 終了動作 |
| `SAVE` / `SEEK` | 保存・シーク |
| `CLON` / `PAST` / `DISC` | 複製 / 貼り付け / 破棄 |
| `DHDR` 系 | Delta-PNG |
| `nEED` | 必要機能の宣言（未対応なら失敗させる判断材料） |

VLC/LCに含まれるチャンクは、それぞれのサブセット仕様の表で確認すること。

## 4. `png` crateとの連携

`png` はPNGシグネチャを要求するので、MNGファイルは直接渡せない。

1. MNGのチャンク列を自前でパースする
2. 埋め込み画像の `IHDR`〜`IEND` のチャンクをそのまま連結する
3. 先頭にPNGシグネチャを付けて `png::Decoder` に渡す

チャンクのバイト列（長さ・型・データ・CRC）は無変更なので、CRCも有効なまま。

```rust
const PNG_SIG: [u8; 8] = [0x89, b'P', b'N', b'G', 0x0D, 0x0A, 0x1A, 0x0A];

struct Chunk<'a> { ty: [u8; 4], raw: &'a [u8], data: &'a [u8] } // raw = 長さ+型+データ+CRC

fn chunks(mut buf: &[u8]) -> impl Iterator<Item = Chunk<'_>> {
    std::iter::from_fn(move || {
        if buf.len() < 12 { return None; }
        let len = u32::from_be_bytes(buf[0..4].try_into().ok()?) as usize;
        let total = len.checked_add(12)?;
        if buf.len() < total { return None; }
        let (raw, rest) = buf.split_at(total);
        buf = rest;
        Some(Chunk { ty: raw[4..8].try_into().ok()?, raw, data: &raw[8..8 + len] })
    })
}

fn decode_embedded(chunks: &[&[u8]]) -> Result<(png::OutputInfo, Vec<u8>), png::DecodingError> {
    let mut stream = PNG_SIG.to_vec();
    chunks.iter().for_each(|c| stream.extend_from_slice(c)); // IHDR..IEND
    let mut reader = png::Decoder::new(std::io::Cursor::new(stream)).read_info()?;
    let mut buf = vec![0; reader.output_buffer_size()]; // 0.18系はOptionを返す。要確認
    let info = reader.next_frame(&mut buf)?;
    Ok((info, buf))
}
```

### 注意点

- **チャンク順序**: PNG仕様の順序制約（`IHDR` → `gAMA`等 → `PLTE` → `tRNS` → `IDAT`）に従うこと。MNGではパレット等が画像の外側に置かれる場合があるので、その際は適切な位置に注入する。
- **出力形式の正規化**: パレットやtRNSの展開には `Decoder::set_transformations(png::Transformations::EXPAND)` などを使う。
- **バージョン差**: `png` crateのAPIは0.17系と0.18系で差がある。`docs.rs/png` で使う版を確認すること。

## 5. 追加の実装が必要な領域

### Delta-PNG（`DHDR`, `PROM` 等）
- `IDAT` の意味が通常のPNGと異なり、既存オブジェクトへの差分を表す。
- `DHDR` から `IHDR` を合成し、差分適用を自前で書く必要がある。実装コストが大きい。

### JNG
- JPEGデコーダが別途必要（`zune-jpeg` 等が候補。要調査）。
- 構成チャンク: `JHDR`, `JDAT`（JPEG本体）, `JDAA`（JPEG形式のアルファ）, `IDAT`（PNG形式のアルファ）, `JSEP`。
- アルファがPNG形式（`IDAT`）の場合は、グレースケールPNGとして `png` crateに渡せる。`JDAA`（JPEG形式）はJPEGデコーダで復号する。

## 6. 信頼できない入力への備え

- チャンク長・フレーム数・キャンバスサイズ・ループ回数の上限を設ける
- 合成用バッファの確保前にサイズを検証する（`checked_mul` 等）
- `nEED` で未対応機能が要求された場合は明示的にエラーにする

## 7. 検証

- libmngを使うツールの出力と比較する（GIMPやQtのimageformatsはlibmngに依存している）
- サブセットごと（VLC → LC → JNG → 全機能）にテストデータを分けて段階的に確認する
