関連


16進数(Hex)カラーコード

6桁の16進数で表され、各2桁がそれぞれ赤、緑、青(RGB)の強度を示します。例えば、#FFFFFFは白、#000000は黒です。

RGB

RGB(Red, Green, Blue)の3つの値で色を指定します。各値は0から255の範囲で指定されます。例えば、rgb(255, 255, 255)は白、rgb(0, 0, 0)は黒です。

RGBA

RGBに加えて透明度(Alpha)を指定します。Alphaは0(完全に透明)から1(完全に不透明)の範囲です。例えば、rgba(255, 255, 255, 0.5)は半透明の白です。

HSBとHSV

HSB(Hue, Saturation, Brightness)とHSV(Hue, Saturation, Value)は色の表現方法で、特に色調整や画像処理でよく使われます。これらはRGB(Red, Green, Blue)よりも直感的に色を操作しやすいとされています。

HSB(Hue, Saturation, Brightness)

  • 色相 (Hue): 色相環(カラーホイール)を回るように、赤、橙、黄、緑、青、紫といった色を表します。

  • 彩度 (Saturation): 色がどれだけ強く発色するか。低い彩度は灰色に近く、高い彩度は鮮やかな色です。

  • 明度 (Value / Brightness): 色の明るさ。低い値は暗く、高い値は明るくなります。

HSV(Hue, Saturation, Value)

  • Hue(色相): 色の種類を0から360度の範囲で表します。例えば、0度は赤、120度は緑、240度は青です。

  • Saturation(彩度): 色の鮮やかさを0%から100%で表します。0%は灰色(無彩色)、100%は最も鮮やかな色です。

  • Value(明度): 色の明るさを0%から100%で表します。0%は黒、100%は最大の明るさです。

HSBとHSVはほぼ同じ概念です。「Brightness」を「Value」をと呼んでいるだけです。実質的には同じものと考えてよいです。
コード書いてると変数名h, s, bと省略してbがblueと被るのでh, s, vのが楽かもしれません。逆に変数名をちゃんと書くならvalueがなんのこっちゃ分からなくなるかもしれません。

HSL

HSL(Hue, Saturation, Lightness)は、色相(Hue)、彩度(Saturation)、輝度(Lightness)の3つの値で色を指定します。RGBやHSB/HSVに比べて、自然な色の変化をより直感的に扱うことができます。

  • Hue(色相): 0度から360度の範囲で色の種類を表します。0度は赤、120度は緑、240度は青です。

  • Saturation(彩度): 色の鮮やかさを0%から100%で表します。0%は灰色、100%は最も鮮やかな色です。

  • Lightness(輝度): 色の明るさを0%から100%で表します。0%は黒、100%は白です。

HSBとHSL

HSBあるいはHSV (Hue, Saturation, Value) と HSL (Hue, Saturation, Lightness) は、色を表現する方法としてよく似ていますが、彼らのサチュレーションと明度/値の計算方法が異なります。その結果、同じ色でも、HSVとHSLでの数値が異なる場合があります。

具体的には:

  • Hue (H):
    どちらのモデルにおいても色相は同じです。通常、0°から360°の範囲で表されます。

  • Saturation (S):
    この計算方法はモデルによって異なります。

    • HSV: $${ S_{HSV} = \frac{\text{max} - \text{min}}{\text{max}} }$$

    • HSL: $${ S_{HSL} = \frac{\text{max} - \text{min}}{1 - |\text{max} + \text{min} - 1|} }$$

  • Value/Lightness (V/L):
    こちらも計算方法が異なります。

    • HSV: $${ V = \text{max} ) (RGB値の中で最も高い値}$$

    • HSL: $${ L = \frac{\text{max} + \text{min}}{2} }$$

上記の式では、$${\text{max}}$$ と $${\text{min}}$$ は、与えられた色のRGB成分の最大値と最小値を示しています。

これらの違いのため、HSLとHSVで色の見え方や特性を説明する際に、いくつかの状況で異なる結果や感じ方が生じる可能性があります。

CMYK

CMYK(Cyan, Magenta, Yellow, Key/Black)は印刷で使用される色の表現方法で、シアン、マゼンタ、イエロー、ブラックの4色の組み合わせで色を表します。

  • Cyan(シアン)

  • Magenta(マゼンタ)

  • Yellow(イエロー)

  • Key/Black(黒)

各値は0%から100%の範囲で指定され、これにより特定の色が生成されます。RGBとは異なり、CMYKは減法混色であり、色を混ぜることで暗くなります。

