toruのブログ

月1くらいで記事書きたい。

OpenImageIO を使って PNG画像に HDRのメタデータを埋め込んでみた

背景

目的

  • OpenImageIO を使って PNG に CICP情報を埋め込み HDR画像にする
    • 埋め込む情報は CICP のみとする
    • 実は PNGHDR対応では以下のチャンクも追加されてるのだが、これらの検証は面倒なので省略する
  • その後に Webブラウザで表示を行い、狙い通りの結果が得られるか確認する

結論

  • リリース版ではなく、最新版の OpenImageIO を使うことで PNG 画像への CICP の埋め込みに成功した
  • また Webブラウザでも狙い通りの表示ができた
Python での実装例

OpenImageIO の Pythonラッパーを使い CICP の埋め込みをしたコードの例を以下に示す。

import numpy as np
from OpenImageIO import (
    ImageSpec,
    ImageOutput,
    UINT16
)

width = 1920
height = 1080
output_filename = "./P3D65_ST2084_with_CICP.png"

# create ramp image
line = np.linspace(0.0, 1.0, width)
img = line.reshape(1, -1, 1).repeat(3, axis=2).repeat(height, axis=0)

# output png via oiio
output = ImageOutput.create(filename=output_filename)
yres, xres, channels = img.shape
image_spec = ImageSpec(xres, yres, channels, UINT16)
image_spec.attribute("CICP", "int[4]", [12, 16, 0, 1])
output.open(filename=output_filename, spec=image_spec)
output.write_image(img)
output.close()
cICP チャンクの確認

PNG画像に cICP チャンクが埋め込まれているかは pngcheck というツールで確認できる。 上記のコードで作成したファイルに対して pngcheck で確認した様子を以下に示す。

$ pngcheck P3D65_ST2084_with_CICP_oiio.png 
  CICP(Decimal) = 12, 16, 0, 1
P3D65_ST2084_with_CICP_oiio.png cICP   P3D65-PQ 
OK: P3D65_ST2084_with_CICP_oiio.png (1920x1080, 48-bit RGB, non-interlaced, static, 99.3%).
AVIF と PNGHDR画像比較

テストパターン画像を CICP付きの AVIF と CICP付きの PNG にそれぞれ変換し、比較表示した結果を以下に示す。 同じ表示となることが分かり、PNG の CICP が正しく解釈されることを確認できる。

同一ソースを AVIF と PNG にして比較したもの。左: AVIF、右: PNG

なお、確認には Windows 11 の Microsoft Edge を使ったのだが、OS の HDR設定は 筆者の過去記事の環境 に従った。ご注意頂きたい。

補足情報

CICP について

筆者の過去記事の「4.1. HDR の静止画ファイルに必要なメタデータについて」を参照。

trev16.hatenablog.com

CICP に対応した最新版の OpenImageIO のビルドについて

結論に書いた通り、ブログ執筆時点ではリリース版の OpenImageIO が PNG の CICP 埋め込みに対応していなかった。そのため試すには自前でビルドが必要である。 ビルド方法については公式ページの INSTALL.md を参照して頂きたい。

ただし、libpng に関しては 1.6.45 以降 のバージョンが必要となるので注意して頂きたい。

参考までに筆者の Dockerfile へのリンク を残しておく。

OpenImageIO の使い方に関する補足情報

公式ドキュメントの 最新版 に必要な情報はまとまっているが、 初見だと分かりにくい箇所もあるので、筆者が躓いた箇所を中心に少し補足説明する。

ImageSpec インスタンスに設定できる Attribute 情報について

冒頭のサンプルコードで示した通り、CICP情報は ImageSpec インスタンスに対して attribute メソッドを使って設定する。

image_spec = ImageSpec(xres, yres, channels, UINT16)
image_spec.attribute("CICP", "int[4]", [12, 16, 0, 1])

attribute メソッドの基本的な使い方は Arbitrary metadata のぺージで確認できる。 ただし、設定可能なパラメータはファイルフォーマット毎に異なっており、それは Bundled ImageIO Plugins のページで確認できる。

例えば PNG は以下のようになっている。

ブログ執筆時点での PNG の ImageSpec Attribute 一覧

OpenImageIO で各種メタデータを埋め込む時は、これらのドキュメントを参照すると効率よくコードが書けると考える。

感想

ようやく PNGHDR画像を簡単に作れるようになった。これにより Webブラウザ上でメジャーな(?) AVIF / Ultra HDR / PNG での HDR表示の比較が可能となった。*1

実は既に軽く確認を始めており、Chromium系のブラウザでは不審な挙動を観測したので、どこかで記事にまとめたい。

参考資料

[1] W3C, "Portable Network Graphics (PNG) Specification (Third Edition)", https://www.w3.org/TR/png-3/
[2] Chris Lilley, "cICP in PNG, explained", https://svgees.us/blog/cICP.html
[3] W3C, "PNG, Third Edition: Implementation Report", https://w3c.github.io/png/Implementation_Report_3e/

*1:個人的な本命は JPEG XL なんだけど、Google さん何でサポート辞めちゃったんですか…

Ultra HDR の HDR画像を真面目に作ってみた

1. 背景

  • 筆者は昨年に Ultra HDR の画像を作って HDR表示ができるか確認をした

trev16.hatenablog.com

  • しかし、この時は「表示が HDRとなるか」に注目しており、正しい輝度で表示されるかは未確認だった
  • そこでもう少し真面目に Ultra HDRHDR画像を作り、生成された HDR画像の輝度を確認することにした

2. 目的

  • Ultra HDRフォーマットの HDR画像を真面目に作る
  • 作成時の注意事項や参考情報などがあれば記しておく

3. おことわり

Gain Map の専門用語について

