Newer
Older
SmTIAS-LightSim / src / sm_tias_lightsim / render / output.py
"""HDR 画像出力モジュール.

レンダリング結果の HDR float 配列を PNG または luminance.npy として保存する.
"""

from __future__ import annotations

from pathlib import Path

import numpy
from PIL import Image

from sm_tias_lightsim.uniformity import REC709_COEFF_B, REC709_COEFF_G, REC709_COEFF_R


def save_luminance(image: numpy.ndarray, path: Path) -> None:
    """HDR 画像を Rec.709 輝度に変換して .npy として保存する.

    uniformity.py の Rec.709 係数定数を再利用する(DRY).
    HDR 値域(0〜∞)をそのまま保持するため clip は行わない.

    Args:
        image: HDR 画像 (H, W, 3) float.
        path: 出力 .npy ファイルパス.
    """
    luminance = (
        REC709_COEFF_R * image[..., 0]
        + REC709_COEFF_G * image[..., 1]
        + REC709_COEFF_B * image[..., 2]
    )
    numpy.save(path, luminance.astype(numpy.float64))


def save_png(image: numpy.ndarray, path: Path, gain: float = 1.0) -> None:
    """HDR 画像をトーンマップして 8-bit PNG として保存する.

    線形スケール(gain 倍)して [0, 1] にクリップ後,uint8 [0, 255] に変換する.
    物理的に妥当なトーンマッピングではなく,目視確認・デバッグ用途の簡易実装.

    Args:
        image: HDR 画像 (H, W, 3) float.
        path: 出力 PNG パス.
        gain: 露出補正係数.既定 1.0.
    """
    # gain を掛けて [0, 1] にクリップし uint8 化
    tonemapped = numpy.clip(image * gain, 0.0, 1.0)
    uint8_image = (tonemapped * 255.0).astype(numpy.uint8)
    pil_image = Image.fromarray(uint8_image, mode="RGB")
    pil_image.save(str(path))