このエントリの情報は古いです。現行 (2018年11月) のブラウザでは canvas の色の扱いが改善され、すべて sRGB で取り扱われるようになったため、以下のようなハックはできません。(できなくなったことは喜ばしいことです。)

(黒魔術) CSS の色を sRGB にあわせるには | tech - 氾濫原 というエントリを書いてから、ふと「もしかしてユーザー環境の色域 (モニタのプロファイル) を推定することができるのではないか」と思い至ったのでやってみました。

デモ: https://lowreal.net/2017/gamutdetect/

前提

ICC Profile を埋めこんだ画像を canvas に drawImage すると、プロファイル変換されたRGB値が描かれる。getImageData 経由でピクセル値を読むことでプロファイル変換済みのRGB値が得られる。

Chrome と Firefox だけで確認しています。これらのブラウザは上記の前提があてはまるからです。基本的に不具合に近い仕様だと思うので、そのうち動かなくなりそうです。あくまでネタってことです。

Safari と IE は OS レイヤーのカラーマネジメントなのが関係しているのか、putImageData や getImageData も sRGB として扱われていそうです。

戦略

プロファイルに影響される値がとれるということは、間接的にプロファイルを推定できるはずです。具体的には sRGB プロファイルで 0,255,0 という画像データがあったとき、これは sRGB の色域で 0,255,0 という色ですが、もしモニタプロファイルが Adobe RGB など広色域であれば、取得できる値は Adobe RGB の色域のためにもっと小さい (飽和していない) 値になると予想できます。

判定

実際にはモニタプロファイルを推定しているので「Adobe RGB の色域だ!」というのはおかしいのですが、近似として Adobe RGB / DCI P3 / sRGB の3種類の計算値の中から、近そうな色域として判定を出しています。

判定方法は sRGB で 0,255,0 (緑) の色が現在のモニタでどういう RGB 値になるかで行っています。255,0,0 (赤) と 0,0,255 (青) を判定にいれるとどうも変なので判定につかってません。もうちょっと考慮の余地がありそうです。

xy色図に色域をプロットする

sRGB の 200,55,55 / 55,200,55 / 55,55,200 をレンダリングした結果を読みとり、三元一次連立方程式を3つ解いてXYZへの変換マトリクスを逆算し、あらためてRGB各頂点のXYZ値を求めるという方法でxy色図に色域をプロットするようにしてみました。中途半端な値を使っているのは飽和させないためです。


こうするとやっていることが多少わかりやすいかなという気がします。

  1. トップ
  2. tech
  3. JavaScript で閲覧者モニタの色域を推定する

何かを変えるためには習慣を変えるほかないということで、とりあえず毎日1時間ぐらい歩くことをはじめてみてる。

大まかに以下の通り4通りのパターンが考えられる。

  1. 朝、自宅最寄り駅に近い数駅歩く
  2. 朝、会社最寄り駅に近い数駅歩く
  3. 夜、自宅最寄り駅に近い数駅歩く
  4. 夜、会社最寄り駅に近い数駅歩く

しかし通勤時間帯を避けようと思うと、2 は無理。夜歩くと帰宅時間が結構遅くなるので嫌。ということで1になる。

しかし最寄り駅付近は歩いても面白くないという致命的な問題がある。立地の事情により特定ルートを通らざるを得なく続けていると単調になってしまう。

変化

正直いってあんまりない。便通が改善したかもしれない。晴れている日が多くて気持ちいいけど精神的に良くなったりはしてない。

Chrome, Firefox, Safari で調べたところ、

  • Chrome: カラーマネジメントされない ( sRGB は適用されない)
  • Firefox: デフォルカラーマネジメントされない (後述。オプションによる)
  • Safari: sRGB が適用される

Firefox の場合

gfx.color_management.mode

によって挙動が変わる。2がデフォルトだが、2の場合は sRGB が適用されない。1の場合は sRGB が適用される。

  • カラープロファイルがない画像
  • CSS の色指定
  • canvas の色指定

に影響する。