本記事では Ultra HDR で使われる Gain Map の専門用語が出てきます。ただ、過去記事で説明した専門用語については本記事では改めて説明しない場合が多いです。詳しく知りたい方は過去記事か Adobe/Apple が作成した Gain Map の仕様書 を参照ください。

trev16.hatenablog.com

本記事に添付した HDR画像について

静止画の HDR画像は OS側の設定によって表示が変わってしまうことが多いです。本記事に添付した HDR画像は以下の記事の「3.3. 静止画の HDRコンテンツの加工を防ぐ方法」を適用した場合にのみ、筆者の意図通りに表示されます。ご了承ください。

trev16.hatenablog.com

4. 結論

4.1. 正しい輝度で生成できるか

  • libultrahdr のultrahdr_appScenario 4 を使うことで概ね狙い通りの輝度で生成が可能 *1
    • ブラウザで表示すると微妙にズレるのだが原因は分からなかった *2
    • 別途 ultrahdr_appの Scenario 1 も試したが、こちらは狙った輝度にならなかったので筆者は利用を控えることにした
  • 作成した Ultra HDR画像の例を図1、図2、図3 に示す
    • 図1 は HDR Capacity を 1.000 に設定したもの。400 nits 以上のモニターなら HDRとして表示される
    • 図2 は HDR Capacity を 5.622 に設定したもの。10000 nits 未満のモニターなら HDR と SDR がブレンドされて表示される
    • 図3 は AVIF の ISO HDR と Ultra HDR (HDR Capacity: 1.0) の比較。ISO HDR と変わらずに表示されているのが分かる
図1. 左側は参考の SDR画像。右側が HDR Capacity を 1.0 に設定した Ultra HDR画像。


図2. 左側は参考の SDR画像。右側が HDR Capacity を 5.622 に設定した Ultra HDR画像。SDR と HDRブレンド画像が表示される


図3. 左側は ISO HDR の AVIF画像。右側が HDR Capacity を 1.0 に設定した Ultra HDR画像。筆者の環境では同じ表示となる。

4.2. 注意事項

ultrahdr_appの scenario 4 では Gain Map 関連のメタデータを以下の形式で .cfg ファイルとして技術する必要がある。

--maxContentBoost 9.543677312075925 14.438654173044087 15.327744617484997
--minContentBoost 0.9832689487170947 0.983268948717095 0.9734345880389386
--gamma 1.0 1.0 1.0
--offsetSdr 0.0078125 0.0078125 0.0078125
--offsetHdr 0.0078125 0.0078125 0.0078125
--hdrCapacityMin 1.0
--hdrCapacityMax 4.924577653379664
--useBaseColorSpace 1

しかしlibultrahdr側の 文字列の定義に誤りがあった ため注意が必要であった。 .cfg ファイルに記載が必要な各種パラメータのうち、--hdrCapacityMin--hdrCapacityMaxは文字列通りに値を記述すると破綻した。以下に示すように Log2 ではなく Linear値での記述が必要であった。

文字列 公式ドキュメント での定義 実際に記載すべき値
hdrCapacityMin weight_factorの計算に使用するdisplay boostの最小値をLog2したもの weight_factorの計算に使用するdisplay boostの最小値の Linear値
hdrCapacityMax weight_factorの計算に使用するdisplay boostの最大値をLog2したもの weight_factorの計算に使用するdisplay boostの最大値の Linear値

4.3. 参考情報

Gain Map のメタデータは APP1 に XMP形式で入れる場合と、APP2 に ISO 21496-1 形式で入れる場合の2通りがある。公式ドキュメントから引用した図を以下に示す。

説明
図4. APP1 に XMP 形式で記述する例
図5. APP2 に ISO 21496-1 形式で記述する例

libultrahdr v1.4.0 では 標準設定でビルド すると APP2 に ISO 21496-1 形式で入れる方法が選択され、XMP の方は無効化 される。今後は他の画像フォーマットでも ISO 21496-1 形式が主流になると推測する(既になってるかもしれないが)。

5. 詳細

ここでは、結論で述べた内容を補足する情報を記述していく。

5.1. 使用した libultrahdr のバージョン

現時点での最新リリースである v1.4.0 を使用した。

ビルドや動作は Docker を使いコンテナ上で実施した。Dockerfile の内容は リンク先 を参照(root権限ゴリ押し環境なので注意)。

5.2. libultrahdr のultrahdr_appの概要

C++ のコードを書かなくても Ultra HDR の画像を簡単に生成可能とするコマンドラインアプリである。

Scenario 0~4 の 5種類の方法 で Ultra HDR 画像を生成できる。

筆者は Gain Map のメタデータを細かく設定したかったので、それが可能な Scenario 4 で Ultra HDR画像を作成した。

5.3. ultrahdr_appの Scenario 4 で画像を作る方法

必要なものは以下の 4つである。

  • ① SDR画像
  • HDR画像
  • ③ Gain Map画像 (① と ② から作成)
  • ④ Metadata を記述した .cfg ファイル

少し長くなるが、それぞれの準備の様子を述べていく。

5.3.1. SDR画像(①) とHDR画像(②)の作成について

これは AVIF の Gain Map 検証時 に作成した画像を流用した。

DaVinci Resolve を使い Linear空間で 0~10000 nits の輝度レンジを持つ画像を作り、ファイル吐き出し時に HDR版と SDR版を生成する方式をとった。 HDR版は 0~10000 nits の範囲を ST 2084 で吐き出し、SDR版は 0~203 nits の範囲を 100/203 倍してから sRGB で吐き出した。

100/203 倍している理由は、Windows では HDR Reference White が 203 nit であり、Reference環境 *3 では SDR画像は 勝手に 2.03倍の明るさで表示される ので、それを打ち消す係数を適用するためである。

作成した SDR画像、HDR画像を以下に示す。

