From 6bd029faaef4839e2ee881f13726326709af567f Mon Sep 17 00:00:00 2001 From: Egor Date: Sun, 23 Apr 2017 23:48:46 -0700 Subject: [PATCH] better color conversion --- .../pods/components/lights-tab/component.js | 115 ++++---------- mobile/ember-cli-build.js | 1 + mobile/vendor/cie-rgb-converter.js | 141 ++++++++++++++++++ .../pods/components/lights-tab/component.js | 122 ++++----------- web/ember-cli-build.js | 3 +- web/public/assets/images/huegasm.png | Bin 8220 -> 3876 bytes web/public/assets/images/logo.png | Bin 5619 -> 7492 bytes web/vendor/cie-rgb-converter.js | 141 ++++++++++++++++++ 8 files changed, 337 insertions(+), 186 deletions(-) create mode 100644 mobile/vendor/cie-rgb-converter.js create mode 100644 web/vendor/cie-rgb-converter.js diff --git a/mobile/app/pods/components/lights-tab/component.js b/mobile/app/pods/components/lights-tab/component.js index 3642a95..22c9db8 100644 --- a/mobile/app/pods/components/lights-tab/component.js +++ b/mobile/app/pods/components/lights-tab/component.js @@ -69,7 +69,7 @@ export default Component.extend({ rgbPreview: observer('rgb', function () { let rgb = this.get('rgb'), - xy = this.rgbToXy(rgb[0], rgb[1], rgb[2]); + xy = rgbToCie(rgb[0], rgb[1], rgb[2]); this.set('colorLoopOn', false); @@ -85,6 +85,30 @@ export default Component.extend({ $('.color').css('background', 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'); }), + onActiveLightsChange: on('init', observer('activeLights.[]', function () { + let lightsData = this.get('lightsData'), + xy = null, + setRGB = true; + + this.get('activeLights').forEach((i) => { + let light = lightsData[i]; + + if (xy !== null && xy[0] !== light.state.xy[0] && xy[1] !== light.state.xy[1]) { + setRGB = false; + } + + xy = light.state.xy; + }); + + if (setRGB && xy) { + let rgb = cieToRgb(xy[0], xy[1]); + + $('.color').css('background', 'rgb(' + Math.abs(rgb[0]) + ',' + Math.abs(rgb[1]) + ',' + Math.abs(rgb[2]) + ')'); + } else { + $('.color').css('background', 'rgb(' + 255 + ',' + 255 + ',' + 255 + ')'); + } + })), + // determines whether the lights are on/off for the lights switch lightsOnChange: on('init', observer('lightsData.@each.state.on', 'activeLights.[]', function () { if (!this.get('strobeOn')) { @@ -250,92 +274,5 @@ export default Component.extend({ dimmerOnClass: computed('dimmerOn', function () { return this.get('dimmerOn') ? 'dimmerOn' : null; - }), - - // **************** STROBE LIGHT FINISH **************** - // http://www.developers.meethue.com/documentation/color-conversions-rgb-xy - rgbToXy(red, green, blue) { - let X, Y, Z, x, y; - - // normalize - red = Number((red / 255)); - green = Number((green / 255)); - blue = Number((blue / 255)); - - // gamma correction - 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 to XYZ - X = red * 0.664511 + green * 0.154324 + blue * 0.162028; - Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; - Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; - - x = X / (X + Y + Z); - y = Y / (X + Y + Z); - - return [x, y]; - }, - - xyToRgb(x, y) { - let r, g, b, X, Y = 1.0, Z; - - X = (Y / y) * x; - Z = (Y / y) * (1 - x - y); - - r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; - g = X * -0.707196 + Y * 1.655397 + Z * 0.036152; - b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; - - if (r > b && r > g && r > 1.0) { - // red is too big - g = g / r; - b = b / r; - r = 1.0; - } else if (g > b && g > r && g > 1.0) { - // green is too big - r = r / g; - b = b / g; - g = 1.0; - } else if (b > r && b > g && b > 1.0) { - // blue is too big - r = r / b; - g = g / b; - b = 1.0; - } - - r = (r <= 0.0031308) ? 12.92 * r : 1.055 * Math.pow(r, (1.0 / 2.4)) - 0.055; - g = (g <= 0.0031308) ? 12.92 * g : 1.055 * Math.pow(g, (1.0 / 2.4)) - 0.055; - b = (b <= 0.0031308) ? 12.92 * b : 1.055 * Math.pow(b, (1.0 / 2.4)) - 0.055; - - if (r > b && r > g) { - // red is biggest - if (r > 1.0) { - g = g / r; - b = b / r; - r = 1.0; - } - } else if (g > b && g > r) { - // green is biggest - if (g > 1.0) { - r = r / g; - b = b / g; - g = 1.0; - } - } else if (b > r && b > g) { - // blue is biggest - if (b > 1.0) { - r = r / b; - g = g / b; - b = 1.0; - } - } - - r = r * 255; - g = g * 255; - b = b * 255; - - return [r, g, b]; - } + }) }); diff --git a/mobile/ember-cli-build.js b/mobile/ember-cli-build.js index 79d30d2..52985b6 100644 --- a/mobile/ember-cli-build.js +++ b/mobile/ember-cli-build.js @@ -11,6 +11,7 @@ module.exports = function(defaults) { }); app.import('vendor/dancer.js'); + app.import('vendor/cie-rgb-converter.js'); app.import('bower_components/intro.js/intro.js'); app.import('bower_components/intro.js/introjs.css'); diff --git a/mobile/vendor/cie-rgb-converter.js b/mobile/vendor/cie-rgb-converter.js new file mode 100644 index 0000000..eabd049 --- /dev/null +++ b/mobile/vendor/cie-rgb-converter.js @@ -0,0 +1,141 @@ +/* +With these functions you can convert the CIE color space to the RGB color space and vice versa. + +The developer documentation for Philips Hue provides the formulas used in the code below: +https://developers.meethue.com/documentation/color-conversions-rgb-xy + +I've used the formulas and Objective-C example code and transfered it to JavaScript. + + +Examples: + +var rgb = cie_to_rgb(0.6611, 0.2936) +var cie = rgb_to_cie(255, 39, 60) + +------------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2017 www.usolved.net +Published under https://github.com/usolved/cie-rgb-converter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + + + +/** + * Converts CIE color space to RGB color space + * @param {Number} x + * @param {Number} y + * @param {Number} brightness - Ranges from 1 to 254 + * @return {Array} Array that contains the color values for red, green and blue + */ +function cieToRgb(x, y, brightness) { + //Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons) + if (brightness === undefined) { + brightness = 254; + } + + var z = 1.0 - x - y; + var Y = (brightness / 254).toFixed(2); + var X = (Y / y) * x; + var Z = (Y / y) * z; + + //Convert to RGB using Wide RGB D65 conversion + var red = X * 1.656492 - Y * 0.354851 - Z * 0.255038; + var green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; + var 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]; +} + + +/** + * Converts RGB color space to CIE color space + * @param {Number} red + * @param {Number} green + * @param {Number} blue + * @return {Array} Array that contains the CIE color values for x and y + */ +function rgbToCie(red, green, blue) { + var X, Y, Z, x, y; + + // normalize + red = Number((red / 255)); + green = Number((green / 255)); + blue = Number((blue / 255)); + + // gamma correction + 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 to XYZ + X = red * 0.664511 + green * 0.154324 + blue * 0.162028; + Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; + Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; + + x = X / (X + Y + Z); + y = Y / (X + Y + Z); + + return [x, y]; +} \ No newline at end of file diff --git a/web/app/pods/components/lights-tab/component.js b/web/app/pods/components/lights-tab/component.js index 875c30f..c4001dd 100644 --- a/web/app/pods/components/lights-tab/component.js +++ b/web/app/pods/components/lights-tab/component.js @@ -1,13 +1,6 @@ import Ember from 'ember'; -const { - Component, - observer, - computed, - on, - run: { later, once }, - $ -} = Ember; +const { Component, observer, computed, on, run: { later, once }, $ } = Ember; export default Component.extend({ classNames: ['col-sm-10', 'col-sm-offset-1', 'col-xs-12'], @@ -70,7 +63,7 @@ export default Component.extend({ rgbPreview: observer('rgb', function () { let rgb = this.get('rgb'), - xy = this.rgbToXy(rgb[0], rgb[1], rgb[2]); + xy = rgbToCie(rgb[0], rgb[1], rgb[2]); this.set('colorLoopOn', false); @@ -86,6 +79,30 @@ export default Component.extend({ $('.color').css('background', 'rgb(' + rgb[0] + ',' + rgb[1] + ',' + rgb[2] + ')'); }), + onActiveLightsChange: on('init', observer('activeLights.[]', function () { + let lightsData = this.get('lightsData'), + xy = null, + setRGB = true; + + this.get('activeLights').forEach((i) => { + let light = lightsData[i]; + + if (xy !== null && xy[0] !== light.state.xy[0] && xy[1] !== light.state.xy[1]) { + setRGB = false; + } + + xy = light.state.xy; + }); + + if (setRGB && xy) { + let rgb = cieToRgb(xy[0], xy[1]); + + $('.color').css('background', 'rgb(' + Math.abs(rgb[0]) + ',' + Math.abs(rgb[1]) + ',' + Math.abs(rgb[2]) + ')'); + } else { + $('.color').css('background', 'rgb(' + 255 + ',' + 255 + ',' + 255 + ')'); + } + })), + // determines whether the lights are on/off for the lights switch lightsOnChange: on('init', observer('lightsData.@each.state.on', 'activeLights.[]', function () { if (!this.get('strobeOn')) { @@ -257,92 +274,5 @@ export default Component.extend({ toggleDimmer() { this.sendAction('toggleDimmer'); } - }, - - // **************** STROBE LIGHT FINISH **************** - // http://www.developers.meethue.com/documentation/color-conversions-rgb-xy - rgbToXy(red, green, blue) { - let X, Y, Z, x, y; - - // normalize - red = Number((red / 255)); - green = Number((green / 255)); - blue = Number((blue / 255)); - - // gamma correction - 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 to XYZ - X = red * 0.664511 + green * 0.154324 + blue * 0.162028; - Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; - Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; - - x = X / (X + Y + Z); - y = Y / (X + Y + Z); - - return [x, y]; - }, - - xyToRgb(x, y) { - let r, g, b, X, Y = 1.0, Z; - - X = (Y / y) * x; - Z = (Y / y) * (1 - x - y); - - r = X * 1.656492 - Y * 0.354851 - Z * 0.255038; - g = X * -0.707196 + Y * 1.655397 + Z * 0.036152; - b = X * 0.051713 - Y * 0.121364 + Z * 1.011530; - - if (r > b && r > g && r > 1.0) { - // red is too big - g = g / r; - b = b / r; - r = 1.0; - } else if (g > b && g > r && g > 1.0) { - // green is too big - r = r / g; - b = b / g; - g = 1.0; - } else if (b > r && b > g && b > 1.0) { - // blue is too big - r = r / b; - g = g / b; - b = 1.0; - } - - r = (r <= 0.0031308) ? 12.92 * r : 1.055 * Math.pow(r, (1.0 / 2.4)) - 0.055; - g = (g <= 0.0031308) ? 12.92 * g : 1.055 * Math.pow(g, (1.0 / 2.4)) - 0.055; - b = (b <= 0.0031308) ? 12.92 * b : 1.055 * Math.pow(b, (1.0 / 2.4)) - 0.055; - - if (r > b && r > g) { - // red is biggest - if (r > 1.0) { - g = g / r; - b = b / r; - r = 1.0; - } - } else if (g > b && g > r) { - // green is biggest - if (g > 1.0) { - r = r / g; - b = b / g; - g = 1.0; - } - } else if (b > r && b > g) { - // blue is biggest - if (b > 1.0) { - r = r / b; - g = g / b; - b = 1.0; - } - } - - r = r * 255; - g = g * 255; - b = b * 255; - - return [r, g, b]; } }); diff --git a/web/ember-cli-build.js b/web/ember-cli-build.js index 5cb0345..a33a175 100644 --- a/web/ember-cli-build.js +++ b/web/ember-cli-build.js @@ -15,7 +15,8 @@ module.exports = function(defaults) { }); app.import('vendor/dancer.js'); - + app.import('vendor/cie-rgb-converter.js'); + app.import('bower_components/bootstrap-sass/assets/javascripts/bootstrap/tooltip.js'); app.import('bower_components/intro.js/intro.js'); app.import('bower_components/intro.js/introjs.css'); diff --git a/web/public/assets/images/huegasm.png b/web/public/assets/images/huegasm.png index d437a3738c507f956e36f6500fd7cf3307d7a9f1..649034280ef5399823e8ae167c4e8e7d55c3ba4d 100644 GIT binary patch literal 3876 zcmX9=c|6qJ7rwJvhq3QWLz0Ljdl|Ba#xj+q*GO4YvS;g?Nhn*^Q1%xgyR0$R5VE9_ zZ6evqzDs_3f6wQ0p7Y%MoO}K{_kM0T(O4J5$j=A>!078~nI2{G-=Iev@vu(lUjyl& zVWa^-Sv1pcTh!4S=3%O<35xp!=K(+fqM^C=;o;%Y{{Lasuo<=CC9vVgzi~%k%}L;} z12I3RHa265fP>}5)9b%*)3dreE10XTW!f!o4*OxN)`F`f!NV1B_#O^*zdZa3RxbR7^-J z2*E_eq;Nn{nT3r5A%X=v0Rq==;0J7Zo`u46L=Z3se{TWC{9b=bh7F%E)2DE@7)|Cm zyl?;snR%6^f9j_D1v*1Xgu1O>aS=ZEwHoy~yxJX;@QTz-VH!zhdS=RmBN>{bP=I9d zioj;%ZnhZ1`QY&xL`6N&FnZyGxY|)E)AClZsIbUJlpJ{9mc;(VbD*>74MOa1dR4%K%>S-oesA zt~eAPqQf}a4jL*^>vO2`2K~J`&^+NeSO8nAM|wMfA8D)`?J(W4Y{@5-l^m>NQ&xlv zV)-$zYga*^9G?v}zwH9g1{IOs;8fadv8pZ{M}>Z=3s1zct%dQ;PD`0&M67)U4x!i7 zTdG&3g+qH%mFml<<_t;k1pA{IJ>h9;YzF&dwg2Ck!Z-$*SGS}K(fF1ir77Mx&-t#f zF9?^K8>u8?f-e6-ZOzf8h}PHAFn63NO7ZbE`^-f>-sCcW-qnTmQle{DwRA}Apf*DP zt9yT$G%W##Ekc5KLmoMREtBm#n0xjaVLP-i6cBt4=4jtU1p9+Pl2Zh^> zUdss+aZdBAp*<(8w(}}J)RuSUgmW>0#FiG#4Wn4CXVTv>Ij6dr3@+i>nk1hM2AiwU zwjSS(dbE~G4Hn6{%AF+xu_D(LjeIq@HsZ2kR7s}Ri4pA-lf3us_g;)Jlg@aYPCCmU zF>0rOS;;iUd-RD|m^VZ|Gf!WVcKVUzZ(;xVI2Mkx$%(nS3k&*j;x2MdI)Q;#JreS1 z)+YXP*0C(d<&m;rMJ;;5A9vd)lj4%wJ9@9Ix}?dU){%Mf^W+29-!;nzD|za>eMwE5 zRK-Sb@?hPFZNagpIiO++X!s&Mh5NO=pl$lVT`Z{}IziU8be<$zFoiA>l>x12v^tHZX zj{qDiwKh`3g9V&iZJA%*G;B8d3pXt?wPI-;7nxz{x{xlG#v^J|Gd*9H`bo&@6TPwx z!K+q(kM%W7?q%!pqoG9go%Q|Y`q_z8bb!zIq$S?7TMn!TzAY15mb_BBjB~0MK)YS= z#pYPG$nV}m`kmp{(NngSW4y#Ec87zx{c584%#_=?tdP&>CT!CEb zw}&6pcHdi})VzxmpW-GBG}_>U#;dzFlPa_UZ^v!;d5(~8W=#hVfcLqSOP+wrP5Cej z%bzWTlplkf;Jh%EYu$?R+)IwV6&BDamiFN-4AGeTul@= zgRVyw%V)4$jjcmo(1uycyr~Lvf9=_wp!{P<%s%Of(g_~7GViHD(Yv?tknX8ol!Y-> z{04aK72|;pOqssyYgzc=*tQG|K7%aIoZhXlTY%=x{G+Rjun1u<{_)|# zv4698&~FK%*EU}mIuZ0)2u{7YN9GyM1LnOTb~WsytqUH$i??P-Mc13h!ZP zdMzBBWapO_q;KN#w9Rud3y*#x;-$EZ?6Bb z`(y-XBTx_vkeIH&iT(()^D>rf$q0^Qd-~Z>UiBTJ-B0y)9e4a3Hy7Uef5s86 zWSwk02IR9pRk9PB!oCJqzQ`&guaPJiUA~4b1@{>#AS)2XObE;j1;$+pE&S#+DHpoc zQ`16LW1`Ro83lkA>be~_;wd22`tD<)|1J z)Jm849d*0Ji}ZctX6>|SytcG>%|9Dh%%6!V=1yaRt^8S;457Uhe;BS_L@x_ggoBT= zxIl`t&Ft+AY&-7)py34f)pKF^j4Ium*(pbT0~DmuY3+T$x2Q(z2cr7)`c*nZ>_yKwA`2B{E&fa&m$v%xS}W-GT?Su z95VG7wZ*t)>p#KdXtp36V!0M9xoJgT$rbWMc=!NN&eT9Kk7xPvK{u3k*Je;m{ft#^ zt5fYSU%q_sFL+;CvKArD&4?|3y4c08B*4QP9^0=fln$5ODZHtr>x*Sxt zTlG6+91?x@Z}*AeJJ>Ym12==A8Yhjgeh& zozthKODu+_MD&U{LV3~78YxUf8M>309Os;5M^0npjCHwi-u3H3NH?u+uIn5p4|Mp0 z7^tj1Sn8#kwDQGjuE@P11~N0lpqc;+CX9N&t?MAGPz;uHKC&%YXi2Pn$%{QlMSO4w z-1@4@PwT%wCuP)cCgZkGKDYr_Dq!i_tI|WuUeLQYlp8BhGCoR{N5yNS(?twgirdX= zCzu0SVjSN041<5=P&NMB_ZyyIF;50|Kl4H-Y=w~2J!+&1JkX)Kp5b_E-DUV)!QvY# z{0a7Z7+rzMD)XqPi%!&?s(3aca#$n>$TX;$oD}c=(|pBDav+Q`f3WA)9U&VNg96ju z^o+YghZ`l5l40dQ{8w5!&z$VZ8B2ags+YCt45590L-L2E$4Nt#2qUdr7k=!MW}#&w zv|U(rgHqV`-XnkBuVN$_&qn?dhvKJF;yL=R>w^*_&05J+Z11Y-eA$d|g0gi^pk|vD z>Hzbu#gV!m9>#IULcch1@{=WA()=2{=w z@s)7oim_gUY9rwdVh)y>EE!57z){3|26WdhB&dKGgoGW;&x4ICS4Z5EDH|mb(H|ki z&_d!Q+r2?63t^ztmF697>3-5rEV3k&vdP3iUewxDxmbtA?gR)$qReX+@+J6v=1I>k z`Kg`pST6|t800o4HCa+nEz>ZuatYyEchl!=`nR5V-33$+yj=4Vp_X|@WrT>3pD2T( zb9=PPp>elz>x{ej+Aj~KF#R1L6(1dNJ_*pvfg2BC z@&F2|6tcG$8AMq7mN9-seB9jwrjbY?u!Sf%KUzEsenj}P)v0alC)wNuvV70!-_k1) zJ<{I#TXWNJD(Y+LtEF!4?(7^|%u+SdG%_3KH?t2myEJ{~vEV?TB+{$(aQbpLtSxLX zU(_2^YM5NPe!j45W96A>bd;cSKux9O<(DRj4C4XM{fq_qFi9vJP7E*AL1%Nmwev(S z?qpk6u+{lb5gk#=;}i)C<@-A%v0GyC5J6K%D>E}%{|6FtvwHvl literal 8220 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 diff --git a/web/public/assets/images/logo.png b/web/public/assets/images/logo.png index cc9d381368ea56579cd4baacd22313862e82c641..3fc8b5e7e657061d424f819ab7d724b028ff30f6 100644 GIT binary patch literal 7492 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 K$koYMg#90*f>NRY literal 5619 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 diff --git a/web/vendor/cie-rgb-converter.js b/web/vendor/cie-rgb-converter.js new file mode 100644 index 0000000..eabd049 --- /dev/null +++ b/web/vendor/cie-rgb-converter.js @@ -0,0 +1,141 @@ +/* +With these functions you can convert the CIE color space to the RGB color space and vice versa. + +The developer documentation for Philips Hue provides the formulas used in the code below: +https://developers.meethue.com/documentation/color-conversions-rgb-xy + +I've used the formulas and Objective-C example code and transfered it to JavaScript. + + +Examples: + +var rgb = cie_to_rgb(0.6611, 0.2936) +var cie = rgb_to_cie(255, 39, 60) + +------------------------------------------------------------------------------------ + +The MIT License (MIT) + +Copyright (c) 2017 www.usolved.net +Published under https://github.com/usolved/cie-rgb-converter + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + + + + +/** + * Converts CIE color space to RGB color space + * @param {Number} x + * @param {Number} y + * @param {Number} brightness - Ranges from 1 to 254 + * @return {Array} Array that contains the color values for red, green and blue + */ +function cieToRgb(x, y, brightness) { + //Set to maximum brightness if no custom value was given (Not the slick ECMAScript 6 way for compatibility reasons) + if (brightness === undefined) { + brightness = 254; + } + + var z = 1.0 - x - y; + var Y = (brightness / 254).toFixed(2); + var X = (Y / y) * x; + var Z = (Y / y) * z; + + //Convert to RGB using Wide RGB D65 conversion + var red = X * 1.656492 - Y * 0.354851 - Z * 0.255038; + var green = -X * 0.707196 + Y * 1.655397 + Z * 0.036152; + var 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]; +} + + +/** + * Converts RGB color space to CIE color space + * @param {Number} red + * @param {Number} green + * @param {Number} blue + * @return {Array} Array that contains the CIE color values for x and y + */ +function rgbToCie(red, green, blue) { + var X, Y, Z, x, y; + + // normalize + red = Number((red / 255)); + green = Number((green / 255)); + blue = Number((blue / 255)); + + // gamma correction + 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 to XYZ + X = red * 0.664511 + green * 0.154324 + blue * 0.162028; + Y = red * 0.283881 + green * 0.668433 + blue * 0.047685; + Z = red * 0.000088 + green * 0.072310 + blue * 0.986039; + + x = X / (X + Y + Z); + y = Y / (X + Y + Z); + + return [x, y]; +} \ No newline at end of file