# 帳票設計書 10-画像EXR

## 概要

本ドキュメントは、ImageクラスのデータをOpenEXR形式でファイルに保存する帳票の設計書である。

### 本帳票の処理概要

本帳票は、Godotエンジン内部のImageクラスデータをOpenEXR形式でファイルに保存する機能を提供する。HDR（High Dynamic Range）画像、ライトマップ、環境マップなど、広いダイナミックレンジを必要とする画像データを高精度で保存する。

**業務上の目的・背景**：EXR（OpenEXR）は映画・ゲーム業界で広く使われるHDR画像フォーマットである。通常の8ビット画像では表現できない明るさの範囲（太陽光から暗闘まで）を16ビット/32ビット浮動小数点で記録できる。ライトマップのベイク結果、HDR環境マップ、物理ベースレンダリング用テクスチャなどに使用される。

**帳票の利用シーン**：主に以下のシーンで利用される。1) ライトマップのベイク結果保存、2) HDR環境マップの出力、3) 浮動小数点テクスチャの保存、4) ポストプロセス用中間画像の出力。

**主要な出力内容**：
1. 浮動小数点ピクセルデータ（Half/Float）
2. 複数チャンネル（R/G/B/A または Y）
3. PIZ圧縮されたデータ
4. EXR標準ヘッダー

**帳票の出力タイミング**：Image::save_exr()メソッドが呼び出された際、またはImage::save_exr_to_buffer()でバッファに出力される。

**帳票の利用者**：テクニカルアーティスト、ライティングアーティスト、エンジン内部処理

## 帳票種別

画像エクスポート / HDRデータ出力

## 利用画面

| 画面No | 画面名 | URL/ルーティング | 出力操作 |
|--------|--------|-----------------|---------|
| - | スクリプトAPI | Image::save_exr(path, grayscale) | GDScript/C#から呼び出し |

## 出力形式

### 基本仕様

| 項目 | 内容 |
|-----|------|
| ファイル形式 | OpenEXR |
| 用紙サイズ | N/A（画像ファイル） |
| 向き | N/A |
| ファイル名 | 呼び出し側で指定 |
| 出力方法 | ファイルパス指定またはバッファ出力 |
| 文字コード | N/A（バイナリ） |

### EXR固有設定

| 項目 | 内容 |
|-----|------|
| 圧縮方式 | PIZ（ロスレス） |
| ピクセルタイプ | Half（16bit float）またはFloat（32bit float） |
| チャンネル | RGBA（カラー）または Y（グレースケール） |
| エンディアン | リトルエンディアン |

## 帳票レイアウト

### レイアウト概要

EXRファイルはOpenEXR仕様に従う。

```
┌─────────────────────────────────────────────────────────────┐
│              EXRマジックナンバー（4バイト）                    │
├─────────────────────────────────────────────────────────────┤
│              バージョン情報                                   │
├─────────────────────────────────────────────────────────────┤
│              ヘッダー（チャンネル情報、サイズ等）               │
├─────────────────────────────────────────────────────────────┤
│              オフセットテーブル                               │
├─────────────────────────────────────────────────────────────┤
│              ピクセルデータ（PIZ圧縮）                        │
└─────────────────────────────────────────────────────────────┘
```

### ヘッダー部

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | channels | チャンネル情報 | Image::get_format()から判定 | EXRChannelInfo配列 |
| 2 | compression | 圧縮方式 | 固定値 | TINYEXR_COMPRESSIONTYPE_PIZ |
| 3 | dataWindow | データ範囲 | Image::get_width/height() | Box2i |
| 4 | displayWindow | 表示範囲 | 同上 | Box2i |
| 5 | lineOrder | ライン順序 | 固定値 | INCREASING_Y |
| 6 | pixelAspectRatio | ピクセルアスペクト比 | 固定値 | 1.0 |

### 明細部（ピクセルデータ）

| No | 項目名 | 説明 | データ取得元 | 表示形式 |
|----|-------|------|-------------|---------|
| 1 | チャンネルデータ | 各チャンネルのピクセル値 | Image::get_data() | Half/Float配列 |

## 出力条件

### 抽出条件

| 条件名 | 説明 | 必須 |
|-------|------|-----|
| 有効な画像 | Imageが空でない | Yes |
| 対応フォーマット | is_supported_format()がtrueを返す | Yes |

### 対応フォーマット