sdr hdr
図6. SDR画像 図7. HDR画像
5.3.2. Gain Map画像(③) の作成について

Gain Map画像の作成は基本的には仕様書に従って計算するだけなのだが、注意すべき点が2つあったため以下に記しておく。

それは「色域」と「SDR画像の輝度」である。

まずは「色域」について。色域は SDR も HDR も Rec.2020 で固定化し、Rec.709 と Rec.2020 が混じらないようにした。 ultrahdr_appとしては混ぜても動作するようなのだが、検証する内容を増やしたくなかったので、今回は Rec.2020 に固定化した。

次に「SDR画像の輝度」について。Gain Map の計算時に SDR/HDR 画像は両方とも絶対輝度に変換する必要がある。HDR版は ST 2084 の EOTF を適用するだけで良いのだが、 SDR版は相対輝度で定義されているため、絶対輝度への変換時は作業者が基準となる輝度を指定する必要がある。

今回は 203 nits を指定した。別の言い方をすると 8-bit の sRGB の SDR画像の 255 CV が 203 nits となるように 絶対輝度への変換を行った。理由は Windows では HDR Reference White が 203 nits であり、この値で変換しないと色々と不都合が生じるからである。

5.3.3. Metadata を記述した .cfg ファイルの作成(④) について

基本的には③の Gain Map の計算途中で得た数値を .cfg ファイルに記載するだけよい。

が、結論欄でも述べた通り--hdrCapacityMin--hdrCapacityMaxは以下の表の通りに Linear値を記述する必要があるので注意が必要であった。

文字列 公式ドキュメント での定義 実際に記載すべき値
hdrCapacityMin weight_factorの計算に使用するdisplay boostの最小値をLog2したもの weight_factorの計算に使用するdisplay boostの最小値の Linear値
hdrCapacityMax weight_factorの計算に使用するdisplay boostの最大値をLog2したもの weight_factorの計算に使用するdisplay boostの最大値の Linear値

上記で述べた誤りは「Ultra HDR 画像のメタデータ解析」と「HDR Capacity の動作の確認」によって発覚した。 それらについては、後ほど詳細を述べていく。

5.4. ultrahdr_appコマンドライン引数について

筆者がultrahdr_appを実行する時に使用したコマンドライン引数の例を以下に示す。なお、以下は図1の画像を作成した時のものである。

ultrahdr_app \
  -m 0 \
  -i ./src_png/1920x1080_sRGB_Rec.2020_0.5x_8bit.jpeg \
  -g ./gain_map_img/gain_map_1920x1080_ST2084_Rec.2020-1920x1080_sRGB_Rec.2020_0.5x.jpeg \
  -q 100 \
  -Q 100 \
  -D 1 \
  -C 2 \
  -c 2 \
  -t 2 \
  -R 1 \
  -M 1 \
  -f ./metadata/metadata_1920x1080_ST2084_Rec.2020-1920x1080_sRGB_Rec.2020_0.5x_hdr_capacity-1.000.cfg \
  -z ./gain_map_img/1920x1080_sRGB_Rec.2020_0.5x-metadata_1920x1080_ST2084_Rec.2020-1920x1080_sRGB_Rec.2020_0.5x_hdr_capacity-1.000.jpg

5.5. UltraHDR 画像のメタデータ解析について

筆者は検証中に--hdrCapacityMin--hdrCapacityMaxの動作に不信感を抱いていたため、JPEG画像に埋め込まれたメタデータの解析を行った。 具体的には .cfg で指定した値が「JPEG画像のメタデータにそのまま入っているか」を確認した。

メタデータについて少し説明する。Ultra HDR 画像のメタデータJPEG の Application Segment (APP1 や APP2) に入る仕様となっている。公式ドキュメントから引用した画像を下図に示す。

説明
図4 (再掲). APP1 に XMP 形式で記述する例
図5 (再掲). APP2 に ISO 21496-1 形式で記述する例

libavif の v1.4.0 では標準設定でコンパイルすると APP2 の ISO 21496-1 形式のみが有効化される仕様となっている。そのため、今回は ISO 21496-1 形式のメタデータを解析した。

github.com

バイナリ解析となるため、今回は ImHex という高性能なバイナリエディタを使って解析した。Ultra HDR の解析をした様子は別記事にまとめたので興味があれば読んでいただきたい。

trev16.hatenablog.com

いくつかのテストパターンで解析を行ったところ、.cfg の --hdrCapacityMin--hdrCapacityMaxは、そのままメタデータとしてファイルに埋め込まれるのではなくLog2 した値がメタデータとなることが分かった。そのため .cfg の --hdrCapacityMin--hdrCapacityMaxLog2した値を書くと 2重にLog2が適用 されてしまい、想定と異なる表示となることが分かった。

5.6. HDR Capacity の動作の確認

メタデータの解析に加えて、HDR Capacity の実際の動作も合わせて確認した。下記リンクのサイトを作り、AVIF と Ultra HDR が同じ挙動となるかを確認した。

Windows 11 上で MHC Profile と SDR content brightness を様々な値に変更することでモニターの HDR Capacity を値に変更し、モニターの HDR Capacity に合った形で Ultra HDR画像の Adaptive HDR が生成されるかを確認した。その結果、.cfg の --hdrCapacityMin--hdrCapacityMaxにはLog2ではなく Linear値を記述する必要があることを確信できた。

toru-ver4.github.io

6. 感想

Ultra HDRJPEG がベースであり不可逆圧縮前提なので自分としては扱いづらい印象である。一方で Base Rendition を SDR にした場合の後方互換性は最強なので、なんだかんだ今後は普及するのではと考えている(OpenImageIO も v3.1.4.0 で Ultra HDR をサポートするようである)。