CSS の色と画像の色をあわせるには

現状では画像に ICC Profile をつけないようにするのがベスト。色の再現を捨ててあわせにいく感じ。

CSS の色を sRGB にあわせたい場合は

CSS も含めてあわせるのは無理 (ということにしておくと吉)

備考

Firefox Chrome ともに起動時にディスプレイプロファイルを読みこむので、あとから変えても反映されない。また、プライマリディスプレイのプロファイルしか読みこまないので注意。

CSS の色を sRGB にあわせるには

実は少し可能なことがわかったので「(黒魔術) CSS の色を sRGB にあわせるには」というのをあとで書く → 書いた https://lowreal.net/2017/02/07/4

  1. トップ
  2. tech
  3. CSS の色空間は sRGB のはずだけど…

Chrome と Firefox では CSS の色にカラーマネジメントが適用されず、sRGB の画像と色をあわせることが基本的に無理です。

で、無理なんですが無理ではなくて、実は頑張れば sRGB にあわせることができることがわかりました。

デモ

以下のような感じになっていて、書いてある通りです。Chrome と Firefox だと上3つと下3つの色が異なります。Safari だと全て sRGB 扱いされるので全部同じに見えるはずです。ただしカラーマネジメントが有効かどうかの判定を入れてないので、カラーマネジメントに対応してないシステムでも全部同じになっています。

一番下の色が JS で CSS のルールを書きかえて sRGB にしたものです。

やっていること

要は使う色をあらかじめ sRGB プロファイルの PNG に描いておき、この画像のレンダリング結果を canvas に drawImage して、getPixelData を行うとディスプレイプロファイルが適用されたRGB値を取得することができるので、これを動的に CSS の色として適用すれば sRGB にできます。

sRGB の画像を canvas に描くってところがポイントです。

自動的にCSSから色を抽出して sRGB を適用する

が、これは手動でやろうとすると面倒なので、CSSから色を抽出し、動的に sRGB PNG を作ってRGB値を変換してやるというのをやってみました。実用的かというと実用的ではないんですが、できそうですねってところです。

  1. CSS から色を抽出 (cssRules から)
  2. canvas に全部の色を描く
  3. canvas から toDataURL で PNG を得る
  4. この PNG に sRGB プロファイルを埋めこむ
  5. img 要素で読みこませる
  6. canvas に img 要素を drawImage する
  7. canvas の色を読む
  8. CSS に色を書き戻す

という手順でやります。

document.styleSheets の内容を書きかえているので、要素個別に書かれたスタイルについては対応できてません。

CSS の色を抽出

雑に以下のようにしました。当然完璧ではありませんが…

function scanStyleSheetColors (cb) {
	for (var i = 0, stylesheet; (stylesheet = document.styleSheets[i]); i++) {
		for (var j = 0, rule; (rule = stylesheet.cssRules[j]); j++) {
			for (var k = 0, name; (name = rule.style[k]); k++) {
				var val = rule.style.getPropertyValue(name);
				if (/rgb\((\d+),\s*(\d+),\s*(\d+)\)/.test(val)) {
					var r = RegExp.$1;
					var g = RegExp.$2;
					var b = RegExp.$2;
					var ret = cb(r, g, b);
					if (ret) {
						var newVal = 'rgb(' + ret.join(',') + ')';
						console.log(rule, name, val, '->', newVal);
						rule.style.setProperty(name, newVal, rule.style.getPropertyPriority(name));
					}
				}
			}
		}
	}
}

canvas に sRGB プロファイルを適用して PNG の data URL 化