| フォーマット | ソースピクセルタイプ | 出力ピクセルタイプ |
|------------|------------------|-----------------|
| FORMAT_RF | SRC_FLOAT | TINYEXR_PIXELTYPE_FLOAT |
| FORMAT_RGF | SRC_FLOAT | TINYEXR_PIXELTYPE_FLOAT |
| FORMAT_RGBF | SRC_FLOAT | TINYEXR_PIXELTYPE_FLOAT |
| FORMAT_RGBAF | SRC_FLOAT | TINYEXR_PIXELTYPE_FLOAT |
| FORMAT_RH | SRC_HALF | TINYEXR_PIXELTYPE_HALF |
| FORMAT_RGH | SRC_HALF | TINYEXR_PIXELTYPE_HALF |
| FORMAT_RGBH | SRC_HALF | TINYEXR_PIXELTYPE_HALF |
| FORMAT_RGBAH | SRC_HALF | TINYEXR_PIXELTYPE_HALF |
| FORMAT_R8 | SRC_BYTE | TINYEXR_PIXELTYPE_HALF（変換） |
| FORMAT_RG8 | SRC_BYTE | TINYEXR_PIXELTYPE_HALF（変換） |
| FORMAT_RGB8 | SRC_BYTE | TINYEXR_PIXELTYPE_HALF（変換） |
| FORMAT_RGBA8 | SRC_BYTE | TINYEXR_PIXELTYPE_HALF（変換） |

### ソート順

N/A（ピクセル順に出力）

### 改ページ条件

N/A（バイナリファイルのため改ページなし）

## データベース参照仕様

### 参照テーブル一覧

本帳票はデータベースを参照せず、Imageオブジェクトからデータを取得する。

| データソース | 用途 | 取得方法 |
|-----------|------|---------|
| Image | 画像データ本体 | get_data() |
| Image | 画像サイズ | get_width(), get_height() |
| Image | フォーマット | get_format() |

## 計算仕様

### 計算項目一覧

| 項目名 | 計算式 | 端数処理 | 備考 |
|-------|-------|---------|------|
| 8bit→Half変換 | Math::make_half_float(byte / 255.0f) | N/A | バイト画像のHalf化 |
| PIZ圧縮 | tinyexr内部 | N/A | ロスレス圧縮 |
| チャンネルデインターリーブ | ピクセルごとに分離 | N/A | RGBAからR,G,B,A配列へ |

## 処理フロー

### 出力フロー

```mermaid
flowchart TD
    A[save_exr呼び出し] --> B[save_exr_buffer呼び出し]
    B --> C{is_supported_format?}
    C -->|No| D[エラーメッセージ出力]
    D --> E[空のVector返却]
    C -->|Yes| F[EXRHeader/Image初期化]
    F --> G[チャンネル数判定]
    G --> H[ターゲットピクセルタイプ判定]
    H --> I[チャンネルデインターリーブループ]
    I --> J{ソースタイプ判定}
    J -->|SRC_FLOAT| K[そのままコピー]
    J -->|SRC_HALF| L[そのままコピー]
    J -->|SRC_BYTE| M[Half変換してコピー]
    K --> N[チャンネル情報設定]
    L --> N
    M --> N
    N --> O[SaveEXRImageToMemory呼び出し]
    O --> P{エラー?}
    P -->|Yes| E
    P -->|No| Q[Vectorにコピー]
    Q --> R[FileAccess::open]
    R --> S[store_buffer]
    S --> T[OK返却]
    E --> U[終了]
    T --> U
```

## エラー処理

### エラーケース一覧

| エラー種別 | 発生条件 | 表示メッセージ | 対処方法 |
|----------|---------|--------------|---------|
| 非対応フォーマット | is_supported_format() == false | "Image format not supported for saving as EXR. Consider saving as PNG." | print_error()、空Vector返却 |
| グレースケールエラー | grayscale && channel_count != 1 | なし | ERR_FAIL_COND_V |
| 保存失敗 | SaveEXRImageToMemory失敗 | "Saving EXR failed." | print_error()、ERR_FILE_CANT_WRITE |
| ファイルオープン失敗 | FileAccess::open失敗 | なし | ERR_FAIL_COND_V、ERR_FILE_CANT_WRITE |

## パフォーマンス要件

| 項目 | 内容 |
|-----|------|
| 想定データ件数 | 単一画像 |
| 目標出力時間 | 画像サイズに依存（数十ms〜数秒） |
| 同時出力数上限 | スレッド依存 |

## セキュリティ考慮事項

- 出力先パスはユーザー/スクリプトが指定
- ファイルシステム権限に依存
- EXRファイルは通常のビューアーで表示できない場合がある

## 備考

- tinyexrライブラリを使用してEXRエンコード
- PIZ圧縮はロスレスで高い圧縮率を実現
- チャンネル順序はBGRA（Blender/GIMP互換）
- grayscaleオプションでYチャンネルのみ出力可能
- 8ビットフォーマットはHalfに変換される（EXRは8bitをサポートしない）