個人的には JPEG XL が流行って欲しいのだが、Chromium で非対応となっているので覇権を取るのは難しいのかもしれない…。

7. 参考資料

*1:細かいことを言うと、確認したのは HDR Rendition の輝度のみ。SDR Rendition と HDR Rendition の中間の Adaptive HDR に関しては簡易確認しかしていない

*2:ズレの候補は ①ブラウザの問題、②Windows の問題、③libultrahdr の問題(LUT化による量子化誤差)、など色々とあったのだが、検証をしてもズレの原因を絞り込めなかった

*3:HDRコンテンツをなるべく加工させずにオリジナルのまま表示させる環境

バイナリエディタの ImHex を使ってみた

背景

  • UltraHDR の調査中に画像ファイルのメタデータを確認する必要が生じた
  • 今までは VS CodeHex Editor を使っていたが、もっと拡張性のあるツールを使わないと解析が厳しかった
  • Chat GPT 先生にバイナリエディタについて相談したところ、ImHex を勧められたので実際に使ってみた

目的

結論

  • ImHex は大変便利なバイナリエディタであった
  • Pattern editor でデータの取得、および取得したデータを使った計算ができるのは大変助かった
    • ImHex の Pattern editor で解析をしている様子を図1 に示す。
  • 一方で ImHex はとても多機能なバイナリエディタであり、使いこなすには修練が必要だと感じた

図1. ImHex の Pattern editor を使い JPEGメタデータを確認している様子

詳細

解析対象の紹介

今回、筆者が解析したのは UltraHDR形式の JPEG ファイルの Application Segment の ISO 21496-1 の Gain Map のメタデータである。

筆者は ISO 21496-1 を購入するほどのモチベーションはなかったため、メタデータの構造に関しては libultrahdr のソースコードから判断した。下記リンクのコードのuseCommonDenominator == falseかつchannelCount == 3が筆者が解析したいメタデータの内容である。

github.com

上記のソースコードから ImHex で扱えるように構造体を作った結果が以下となる*1s32u32などの型の定義については Pattern Language の Data Types の項目を参照(Pattern Language は ImHex 用に開発された専用言語である)。

// 1チャンネル分(useCommonDenominator == false のときの並び)
struct ChannelFrac {
    s32 gainMapMinN;        // streamWriteS32
    u32 gainMapMinD;        // streamWriteU32
    s32 gainMapMaxN;        // streamWriteS32
    u32 gainMapMaxD;        // streamWriteU32
    u32 gainMapGammaN;      // streamWriteU32
    u32 gainMapGammaD;      // streamWriteU32
    s32 baseOffsetN;        // streamWriteS32
    u32 baseOffsetD;        // streamWriteU32
    s32 alternateOffsetN;   // streamWriteS32
    u32 alternateOffsetD;   // streamWriteU32
};

// ヘッダ+本体(useCommonDenominator == false / channelCount == 3 固定)
struct GainmapMetadata {
    // ヘッダ
    u16 min_version;        // streamWriteU16
    u16 writer_version;     // streamWriteU16
    u8  flags;              // streamWriteU8(bit3 は 0 の想定)

    // 共通分母を使わない経路(順序に注意)
    u32 baseHdrHeadroomN;       // streamWriteU32
    u32 baseHdrHeadroomD;       // streamWriteU32
    u32 alternateHdrHeadroomN;  // streamWriteU32
    u32 alternateHdrHeadroomD;  // streamWriteU32

    // 3 チャンネル分
    ChannelFrac channels[3];
};

ImHex の Pattern editor を使ったメタデータの抽出

ImHex の画面右側には Pattern editor という画面がある。Pattern editor 内に構造体を定義して、構造体の先頭アドレスを指定すると Pattern editor に構造体の情報が表示される。言葉だけだとわかりづらいので、実際に作業をした様子を図2 の ①~③ に示す。

図2. Pattern Data で定義した構造体の情報を表示している様子

先頭アドレスは ImHex の Find タブを使って調べた。Find タブはメニューバーの View -> Find で表示できる。

UltraHDR の ISO 21496-1 のメタデータは、以下のソースコードを見るとkIsoNameSpace.c_str()の後に続いていることが分かる。これは"urn:iso:std:iso:ts:21496:-1"という文字列で定義されているので、この文字列を Find タブで検索した*2

github.com

ImHex の Pattern editor を使った簡単な計算の実施

Pattern editor では構造体の定義だけでなく、バイナリから取得したデータを使った簡単な計算も可能である。

Gain Map のメタデータは Numerator と Denominator に分かれて格納されているため、実際の値を知るには割り算が必要だったが、Pattern editor の機能を使って簡単に計算することができた。各種パラメータの値を計算、表示している様子を図3 の ①~③ に示す。

図3. Pattern editor を使って簡単な計算をしている様子

感想

凄く便利だった。ドキュメントを見ると非常に多機能であり、バイナリ解析を日常的に行う人は凄く重宝するのだと感じた。

筆者はバイナリ解析はそんなに頻繁にやらないので、すぐに使い方を忘れてしまいそうで心配である(というのもあって記事を書いた)。

参考資料

[1] Pattern Language, "Data Types", https://docs.werwolv.net/pattern-language/core-language/data-types

*1:作成は Chat GPT先生にやってもらった

*2:細かいが "urn:iso:std:iso:ts:21496:-1" の後に終端文字が 1文字入っているので、1Byte ほど間を開けたアドレスをメタデータの先頭アドレスとしている

Nintendo Switch 2 の HDR調整について軽く調べた

更新履歴

日付 内容
2025/09/07 初版作成
2025/09/08 HDR調整がゲームの映像に与える影響」のP1パラメータの変化を以下に変更 *1
"23/992 --> 46/992 --> 92/992"