YCbCr

YCbCrはデジタル映像処理で使用される色の表現方法です。輝度(Y)、青色差(Cb)、赤色差(Cr)の3つの値で色を表します。

  • Y(輝度): 明るさの成分。

  • Cb(青色差): 青色の成分と輝度の差。

  • Cr(赤色差): 赤色の成分と輝度の差。

YCbCrはRGBよりも映像の圧縮に適しており、JPEGや動画圧縮などで広く使用されています。

Lab

Lab色空間は、人間の視覚に基づいた色の表現方法で、L(明度)、a(赤から緑の範囲)、b(青から黄の範囲)の3つの成分で色を表します。

  • L(明度): 色の明るさを0から100の範囲で表します。0は黒、100は白です。

  • a: 赤から緑の色成分を示し、正の値は赤、負の値は緑を示します。

  • b: 青から黄の色成分を示し、正の値は黄、負の値は青を示します。

Lab色空間はRGBやCMYKに比べて、人間の視覚により近い形で色を表現でき、色の違いをより正確に測定できます。


変換

HexToRGB

function hexToRgb(hex) {
    let bigint = parseInt(hex, 16);
    let r = (bigint >> 16) & 255;
    let g = (bigint >> 8) & 255;
    let b = bigint & 255;
    return [r, g, b];
}

・上の例では入力は6桁の16進数文字列が前提
・#などが存在する場合の処理もしていない。

#込みのアプローチ、ただしリターンはオブジェクト。

static HexToRgb(hex) {
    if (hex.startsWith("#")) {
        hex = hex.slice(1);
    }
    let bigint = parseInt(hex, 16);
    let r = (bigint >> 16) & 255;
    let g = (bigint >> 8) & 255;
    let b = bigint & 255;
    return {r, g, b};
}

parseIntで文字列から16進数の数値へ。
Hexは16進数のRRGGBBだから、2進数だと8bit×3の24bit。
1色につき$${2^8=256}$$であって、0-255が3色である。

ビットシフトによって16桁、および8桁ずらすことで
bigint変数は
00000000 00000000 RRRRRRRR
00000000 RRRRRRRR GGGGGGGG
RRRRRRRR GGGGGGGG BBBBBBBB
となる。ここで255は
00000000 00000000 11111111
であるから、アンドを取ることで下8桁を取得する。

また、こんなアプローチも。

    let red =  parseInt(hex.slice(1, 3), 16);
    let green =  parseInt(hex.slice(3, 5), 16);
    let blue = parseInt(hex.slice(5, 7), 16);

これは#込みHexからsliceでRGBを切り出す。

RGBToHex

export function rgbToHex(r, g, b) {
  return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
}


RGBToHSB

RGB(Red, Green, Blue)とHSB(Hue, Saturation, Brightness、別名HSV:Hue, Saturation, Value)は色を表現する2つの異なる方式です。RGBは光の三原色を利用して色を表現します。一方、HSBは色の視覚的側面を強調しており、色相、彩度、明度の3つの要素で色を表現します。

  1. RGBの値は0~255の範囲で与えられると仮定します。まず、これを0~1の範囲に正規化します。

$$
R' = R/255\\
G' = G/255\\
B' = B/255
$$

2.最大値 Max と最小値 Min を求めます。

