ゲーム作ってたときに使ってたものです。


計算方法は、全面的に次のサイト様を参考にさせていただきました。

  • 小数の精度の都合上、完全な双方向の変換はできない場合があります。
  • RGBは 0 ~ 255, 色相は 0 ~ 360, 彩度と明度は 0 ~ 100 の範囲を想定してます。
  • 結果は小数にもなります。スタイルに使う場合は丸めてください。
RGB -> HSL
/**
 * RGBからHSLを算出して返却
 * @param  {Number} r - 赤 (0~255)
 * @param  {Number} g - 緑 (0~255)
 * @param  {Number} b - 青 (0~255)
 * @return {Object} h: 色相 (0~360), s: 彩度 (0~100), l: 明度 (0~100)
 */
const rgb2hsl = function(r, g, b) {
	const RGB_MAX = 255;
	const HUE_MAX = 360;
	const SATURATION_MAX = 100;
	const LIGHTNESS_MAX = 100;

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

	// Hue
	const hp = HUE_MAX / 6;
	if (max == min) {
		h = 0;
	} else if (r == max) {
		h = hp * ((g - b) / (max - min));
	} else if (g == max) {
		h = hp * ((b - r) / (max - min)) + HUE_MAX / 3;
	} else {
		h = hp * ((r - g) / (max - min)) + HUE_MAX * 2 / 3;
	}
	if (h < 0) {
		h += HUE_MAX;
	}

	// Saturation
	const cnt = (max + min) / 2;
	if (cnt < RGB_MAX / 2) {
		if (max + min <= 0) {
			s = 0;
		} else {
			s = (max - min) / (max + min) * SATURATION_MAX;
		}
	} else {
		s = (max - min) / (RGB_MAX * 2 - max - min) * SATURATION_MAX;
	}

	// Lightness
	l = (max + min) / RGB_MAX / 2 * LIGHTNESS_MAX;
	
	return {
		h: h,
		s: s,
		l: l
	};
};
HSL -> RGB
/**
 * HSLからRGBを算出して返却
 * @param  {Number} h - 色相 (0~360)
 * @param  {Number} s - 彩度 (0~100)
 * @param  {Number} l - 明度 (0~100)
 * @return {Object} r: 赤 (0~255), g: 緑 (0~255), b: 青 (0~255)
 */