"0/992 --> 17/992 --> 42/992 --> 92/992"
2025/09/08 付録に「パラメータP2に関する考察」を追記

背景

  • ついに筆者も Nintendo Switch 2 を購入することができた *2 *3
  • HDR対応ゲームを快適に楽しむために Switch 2 の HDR調整について調べたのだが、公式のサポートページ には HDR調整に関する説明が特に無かった
  • 筆者は、動作原理が分からないまま HDR調整を行うと HDR対応ゲームの映像が意図しないものとなる危険性があると考えた
  • そのため HDMI信号をキャプチャして Switch 2 の HDR調整の仕様を簡単に調べることにした
  • なお、筆者は以前に PlayStation 5 の HDR調整について調べた経験がある。参考までに過去記事のリンクを掲載しておく

trev16.hatenablog.com

目的

  • Switch 2 の HDR調整について以下の点を調べる
    • どのような調整 UI となっているか
    • HDR調整によってゲームの映像がどう変化するのか
    • HDR調整のパラメータはどのような意味を持つのか

おことわり

  • 今回の調査は Switch 2 のシステムバージョン「20.4.0」にて実施した
    • 今後のアップデートで仕様が変わる可能性があるので、あくまでも参考情報として読んで頂きたい。
  • 今回の調査は Switch 2 を TVモードにして実施した
    • 携帯モードでの挙動については何も確認していないので注意して頂きたい

結論

HDR調整の UI について

  • Switch 2 には HDR調整として 2種類の UI が用意されている
    • ① 10% Window を使いモニターの輝度値を 993段階 で調整する UI (図1)
      • 以後、この UI で調整するパラメータをP1と呼ぶ
      • P1の範囲は 0~992 である *4
    • ② ゲーム画面を見ながら画面全体の明るさを調整する UI (図2)
      • 以後、この UI で調整するパラメータをP2と呼ぶ
      • P2の範囲は筆者の独断で 0.0~1.0 と定義する *5
図1. HDR調整の UI ① 図2. HDR調整の UI ②

HDR調整がゲームの映像に与える影響

  • HDR調整の結果により画面の明るさが変化する
  • P1を 0/992 --> 17/992 --> 43/992 --> 92/992、P2を 0.0 --> 約0.5 --> 1.0 と変化させた場合の画面の様子を以下に示す
HDR調整のUI②の画面
P2 = 0.0 P2 ≒ 0.5 P2 = 1.0
P1 = 0/992 1 1 1
P1 = 17/992 1 1 1
P1 = 42/992 1 1 1
P1 = 92/992 1 1 1
マリオカートワールドの画面
P2 = 0.0 P2 ≒ 0.5 P2 = 1.0
P1 = 0/992 1 1 1
P1 = 17/992 1 1 1
P1 = 42/992 1 1 1
P1 = 92/992 1 1 1


パラメータP1に関する考察

筆者はP1は接続しているモニターのピーク輝度情報の計算に使っていると推測する。

モニターのピーク輝度情報 (nits) をLと置くと、P1Lの関係は以下だと推測する。


 \displaystyle
L = 10 \cdot P1 + 80 \ 
[nits]


  • 例:
    • P1 = 52 の場合 L = 600
    • P1 = 92 の場合 L = 1000

詳細については付録を参照。

パラメータP2について

残念ながら筆者はP2が具体的に何を意味するパラメータか分からなかった。なので結論欄で述べることは無い。

一方で少し調べた内容はあるので付録に記載をしておく。

HDR調整に関する注意事項

  • Switch 2 本体の HDR調整結果が無視されるゲームソフトも存在する
  • 例えば ホグワーツレガシー はゲームソフト内に独自の HDR調整 UI 用意しており、Switch 2 の HDR調整結果は無視された
  • Switch 2 本体の HDR調整結果を使うかどうかはゲームソフト側に委ねられていると推測する

図3. ゲームソフト内に専用の HDR調整項目がある例(Switch 2 の HDR調整は無視される)

付録

ここでは、結論欄の内容にたどり着くまでに筆者が行った作業や、ゲームをプレイした感想などを述べていく。

10% Window を使った HDR調整 UI ①の分析

筆者は結論欄でP1は 0~992 の 993段階だと述べたが、これは地道に回数をカウントして確認した。 確認した様子を動画にしたので以下に添付しておく。

続いて筆者は 1 step あたりの輝度の変化量を確認した。DaVinci Resolve を使って 右側の☀マークの Code Value と対応する輝度値を確認した結果を以下に示す。

P1 Code Value (10-bit) Luminance (nits)
0 497 80.0
1 509 90.0
2 520 100.2
3 530 110.4
4 539 120.5
5 546 128.8
52 711 593
92 770 1007.9
992 1021 9815


上記の表から以下の 3点が分かった。

  • 1 step で 10 nits 程度増えている
  • 最小輝度は 80 nits である
  • 最大輝度は約10000 nits である

上記を踏まえると HDR調整が 993段階という中途半端な数値だったことにも説明がつく。なぜならば以下の数式が成立するからである。


 \displaystyle
\frac{10000 - 80}{10} = 992


この結果から筆者は、HDR調整画面の右側の☀マークの輝度値LP1の関係性は以下であると推測した(微妙に誤差があるものの、キャプチャデバイス側の誤差だと考えて無視することにした)。


 \displaystyle
L = 10 \cdot P1 + 80
[nits]


パラメータP2に関する考察

パラメータP2について筆者が理解したことは以下の 2点である。

  • (a)P2に小さい値を設定すると画面が暗くなる (ただしP1=0/0992の場合を除く)
  • (b) ゲームソフトはP1P2を掛け算した値を使う訳ではない

(a) に関しては結論欄のスクリーンショットのテーブルを見れば自明である。

