リニアRGBのうんちく

最近になって、線形色空間が話題になっているので、このブームに便乗してみます。

<blockquote class="twitter-tweet" data-lang="ja"><p lang="ja" dir="ltr">HTML5のCanvasにfloat16の線形色空間で描けるようにしよう、という提案がなされている。色を正確に再現する必要があるコンテンツの表示や、物理的に正しい色の合成、ハイダイナミックレンジディスプレイの活用等に利用できる</p>&mdash; Fadis (@fadis_) <a href="https://twitter.com/fadis_/status/996744759971536897">2018年5月16日</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8">

ここでいう線形は雑にいうと、元の値が倍になったら対応する値も倍になるといった程度の意味合いです。具体的には、ロウソクが1本から2本に増えたら値を倍にする感じです。

| |![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png)  ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) | ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/468888f7-5f97-ae26-6acc-45200797e33d.png) |
|---|---|---|---|---|
| 本数 | 1 | 2 |  4 | 8 |
|リニア (x10)  | 10 | 20 | 40 | 80 |
|非リニア(log_2) | 0  | 1  | 2  |  3 |

線形を定義できる色空間は色々ありますが、本記事では RGB を例に説明します。

# sRGB

RGB の値が物理的な実際の色とどう対応するかの規格は色々ありまして、Web でやり取りする画像データの殆どは sRGB 規格に合わせたものです。

- https://www.w3.org/Graphics/Color/sRGB.html (古いけど無料で読める)
- https://webstore.iec.ch/publication/6168 (売り物。ちょっとお高い)

なお、sRGB 以外にも、DTP 業界では印刷物の発色に合わせて AdobeRGB 色空間が昔から使われてますし、iPhone7 発売の頃から P3 色空間もじわじわと普及してきてます。

|  |
|---|
|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/ef2f9b9b-efb7-818a-192a-4987aaff577b.png)|
(c)https://www.reddit.com/r/apple/comments/5287c3/lets_talk_about_that_new_display_in_iphone_7/#d7i93i7

しかしながら、いまでも色空間を特に明示しない場合は sRGB である事が多いので、これをメインに語ります。

さて、この sRGB 。冒頭で紹介したツイートで語られているのと同様、物足りない点が幾つもあります。

- 色を正確に再現したいコンテンツの表示
- 物理的に正しい色の合成
- HDR(ハイダイナミックレンジ)ディスプレイの活用

# 色を正確に再現したいコンテンツの表示

こちらは線形色空間ではなく、float16 の精度の強みです。
HTML Canvas で float16 指定を<strong>しない</strong>場合、RGB の各々を8bit(256階調)で表現します。
ところが人の眼は256階調より細かく判別できるそうで(個人差あり)、これでは物足りません。
特にきれいなグラデーションに色のムラが発生しがちです。

float16 は8ビット整数より精度が上がるので、この問題を解決します。

なお、「正確に再現」 は 「HDR(ハイダイナミックレンジ)ディスプレイの活用」と少しかぶるので、そっちの観点については後述します。

# 物理的に正しい色の合成

ここからようやく線形色空間の話です。

## リニアRGB

普段、画像データのやり取りや変換処理に使うRGB値は、多くの場合は物理的な輝度に対してリニアでなく、sRGB 規格の場合だとガンマ補正がかかっています。
対して、このガンマ補正が「かかっていない」ものを「リニアRGB」、sRGBを元にする場合は特に「リニアsRGB」と呼ぶ事もあります。
物理的な輝度に対して素直に比例する値を使うのが線形色空間です。

## ガンマ補正

前提知識としてのガンマ補正解説はこちらを参照して下さい。

|ガンマ補正のうんちく|
|---|
|![gammma_transfer.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1798/ae978465-03fb-5267-932a-effcf54bc137.png)|
| https://qiita.com/yoya/items/122b93970c190068c752 |

そもそも何故、素直に物理的な輝度をそのままRGB値にしないのか。

昔のブラウン管テレビは、電圧に対して以下のグラフの CRTgamma2.2(右下の実線)の強さで光を出す陰極線管の物理特性がありました。低輝度がより低輝度になります。