const hsl2rgb = function(h, s, l) {
	const RGB_MAX = 255;
	const HUE_MAX = 360;
	const SATURATION_MAX = 100;
	const LIGHTNESS_MAX = 100;
	let r, g, b, max, min;
	
	h = h % HUE_MAX;
	s = s / SATURATION_MAX;
	l = l / LIGHTNESS_MAX;
	
	if (l < 0.5) {
		max = l + l * s;
		min = l - l * s;
	} else {
		max = l + (1 - l) * s;
		min = l - (1 - l) * s;
	}
	
	const hp = HUE_MAX / 6;
	const q = h / hp;
	if (q <= 1) {
		r = max;
		g = (h / hp) * (max - min) + min;
		b = min;
	} else if (q <= 2) {
		r = ((hp * 2 - h) / hp) * (max - min) + min;
		g = max;
		b = min;
	} else if (q <= 3) {
		r = min;
		g = max;
		b = ((h - hp * 2) / hp) * (max - min) + min;
	} else if (q <= 4) {
		r = min;
		g = ((hp * 4 - h) / hp) * (max - min) + min;
		b = max;
	} else if (q <= 5) {
		r = ((h - hp * 4) / hp) * (max - min) + min;
		g = min;
		b = max;
	} else {
		r = max;
		g = min;
		b = ((HUE_MAX - h) / hp) * (max - min) + min;
	}

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

テストケース

RGB -> HSL
console.log(rgb2hsl(  255,     0,     0));  // -> h:   0, s: 100, l:  50
console.log(rgb2hsl(  255, 127.5,     0));  // -> h:  30, s: 100, l:  50
console.log(rgb2hsl(  255,   255,     0));  // -> h:  60, s: 100, l:  50
console.log(rgb2hsl(127.5,   255,     0));  // -> h:  90, s: 100, l:  50
console.log(rgb2hsl(    0,   255,     0));  // -> h: 120, s: 100, l:  50
console.log(rgb2hsl(    0,   255, 127.5));  // -> h: 150, s: 100, l:  50
console.log(rgb2hsl(    0,   255,   255));  // -> h: 180, s: 100, l:  50
console.log(rgb2hsl(    0, 127.5,   255));  // -> h: 210, s: 100, l:  50
console.log(rgb2hsl(    0,     0,   255));  // -> h: 240, s: 100, l:  50
console.log(rgb2hsl(127.5,     0,   255));  // -> h: 270, s: 100, l:  50
console.log(rgb2hsl(  255,     0,   255));  // -> h: 300, s: 100, l:  50
console.log(rgb2hsl(  255,     0, 127.5));  // -> h: 330, s: 100, l:  50
console.log(rgb2hsl(    0,     0,     0));  // -> h:   0, s:   0, l:   0
console.log(rgb2hsl(   51,    51,    51));  // -> h:   0, s:   0, l:  20
console.log(rgb2hsl(127.5, 127.5, 127.5));  // -> h:   0, s:   0, l:  50
console.log(rgb2hsl(  255,   255,   255));  // -> h:   0, s:   0, l: 100
console.log(rgb2hsl(   51,     0,     0));  // -> h:   0, s: 100, l:  10
console.log(rgb2hsl(  102,     0,     0));  // -> h:   0, s: 100, l:  20
console.log(rgb2hsl(  153,     0,     0));  // -> h:   0, s: 100, l:  30
console.log(rgb2hsl(  204,     0,     0));  // -> h:   0, s: 100, l:  40
console.log(rgb2hsl(  153,   102,   102));  // -> h:   0, s:  20, l:  50
console.log(rgb2hsl(178.5,  76.5,  76.5));  // -> h:   0, s:  40, l:  50
console.log(rgb2hsl(  204,    51,    51));  // -> h:   0, s:  60, l:  50
console.log(rgb2hsl(229.5,  25.5,  25.5));  // -> h:   0, s:  80, l:  50
HSL -> RGB
console.log(hsl2rgb(   0, 100,  50 ));  // -> r:   255, g:     0, b:     0
console.log(hsl2rgb(  30, 100,  50 ));  // -> r:   255, g: 127.5, b:     0
console.log(hsl2rgb(  60, 100,  50 ));  // -> r:   255, g:   255, b:     0
console.log(hsl2rgb(  90, 100,  50 ));  // -> r: 127.5, g:   255, b:     0
console.log(hsl2rgb( 120, 100,  50 ));  // -> r:     0, g:   255, b:     0
console.log(hsl2rgb( 150, 100,  50 ));  // -> r:     0, g:   255, b: 127.5
console.log(hsl2rgb( 180, 100,  50 ));  // -> r:     0, g:   255, b:   255
console.log(hsl2rgb( 210, 100,  50 ));  // -> r:     0, g: 127.5, b:   255
console.log(hsl2rgb( 240, 100,  50 ));  // -> r:     0, g:     0, b:   255
console.log(hsl2rgb( 270, 100,  50 ));  // -> r: 127.5, g:     0, b:   255
console.log(hsl2rgb( 300, 100,  50 ));  // -> r:   255, g:     0, b:   255
console.log(hsl2rgb( 330, 100,  50 ));  // -> r:   255, g:     0, b: 127.5
console.log(hsl2rgb( 360, 100,  50 ));  // -> r:   255, g:     0, b:     0
console.log(hsl2rgb( 390, 100,  50 ));  // -> r:   255, g: 127.5, b:     0
console.log(hsl2rgb(  99,   0,   0 ));  // -> r:     0, g:     0, b:     0
console.log(hsl2rgb(  99,   0,  20 ));  // -> r:    51, g:    51, b:    51
console.log(hsl2rgb(  99,   0,  50 ));  // -> r: 127.5, g: 127.5, b: 127.5
console.log(hsl2rgb(  99,   0, 100 ));  // -> r:   255, g:   255, b:   255
console.log(hsl2rgb(   0, 100,  10 ));  // -> r:    51, g:     0, b:     0
console.log(hsl2rgb(   0, 100,  20 ));  // -> r:   102, g:     0, b:     0
console.log(hsl2rgb(   0, 100,  30 ));  // -> r:   153, g:     0, b:     0
console.log(hsl2rgb(   0, 100,  40 ));  // -> r:   204, g:     0, b:     0
console.log(hsl2rgb(   0,  20,  50 ));  // -> r:   153, g:   102, b:   102
console.log(hsl2rgb(   0,  40,  50 ));  // -> r: 178.5, g:  76.5, b:  76.5
console.log(hsl2rgb(   0,  60,  50 ));  // -> r:   204, g:    51, b:    51
console.log(hsl2rgb(   0,  80,  50 ));  // -> r: 229.5, g: 25.499999999999993, b: 25.499999999999993
console.log(hsl2rgb(  60, 100,  10 ));  // -> r:    51, g:    51, b:     0
console.log(hsl2rgb(  60, 100,  20 ));  // -> r:   102, g:   102, b:     0
console.log(hsl2rgb(  60, 100,  30 ));  // -> r:   153, g:   153, b:     0
console.log(hsl2rgb(  60, 100,  40 ));  // -> r:   204, g:   204, b:     0
console.log(hsl2rgb( 120, 100,  10 ));  // -> r:     0, g:    51, b:     0
console.log(hsl2rgb( 120, 100,  20 ));  // -> r:     0, g:   102, b:     0
console.log(hsl2rgb( 120, 100,  30 ));  // -> r:     0, g:   153, b:     0
console.log(hsl2rgb( 120, 100,  40 ));  // -> r:     0, g:   204, b:     0
console.log(hsl2rgb( 180, 100,  10 ));  // -> r:     0, g:    51, b:    51
console.log(hsl2rgb( 180, 100,  20 ));  // -> r:     0, g:   102, b:   102
console.log(hsl2rgb( 180, 100,  30 ));  // -> r:     0, g:   153, b:   153
console.log(hsl2rgb( 180, 100,  40 ));  // -> r:     0, g:   204, b:   204
console.log(hsl2rgb( 240, 100,  10 ));  // -> r:     0, g:     0, b:    51
console.log(hsl2rgb( 240, 100,  20 ));  // -> r:     0, g:     0, b:   102
console.log(hsl2rgb( 240, 100,  30 ));  // -> r:     0, g:     0, b:   153
console.log(hsl2rgb( 240, 100,  40 ));  // -> r:     0, g:     0, b:   204
console.log(hsl2rgb( 300, 100,  10 ));  // -> r:    51, g:     0, b:    51
console.log(hsl2rgb( 300, 100,  20 ));  // -> r:   102, g:     0, b:   102
console.log(hsl2rgb( 300, 100,  30 ));  // -> r:   153, g:     0, b:   153
console.log(hsl2rgb( 300, 100,  40 ));  // -> r:   204, g:     0, b:   204