---

## コードリーディングガイド

本帳票を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: 関数ポインタとエントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | image.h | `core/io/image.h` | save_exr_func関数ポインタ宣言（202行目） |

#### Step 2: EXR保存処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | save_exr()（287-299行目） |
| 2-2 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | save_exr_buffer()（147-285行目） |

**主要処理フロー**:
1. **287-288行目**: save_exr_buffer()を呼び出してバッファ生成
2. **289-291行目**: バッファが空ならエラー返却
3. **293-294行目**: FileAccess::openでファイルを開く
4. **295行目**: store_buffer()でデータ書き込み

#### Step 3: バッファ生成処理を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | save_exr_buffer()内部処理 |

**主要処理フロー**:
- **148-155行目**: is_supported_format()でフォーマットチェック
- **157-161行目**: EXRHeader/EXRImage初期化
- **181行目**: get_channel_count()でチャンネル数取得
- **185-190行目**: ターゲットピクセルタイプ判定
- **199-259行目**: チャンネルデインターリーブ処理
- **262-270行目**: EXRヘッダー設定
- **271行目**: TINYEXR_COMPRESSIONTYPE_PIZ設定
- **276行目**: SaveEXRImageToMemory()でメモリに保存

#### Step 4: フォーマット判定を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | is_supported_format()（41-61行目） |
| 4-2 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | get_source_pixel_type()（70-90行目） |
| 4-3 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | get_target_pixel_type()（92-112行目） |
| 4-4 | image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | get_channel_count()（124-145行目） |

### プログラム呼び出し階層図

```
Image::save_exr(path, grayscale)
    │
    └─ save_exr_func(path, this, grayscale)
           │ [関数ポインタ経由]
           │
           └─ save_exr(path, img, grayscale)
                  │
                  ├─ save_exr_buffer(img, grayscale)
                  │      │
                  │      ├─ is_supported_format(format)
                  │      │
                  │      ├─ get_channel_count(format)
                  │      │
                  │      ├─ get_target_pixel_type(format)
                  │      │
                  │      ├─ get_source_pixel_type(format)
                  │      │
                  │      ├─ チャンネルデインターリーブ
                  │      │      │
                  │      │      ├─ [SRC_FLOAT] そのままコピー
                  │      │      ├─ [SRC_HALF] そのままコピー
                  │      │      └─ [SRC_BYTE] Math::make_half_float()変換
                  │      │
                  │      ├─ EXRHeader設定
                  │      │
                  │      └─ SaveEXRImageToMemory()
                  │
                  ├─ FileAccess::open(path, WRITE)
                  │
                  └─ ref->store_buffer(buffer)
```

### データフロー図

```
[入力]                        [処理]                         [出力]

Image ────────────────────▶ get_data() ──────────────────▶ ピクセルデータ
      │                                                    (インターリーブ)
      │                                                         │
      ├─ get_format() ────▶ is_supported_format() ─────────────▶│
      │                                                         │
      │                   get_channel_count() ─────────────────▶│
      │                                                         │
      │                   get_source_pixel_type() ─────────────▶│
      │                                                         │
      │                   get_target_pixel_type() ─────────────▶│
      │                                                         │
      │                                                         │
      │                   チャンネルデインターリーブ
      │                         │
      │                         ├─ R チャンネル
      │                         ├─ G チャンネル
      │                         ├─ B チャンネル
      │                         └─ A チャンネル
      │                                │
      │                   SaveEXRImageToMemory()
      │                                │
      │                                ▼
      │                         [EXR バイナリ]
      │                                │
      │                   FileAccess::store_buffer()
      │                                │
      └─────────────────────────────▶ [.exr ファイル]
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| image.h | `core/io/image.h` | ヘッダー | Imageクラス定義、save_exr_func宣言 |
| image_saver_tinyexr.h | `modules/tinyexr/image_saver_tinyexr.h` | ヘッダー | save_exr()、save_exr_buffer()宣言 |
| image_saver_tinyexr.cpp | `modules/tinyexr/image_saver_tinyexr.cpp` | ソース | EXR保存処理実装 |
| register_types.cpp | `modules/tinyexr/register_types.cpp` | ソース | save_exr_func関数ポインタ設定 |
| tinyexr.h | `thirdparty/tinyexr/tinyexr.h` | ヘッダー | tinyexrライブラリヘッダー |
| file_access.h | `core/io/file_access.h` | ヘッダー | ファイルI/O基盤クラス |
| math_funcs.h | `core/math/math_funcs.h` | ヘッダー | Math::make_half_float()定義 |