(b) に関して、筆者は初めは P1P2を掛け算した値が Switch 2 に保存されると予想していた。実際、HDR調整のUI② ではそのように見えた。

しかし、マリオカートワールドのスクリーンショットを解析したところ、その予想は外れた。パラメータを「P1=92/992, P2=0.0」にした場合と「P1=0/992, P2=1.0」にした場合とでは違いがあった。

図4. `P1=92/992`, `P2=0.0` と `P1=0/992`, `P2=1.0` の比較。両者には違いがある

むしろ、マリオカートワールドではP2を 0.0 にすると SDR になっているように見えた(すみません誤解してたので撤回します)が、そういった挙動を目にしても、筆者はP2が「何を意味するパラメータ」なのか分からなかった。

マリオカートワールドの画面について

(単なる感想です)

パラメータP2を 0.0 に下げても HDR であることが感じられる画面になっており、筆者は大変ありがたく感じている。

筆者がメインで使っている AW3225QF という OLEDモニターは、高輝度を出すことが難しく Final Fantasy 7 Rebirth では輝度の変化に相当に苦しめられた。

trev16.hatenablog.com

しかし、マリオカートワールドは HDR調整により十分に輝度を落とすことができ、かつ輝度を落とした状態でも広いダイナミックレンジが維持されている(SDRにはならない)ため、快適にHDRの画面を堪能することができている。Switch 2 を買って良かったと心の底から思っている。

使用機材の紹介

最後に筆者が HDMIキャプチャに使用した機材を説明しておく。

項目 名称 or Version
HDMI キャプチャデバイス AverMedia製 GC553G2
GC553G2 のファームウェア v1.0.8.1
キャプチャソフト Streaming Center - v1.6.65.1

感想

調査が一区切りついたので、これでやっとゲームに集中できる。いや、その前に Ultra HDR の記事を書かないと…(微妙に生じる誤差の扱いをどうすればよいか迷い中)

*1:P1にはオフセットとして 80 nits が加わることを考慮できていなかったため

*2:以後は Nintendo Switch 2 を Switch 2 と略す

*3:誇張抜きで石川県から県外に6~7回は探しに出かけていた。最終的に新潟のヨドバシカメラで購入した

*4:0~992 である根拠は後述する

*5:刻み幅は存在するはずだが、何段階か分からなかったため本記事では 0.0~1.0 の浮動小数点表記とした

AVIF の Gain Map HDR 画像を作り Chrome と Edge で表示してみる

背景

  • 2023年に Adobe/AppleGain Map という静止画の HDRに対する技術を提唱した[1]
    • 筆者はテクニカルペーパーを読んで 2024年冒頭に解説記事を投稿した[2]

trev16.hatenablog.com

  • 2025年になり Gain Map をサポートするアプリケーションが増えてきた
  • Chrome と Edge が AVIF の Gain Map をサポートしたので動作を確認することにした

目的

  • AVIF を使った Gain Map画像の作成手順を理解する
  • 作成した画像が正しくブラウザ上で表示されるか確認する
    • テストパターンと自然画の2種類で確認する
  • Gain Map を使う上での特記事項があれば情報として残す

おことわり

このページの画像を正しく表示するには、Webブラウザが AVIF フォーマットの Gain Map画像をサポートしつつ、HDR表示モードになっている必要がある。

以下の画像に「HDR: Supported」と青文字で表示されれば問題ないが、「HDR: Not Supported」と赤文字で表示された場合は、本ページのコンテンツを正しく表示できない。注意して頂きたい。

is_gain_map_hdr_supported
図1. お使いの環境が Gain Map画像の HDR表示をサポートしているか確認する画像

結論1: Gain Map画像の作成手順について

