UNPKG

node-red-contrib-huemagic

Version:

Philips Hue node to control bridges, lights, groups, scenes, rules, taps, switches, buttons, motion sensors, temperature sensors and Lux sensors using Node-RED.

382 lines (319 loc) 9.32 kB
// EXTERNAL HELPERS const colornames = require("colornames"); const colornamer = require('color-namer'); const getColors = require('get-image-colors'); // RGB -> XY function rgbToXy(red, green, blue, gamut = null) { var colorGamut = { red: [1.0, 0], green: [0.0, 1.0], blue: [0.0, 0.0] }; if(!!gamut) { colorGamut = { red: [gamut.red.x, gamut.red.y], green: [gamut.green.x, gamut.green.y], blue: [gamut.blue.x, gamut.blue.y] }; } red = parseFloat(red / 255); green = parseFloat(green / 255); blue = parseFloat(blue / 255); red = _getGammaCorrectedValue(red); green = _getGammaCorrectedValue(green); blue = _getGammaCorrectedValue(blue); let x = red * 0.649926 + green * 0.103455 + blue * 0.197109; let y = red * 0.234327 + green * 0.743075 + blue * 0.022598; let z = red * 0.0000000 + green * 0.053077 + blue * 1.035763; let xy = { x: x / (x + y + z), y: y / (x + y + z) }; if(!_xyIsInGamutRange(xy, colorGamut)) { xy = _getClosestColor(xy, colorGamut); } return xy; } function _getGammaCorrectedValue(value) { return (value > 0.04045) ? Math.pow((value + 0.055) / (1.0 + 0.055), 2.4) : (value / 12.92) } function _xyIsInGamutRange(xy, gamut) { if(Array.isArray(xy)) { xy = { x: xy[0], y: xy[1] }; } const v0 = [gamut.blue[0] - gamut.red[0], gamut.blue[1] - gamut.red[1]]; const v1 = [gamut.green[0] - gamut.red[0], gamut.green[1] - gamut.red[1]]; const v2 = [xy.x - gamut.red[0], xy.y - gamut.red[1]]; const dot00 = (v0[0] * v0[0]) + (v0[1] * v0[1]); const dot01 = (v0[0] * v1[0]) + (v0[1] * v1[1]); const dot02 = (v0[0] * v2[0]) + (v0[1] * v2[1]); const dot11 = (v1[0] * v1[0]) + (v1[1] * v1[1]); const dot12 = (v1[0] * v2[0]) + (v1[1] * v2[1]); const invDenom = 1 / (dot00 * dot11 - dot01 * dot01); const u = (dot11 * dot02 - dot01 * dot12) * invDenom; const v = (dot00 * dot12 - dot01 * dot02) * invDenom; return ((u >= 0) && (v >= 0) && (u + v < 1)); } function _getClosestColor(xy, gamut) { let greenBlue = { a: { x: gamut.green[0], y: gamut.green[1] }, b: { x: gamut.blue[0], y: gamut.blue[1] } }; let greenRed = { a: { x: gamut.green[0], y: gamut.green[1] }, b: { x: gamut.red[0], y: gamut.red[1] } }; let blueRed = { a: { x: gamut.red[0], y: gamut.red[1] }, b: { x: gamut.blue[0], y: gamut.blue[1] } }; let closestColorPoints = { greenBlue: _getClosestPoint(xy,greenBlue.a, greenBlue.b), greenRed: _getClosestPoint(xy,greenRed.a, greenRed.b), blueRed: _getClosestPoint(xy,blueRed.a, blueRed.b) }; let distance = { greenBlue: _getLineDistance(xy,closestColorPoints.greenBlue), greenRed: _getLineDistance(xy,closestColorPoints.greenRed), blueRed: _getLineDistance(xy,closestColorPoints.blueRed) }; let closestDistance; let closestColor; for (let i in distance) { if(distance.hasOwnProperty(i)) { if(!closestDistance) { closestDistance = distance[i]; closestColor = i; } if(closestDistance > distance[i]) { closestDistance = distance[i]; closestColor = i; } } } return closestColorPoints[closestColor]; } function _getLineDistance(pointA,pointB) { return Math.hypot(pointB.x - pointA.x, pointB.y - pointA.y); } function _getClosestPoint(xy, pointA, pointB) { let xy2a = [xy.x - pointA.x, xy.y - pointA.y]; let a2b = [pointB.x - pointA.x, pointB.y - pointA.y]; let a2bSqr = Math.pow(a2b[0],2) + Math.pow(a2b[1],2); let xy2a_dot_a2b = xy2a[0] * a2b[0] + xy2a[1] * a2b[1]; let t = xy2a_dot_a2b /a2bSqr; return { x: pointA.x + a2b[0] * t, y: pointA.y + a2b[1] * t } } // XY -> RGB function xyBriToRgb(x, y, bri) { const z = 1.0 - x - y; const Y = bri / 100; const X = (Y / y) * x; const Z = (Y / y) * z; let r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; let g = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; let b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; r = _getReversedGammaCorrectedValue(r); g = _getReversedGammaCorrectedValue(g); b = _getReversedGammaCorrectedValue(b); r = Math.max(r, 0); g = Math.max(g, 0); b = Math.max(b, 0); let max = Math.max(r, g, b); if (max > 1) { r = r / max; g = g / max; b = b / max; } return { r: Math.floor(r * 255), g: Math.floor(g * 255), b: Math.floor(b * 255), }; } function _getReversedGammaCorrectedValue(value) { return value <= 0.0031308 ? 12.92 * value : (1.0 + 0.055) * Math.pow(value, (1.0 / 2.4)) - 0.055; } // RGB -> HEX function rgbHex(red, green, blue, alpha) { const isPercent = (red + (alpha || '')).toString().includes('%'); if (typeof red === 'string') { [red, green, blue, alpha] = red.match(/(0?\.?\d{1,3})%?\b/g).map(component => Number(component)); } else if (alpha !== undefined) { alpha = Number.parseFloat(alpha); } if (typeof red !== 'number' || typeof green !== 'number' || typeof blue !== 'number' || red > 255 || green > 255 || blue > 255 ) { throw new TypeError('Expected three numbers below 256'); } if (typeof alpha === 'number') { if (!isPercent && alpha >= 0 && alpha <= 1) { alpha = Math.round(255 * alpha); } else if (isPercent && alpha >= 0 && alpha <= 100) { alpha = Math.round(255 * alpha / 100); } else { throw new TypeError(`Expected alpha value (${alpha}) as a fraction or percentage`); } alpha = (alpha | 1 << 8).toString(16).slice(1); } else { alpha = ''; } return ((blue | green << 8 | red << 16) | 1 << 24).toString(16).slice(1) + alpha; } // HEX -> RGB function hexRgb(hex, options = {}) { const hexCharacters = 'a-f\\d'; const match3or4Hex = `#?[${hexCharacters}]{3}[${hexCharacters}]?`; const match6or8Hex = `#?[${hexCharacters}]{6}([${hexCharacters}]{2})?`; const nonHexChars = new RegExp(`[^#${hexCharacters}]`, 'gi'); const validHexSize = new RegExp(`^${match3or4Hex}$|^${match6or8Hex}$`, 'i'); if (typeof hex !== 'string' || nonHexChars.test(hex) || !validHexSize.test(hex)) { throw new TypeError('Expected a valid hex string'); } hex = hex.replace(/^#/, ''); let alphaFromHex = 1; if (hex.length === 8) { alphaFromHex = Number.parseInt(hex.slice(6, 8), 16) / 255; hex = hex.slice(0, 6); } if (hex.length === 4) { alphaFromHex = Number.parseInt(hex.slice(3, 4).repeat(2), 16) / 255; hex = hex.slice(0, 3); } if (hex.length === 3) { hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2]; } const number = Number.parseInt(hex, 16); const red = number >> 16; const green = (number >> 8) & 255; const blue = number & 255; const alpha = typeof options.alpha === 'number' ? options.alpha : alphaFromHex; return [red, green, blue]; } // RANDOM HEX COLOR GENERATOR function randomHexColor() { return '#' + (Math.random() * 0xFFFFFF << 0).toString(16).padStart(6, "0"); } function rgbToHsl(r, g, b) { r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if(max == min) { h = s = 0; } else { var 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 [h, s, l]; } function hslToRgb(h, s, l) { var r, g, b; if(s == 0) { r = g = b = l; // achromatic } else { var hue2rgb = function hue2rgb(p, q, t) { if(t < 0) t += 1; if(t > 1) t -= 1; if(t < 1/6) return p + (q - p) * 6 * t; if(t < 1/2) return q; if(t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; } var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = hue2rgb(p, q, h + 1/3); g = hue2rgb(p, q, h); b = hue2rgb(p, q, h - 1/3); } return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)]; } // MIX TWO COLORS function mixColors(rgbA, rgbB, amount = 0.5) { var r = _colorChannelMixer(rgbA[0], rgbB[0], amount); var g = _colorChannelMixer(rgbA[1], rgbB[1], amount); var b = _colorChannelMixer(rgbA[2], rgbB[2], amount); return [r, g, b]; } function _colorChannelMixer(colorChannelA, colorChannelB, amount) { var channelA = colorChannelA * amount; var channelB = colorChannelB * (1 - amount); return parseInt(channelA + channelB); } // GET CURRENT COLOR TEMPERATURE function colorTemperature() { let hour = (new Date()).getHours(); let minute = (new Date()).getMinutes(); let time = hour + minute * 0.01667; let autoTemperature = Math.floor(3.125 * time ** 2 - 87.5 * time + 812); autoTemperature = (autoTemperature < 153) ? 153 : autoTemperature; autoTemperature = (autoTemperature > 500) ? 500 : autoTemperature; return autoTemperature; } // EXPORT module.exports.rgbToXy = rgbToXy; module.exports.rgbHex = rgbHex; module.exports.rgbToHsl = rgbToHsl; module.exports.hslToRgb = hslToRgb; module.exports.xyBriToRgb = xyBriToRgb; module.exports.hexRgb = hexRgb; module.exports.randomHexColor = randomHexColor; module.exports.colornames = colornames; module.exports.colornamer = colornamer; module.exports.getColors = getColors; module.exports.colorTemperature = colorTemperature; module.exports.mixColors = mixColors;