/* This file contains proprietary software owned by Motorola Mobility, Inc.
No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.
(c) Copyright 2011 Motorola Mobility, Inc. All Rights Reserved.
*/ //////////////////////////////////////////////////////////////////////// // var Montage = require("montage/core/core").Montage, Component = require("montage/ui/component").Component; //////////////////////////////////////////////////////////////////////// // exports.ColorModel = Montage.create(Component, { //////////////////////////////////////////////////////////////////// // hasTemplate: { enumerable: false, value: false }, //////////////////////////////////////////////////////////////////// //HSV Value of current color selected _gradient: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //HSV Value of current color selected gradient: { enumerable: true, get: function() { return this._gradient; }, set: function(value) { this._gradient = value; //Updating color selected (converting to all modes) this.updateColorSelected('gradient', value); } }, //////////////////////////////////////////////////////////////////// //HSV Value of current color selected _hsv: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //HSV Value of current color selected hsv: { enumerable: true, get: function() { return this._hsv; }, set: function(value) { this._hsv = value; //Updating color selected (converting to all modes) this.updateColorSelected('hsv', value); } }, //////////////////////////////////////////////////////////////////// //RGB Value of current color selected _rgb: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //RGB Value of current color selected rgb: { enumerable: true, get: function() { return this._rgb; }, set: function(value) { this._rgb = value; //Updating color selected (converting to all modes) this.updateColorSelected('rgb', value); } }, //////////////////////////////////////////////////////////////////// //HSL Value of current color selected _hsl: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //HSL Value of current color selected hsl: { enumerable: true, get: function() { return this._hsl; }, set: function(value) { this._hsl = value; //Updating color selected (converting to all modes) this.updateColorSelected('hsl', value); } }, //////////////////////////////////////////////////////////////////// //HEX Value of current color selected _hex: { numerable: false, value: null }, //////////////////////////////////////////////////////////////////// //HEX Value of current color selected hex: { enumerable: true, get: function() { return this._hex; }, set: function(value) { this._hex = value; //Updating color selected (converting to all modes) this.updateColorSelected('hex', value); } }, //////////////////////////////////////////////////////////////////// //ALPHA Value of current color selected _alpha: { enumerable: false, value: {value: 1, type: 'change', wasSetByCode: true} }, //////////////////////////////////////////////////////////////////// //ALPHA Value of current color selected alpha: { enumerable: true, get: function() { return this._alpha; }, set: function(value) { value.value = Math.ceil(value.value*100)/100; this._alpha = value; // if (this.rgb || this.hsl) { this._dispatchChangeEvent('alpha', value); } } }, //////////////////////////////////////////////////////////////////// //Input (fill or stroke) Value of current color selected _input: { enumerable: false, value: 'fill' }, //////////////////////////////////////////////////////////////////// //Input Value of current color selected input: { enumerable: true, get: function() { return this._input; }, set: function(value) { this._input = value; //Dispatching change event this._dispatchChangeEvent('input', value); } }, //////////////////////////////////////////////////////////////////// //Color mode of current color selected _mode: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //Color mode of current color selected mode: { enumerable: true, get: function() { return this._mode; }, set: function(value) { this._mode = value; //Dispatching change event this._dispatchChangeEvent('mode', value); } }, //////////////////////////////////////////////////////////////////// //Stroke Color Value of current color selected _stroke: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //Stroke Color Value of current color selected stroke: { enumerable: true, get: function() { return this._stroke; }, set: function(value) { this._stroke = value; } }, //////////////////////////////////////////////////////////////////// //Fill Color Value of current color selected _fill: { enumerable: false, value: null }, //////////////////////////////////////////////////////////////////// //Fill Color Value of current color selected fill: { enumerable: true, get: function() { return this._fill; }, set: function(value) { this._fill = value; } }, //////////////////////////////////////////////////////////////////// //History Value array of current color selected colorHistory: { enumerable: false, value: {stroke: [{m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}, {m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}], fill: [{m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}, {m: 'rgb', c: {r: 0, g: 0, b: 0}, a: 1}]} }, //////////////////////////////////////////////////////////////////// //History Value array of current color selected _addColorHistory: { enumerable: true, value: function(input, mode, color, alpha) { //TODO: Add limit if (this.colorHistory[input.toLowerCase()].length > 1) { if (this.colorHistory[input.toLowerCase()][this.colorHistory[input.toLowerCase()].length-1].c !== color || this.colorHistory[input.toLowerCase()][this.colorHistory[input.toLowerCase()].length-1].a !== alpha.value) { this.colorHistory[input.toLowerCase()].push({m: mode, c: color, a: alpha.value}); } } else { this.colorHistory[input.toLowerCase()].push({m: mode, c: color, a: alpha.value}); } } }, //////////////////////////////////////////////////////////////////// // applyNoColor: { enumerable: true, value: function () { // var nocolor = {}; nocolor.wasSetByCode = true; nocolor.type = 'change'; this.updateColorSelected('nocolor', nocolor); } }, //////////////////////////////////////////////////////////////////// //Method to update color selected and convert to other modes updateColorSelected: { enumerable: true, //value: function (input, mode, color, alpha) { value: function (mode, color) { //////////////////////////////////////////////////////////// //Checking for color mode to convert colors switch (mode.toLocaleLowerCase()) { //////////////////////////////////////////////////////// case 'gradient': //Checking for match of previous gradient if (color !== this.gradient) { //Setting value and breaking out of function this.gradient = color; return; } // this._hex = '------'; this._rgb = null; this._hsv = null; this._hsl = null; break; //////////////////////////////////////////////////////// case 'rgb': //Checking for match of previous (RGB) if (color.r !== this.rgb.r && color.g !== this.rgb.g && color.b !== this.rgb.b) { //Setting value and breaking out of function this.rgb = color; return; } //Setting other color mode values this._hsv = this.rgbToHsv(color.r, color.g, color.b); this._hsl = this.rgbToHsl(color.r, color.g, color.b); this._hex = this.rgbToHex(color.r, color.g, color.b); this._gradient = null; break; //////////////////////////////////////////////////////// case 'hsv': //Checking for match of previous (HSV) if (color.h !== this.hsv.h && color.s !== this.hsv.s && color.v !== this.hsv.v) { //Setting value and breaking out of function this.hsv = color; return; } //Setting other color mode values this._rgb = this.hsvToRgb(color.h/(2*Math.PI), color.s, color.v); this._hsl = this.rgbToHsl(this.rgb.r, this.rgb.g, this.rgb.b); this._hex = this.rgbToHex(this.rgb.r, this.rgb.g, this.rgb.b); this._gradient = null; break; //////////////////////////////////////////////////////// case 'hsl': //Checking for match of previous (HSV) if (color.h !== this.hsl.h && color.s !== this.hsl.s && color.l !== this.hsl.l) { //Setting value and breaking out of function this.hsl = color; return; } //Setting other color mode values this._rgb = this.hslToRgb(color.h/360, color.s/100, color.l/100); //This is a hack to keep the values of color spectrum the same on limits (B/W) var hsvTemp = this.rgbToHsv(this.rgb.r, this.rgb.g, this.rgb.b); this._hsv = {h: (this._hsl.h/360)*(2*Math.PI), s: hsvTemp.s, v: hsvTemp.v}; this._hex = this.rgbToHex(this.rgb.r, this.rgb.g, this.rgb.b); this._gradient = null; break; //////////////////////////////////////////////////////// case 'hex': switch (color.length) { case 1: this.applyNoColor(); return; break; case 2: this.applyNoColor(); return; break; case 3: color = color[0]+color[0]+color[1]+color[1]+color[2]+color[2]; break; case 4: this.applyNoColor(); return; break; case 5: this.applyNoColor(); return; break; case 6: //Nothing break; default: this.applyNoColor(); return; break; } //Checking for match of previous (HEX) if (color !== this.hex) { //Setting value and breaking out of function this.hex = color; return; } //Setting other color mode values this._rgb = this.hexToRgb(color); this._hsv = this.rgbToHsv(this.rgb.r, this.rgb.g, this.rgb.b); this._hsl = this.rgbToHsl(this.rgb.r, this.rgb.g, this.rgb.b); this._gradient = null; break; //////////////////////////////////////////////////////// case 'nocolor': // this._hex = '------'; this._rgb = null; this._hsv = null; this._hsl = null; this._gradient = null; break; //////////////////////////////////////////////////////// default: console.log('ERROR: An error occurred in ColorManager. '+mode+' was not identified in updateColorSelected'); break; //////////////////////////////////////////////////////// } //Dispatching change event this._dispatchChangeEvent(mode, color); } }, //////////////////////////////////////////////////////////////////// //Dispatching "Change" event _dispatchChangeEvent: { //Modes: rgb, hsl, hsv, hex, alpha, input, mode value: function(mode, value) { //Creating custom event to dispatch and include values var colorEvent = document.createEvent("CustomEvent"); // if (value && value.type) { if (value.type === "change") { // colorEvent.initEvent("change", true, true); //Storing the selected color in the history array if ((mode === 'rgb' || mode === 'hsv' || mode === 'hsl' || mode === 'hex' || mode === 'nocolor' || mode === 'gradient') && this.input !== 'chip') { this._addColorHistory(this.input, mode, value, this.alpha); } else if (mode === 'alpha' && this.input !== 'chip') { var c = this.colorHistory[this.input][this.colorHistory[this.input].length-1]; this._addColorHistory(this.input, c.m, c.c, this.alpha); } } else if (value.type === "changing") { // colorEvent.initEvent("changing", true, true); } else { // colorEvent.initEvent("error", true, true); } } else { // colorEvent.initEvent("error", true, true); } //Checking for event to be color, if so, adding color data if (mode === 'rgb' || mode === 'hsv' || mode === 'hsl' || mode === 'hex' || mode === 'alpha' || mode === 'nocolor' || mode === 'gradient') { //Returning color history of input if (this.input === 'stroke') { colorEvent.history = this.colorHistory.stroke; } else if (this.input === 'fill') { colorEvent.history = this.colorHistory.fill; } else { colorEvent.history = null; } } //Also returning other color mode values if (this.input) colorEvent.input = this.input; if (this.alpha) colorEvent.alpha = this.alpha.value; if (this.hsl) colorEvent.hsla = {h: this.hsl.h, s: this.hsl.s, l: this.hsl.l, a: this.alpha.value, css: 'hsla('+this.hsl.h+', '+this.hsl.s+'%, '+this.hsl.l+'%, '+this.alpha.value+')'}; if (this.rgb) { colorEvent.rgba = {r: this.rgb.r, g: this.rgb.g, b: this.rgb.b, a: this.alpha.value, css: 'rgba('+ this.rgb.r +', '+this.rgb.g+', '+this.rgb.b+', '+this.alpha.value+')'}; colorEvent.webGlColor = [this.rgb.r/255, this.rgb.g/255, this.rgb.b/255, this.alpha.value]; } if (this.hex) colorEvent.hex = this.hex; //Standard values that apply to any event colorEvent.value = value; colorEvent.mode = mode; if (value && value.wasSetByCode) { colorEvent.wasSetByCode = value.wasSetByCode; } this.dispatchEvent(colorEvent); } }, //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// // COLOR CONVERTION METHODS (public) //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //Convert RGB to HEX (RGB = 0 to 255) rgbToHex: { enumerable: true, value: function (r, g, b) { //Coverting color channel to Hex function getHex (c) { var filter = "0123456789ABCDEF"; c = parseInt (c, 10); if (isNaN(c)) return "00"; c = Math.max(0,Math.min(c,255)); return filter.charAt((c-c%16)/16) + filter.charAt(c%16); } //Returning HEX string return (getHex(r)+getHex(g)+getHex(b)); } }, //////////////////////////////////////////////////////////////////// //Convert HEX to RGB (no # symbol) hexToRgb: { enumerable: true, value: function (value) { //Spliting string converting to values var r = parseInt(value.substring(0,2), 16), g = parseInt(value.substring(2,4), 16), b = parseInt(value.substring(4,6), 16); //Checking for valid values if (isNaN(r)) return null; if (isNaN(g)) return null; if (isNaN(b)) return null; return {r: r, g: g, b: b}; } }, //////////////////////////////////////////////////////////////////// //Convert HSV to RGB (HSV = 0 to 1) hsvToRgb: { enumerable: true, value: function (h, s, v) { var r, g, b, i = Math.floor(h * 6), f = h * 6 - i, p = v * (1 - s), q = v * (1 - f * s), t = v * (1 - (1 - f) * s); // switch (i % 6){ case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } //Returning RGB object return {r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255)}; } }, //////////////////////////////////////////////////////////////////// //Convert RGB to HSV (RGB = 0 to 255) rgbToHsv: { enumerable: true, value: function (r, g, b) { //RGB covertion to percentage r = r/255, g = g/255, b = b/255; var max = Math.max(r, g, b), min = Math.min(r, g, b), h, s, v, d; h = s = v = max; d = max - min; s = max == 0 ? 0 : d / max; //TODO: Check for Hue not to change if S or V = 0 if (max == min) { h = 0; } else { 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; } //Returing HSV object return {h: h*Math.PI*2, s: s, v: v}; } }, //////////////////////////////////////////////////////////////////// //Convert RGB TO HSL (RGB = 0 to 255) rgbToHsl: { enumerable: true, value: function (r, g, b) { //RGB covertion to percentage r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b), h, s, l, d; h = s = l = (max + min) / 2; //TODO: Check for Hue not to change if S or L = 0 if (max == min) { h = s = 0; } else { 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; } //Returing HSL object return {h: h*360, s: s*100, l: l*100}; } }, //////////////////////////////////////////////////////////////////// //Convert HSL to RGB (HSL = 0 to 1) hslToRgb: { enumerable: true, value: function (h, s, l) { var r, g, b; if (s == 0) { r = g = b = l; } else { function getRgb (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 = getRgb(p, q, h + 1/3); g = getRgb(p, q, h); b = getRgb(p, q, h - 1/3); } //Returning RGB object return {r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255)}; } }, //////////////////////////////////////////////////////////////////// //Returns WebGL color array in [r, g, b, a] format where the values are [0,1] given a color object colorToWebGl: { enumerable: true, value: function (color) { var temp; if (color) { if(color.l !== undefined) { temp = this.hslToRgb(color.h/360, color.s/100, color.l/100); } else if (color.r !== undefined) { temp = color; } temp.a = color.a; } //WebGL uses array if (temp) { return [temp.r/255, temp.g/255, temp.b/255, temp.a]; } else { return null; } } }, //////////////////////////////////////////////////////////////////// //Returns CSS string given a WebGL color array in [r, g, b, a] format where the values are [0,1] webGlToCss: { enumerable: true, value: function (color) { if(color && (color.length === 4)) { return 'rgba(' + color[0]*255 + ', ' + color[1]*255 + ', ' + color[2]*255 + ', ' + color[3] +')'; } else { return null; } } } //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////// });