Spaces:
Sleeping
Sleeping
// RGB <-> HSV conversion functions are based on https://github.com/LLK/scratch-vm/blob/develop/src/util/color.js | |
/** | |
* @typedef RGB | |
* @property {number} red red [0-255] | |
* @property {number} green green [0-255] | |
* @property {number} blue blue [0-255] | |
*/ | |
/** | |
* @typedef HSV | |
* @property {number} hue hue [0-360) | |
* @property {number} saturation saturation [0-1] | |
* @property {number} value value [0-1] | |
*/ | |
/** | |
* @param {string} hex | |
* @returns {RGB} | |
*/ | |
export const hexToRgb = (hex) => { | |
const parsed = parseInt(hex.substring(1), 16); | |
return { | |
red: (parsed >> 16) & 0xff, | |
green: (parsed >> 8) & 0xff, | |
blue: parsed & 0xff | |
}; | |
}; | |
/** | |
* @param {RGB} color | |
* @returns {string} | |
*/ | |
export const rgbToHex = (color) => { | |
const format = (n) => n.toString(16).padStart(2, '0'); | |
return `#${format(color.red)}${format(color.green)}${format(color.blue)}`; | |
}; | |
/** | |
* @param {RGB} color | |
* @returns {HSV} | |
*/ | |
export const rgbToHsv = (color) => { | |
const r = color.red / 255; | |
const g = color.green / 255; | |
const b = color.blue / 255; | |
const x = Math.min(Math.min(r, g), b); | |
const v = Math.max(Math.max(r, g), b); | |
// For grays, hue will be arbitrarily reported as zero. Otherwise, calculate | |
let h = 0; | |
let s = 0; | |
if (x !== v) { | |
const f = r === x ? g - b : g === x ? b - r : r - g; | |
const i = r === x ? 3 : g === x ? 5 : 1; | |
h = ((i - f / (v - x)) * 60) % 360; | |
s = (v - x) / v; | |
} | |
return { | |
hue: h, | |
saturation: s, | |
value: v | |
}; | |
}; | |
/** | |
* @param {HSV} color | |
* @returns {RGB} | |
*/ | |
export const hsvToRgb = (color) => { | |
let h = color.hue % 360; | |
if (h < 0) h += 360; | |
const s = Math.max(0, Math.min(color.saturation, 1)); | |
const v = Math.max(0, Math.min(color.value, 1)); | |
const i = Math.floor(h / 60); | |
const f = h / 60 - i; | |
const p = v * (1 - s); | |
const q = v * (1 - s * f); | |
const t = v * (1 - s * (1 - f)); | |
let r; | |
let g; | |
let b; | |
switch (i) { | |
default: | |
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 { | |
red: Math.floor(r * 255), | |
green: Math.floor(g * 255), | |
blue: Math.floor(b * 255), | |
}; | |
}; | |
/** | |
* @param {string} hex | |
* @returns {string} | |
*/ | |
export const darken = (hex) => { | |
const rgb = hexToRgb(hex); | |
const hsv = rgbToHsv(rgb); | |
// don't need to clamp value; hsvToRgb will do it for us | |
hsv.value -= 0.1; | |
const newRgb = hsvToRgb(hsv); | |
return rgbToHex(newRgb); | |
}; | |