カラーピッカーツールを作成しています。HSLスライダーの場合、RGBをHSLに変換できる必要があります。 SOで変換を行う方法を検索したところ、この質問 HSLからRGBカラーへの変換 が見つかりました。
RGBからHSLへの変換を行う機能を提供しますが、計算で実際に何が行われているかについての説明はありません。よりよく理解するために、ウィキペディアで HSLおよびHSV を読みました。
後で、「HSLおよびHSV」ページの計算を使用して、「HSLからRGB色への変換」から関数を書き直しました。
Rが最大値の場合、色相の計算にこだわっています。 「HSLおよびHSV」ページの計算を参照してください。
これは別の wikiページ からのもので、オランダ語です:
これは answers から「HSLからRGBカラーへの変換」からです。
case r: h = (g - b) / d + (g < b ? 6 : 0); break; // d = max-min = c
いくつかのRGB値を使用して3つすべてをテストしましたが、(正確ではないにしても)同様の結果が得られるようです。私が疑問に思っているのは、彼らが同じことを実行しているということですか?特定のRGB値に対して異なる結果が得られますか?どちらを使用すべきですか?
hue = (g - b) / c; // dutch wiki
hue = ((g - b) / c) % 6; // eng wiki
hue = (g - b) / c + (g < b ? 6 : 0); // SO answer
function rgb2hsl(r, g, b) {
// see https://en.wikipedia.org/wiki/HSL_and_HSV#Formal_derivation
// convert r,g,b [0,255] range to [0,1]
r = r / 255,
g = g / 255,
b = b / 255;
// get the min and max of r,g,b
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
// lightness is the average of the largest and smallest color components
var lum = (max + min) / 2;
var hue;
var sat;
if (max == min) { // no saturation
hue = 0;
sat = 0;
} else {
var c = max - min; // chroma
// saturation is simply the chroma scaled to fill
// the interval [0, 1] for every combination of hue and lightness
sat = c / (1 - Math.abs(2 * lum - 1));
switch(max) {
case r:
// hue = (g - b) / c;
// hue = ((g - b) / c) % 6;
// hue = (g - b) / c + (g < b ? 6 : 0);
break;
case g:
hue = (b - r) / c + 2;
break;
case b:
hue = (r - g) / c + 4;
break;
}
}
hue = Math.round(hue * 60); // °
sat = Math.round(sat * 100); // %
lum = Math.round(lum * 100); // %
return [hue, sat, lum];
}
私はいくつかのwikiページを読んでさまざまな計算をチェックし、六角形へのRGBキューブ投影の視覚化を作成してきました。そして、この変換についての私の理解を投稿したいと思います。この変換(幾何学的図形を使用したカラーモデルの表現)が興味深いと思うので、できる限り徹底的になるようにします。まず、RGBから始めましょう。
まあ、これは本当に多くの説明を必要としません。最も単純な形式では、R、G、Bの3つの値が[0,255]の範囲にあります。たとえば、_51,153,204
_。棒グラフを使用して表現できます。
3D空間で色を表現することもできます。 R
、G
、B
に対応する3つの値X
、Y
、Z
があります。 3つの値はすべて_[0,255]
_の範囲にあり、その結果キューブになります。しかし、RGBキューブを作成する前に、まず2D空間で作業しましょう。 R、G、Bの2つの組み合わせにより、RG、RB、GBが得られます。これらを平面上にグラフ化すると、次のようになります。
これらは、RGBキューブの最初の3つの側面です。それらを3D空間に配置すると、半立方体になります。
上記のグラフを確認すると、2つの色を混ぜることで、(255,255)の新しい色が得られます。これらは黄色、マゼンタ、シアンです。繰り返しますが、YM、YC、MCの2つの組み合わせがあります。これらは、キューブの欠けている側面です。それらを追加すると、完全なキューブが得られます。
そして、このキューブ内の_51,153,204
_の位置:
RGBキューブができたので、六角形に投影しましょう。まず、キューブをx
で45°傾け、次にy
で35.264°傾けます。 2番目の傾斜の後、黒い角が下に、白い角が上にあり、両方ともz
軸を通過します。
ご覧のとおり、立方体を上から見ると、正しい色相の順序で六角形の外観が得られます。しかし、これを実際の六角形に投影する必要があります。私たちがしているのは、立方体の上面図と同じサイズの六角形を描くことです。六角形のすべての角は立方体の角と色に対応し、白色の立方体の上部の角は六角形の中心に投影されます。黒は省略されます。そして、すべての色を六角形にマッピングすると、見た目が正しくなります。
そして、六角形上の_51,153,204
_の位置は次のようになります。
計算を行う前に、色相を定義しましょう。
色相は、おおよそ、投影のある点に対するベクトルの角度で、赤は0°です。
...色相は、その六角形のエッジの周囲にあるポイントの距離です。
これは HSLおよびHSV wikiページからの計算です。この説明ではこれを使用します。
六角形とその上の_51,153,204
_の位置を調べます。
まず、R、G、Bの値をスケーリングして、[0,1]間隔を埋めます。
_R = R / 255 R = 51 / 255 = 0.2
G = G / 255 G = 153 / 255 = 0.6
B = B / 255 B = 204 / 255 = 0.8
_
次に、_R, G, B
_のmax
およびmin
値を見つけます
_M = max(R, G, B) M = max(0.2, 0.6, 0.8) = 0.8
m = min(R, G, B) m = min(0.2, 0.6, 0.8) = 0.2
_
次に、C
(彩度)を計算します。クロマは次のように定義されます:
...彩度は、ほぼ原点からの点の距離です。
クロマは、ポイントを通過する六角形の相対的なサイズです...
_C = OP / OP'
C = M - m
C = 0.8- 0.2 = 0.6
_
これで、R
、G
、B
、およびC
の値が得られました。条件をチェックすると、_if M = B
_は_51,153,204
_に対してtrueを返します。したがって、H'= (R - G) / C + 4
を使用します。
六角形をもう一度確認しましょう。 _(R - G) / C
_は、BP
セグメントの長さを示します。
_segment = (R - G) / C = (0.2 - 0.6) / 0.6 = -0.6666666666666666
_
このセグメントを内側の六角形に配置します。六角形の開始点は0°のR(赤)です。セグメントの長さが正の場合、RY
にあり、負の場合、RM
にあります。この場合、負の_-0.6666666666666666
_であり、RM
エッジにあります。
次に、セグメントの位置をシフトするか、_P₁
_がB
に警告します(_M = B
_のため)。青は_240°
_にあります。六角形には6つの側面があります。各側は_60°
_に対応します。 _240 / 60 = 4
_。 _P₁
_を_4
_(240°)だけシフト(インクリメント)する必要があります。シフト後、_P₁
_はP
になり、RYGCP
の長さを取得します。
_segment = (R - G) / C = (0.2 - 0.6) / 0.6 = -0.6666666666666666
RYGCP = segment + 4 = 3.3333333333333335
_
六角形の円周は_6
_で、これは_360°
_に対応します。 _53,151,204
_から_0°
_への距離は_3.3333333333333335
_です。 _3.3333333333333335
_に_60
_を掛けると、その位置を度で取得します。
_H' = 3.3333333333333335
H = H' * 60 = 200°
_
_if M = R
_の場合、セグメントの一方の端をR(0°)に配置するため、セグメントの長さが正の場合、セグメントをRにシフトする必要はありません。 _P₁
_の位置は正になります。しかし、セグメントの長さが負の場合、負の値はangular位置が180°より大きいことを意味し、完全に回転する必要があるため、6だけシフトする必要があります。
そのため、オランダ語のWikiソリューションhue = (g - b) / c;
もEng wikiソリューションhue = ((g - b) / c) % 6;
も、セグメント長が負の場合は機能しません。 SO answer hue = (g - b) / c + (g < b ? 6 : 0);
]のみが負の値と正の値の両方で機能します。
JSFiddle:rgb(255,71,99)の3つのメソッドすべてをテストする
JSFiddle:RGB Cubeと色相の六角形で色の位置を視覚的に見つけます
作業色相計算:
_console.log(rgb2hue(51,153,204));
console.log(rgb2hue(255,71,99));
console.log(rgb2hue(255,0,0));
console.log(rgb2hue(255,128,0));
console.log(rgb2hue(124,252,0));
function rgb2hue(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
var max = Math.max(r, g, b);
var min = Math.min(r, g, b);
var c = max - min;
var hue;
if (c == 0) {
hue = 0;
} else {
switch(max) {
case r:
var segment = (g - b) / c;
var shift = 0 / 60; // R° / (360° / hex sides)
if (segment < 0) { // hue > 180, full rotation
shift = 360 / 60; // R° / (360° / hex sides)
}
hue = segment + shift;
break;
case g:
var segment = (b - r) / c;
var shift = 120 / 60; // G° / (360° / hex sides)
hue = segment + shift;
break;
case b:
var segment = (r - g) / c;
var shift = 240 / 60; // B° / (360° / hex sides)
hue = segment + shift;
break;
}
}
return hue * 60; // hue is in [0,6], scale it up
}
_
私のコメントを続けると、英語版は正しいように見えますが、WIKIページが理解できないため、オランダ語版で何が起こっているのかわかりません。
以下は、英語のWIKIページから作成したES6バージョンと、WIKIの例に一致するように見えるサンプルデータです(Javascriptの数値精度を与えるか、取得します)。うまくいけば、独自の関数を作成するときに役立つかもしれません。
// see: https://en.wikipedia.org/wiki/RGB_color_model
// see: https://en.wikipedia.org/wiki/HSL_and_HSV
// expects R, G, B, Cmax and chroma to be in number interval [0, 1]
// returns undefined if chroma is 0, or a number interval [0, 360] degrees
function hue(R, G, B, Cmax, chroma) {
let H;
if (chroma === 0) {
return H;
}
if (Cmax === R) {
H = ((G - B) / chroma) % 6;
} else if (Cmax === G) {
H = ((B - R) / chroma) + 2;
} else if (Cmax === B) {
H = ((R - G) / chroma) + 4;
}
H *= 60;
return H < 0 ? H + 360 : H;
}
// returns the average of the supplied number arguments
function average(...theArgs) {
return theArgs.length ? theArgs.reduce((p, c) => p + c, 0) / theArgs.length : 0;
}
// expects R, G, B, Cmin, Cmax and chroma to be in number interval [0, 1]
// type is by default 'bi-hexcone' equation
// set 'luma601' or 'luma709' for alternatives
// see: https://en.wikipedia.org/wiki/Luma_(video)
// returns a number interval [0, 1]
function lightness(R, G, B, Cmin, Cmax, type = 'bi-hexcone') {
if (type === 'luma601') {
return (0.299 * R) + (0.587 * G) + (0.114 * B);
}
if (type === 'luma709') {
return (0.2126 * R) + (0.7152 * G) + (0.0772 * B);
}
return average(Cmin, Cmax);
}
// expects L and chroma to be in number interval [0, 1]
// returns a number interval [0, 1]
function saturation(L, chroma) {
return chroma === 0 ? 0 : chroma / (1 - Math.abs(2 * L - 1));
}
// returns the value to a fixed number of digits
function toFixed(value, digits) {
return Number.isFinite(value) && Number.isFinite(digits) ? value.toFixed(digits) : value;
}
// expects R, G, and B to be in number interval [0, 1]
// returns a Map of H, S and L in the appropriate interval and digits
function RGB2HSL(R, G, B, fixed = true) {
const Cmin = Math.min(R, G, B);
const Cmax = Math.max(R, G, B);
const chroma = Cmax - Cmin;
// default 'bi-hexcone' equation
const L = lightness(R, G, B, Cmin, Cmax);
// H in degrees interval [0, 360]
// L and S in interval [0, 1]
return new Map([
['H', toFixed(hue(R, G, B, Cmax, chroma), fixed && 1)],
['S', toFixed(saturation(L, chroma), fixed && 3)],
['L', toFixed(L, fixed && 3)]
]);
}
// expects value to be number in interval [0, 255]
// returns normalised value as a number interval [0, 1]
function colourRange(value) {
return value / 255;
};
// expects R, G, and B to be in number interval [0, 255]
function RGBdec2HSL(R, G, B) {
return RGB2HSL(colourRange(R), colourRange(G), colourRange(B));
}
// converts a hexidecimal string into a decimal number
function hex2dec(value) {
return parseInt(value, 16);
}
// slices a string into an array of paired characters
function pairSlicer(value) {
return value.match(/../g);
}
// prepend '0's to the start of a string and make specific length
function prePad(value, count) {
return ('0'.repeat(count) + value).slice(-count);
}
// format hex pair string from value
function hexPair(value) {
return hex2dec(prePad(value, 2));
}
// expects R, G, and B to be hex string in interval ['00', 'FF']
// without a leading '#' character
function RGBhex2HSL(R, G, B) {
return RGBdec2HSL(hexPair(R), hexPair(G), hexPair(B));
}
// expects RGB to be a hex string in interval ['000000', 'FFFFFF']
// with or without a leading '#' character
function RGBstr2HSL(RGB) {
const hex = prePad(RGB.charAt(0) === '#' ? RGB.slice(1) : RGB, 6);
return RGBhex2HSL(...pairSlicer(hex).slice(0, 3));
}
// expects value to be a Map object
function logIt(value) {
console.log(value);
document.getElementById('out').textContent += JSON.stringify([...value]) + '\n';
};
logIt(RGBstr2HSL('000000'));
logIt(RGBstr2HSL('#808080'));
logIt(RGB2HSL(0, 0, 0));
logIt(RGB2HSL(1, 1, 1));
logIt(RGBdec2HSL(0, 0, 0));
logIt(RGBdec2HSL(255, 255, 254));
logIt(RGBhex2HSL('BF', 'BF', '00'));
logIt(RGBstr2HSL('008000'));
logIt(RGBstr2HSL('80FFFF'));
logIt(RGBstr2HSL('8080FF'));
logIt(RGBstr2HSL('BF40BF'));
logIt(RGBstr2HSL('A0A424'));
logIt(RGBstr2HSL('411BEA'));
logIt(RGBstr2HSL('1EAC41'));
logIt(RGBstr2HSL('F0C80E'));
logIt(RGBstr2HSL('B430E5'));
logIt(RGBstr2HSL('ED7651'));
logIt(RGBstr2HSL('FEF888'));
logIt(RGBstr2HSL('19CB97'));
logIt(RGBstr2HSL('362698'));
logIt(RGBstr2HSL('7E7EB8'));
<pre id="out"></pre>
HSLの色相は、円の中の角度のようなものです。このような角度に関連する値は、0..360間隔にあります。ただし、負の値が計算から出てくる場合があります。そのため、これら3つの式は異なります。これらは最終的に同じことを行い、0..360間隔以外の値を異なる方法で処理します。または、正確には、最終的に60から0..360を掛けた0..6間隔
hue = (g - b) / c; // dutch wiki
は負の値では何もせず、後続のコードが負のH値を処理できると仮定します。
hue = ((g - b) / c) % 6; // eng wiki
は_%
_演算子を使用して、0..6間隔内の値に適合します
hue = (g - b) / c + (g < b ? 6 : 0); // SO answer
は、+ 6を追加して正の値にすることで負の値を処理します
これらは表面的な違いにすぎないことがわかります。 2番目または3番目の式のいずれかが正常に機能します。