platypush/platypush/backend/http/webapp/src/components/panels/Light/color.js

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)
}
}