// to PNG data URL with sRGB profile
function applyProfile (canvas) {
	function pngChunk (type, data) {
		var LEN_LENGTH = 4
		var LEN_TYPE = 4;
		var LEN_CRC = 4;
		var buf = new ArrayBuffer(LEN_LENGTH + LEN_TYPE + data.length + LEN_CRC);
		var view = new DataView(buf);

		var pos = 0;
		view.setUint32(0, data.length);
		pos += LEN_LENGTH;
		for (var i = 0, len = type.length; i < len; i++) {
			view.setUint8(pos++, type.charCodeAt(i));
		}
		for (var i = 0, len = data.length; i < len; i++) {
			view.setUint8(pos++, data.charCodeAt(i));
		}
		var crc = CRC32.bstr(type + data);
		view.setUint32(pos, crc);

		return String.fromCharCode.apply(null, new Uint8Array(buf));
	}

	var dataURL = canvas.toDataURL('image/png');
	var matched = dataURL.match(/^data:image\/png;base64,(.+)/);
	var base64 = matched[1];
	var png = atob(base64);
	// sRGB with Perceptual (0) rendering intent
	png = png.replace(/(....IDAT)/, pngChunk("sRGB", "\x00") + "$1");
	return 'data:image/png;base64,' + btoa(png);
}

こんなんでできます。やってることは sRGB チャンクを追加してるだけです。PNG は sRGB に関しては ICC プロファイルを埋めこむ必要なく、13バイト追加するだけですみます。

備考

Safari だと drawImage がうまくいかないです。ちゃんと追ってません。Safari はそもそも sRGB なのでやる必要ないです。

  1. トップ
  2. tech
  3. (黒魔術) CSS の色を sRGB にあわせるには

そういえば一ヶ月前ぐらい?から、ロードとパースに結構時間がかかっているのが嫌だなと思って HatenaStar.js を読みこむのをやめております。

簡単でポジティブなフィードバック手段がなくなったので寂しい気もしますが、しばらくこれでいこうという感じです。モダンブラウザ向けのミニマムな実装を書く気になったら復活するかもしれないけど、今のところそんな気力はない。

なんか他にもポジティブ評価しかできないフィードバック方法があればいいんですけどね。PVには色気がなく、はてブには善意がない。はてなスターはちょうどいいんだけどなあ。

もっとささやかな目標を少しずつ達成する必要があるのではないか。

例えば社会人になってから箸の持ちかたを改善したが、これは主観的に満足度が高かった。誰の得にもならなくても目標を達成できるのは良いことで、人生を良くすると感じる。

「日々の善行」を目標としてみたいが、正直いって荷が重い気がする。自然に毎日達成できるようなことではないからだ。心掛けはできても目標にはできない。善行チャンスを逃すなという心持ちではいたい。

「昨日よりも今日を良い日にする」はまぁまぁいい気がする。定量的な目標ではなく達成条件が比較的曖昧なのは良い。1週間のうち半分以上で達成できれば、全体としてプラスといえる。しかし曖昧すぎて難しい。「良い日」ってなんだろうか?

認知行動療法のうち認知療法はいまいちうさんくさい (結局性格を楽観的にしろとしか言っていないように感じるから) が、マインドフルネスはメタ認知しろという話なので比較的納得感がある。でも独りでマインドフルネス実践するのは難しいように感じる。

瞑想ってのが難しいポイントで、やりなれていなければできないし、やってみても何が正しい瞑想なのかわからない。「これであってるのか?」という疑問をいだきつつやっても不愉快になってしまうしやる気が起きない。

思考は思考で上書きするほうが簡単なので、自動思考が起きたとき他の思考を強制的に挟みこむように解決できないんだろうか。メタ認知は今起きていることの受容と洞察がキモだと思うけど、瞑想を通すと難しい。

立川 → 阿豆佐味天神社 → 東京都薬用植物園。

途中で南極・北極科学館というのを見かけたのでふらっと入ったけど面白かった。

機械遺産の極点到達雪上車が展示されているんだけど、なんと驚くことに中に入れる。4人分の寝るスペースが一応ある車なんだけど、とても狭い。機材がちょっと残っていてコリンズKWM-2Aという真空管無線機が置いてあった。

阿豆佐味天神社はそれほど面白くなかったので特筆するようなことはなし。

東京都薬用植物園は季節も季節なのであまり見所はなかった。初夏あたりが一番良いのかな? でも混んでなかったのでよかった。






  1. トップ
  2. photo
  3. 立川散歩