$$
Max = max(R', G', B')\\
Min = min(R', G', B')
$$

3.明度 V を計算します。

$$
V = Max
$$

4.彩度 S を計算します。

$$
S =
\begin{cases}
0 & \text{if Max} = 0 \\
\frac{\text{Max} - \text{Min}}{\text{Max}} & \text{otherwise}
\end{cases}
$$

5.色相 H を計算します。

$$
H=
\begin{cases}
60 \times \left( \frac{G' - B'}{\text{Max} - \text{Min}} + 0 \right) & \text{if } & \text{Max} = R': \\
60 \times \left( \frac{B' - R'}{\text{Max} - \text{Min}} + 2 \right) & 
\text{if} &  \text{Max} = G': \\
60 \times \left( \frac{R' - G'}{\text{Max} - \text{Min}} + 4 \right) & \text{if} & \text{Max} = B': \\
\end{cases}
$$

Hが負の場合、360を加えて正の値にします。

$$
\text{if }  H < 0: \\
H = H + 360
$$

function rgbToHsb(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;

    let max = Math.max(r, g, b);
    let min = Math.min(r, g, b);
    let h, s, v = max;

    let d = max - min;
    s = max === 0 ? 0 : d / max;

    if (max === min) {
        h = 0; // achromatic
    } else {
        switch (max) {
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h * 360, s * 100, v * 100];
}

HSBToRGB

この変換は少し複雑ですが、基本的な考え方は、Hの値(色相)に基づいてRGBの各成分を決定することです。

  1. Hを60で割った結果をHiとして、その余りをfとして定義します。

$$
Hi = \lfloor H / 60 \rfloor  \text{mod}  6\\
f = (H / 60) - \lfloor H / 60 \rfloor \\
$$

色空間であるHSB(またはHSV)におけるH(Hue: 色相)は通常、0°から359°の範囲で表されます。このHの値は、色環上の位置を示しており、具体的には、赤が0°、緑が120°、青が240°となります。

これをHを60で割ったときの商と余りに分解すると、次のような意味になります:

  1. Hi (商): 色相Hがどの部分色環に属しているかを示します。色環は6つの部分色環に分けられると考えることができ、それぞれが次のようになります:

    • 0: 赤から黄色の間

    • 1: 黄色から緑の間

    • 2: 緑からシアンの間

    • 3: シアンから青の間

    • 4: 青から紫の間

    • 5: 紫から赤の間

  2. f (余り): 該当する部分色環内でのHの位置を示します。fは0から59の間の値を持ち、それにより具体的な色相の位置が示されます。

このように、Hを60で割ることで、簡単に色相の位置を特定することができます。そして、これを利用すると、HSVからRGBへの変換の際に、どの2つの主要な色(赤、緑、青)を基にして補間すればよいか、またその補間の方法を容易に計算することができます。

2.中間値を計算します。

$$
p = V * (1 - S) \\
q = V * (1 - f * S)\\
t = V * (1 - (1 - f) * S)
$$

3.Hiの値に応じてRGBの各成分を決定します。

$$
(R',G',B')=
\begin{cases}
(V, t, p) & \text{if } & \text{Hi} = 0: \\
(q, V, p) & \text{if } & \text{Hi} = 1: \\
(p, V, t) &  \text{if } & \text{Hi} = 2: \\
( p, q, V) & \text{if } & \text{Hi} = 3: \\
(t, p, V) & \text{if } & \text{Hi} = 4: \\
( V, p, q) & \text{if } & \text{Hi} = 5: \\
\end{cases}
$$

最後に、RGBの値を0~255の範囲に変換します。

$$
R = int(R' * 255)\\
G = int(G' * 255)\\
B = int(B' * 255)
$$

function hsbToRgb(h, s, v) {
    h /= 360;
    s /= 100;
    v /= 100;

    let r, g, b;

    let i = Math.floor(h * 6);
    let f = h * 6 - i;
    let p = v * (1 - s);
    let q = v * (1 - f * s);
    let t = v * (1 - (1 - f) * s);

    switch (i % 6) {
        case 0: r = v, g = t, b = p; break;
        case 1: r = q, g = v, b = p; break;
        case 2: r = p, g = v, b = t; break;
        case 3: r = p, g = q, b = v; break;
        case 4: r = t, g = p, b = v; break;
        case 5: r = v, g = p, b = q; break;
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}


RGBToHSL

function rgbToHsl(r, g, b) {
    r /= 255;
    g /= 255;
    b /= 255;

    let max = Math.max(r, g, b);
    let min = Math.min(r, g, b);
    let h, s, l = (max + min) / 2;

    if (max === min) {
        h = s = 0; // achromatic
    } else {
        let d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);

        switch (max) {
            case r:
                h = (g - b) / d + (g < b ? 6 : 0);
                break;
            case g:
                h = (b - r) / d + 2;
                break;
            case b:
                h = (r - g) / d + 4;
                break;
        }

        h /= 6;
    }

    return {
        h: Math.round(h * 360),
        s: Math.round(s * 100),
        l: Math.round(l * 100)
    };
}

// 使用例
let hsl = rgbToHsl(255, 0, 0);
console.log(`H: ${hsl.h}, S: ${hsl.s}%, L: ${hsl.l}%`); // H: 0, S: 100%, L: 50%

HSLToRBG

function hslToRgb(h, s, l) {
    s /= 100;
    l /= 100;

    let c = (1 - Math.abs(2 * l - 1)) * s;
    let x = c * (1 - Math.abs((h / 60) % 2 - 1));
    let m = l - c / 2;
    let r = 0, g = 0, b = 0;

    if (0 <= h && h < 60) {
        r = c;
        g = x;
        b = 0;
    } else if (60 <= h && h < 120) {
        r = x;
        g = c;
        b = 0;
    } else if (120 <= h && h < 180) {
        r = 0;
        g = c;
        b = x;
    } else if (180 <= h && h < 240) {
        r = 0;
        g = x;
        b = c;
    } else if (240 <= h && h < 300) {
        r = x;
        g = 0;
        b = c;
    } else if (300 <= h && h < 360) {
        r = c;
        g = 0;
        b = x;
    }

    r = Math.round((r + m) * 255);
    g = Math.round((g + m) * 255);
    b = Math.round((b + m) * 255);

    return {
        r: r,
        g: g,
        b: b
    };
}

// 使用例
let rgb = hslToRgb(0, 100, 50);
console.log(`R: ${rgb.r}, G: ${rgb.g}, B: ${rgb.b}`); // R: 255, G: 0, B: 0

RGBToLab

export function rgbToLab(rgb) {
  const [r, g, b] = rgb.map(v => v / 255);
  let x = r > 0.04045 ? Math.pow((r + 0.055) / 1.055, 2.4) : r / 12.92;
  let y = g > 0.04045 ? Math.pow((g + 0.055) / 1.055, 2.4) : g / 12.92;
  let z = b > 0.04045 ? Math.pow((b + 0.055) / 1.055, 2.4) : b / 12.92;

  x *= 100;
  y *= 100;
  z *= 100;

  x = x * 0.4124 + y * 0.3576 + z * 0.1805;
  y = x * 0.2126 + y * 0.7152 + z * 0.0722;
  z = x * 0.0193 + y * 0.1192 + z * 0.9505;

  x /= 95.047;
  y /= 100;
  z /= 108.883;

  x = x > 0.008856 ? Math.pow(x, 1/3) : (7.787 * x) + (16 / 116);
  y = y > 0.008856 ? Math.pow(y, 1/3) : (7.787 * y) + (16 / 116);
  z = z > 0.008856 ? Math.pow(z, 1/3) : (7.787 * z) + (16 / 116);

  const l = (116 * y) - 16;
  const a = 500 * (x - y);
  const b_lab = 200 * (y - z);

  return [l, a, b_lab];
}

LabToRGB

export function labToRgb(l, a, b) {
  let y = (l + 16) / 116;
  let x = a / 500 + y;
  let z = y - b / 200;

  x = 95.047 * ((x ** 3 > 0.008856) ? x ** 3 : (x - 16 / 116) / 7.787);
  y = 100 * ((y ** 3 > 0.008856) ? y ** 3 : (y - 16 / 116) / 7.787);
  z = 108.883 * ((z ** 3 > 0.008856) ? z ** 3 : (z - 16 / 116) / 7.787);

  x = x / 100;
  y = y / 100;
  z = z / 100;

  let r = x * 3.2406 + y * -1.5372 + z * -0.4986;
  let g = x * -0.9689 + y * 1.8758 + z * 0.0415;
  let b_rgb = x * 0.0557 + y * -0.2040 + z * 1.0570;

  r = r > 0.0031308 ? 1.055 * (r ** (1 / 2.4)) - 0.055 : r * 12.92;
  g = g > 0.0031308 ? 1.055 * (g ** (1 / 2.4)) - 0.055 : g * 12.92;
  b_rgb = b_rgb > 0.0031308 ? 1.055 * (b_rgb ** (1 / 2.4)) - 0.055 : b_rgb * 12.92;

  return [Math.max(0, Math.min(255, Math.round(r * 255))),
          Math.max(0, Math.min(255, Math.round(g * 255))),
          Math.max(0, Math.min(255, Math.round(b_rgb * 255)))];
}

RGBToYCbCr

export function rgbToYCbCr(r, g, b) {
  const y = 0.299 * r + 0.587 * g + 0.114 * b;
  const cb = 128 - 0.168736 * r - 0.331264 * g + 0.5 * b;
  const cr = 128 + 0.5 * r - 0.418688 * g - 0.081312 * b;
  return [y, cb, cr];
}

YCbCrToRGB

export function ycbcrToRgb(y, cb, cr) {
  const r = y + 1.402 * (cr - 128);
  const g = y - 0.344136 * (cb - 128) - 0.714136 * (cr - 128);
  const b = y + 1.772 * (cb - 128);
  return [Math.max(0, Math.min(255, Math.round(r))),
          Math.max(0, Math.min(255, Math.round(g))),
          Math.max(0, Math.min(255, Math.round(b)))];
}


距離

RGB

function rgbDistance(color1, color2) {
    const dr = color1.r - color2.r;
    const dg = color1.g - color2.g;
    const db = color1.b - color2.b;
    return Math.sqrt(dr*dr + dg*dg + db*db);
}

// 使用例
const color1 = {r: 255, g: 0, b: 0};
const color2 = {r: 0, g: 255, b: 0};
console.log(rgbDistance(color1, color2));

HSV

function hsvDistance(hsv1, hsv2) {
    const dh = Math.min(Math.abs(hsv1.h - hsv2.h), 360 - Math.abs(hsv1.h - hsv2.h)) / 180.0;
    const ds = Math.abs(hsv1.s - hsv2.s);
    const dv = Math.abs(hsv1.v - hsv2.v) / 100.0;
    return Math.sqrt(dh*dh + ds*ds + dv*dv);
}

// 使用例
const hsv1 = {h: 0, s: 100, v: 100};
const hsv2 = {h: 120, s: 100, v: 100};
console.log(hsvDistance(hsv1, hsv2));

Lab

function labDistance(lab1, lab2) {
    const dl = lab1.l - lab2.l;
    const da = lab1.a - lab2.a;
    const db = lab1.b - lab2.b;
    return Math.sqrt(dl*dl + da*da + db*db);
}

// 使用例
const lab1 = {l: 50, a: 50, b: 50};
const lab2 = {l: 60, a: 60, b: 60};
console.log(labDistance(lab1, lab2));

CMYK

function cmykDistance(cmyk1, cmyk2) {
    const dc = cmyk1.c - cmyk2.c;
    const dm = cmyk1.m - cmyk2.m;
    const dy = cmyk1.y - cmyk2.y;
    const dk = cmyk1.k - cmyk2.k;
    return Math.sqrt(dc*dc + dm*dm + dy*dy + dk*dk);
}

// 使用例
const cmyk1 = {c: 0, m: 100, y: 100, k: 0};
const cmyk2 = {c: 100, m: 0, y: 100, k: 0};
console.log(cmykDistance(cmyk1, cmyk2));

色の生成とソート

ランダムに作成された色オブジェクトの配列を、指定された要素数分作成する関数を作ってください。
また、色配列をソートする関数を作ってください。ソートの基準はrgbaの値を並べた12桁の整数の大小です。昇順と降順にできるようにしてください。

ランダムな色オブジェクトの配列を生成する関数

function generateRandomColors(count) {
  const colors = [];
  for (let i = 0; i < count; i++) {
    const color = {
      a: Math.floor(Math.random() * 256),
      r: Math.floor(Math.random() * 256),
      g: Math.floor(Math.random() * 256),
      b: Math.floor(Math.random() * 256)
    };
    colors.push(color);
  }
  return colors;
}

色配列をソートする関数

この関数は、色配列を引数として受け取り、ソート順(昇順または降順)を指定するフラグも受け取ります。RGBAの各値を結合して生成される12桁の整数を基準にソートを行います。

function sortColors(colors, ascending = true) {
  return colors.sort((a, b) => {
    const aValue = parseInt(`${String(a.r).padStart(3, '0')}${String(a.g).padStart(3, '0')}${String(a.b).padStart(3, '0')}${String(a.a).padStart(3, '0')}`);
    const bValue = parseInt(`${String(b.r).padStart(3, '0')}${String(b.g).padStart(3, '0')}${String(b.b).padStart(3, '0')}${String(b.a).padStart(3, '0')}`);
    
    if (ascending) {
      return aValue - bValue;
    } else {
      return bValue - aValue;
    }
  });
}

使用例

以下のコードは、これらの関数の使用例を示しています。

// ランダムな色オブジェクトの配列を10個生成
const randomColors = generateRandomColors(10);
console.log("Generated Colors:", randomColors);

// 色配列を昇順でソート
const sortedColorsAscending = sortColors(randomColors, true);
console.log("Sorted Colors (Ascending):", sortedColorsAscending);

// 色配列を降順でソート
const sortedColorsDescending = sortColors(randomColors, false);
console.log("Sorted Colors (Descending):", sortedColorsDescending);

これらの関数を使用して、ランダムに生成された色オブジェクトの配列を簡単に作成し、指定した基準に基づいてソートすることができます。ソート関数は、ビジュアルプログラミングやデータの視覚化で特に有用です。

この記事が気に入ったらサポートをしてみませんか?