forked from platypush/platypush
215 lines
6.9 KiB
JavaScript
215 lines
6.9 KiB
JavaScript
export class ColorConverter {
|
|
constructor(ranges) {
|
|
this.ranges = {
|
|
hue: [0, 360],
|
|
sat: [0, 100],
|
|
bri: [0, 100],
|
|
ct: [154, 500],
|
|
}
|
|
|
|
if (ranges)
|
|
for (const attr of Object.keys(this.ranges))
|
|
if (ranges[attr])
|
|
this.ranges[attr] = ranges[attr]
|
|
}
|
|
|
|
normalize(x, xRange, yRange) {
|
|
return yRange[0] + (((x-xRange[0]) * (yRange[1]-yRange[0])) / (xRange[1]-xRange[0]))
|
|
}
|
|
|
|
hslToRgb(h, s, l) {
|
|
[h, s, l] = [
|
|
this.normalize(h, this.ranges.hue, [0, 360]),
|
|
this.normalize(s, this.ranges.sat, [0, 100]),
|
|
this.normalize(l, this.ranges.bri, [0, 100]),
|
|
]
|
|
|
|
l /= 100
|
|
const a = s * Math.min(l, 1 - l) / 100
|
|
const f = n => {
|
|
const k = (n + h / 30) % 12
|
|
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1)
|
|
return Math.round(255 * color)
|
|
}
|
|
|
|
return [f(0), f(8), f(4)]
|
|
}
|
|
|
|
rgbToHsl(r, g, b){
|
|
r /= 255
|
|
g /= 255
|
|
b /= 255;
|
|
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
let h, s, l = (max + min) / 2;
|
|
|
|
if(max === min){
|
|
h = s = 0; // achromatic
|
|
} else {
|
|
const 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 [
|
|
parseInt(this.normalize(h, [0, 1], this.ranges.hue)),
|
|
parseInt(this.normalize(s, [0, 1], this.ranges.sat)),
|
|
parseInt(this.normalize(l, [0, 1], this.ranges.bri)),
|
|
]
|
|
}
|
|
|
|
xyToRgb(x, y, brightness) {
|
|
// Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons)
|
|
if (brightness == null)
|
|
brightness = this.ranges.bri[1];
|
|
|
|
const z = 1.0 - x - y;
|
|
const Y = (brightness / (this.ranges.bri[1]-1)).toFixed(2);
|
|
const X = (Y / y) * x;
|
|
const Z = (Y / y) * z;
|
|
|
|
//Convert to RGB using Wide RGB D65 conversion
|
|
let red = X * 1.656492 - Y * 0.354851 - Z * 0.255038;
|
|
let green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152;
|
|
let blue = X * 0.051713 - Y * 0.121364 + Z * 1.011530;
|
|
|
|
//If red, green or blue is larger than 1.0 set it back to the maximum of 1.0
|
|
if (red > blue && red > green && red > 1.0) {
|
|
green = green / red;
|
|
blue = blue / red;
|
|
red = 1.0;
|
|
} else if (green > blue && green > red && green > 1.0) {
|
|
red = red / green;
|
|
blue = blue / green;
|
|
green = 1.0;
|
|
} else if (blue > red && blue > green && blue > 1.0) {
|
|
red = red / blue;
|
|
green = green / blue;
|
|
blue = 1.0;
|
|
}
|
|
|
|
//Reverse gamma correction
|
|
red = red <= 0.0031308 ? 12.92 * red : (1.0 + 0.055) * Math.pow(red, (1.0 / 2.4)) - 0.055;
|
|
green = green <= 0.0031308 ? 12.92 * green : (1.0 + 0.055) * Math.pow(green, (1.0 / 2.4)) - 0.055;
|
|
blue = blue <= 0.0031308 ? 12.92 * blue : (1.0 + 0.055) * Math.pow(blue, (1.0 / 2.4)) - 0.055;
|
|
|
|
//Convert normalized decimal to decimal
|
|
red = Math.round(red * 255);
|
|
green = Math.round(green * 255);
|
|
blue = Math.round(blue * 255);
|
|
|
|
if (isNaN(red))
|
|
red = 0;
|
|
if (isNaN(green))
|
|
green = 0;
|
|
if (isNaN(blue))
|
|
blue = 0;
|
|
|
|
return [red, green, blue].map(
|
|
(c) => Math.min(Math.max(0, c), 255)) // lgtm [js/automatic-semicolon-insertion]
|
|
}
|
|
|
|
rgbToXY(red, green, blue) {
|
|
if (red > 1) { red /= 255; }
|
|
if (green > 1) { green /= 255; }
|
|
if (blue > 1) { blue /= 255; }
|
|
|
|
//Apply a gamma correction to the RGB values, which makes the color more vivid and more the like the color displayed on the screen of your device
|
|
red = (red > 0.04045) ? Math.pow((red + 0.055) / (1.0 + 0.055), 2.4) : (red / 12.92);
|
|
green = (green > 0.04045) ? Math.pow((green + 0.055) / (1.0 + 0.055), 2.4) : (green / 12.92);
|
|
blue = (blue > 0.04045) ? Math.pow((blue + 0.055) / (1.0 + 0.055), 2.4) : (blue / 12.92);
|
|
|
|
//RGB values to XYZ using the Wide RGB D65 conversion formula
|
|
const X = red * 0.664511 + green * 0.154324 + blue * 0.162028;
|
|
const Y = red * 0.283881 + green * 0.668433 + blue * 0.047685;
|
|
const Z = red * 0.000088 + green * 0.072310 + blue * 0.986039;
|
|
|
|
//Calculate the xy values from the XYZ values
|
|
let x = parseFloat((X / (X + Y + Z)).toFixed(4));
|
|
let y = parseFloat((Y / (X + Y + Z)).toFixed(4));
|
|
|
|
if (isNaN(x))
|
|
x = 0;
|
|
if (isNaN(y))
|
|
y = 0;
|
|
|
|
return [x, y];
|
|
}
|
|
|
|
rgbToBri(red, green, blue) {
|
|
return Math.min(2 * this.rgbToHsl(red, green, blue)[2], this.ranges.bri[1])
|
|
}
|
|
|
|
getRGB(color) {
|
|
if (color.red != null && color.green != null && color.blue != null)
|
|
return [color.red, color.green, color.blue]
|
|
if (color.r != null && color.g != null && color.b != null)
|
|
return [color.r, color.g, color.b]
|
|
if (color.rgb)
|
|
return color.rgb
|
|
}
|
|
|
|
getXY(color) {
|
|
if (color.x != null && color.y != null)
|
|
return [color.x, color.y]
|
|
if (color.xy)
|
|
return color.xy
|
|
}
|
|
|
|
toRGB(color) {
|
|
const rgb = this.getRGB(color)
|
|
if (rgb)
|
|
return rgb
|
|
|
|
const xy = this.getXY(color)
|
|
if (xy && color.bri)
|
|
return this.xyToRgb(...xy, color.bri)
|
|
if (color.hue && color.sat && color.bri)
|
|
return this.hslToRgb(color.hue, color.sat, color.bri)
|
|
|
|
console.debug('Could not determine color space')
|
|
console.debug(color)
|
|
}
|
|
|
|
toXY(color) {
|
|
const xy = this.getXY(color)
|
|
if (xy && color.bri)
|
|
return [xy[0], xy[1], color.bri]
|
|
|
|
const rgb = this.getRGB(color)
|
|
if (rgb)
|
|
return this.rgbToXY(...rgb)
|
|
|
|
if (color.hue && color.sat && color.bri) {
|
|
const rgb = this.hslToRgb(color.hue, color.sat, color.bri)
|
|
return this.rgbToXY(...rgb)
|
|
}
|
|
|
|
console.debug('Could not determine color space')
|
|
console.debug(color)
|
|
}
|
|
|
|
toHSL(color) {
|
|
if (color.hue && color.sat && color.bri)
|
|
return [color.hue, color.sat, color.bri]
|
|
|
|
const rgb = this.getRGB(color)
|
|
if (rgb)
|
|
return this.rgbToHsl(...rgb)
|
|
|
|
const xy = this.getXY(color)
|
|
if (xy && color.bri) {
|
|
const rgb = this.xyToRgb(...xy, color.bri)
|
|
return this.rgbToHsl(...rgb)
|
|
}
|
|
|
|
console.debug('Could not determine color space')
|
|
console.debug(color)
|
|
}
|
|
}
|