RGB等色関数のグラフではRGBの値が負の値をとることがある。つまり3原色ではこの色を再現できないということなのだが、意味がよくわかっていなかった。なぜ負になるのか? なぜ再現できない色が生じるのか? 改めて納得できるまで調べてみた。

短い結論

錐体細胞の感度のうち L錐体 と M錐体 の感度が近いため、RGBのうち特にG(緑)は純粋に緑とはみなせないから。

等色関数とは?

単一波長の光 (例えばレーザーのような) と、混色光 (RGB光) を比較して、どう混色すれば単色光と「同じ」に見えるかを関数化したもの。

人間の目の網膜のうち、色を感じる細胞である錐体細胞は3種類しかない。このため単色光で3つの錐体細胞が受ける刺激値を、3つの波長からなる混色光で再現できるならば、この単色と混色の光は人間の目には同一に見える (区別ができない)。

RGB等色関数とは以下のようなものである。ここでは 1931 CIE RGB 等色関数とした。このときの RGB の波長はそれぞれ 700nm/546.1nm/435.8nm。

一部の領域で R が負の値になることがわかる。これはBやGをどのように混色しても、この波長域の色を再現できず (具体的には彩度が足りない)、単色光のほうにRを足して等色したことを表している。

すなわちマイナスが含まれる領域はRGBで再現できない色となる。

備考 1931 CIE RGB 等色関数

上のグラフは厳密には 1931 CIE XYZ 等色関数を CIE RGB に変換するマトリクスをかけて求めたもの。XYZ 等色関数は LMS Fundamentals から変換した「生理学的に妥当な」ものが新しいものとしてあるが、これを CIE RGB に変換するとそれぞれの原色光のとき他の原色の値が0にならない。

XYZ から CIE RGB (白色点は等エネルギー点である E) への変換は以下の通り

人間の網膜細胞の感度

色を感じる細胞である錐体細胞には、L錐体 (Long=赤) M錐体 (Medium=緑) S錐体 (Short=青) と波長別に3つの種類がある。暗所で働く杆体という細胞もあるが、これは色には関係しないので今回は無視する。

それぞれの錐体にはRGBに波長のピークがあるが、実際の感度では重なりあう領域も大きい。特にL錐体とM錐体はかなりピークが近く、感度も似ている。これは進化の途中で一度M錐体相当のものを失い、再度L錐体から変異する形で獲得しなおしたという経緯があるためといわれている。

RGBの三原色で作り出せない単色光

錐体の感度と、等色関数を並べて、CIE RGB の原色光の波長に線をひいてみた。原色とは前にも書いた通り

  • 435.8 nm 青
  • 546.1 nm 緑
  • 700 nm 赤

青と緑の混合色で青緑の単色光を作ろうとしても彩度が足りないので再現ができないのはなぜか?

LMS のうち L錐体 と M錐体は感度ピークが近いことから、原色の緑とした 546.1nm の単色光は実は M錐体だけではなく、 L 錐体も反応させてしまう。例えば 500nm の単色光で のL 錐体での感度よりも、546.1nm でのL錐体の感度のほうが高いため、錐体への赤みの刺激が相対的に多くなり、純粋に青緑の色ではなくなってしまう。すなわち彩度が下がる。

LMS Fundamentals のグラフ 546.1nm のL錐体の感度は、500nm のときのL錐体の感度よりも大きい (赤みが強い)。

言い換えると、536.1nm は純粋にM錐体を刺激できるような「緑の光」ではないため、これを青と混色しても高彩度の青〜緑は再現できない。

青〜緑の波長領域のL錐体の感度が、546.1nmでのL錐体の感度よりも低い場合、その波長は青と緑の混色光では再現できないことになり、Rは負の値をとる。

ref

  1. トップ
  2. tech
  3. RGB等色関数で現れる負の値の正体

自分のことを尊重してくれない社会を尊重しようと思うか。

倫理観はこれで失われる。