hue-hacking-node
Version:
Utility to control Philips Hue light bulbs
345 lines • 15 kB
JavaScript
"use strict";
/**
* Color utility functions, exposed as a Typescript class.
* No external dependencies.
* Special thanks for the RGB to CIE conversion code goes out to the Q42 team
* for their Q42.HueApi work. Dank u!
* More info: https://github.com/Q42/Q42.HueApi.
*
* https://github.com/bjohnso5/hue-hacking
* Copyright (c) 2013 Bryan Johnson; Licensed MIT */
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.HueColors = exports.hexFullWhite = exports.hexFullBlue = exports.hexFullGreen = exports.hexFullRed = exports.CIEBlue = exports.CIELime = exports.CIERed = void 0;
var hue_css_colors_js_1 = require("./hue-css-colors.js");
var hue_interfaces_js_1 = require("./hue-interfaces.js");
exports.CIERed = new hue_interfaces_js_1.XYPoint(0.675, 0.322);
exports.CIELime = new hue_interfaces_js_1.XYPoint(0.4091, 0.518);
exports.CIEBlue = new hue_interfaces_js_1.XYPoint(0.167, 0.04);
exports.hexFullRed = 'FF0000';
exports.hexFullGreen = '00FF00';
exports.hexFullBlue = '0000FF';
exports.hexFullWhite = 'FFFFFF';
var HueColors = /** @class */ (function () {
function HueColors() {
this.cssColors = new hue_css_colors_js_1.CssColors();
}
/**
* Parses a valid hex color string and returns the Red RGB integer value.
*
* @param {string} hex Hex color string.
* @return {number} Red integer value.
*/
HueColors.prototype.hexToRed = function (hex) {
return parseInt(hex.substring(0, 2), 16);
};
/**
* Parses a valid hex color string and returns the Green RGB integer value.
*
* @param {string} hex Hex color string.
* @return {number} Green integer value.
*/
HueColors.prototype.hexToGreen = function (hex) {
return parseInt(hex.substring(2, 4), 16);
};
/**
* Parses a valid hex color string and returns the Blue RGB integer value.
*
* @param {string} hex Hex color string.
* @return {number} Blue integer value.
*/
HueColors.prototype.hexToBlue = function (hex) {
return parseInt(hex.substring(4, 6), 16);
};
/**
* Converts a valid hex color string to an RGB array.
*
* @param {string} h Hex color String (e.g. FF00FF)
* @return {Array<number>} Array containing R, G, B values
*/
HueColors.prototype.hexToRGB = function (h) {
return new hue_interfaces_js_1.RGB(this.hexToRed(h), this.hexToGreen(h), this.hexToBlue(h));
};
/**
* Converts an RGB component to a hex string.
*
* @param {number} c RGB value, integer between 0 and 255.
* @returns {string} Hex value string (e.g. FF)
*/
HueColors.prototype.componentToHex = function (c) {
var hex = c.toString(16);
return hex.length == 1 ? "0".concat(hex) : hex;
};
/**
* Converts RGB color components to a valid hex color string.
*
* @param {RGB} rgb RGB components with values between 0 and 255.
* @returns {string} Hex color string (e.g. FF0000)
*/
HueColors.prototype.rgbToHex = function (rgb) {
return (this.componentToHex(rgb.r) +
this.componentToHex(rgb.g) +
this.componentToHex(rgb.b));
};
/**
* Generates a random number between 'from' and 'to'.
*
* @param {number} from Number representing the start of a range.
* @param {number} to Number representing the end of a range.
*/
HueColors.prototype.randomFromInterval = function (from, to) {
return Math.floor(Math.random() * (to - from + 1) + from);
};
/**
* Return a random Integer in the range of 0 to 255, representing an RGB
* color value.
*
* @return {number} Integer between 0 and 255.
*/
HueColors.prototype.randomRGBValue = function () {
return this.randomFromInterval(0, 255);
};
/**
* Returns the cross product of two XYPoints.
*
* @param {XYPoint} p1 Point 1
* @param {XYPoint} p2 Point 2
* @return {number} Cross-product of the two XYPoints provided
*/
HueColors.prototype.crossProduct = function (p1, p2) {
return p1.x * p2.y - p1.y * p2.x;
};
/**
* Check if the provided XYPoint can be recreated by a Hue lamp.
*
* @param {XYPoint} p XYPoint to check
* @return {boolean} Flag indicating if the point is within reproducible range
*/
HueColors.prototype.checkPointInLampsReach = function (p) {
var v1 = new hue_interfaces_js_1.XYPoint(exports.CIELime.x - exports.CIERed.x, exports.CIELime.y - exports.CIERed.y), v2 = new hue_interfaces_js_1.XYPoint(exports.CIEBlue.x - exports.CIERed.x, exports.CIEBlue.y - exports.CIERed.y), q = new hue_interfaces_js_1.XYPoint(p.x - exports.CIERed.x, p.y - exports.CIERed.y), s = this.crossProduct(q, v2) / this.crossProduct(v1, v2), t = this.crossProduct(v1, q) / this.crossProduct(v1, v2);
return s >= 0.0 && t >= 0.0 && s + t <= 1.0;
};
/**
* Find the closest point on a line. This point will be reproducible by a Hue lamp.
*
* @param {XYPoint} A The point where the line starts
* @param {XYPoint} B The point where the line ends
* @param {XYPoint} P The point which is close to the line
* @return {XYPoint} A point that is on the line, and closest to the XYPoint provided
*/
HueColors.prototype.getClosestPointToLine = function (A, B, P) {
var AP = new hue_interfaces_js_1.XYPoint(P.x - A.x, P.y - A.y), AB = new hue_interfaces_js_1.XYPoint(B.x - A.x, B.y - A.y), ab2 = AB.x * AB.x + AB.y * AB.y, ap_ab = AP.x * AB.x + AP.y * AB.y, t = ap_ab / ab2;
if (t < 0.0) {
t = 0.0;
}
else if (t > 1.0) {
t = 1.0;
}
return new hue_interfaces_js_1.XYPoint(A.x + AB.x * t, A.y + AB.y * t);
};
/**
* Find the closest Hue-producible point to a provided point.
*
* @param {XYPoint} xyPoint The point to find the closest reproducible point to
* @return {XYPoint} The closest Hue-reproducible point to the provided point
*/
HueColors.prototype.getClosestPointToPoint = function (xyPoint) {
// Color is unreproducible, find the closest point on each line in the CIE 1931 'triangle'.
var pAB = this.getClosestPointToLine(exports.CIERed, exports.CIELime, xyPoint), pAC = this.getClosestPointToLine(exports.CIEBlue, exports.CIERed, xyPoint), pBC = this.getClosestPointToLine(exports.CIELime, exports.CIEBlue, xyPoint),
// Get the distances per point and see which point is closer to our Point.
dAB = this.getDistanceBetweenTwoPoints(xyPoint, pAB), dAC = this.getDistanceBetweenTwoPoints(xyPoint, pAC), dBC = this.getDistanceBetweenTwoPoints(xyPoint, pBC), lowest = dAB, closestPoint = pAB;
if (dAC < lowest) {
lowest = dAC;
closestPoint = pAC;
}
if (dBC < lowest) {
lowest = dBC;
closestPoint = pBC;
}
return closestPoint;
};
/**
* Returns the distance between two XYPoints.
*
* @param {XYPoint} one The first point
* @param {XYPoint} two The second point
* @return {number} The distance between points one and two
*/
HueColors.prototype.getDistanceBetweenTwoPoints = function (one, two) {
var dx = one.x - two.x, // horizontal difference
dy = one.y - two.y; // vertical difference
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Returns an XYPoint object containing the closest available CIE 1931
* coordinates based on the RGB input values.
*
* @param {RGB} rgb RGB color object
* @return {XYPoint} CIE 1931 XY coordinates, corrected for reproducibility
*/
HueColors.prototype.getXYPointFromRGB = function (rgb) {
var r = rgb.r > 0.04045
? Math.pow((rgb.r + 0.055) / (1.0 + 0.055), 2.4)
: rgb.r / 12.92, g = rgb.g > 0.04045
? Math.pow((rgb.g + 0.055) / (1.0 + 0.055), 2.4)
: rgb.g / 12.92, b = rgb.b > 0.04045
? Math.pow((rgb.b + 0.055) / (1.0 + 0.055), 2.4)
: rgb.b / 12.92, X = r * 0.4360747 + g * 0.3850649 + b * 0.0930804, Y = r * 0.2225045 + g * 0.7168786 + b * 0.0406169, Z = r * 0.0139322 + g * 0.0971045 + b * 0.7141733, cx = X / (X + Y + Z), cy = Y / (X + Y + Z);
cx = isNaN(cx) ? 0.0 : cx;
cy = isNaN(cy) ? 0.0 : cy;
//Check if the given XY value is within the colourreach of our lamps.
var xyPoint = new hue_interfaces_js_1.XYPoint(cx, cy), inReachOfLamps = this.checkPointInLampsReach(xyPoint);
if (!inReachOfLamps) {
var closestPoint = this.getClosestPointToPoint(xyPoint);
cx = closestPoint.x;
cy = closestPoint.y;
}
return new hue_interfaces_js_1.XYPoint(cx, cy);
};
/**
* Returns a rgb array for given x, y values. Not actually an inverse of
* getXYPointFromRGB. Implementation of the instructions found on the
* Philips Hue iOS SDK docs: http://goo.gl/kWKXKl
*
* @param {XYPoint} coords CIE 1931 x,y coordinates
* @param {number} bri Brightness value between 0 and 1
* @return {RGB} RGB color object
*/
HueColors.prototype.getRGBFromXYAndBrightness = function (coords, bri) {
// Check if the xy value is within the color gamut of the lamp.
// If not continue with step 2, otherwise step 3.
// We do this to calculate the most accurate color the given light can actually do.
if (!this.checkPointInLampsReach(coords)) {
// Calculate the closest point on the color gamut triangle
// and use that as xy value See step 6 of color to xy.
coords = this.getClosestPointToPoint(coords);
}
// Calculate XYZ values Convert using the following formulas:
var Y = bri, X = Y / coords.y * coords.x, Z = Y / coords.y * (1 - coords.x - coords.y);
// Convert to RGB using Wide RGB D65 conversion.
var rgb = [
X * 1.612 - Y * 0.203 - Z * 0.302,
-X * 0.509 + Y * 1.412 + Z * 0.066,
X * 0.026 - Y * 0.072 + Z * 0.962
];
// Apply reverse gamma correction.
rgb = rgb.map(function (x) {
return x <= 0.0031308
? 12.92 * x
: (1.0 + 0.055) * Math.pow(x, 1.0 / 2.4) - 0.055;
});
// Bring all negative components to zero.
rgb = rgb.map(function (x) {
return Math.max(0, x);
});
// If one component is greater than 1, weight components by that value.
var max = Math.max(rgb[0], rgb[1], rgb[2]);
if (max > 1) {
rgb = rgb.map(function (x) {
return x / max;
});
}
rgb = rgb.map(function (x) {
return Math.floor(x * 255);
});
return new (hue_interfaces_js_1.RGB.bind.apply(hue_interfaces_js_1.RGB, __spreadArray([void 0], rgb, false)))();
};
/**
* Converts hexadecimal colors represented as a String to approximate
* CIE 1931 coordinates. May not produce accurate values.
*
* @param {string} h Value representing a hexadecimal color value
* @return {XYPoint} Approximate CIE 1931 x,y coordinates
*/
HueColors.prototype.hexToCIE1931 = function (h) {
var rgb = this.hexToRGB(h);
return this.rgbToCIE1931(rgb);
};
/**
* Converts red, green and blue integer values to approximate CIE 1931
* x and y coordinates. Algorithm from:
* http://www.easyrgb.com/index.php?X=MATH&H=02#text2. May not produce
* accurate values.
*
* @param {RGB} rgb RGB color object
* @return {XYPoint} Approximate CIE 1931 x,y coordinates
*/
HueColors.prototype.rgbToCIE1931 = function (rgb) {
return this.getXYPointFromRGB(rgb);
};
/**
* Returns the approximate CIE 1931 x,y coordinates represented by the
* supplied hexColor parameter, or of a random color if the parameter
* is not passed.
*
* @param {string} hexColor String representing a hexidecimal color value OR a named CSS color (e.g. "red", "yellow", etc.)
* @return {XYPoint} Approximate CIE 1931 x,y coordinates
*/
HueColors.prototype.getCIEColor = function (hexColor) {
var hex = hexColor || null, xy = null, lowerCaseHex = hex !== null ? hexColor.toString().toLowerCase() : null, cssColor = this.cssColors.getHexCode(lowerCaseHex);
if (cssColor !== undefined) {
xy = this.hexToCIE1931(cssColor);
}
else if (null !== hex) {
xy = this.hexToCIE1931(hex);
}
else {
var rgb = new hue_interfaces_js_1.RGB(this.randomRGBValue(), this.randomRGBValue(), this.randomRGBValue());
xy = this.rgbToCIE1931(rgb);
}
return xy;
};
/**
* Returns the approximate hexColor represented by the supplied
* CIE 1931 x,y coordinates and brightness value.
*
* @param {XYPoint} coords CIE 1931 coordinates
* @param {number} bri value expressed between 0 and 1
* @return {string} hex color string
*/
HueColors.prototype.CIE1931ToHex = function (coords, bri) {
if (bri === undefined) {
bri = 1;
}
var rgb = this.getRGBFromXYAndBrightness(coords, bri);
return this.rgbToHex(rgb);
};
/**
* Returns the approximate RGB values represented by the supplied
* CIE 1931 x,y coordinates and brightness value.
*
* @param {XYPoint} coords CIE 1931 coordinates
* @param {number} bri Brightness value expressed between 0 and 1
* @return {string} CSS rgb color expression
*/
HueColors.prototype.CIE1931ToRGB = function (coords, bri) {
if (bri === undefined) {
bri = 1;
}
var rgb = this.getRGBFromXYAndBrightness(coords, bri);
return rgb;
};
/**
* Convert a Mired temperature to its Kelvin equivalent.
*/
HueColors.prototype.miredToKelvin = function (mired) {
return 1e6 / mired;
};
/**
* Convert a Kelvin temperature to its Mired equivalent.
*/
HueColors.prototype.kelvinToMired = function (kelvin) {
return 1e6 / kelvin;
};
return HueColors;
}());
exports.HueColors = HueColors;
//# sourceMappingURL=hue-colors.js.map