|<img src="https://qiita-image-store.s3.amazonaws.com/0/1798/cf69d5c8-6105-85ee-926a-ea01a3cb146f.png" height="70%">|
|---|
|(c) [https://ja.wikipedia.org/wiki/ガンマ値](https://ja.wikipedia.org/wiki/%E3%82%AC%E3%83%B3%E3%83%9E%E5%80%A4) ||

各ご家庭のテレビ全てにこれを補正する装置をつけるより、放送局側で輝度の値を gamma correction 1/2.2(左上の点線)のように変換して電波に流す事でガンマ特性が相殺され、それを受信するテレビ側では頑張らずに意図した表示が出来ます。

また、ガンマ補正は人間の視覚特性にも都合が良いです。
人の目は低輝度の変化に敏感なので、リニア輝度で表現するとデジタル値の処理につきまとう量子化誤差が低輝度ほど目立ちます。ガンマ補正するとその傾向が薄れます。そういった視覚的なスケールに近付く利点があります。

なお、今時の LED 液晶には陰極感のような物理特性は存在しませんが、ガンマ補正は視覚的な利点があるのと、互換性の為に今でもガンマ補正が使われています。

## ガンマ補正がかかったまま色を合成する弊害

色の合成を、RGBの値を何らかの割合で混ぜたりする処理と考えます。
「ガンマ補正のうんちく」の後半で解説していますが、ガンマ補正がかかったまま輝度の計算をすると、物理的な輝度と大きくズレる事があります。

例えば、0〜100の階調の輝度があるとして、100 と 0 の真ん中の輝度が欲しい時、そのまま平均を計算すると50です。

|![image.png](https://qiita-image-store.s3.amazonaws.com/0/1798/6e764fe6-9812-a4bb-703a-ad8c90147b44.png)|
|---|
|(c) https://www.jstage.jst.go.jp/article/itej1997/54/5/54_5_674/_pdf|

しかし、ガンマ補正がかかっている状態では、物理的な輝度平均は 73 辺りです。これは、平均を計算すると実際の輝度が50%でなく、20%程になる事を示します。相当に暗くなります。

具体例として、以下の２つの記事を紹介します。

- グレースケール画像のうんちく - RGB平均 (ガンマ補正)
   - [https://qiita.com/yoya/items/96c36b069e74398796f3##rgb平均-ガンマ補正](https://qiita.com/yoya/items/96c36b069e74398796f3#rgb%E5%B9%B3%E5%9D%87-%E3%82%AC%E3%83%B3%E3%83%9E%E8%A3%9C%E6%AD%A3)
-  ImageMagick リサイズ補間アルゴリズム - Resizing with Colorspace Correction
   - https://qiita.com/yoya/items/b1590de289b623f18639#resizing-with-colorspace-correction

これらの記事では、一度ガンマ補正をほどいてリニアRGBにして、輝度の計算をしてから、またガンマ補正をかけ直して元に戻す手間をかけます。

Canvas で sRGB でなく linear-sRGB を指定すると、はじめからリニアRGBで処理出来るので手間が減らせますし、ガンマ補正をかけ直すと計算処理で誤差が発生して色が不正確になるので、それを避けられる点でも嬉しいです。

また、カラーマネジメントモジュールやグラフィックドライバ次第ですが、最終出力が sRGB だとガンマ補正をかけ直すので、更にそこで計算誤差が発生します。そこで float16 を使って精度をより上げる必要があります。

# ハイダイナミックレンジディスプレイの活用

これは線形色空間とは直接関係ありませんが、float16 だと元々の sRGB だけでなく Rec2020 を指定出来るので、色域でも良い事があります。

## sRGB と Rec2020 の色域

sRGB は古いモニタに合わせて作られた前世紀の古い規格なので、今となっては表現できる色域が狭いです。

| UHDTV vs HDTV Color Gamuts |
|---|
| <img src="https://qiita-image-store.s3.amazonaws.com/0/1798/e1ffe053-ac92-8b3e-345e-1017da63a683.png" height="40%"> |
(c) https://dot-color.com/2012/12/04/so-you-bought-a-4k-tv-now-where-is-the-4k-content/rec2020-vs-rec709-001/ 

この図だと rec 709 が sRGB 相当です。(この２つはガンマ値が違うけど色域はこれ)

sRGB として表示すると狭い色域の範囲しか使いませんが、Rec2020 を使えばディスプレイで発色可能な鮮やかな色が活かせます。
ちなみにカラーマネジメントをサボると sRGB で鮮やかな色が出せたりしますが、不正確で気持ち悪い色になるので注意が必要です。

## canvas におけるRec2020の線形色空間

Canvas では Rec2020 を指定すると線形に変換が出来るような事が書いてあります。自分が Canvas プロポーザルをちゃんと読めていなくて申し訳ないのですが、sRGB の方は sRGB と linear-sRGB で分けているのに、Rec2020 は無条件でリニアになるのが解せぬ。。

- Color managing canvas contents
   - https://github.com/WICG/canvas-color-space/blob/master/CanvasColorSpaceProposal.md

誰か詳しい人、コメント頂けると嬉しいです。(白旗あげ)

## ハイ"ダイナミックレンジ"

実は Rec2020 は HDR でなく SDR 規格です。上記のはうかつな解説であります。^^;
Rec2020 の色域は HDR として十分で、実際 HDR 規格の Rec2100 と同じですが、HDR はあくまで明暗の輝度差をどれだけ表現できるかなので、それに対応した Rec2100 でやっと HDR 対応と言えます。
なお、Rec2100 ではガンマ補正の代わりに、より人の眼の特性に寄り添った PQ や HLG といった補正を行い、低輝度から高輝度まで精度よく表現できるそうです。

エイゾーさんの解説が好きなので再掲

- 新たなHDR国際規格BT.2100
  - http://www.eizo.co.jp/eizolibrary/color_management/hdr/index2.html

# まとめ

float16 PixelFormat バンザイ。
Rec2020 ColorSpace バンザイ。