以下の手順で可能。

  • ①SDR画像と HDR画像 を PNG形式で作成する(筆者は DaVinci Resolve を使って作成した *1
  • ②SDR画像と HDR画像を libavif の avifenc コマンドを使って AVIF へと変換する
  • ③SDR画像と HDR画像を libavif の avifgainmaputil コマンドを使って Gain Map 画像に変換する

avifgainmaputil コマンドの詳細は記事後半の Appendix1 を参照。

結論2: Gain Map画像のブラウザ上での表示について

  • Gain Map 部分に関する挙動は仕様書どおりであり問題ない
    • テストパターンと自然画に対して Gain Map 画像を作成した結果を以下に示す。
説明 画像
図2. SDR と Gain Map HDR の比較 (テストパターン)
図3. SDR と Gain Map HDR の比較 (自然画)

ただし、図2と図3をブラウザで見るだけだと「モニターの輝度設定に応じて」画像が変化する様子を観測できない。 そこで、MHC Profile を使い モニターの輝度情報を変更して Gain Map画像が変化する様子 を配信ソフトの OBS でキャプチャして動画にしてみた。以下に動画を添付する *2

動画1. 画面の輝度情報に応じて Gain Map の画像が変わる様子を False Color表示で示したもの

動画の左画面で使用している False Color 表示に関しては筆者の過去記事を参照。

trev16.hatenablog.com

結論3: Gain Map を使う上での特記事項

①Gain Map用の SDR画像は明るさを 100/203 ≒ 0.5 倍すると輝度ズレが無くなる
  • SDR画像は Webブラウザでは 0~203 nits にマッピングされる
    • そのため、特に何もしないと SDR の 1023/1023 CV は100 nits ではなく 203 nits 扱いとなる *3
    • SDR画像の明るさを 0.5倍しておくと 203 nits にマッピングされても輝度が変わらずに済む
  • 0.5倍をした場合としなかった場合の比較を図4 と図5 に示す
説明 画像
図4. SDR を 1.0倍のままにした例。カラーチェッカーを見ると SDR の方が明るくなっている
図5. SDR を 0.5倍した例。カラーチェッカーは SDR と HDR とで同じ明るさとなっている
②テストパターン画像を作る場合、Base Rendition *4 は慎重に選ぶべき
  • 誤差なく確実に表示したい方を Base Rendition とする必要あり
  • 例として図6 (SDR)、図7 (HDR) のテスト画像から Gain Map画像を作成した際に、Base Rendition によって見え方が変わる例を示す
  • 図8 は SDR 画像から HDR 画像を作っているのだが画像中央に「SDR Alternative Image」の文字が薄く残ってしまっている
SDR_Base HDR_Base
図6. SDR のテストパターン画像 図7. HDR のテストパターン画像


SDR_Base HDR_Base
図8. Base Rendition: SDR。HDRの再現に失敗 図9. Base Rendition: HDRHDRの再現に成功


③AVIF の場合、SDR と HDR とで異なる色域を設定できる
  • Adobe/Apple のテクニカルペーパーは輝度方向の変換しか規定しておらず、SDR と HDR は同一の色域である必要があった
  • AVIF では SDR と HDR に個別の CICP を割り当てが可能となっており、異なる色域にも対応している
  • 具体例は Appendix 1 を参照 *5
Windows 環境では SDR content brightness 設定に注意が必要
  • Windows環境においては 過去記事 で書いた「SDR content brightness」の値に応じて HDR画像の輝度が変わる問題がある [3]
    • そのため SDR content brightness は 31 を設定した状態でのコンテンツ表示を推奨する

Appendix1: libavif の avifgainmaputil コマンドの使い方

2025/5/10 にリリースされた libavif v1.3.0 の avifgainmaputil について説明する。名前から分かるように AVIF の Gain Map関連処理を扱いやすくするためのツールである。

avifgainmaputil の使い方は以下の通りであり、第一引数に command を指定して使う。

avifgainmaputil <command> [options] [arguments...]

commandは7種類ある。それぞれの説明を以下に示す*6

コマンド 説明
help コマンドの使い方(Usage)を表示します。
combine base image と alternate image から gain map を含む AVIF 画像を生成します。
convert gain map 付きの JPEG 画像を AVIF 形式に変換します。
tonemap gain map 付きの AVIF 画像を、指定した HDR headroom(SDR に対するディスプレイの明るさ余裕)でトーンマップします。
swapbase base image と alternate image を入れ替えます(例:base image を SDR、alternate image を HDR → base image を HDR、alternate image を SDR に)。
extractgainmap AVIF ファイルから gain map を画像として抽出・保存します。
printmetadata AVIF ファイルの gain map に関するメタデータを表示します。

表を見れば分かると思うが、Gain Map画像の生成にはcombineコマンドを使う。例として冒頭の図3 の Gain Map画像の生成に使用したコマンドを以下に示す。

avifgainmaputil combine \
    ./src_img/kanazawa_castle_01_sRGB_2K.avif \
    ./src_img/kanazawa_castle_01_BT2100-PQ_2K.avif \
    ./gain_map_img/kanazawa_castle_01_sRGB_2K-kanazawa_castle_01_BT2100-PQ_2K.avif \
    --qgain-map 80 \
    --depth-gain-map 10 \
    --yuv-gain-map 444 \
    --cicp-base 1/13/0 \
    --cicp-alternate 9/16/0 \
    --qcolor 80 \
    --depth 10

上記の例ではavifgainmaputil combineに続いてbase_imagealternate_imageoutput_image を指定している。その他の細かいオプションの詳細は Appendix2 を参照して欲しい。

ここで base image、alternate image ついて説明しておく。それぞれ以下を意味している。

  • base image: Adobe の Gain Map仕様書の Base Rendition を意味する
  • alternate image: Base Rendition でない方の Rendition を意味する

Appendix2: libavif の avifgainmaputilcombine コマンドの help

参考情報として avifgainmaputil combine -help の内容を記載しておく。

$ avifgainmaputil combine -help

       usage: avifgainmaputil combine base_image alternate_image output_image.avif 
       [--downscaling DOWNSCALING] [--qgain-map QGAIN-MAP] 
       [--depth-gain-map {8, 10, 12}] [--yuv-gain-map {444, 422, 420, 400}] 
       [--cicp-base CICP-BASE] [--cicp-alternate CICP-ALTERNATE] [-s SPEED] 
       [-q QCOLOR] [--qalpha QALPHA] [-y {444, 422, 420, 400}] 
       [-d {0, 8, 10, 12}] [--ignore-profile] [-h]

Creates an avif image with a gain map from a base image and an alternate image.

arguments:
  base_image        The base image, that will be shown by viewers that don't 
                    support gain maps
  alternate_image   The alternate image, the result of fully applying the gain 
                    map
  output_image.avif
                    
  --downscaling DOWNSCALING
                    Downscaling factor for the gain map (Default: 1)
  --qgain-map QGAIN-MAP
                    Quality for the gain map (0-100, where 100 is lossless) (Default: 60)
  --depth-gain-map {8, 10, 12}
                    Output depth for the gain map (Default: 8)
  --yuv-gain-map {444, 422, 420, 400}
                    Output format for the gain map (Default: 444)
  --cicp-base CICP-BASE
                    Set or override the cicp values for the base image, 
                    expressed as P/T/M where P = color primaries, T = transfer 
                    characteristics, M = matrix coefficients.
  --cicp-alternate CICP-ALTERNATE
                    Set or override the cicp values for the alternate image, 
                    expressed as P/T/M  where P = color primaries, T = transfer 
                    characteristics, M = matrix coefficients.
  -s SPEED, --speed SPEED
                    Encoder speed (0-10, slowest-fastest) (Default: 6)
  -q QCOLOR, --qcolor QCOLOR
                    Quality for color (0-100, where 100 is lossless) (Default: 60)
  --qalpha QALPHA   Quality for alpha (0-100, where 100 is lossless) (Default: 100)
  -y {444, 422, 420, 400}, --yuv {444, 422, 420, 400}
                    Output YUV format for avif (default = automatic)
  -d {0, 8, 10, 12}, --depth {0, 8, 10, 12}
                    Output depth (0 = automatic)
  --ignore-profile  If the input file contains an embedded color profile, ignore 
                    it (no-op if absent) (Default: false)
  -h, --help        Shows this help message

Appendix3: libavif の avifgainmaputil を改造した記録

v1.3.0 の avifgainmaputil combine コマンドは残念なことに、Gain Map の HDR Capacity に任意の値を指定するオプションが用意されていなかった。 そこで、HDR Capacity をコマンドライン引数で設定できるように少し改造を行った。メモとして残しておく。

やったこと

avifgainmaputil combineに引数として--manual-base-hdr-headroom--manual-alternate-hdr-headroomを追加して任意の値を設定可能とした。 (死ぬほど汚い修正だが)ソースコードの変更内容を以下に示す。

github.com

上記の改造版avifgainmaputil combineは以下のテストページの画像作成で大いに役立った。

toru-ver4.github.io

感想

うーん、調査に凄く苦労した割には、読みにくい記事になってしまった。結論欄に色々と詰め込みすぎたか? 果たして何が悪かったのか、その原因を探るため、私はアマゾンの奥地へと向かうのだった…。

次は Ultra HDR について記事を書く予定。最近、Ultra HDR の方が扱いが楽な気がしてきている。この感覚が本当に正しいか確認したい。

参考資料

[1] Adobe, "Gain Map", https://helpx.adobe.com/camera-raw/using/gain-map.html
[2] toruのブログ, "Apple/Adobe の Gain Map HDR について調べて軽く実装をしてみた", https://trev16.hatenablog.com/entry/2024/01/06/093003
[3] toruのブログ, "【静止画編】Windows 11 で HDR コンテンツを「オリジナルのまま加工せずに」モニターへ出力する", https://trev16.hatenablog.com/entry/2024/07/30/195204

*1:もちろん Lightroom などの現像ツールでも作成可能だが、今回は検証のためブラックボックスな処理が入るのは避けたかった。そこで出力値を完璧にコントロールできる DaVinci Resolve を使用した

*2:正直に行ってこの動画は相当に難解である。なぜならば動画の理解には次の3点の事前知識が必要だからである。①OBSというソフトウェアを使ったウィンドウキャプチャ、②OBS上で適用した HDR解析用の False Color表示、③MHC Profile 切り替えによる輝度情報の更新。…こうなってくると筆者しか理解できない気がしている

*3:実際に 203 nits の明るさで表示されるかは別の要因も絡んでくる、というのが更にわかりづらい…

*4:SDR と HDR のどちらを基準にするか

*5:--cicp-base オプションと --cicp-alternate オプションの例を参照

*6:日本語訳は ChatGPT先生が作ってくれました。感謝

能登半島の先端までドライブに行ってきた

目的

  • 能登半島の道路の状況を実際に走って確認する
  • 能登半島に車で遊びに行く際の注意事項があればまとめておく

総評

思いつきで気軽に行くのはオススメしないが、準備をしてから行くのは問題ない。

所々で悲惨な光景が残っている一方で、復興が進んでいる様子もしっかりと確認できた。観光を通じて能登復興を支援するのは良いと感じた。(が、まだ大量の観光客を捌くキャパは無いと思うので、気軽に周りの人に勧めてよいのか判断できない…) *1

それから、海側の景色は大変良かった。

sample1
図1. 能登半島最先端の写真1 図2. 能登半島最先端の写真2

道路について

  • 通行制限のかかった道路が多く存在するので事前に通行状況を確認した上で走行ルートを選定をすべき
    • 筆者は下記のサイト情報を参考にした
    • 加えて、下記サイトに反映されていない情報もあるため、現地では道路脇の看板を注意深く見る必要がある
      • 筆者は看板の見落としで一箇所だけ現地住民用の道路を走行してしまい深く反省した *2

www.pref.ishikawa.lg.jp

  • 路面の状況は決して良いとは言えないが、車での走行は問題なく可能
  • (当たり前だが)制限速度を守ることは重要。速度オーバーしてると急な段差や迂回路での急カーブで大変なことになる
  • 路面状況が悪い区間では運転には高い集中力を要した
    • 筆者は普段はラジオを聴きながらドライブするが、今回はその余裕が無かった

その他

  • 地図アプリに存在する信号機が、現地では消滅しているケースが幾つかあった。運転しながら地図アプリを見る際は注意が必要
  • 食事する場所をGoogleマップで適当に調べないほうが良い。現地に行ったら営業していないお店が何件もあった
  • 道の駅やコンビニなどは短縮営業をしているケースがあるため注意が必要

図3. とあるコンビニの営業時間の例

  • お土産の発送は問題なくできた(場所に依るかもしれないが)。筆者はイカの駅ツクモールでイカを買って関東に送ったが、普通に翌日に関東に届いた

ikanoeki.com

*1:あと昨今の気象変動のことを考えると、筆者のようにガソリン車で移動するのは良くないかも…

*2:本当に申し訳ない…

Cloudfour Image Compare が はてなブログで動作するか確認

ドキュメント: https://image-compare-component.netlify.app/

GitHub: https://github.com/cloudfour/image-compare

SDR画像と ISO HDR画像の比較

画像フォーマットは AVIF。SDR画像 は HDR 画像をバッサリとクリッピングして作成。

SDR の明るさを何も変えない場合

カラーチェッカーの明るさが一致しないよね

SDR の明るさを 0.5x にした場合

カラーチェッカーの明るさが一致するぅ!