今日も適当ダイアリー

PHP や Javascript や Symfony、BEAR.Sunday などのWeb周りのことを中心に。それ以外のことも気まぐれに投稿します。

RGB色空間とHLS(HSL, HSI)色空間の相互変換 Javascript版

RGB色空間とHSV色空間の相互変換 Javascript版 - 今日も適当ダイアリーJavascript での RGBHSV の相互変換処理を書いたので、今度はRGB と HLS(HSL、HSIと呼ばれる事もある)の相互変換をメモ。

HLS色空間については HLS色空間 - Wikipedia などを参照してください。

RGB から HLS色空間へのコンバート

/**
 * RGB を HLS へ変換します
 *
 * @param   {Number}  r         red値   ※ 0〜255 の数値
 * @param   {Number}  g         green値 ※ 0〜255 の数値
 * @param   {Number}  b         blue値  ※ 0〜255 の数値
 * @return  {Object}  {h, l, s} ※ h は 0〜360の数値、l/s は 0〜1 の数値
 */
function RGBtoHLS(r, g, b) {
  var h, // 0..360
      l, s; // 0..1

  // 0..1 に変換
  r = r / 255;
  g = g / 255;
  b = b / 255;
  var max = Math.max(Math.max(r, g), b),
      min = Math.min(Math.min(r, g), b);

  // hue の計算
  if (max == min) {
    h = 0; // 本来は定義されないが、仮に0を代入
  } else if (max == r) {
    h = 60 * (g - b) / (max - min) + 0;
  } else if (max == g) {
    h = (60 * (b - r) / (max - min)) + 120;
  } else {
    h = (60 * (r - g) / (max - min)) + 240;
  }

  while (h < 0) {
    h += 360;
  }

  // Lightness の計算
  l = (max + min) / 2;

  // Saturation の計算
  if (max == min) {
    s = 0;
  } else {
    s = (l < 0.5)
      ? (max - min) / (max + min)
      : (max - min) / (2.0 - max - min);
  }

  return {'h': h, 'l': l, 's': s, 'type': 'HLS'};
}

HLS から RGB色空間へのコンバート

/**
 * HLS を RGB へ変換します
 *
 * @param   {Number}  h         hue値        ※ 0〜360の数値
 * @param   {Number}  l         lightness値  ※ 0〜1 の数値
 * @param   {Number}  s         saturation値 ※ 0〜1 の数値
 * @return  {Object}  {r, g, b} ※ r/g/b は 0〜255 の数値
 */
function HLStoRGB(h, l, s) {
  var r, g, b; // 0..255

  while (h < 0) {
    h += 360;
  }
  h = h % 360;

  // 特別な場合 saturation = 0
  if (s == 0) {
    // → RGB は V に等しい
    l = Math.round(l * 255);
    return {'r': l, 'g': l, 'b': l, 'type': 'RGB'};
  }

  var m2 = (l < 0.5) ? l * (1 + s) : l + s - l * s,
      m1 = l * 2 - m2,
      tmp;

  tmp = h + 120;
  if (tmp > 360) {
    tmp = tmp - 360
  }

  if (tmp < 60) {
    r = (m1 + (m2 - m1) * tmp / 60);
  } else if (tmp < 180) {
    r = m2;
  } else if (tmp < 240) {
    r = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    r = m1;
  }

  tmp = h;
  if (tmp < 60) {
    g = m1 + (m2 - m1) * tmp / 60;
  } else if (tmp < 180) {
    g = m2;
  } else if (tmp < 240) {
    g = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    g = m1;
  }

  tmp = h - 120;
  if (tmp < 0) {
    tmp = tmp + 360
  }
  if (tmp < 60) {
    b = m1 + (m2 - m1) * tmp / 60;
  } else if (tmp < 180) {
    b = m2;
  } else if (tmp < 240) {
    b = m1 + (m2 - m1) * (240 - tmp) / 60;
  } else {
    b = m1;
  }

  return {'r': Math.round(r * 255), 'g': Math.round(g * 255), 'b': Math.round(b * 255), 'type': 'RGB'};
}

ためしてみる

var r = 10, g = 155, b = 200, hls, rgb;
hls = RGBtoHLS(r, g, b);
rgb = HLStoRGB(hls.h, hls.l, hls.s);

alert('R: ' + r + ', G: ' + g + ', B: ' + b + '\n' +
  ' => H : ' + hls.h + ', L: ' + hls.l + ', S: ' + hls.s + '\n' +
  ' => R : ' + rgb.r + ', G: ' + rgb.g + ', B: ' + rgb.b);