/** * dat-gui JavaScript Controller Library * http://code.google.com/p/dat-gui * * Copyright 2011 Data Arts Team, Google Creative Lab * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 */ /** @namespace */ var dat = dat || {}; /** @namespace */ dat.color = dat.color || {}; /** @namespace */ dat.utils = dat.utils || {}; dat.utils.common = (function () { var ARR_EACH = Array.prototype.forEach; var ARR_SLICE = Array.prototype.slice; /** * Band-aid methods for things that should be a lot easier in JavaScript. * Implementation and structure inspired by underscore.js * http://documentcloud.github.com/underscore/ */ return { BREAK: {}, extend: function(target) { this.each(ARR_SLICE.call(arguments, 1), function(obj) { for (var key in obj) if (!this.isUndefined(obj[key])) target[key] = obj[key]; }, this); return target; }, defaults: function(target) { this.each(ARR_SLICE.call(arguments, 1), function(obj) { for (var key in obj) if (this.isUndefined(target[key])) target[key] = obj[key]; }, this); return target; }, compose: function() { var toCall = ARR_SLICE.call(arguments); return function() { var args = ARR_SLICE.call(arguments); for (var i = toCall.length -1; i >= 0; i--) { args = [toCall[i].apply(this, args)]; } return args[0]; } }, each: function(obj, itr, scope) { if (ARR_EACH && obj.forEach === ARR_EACH) { obj.forEach(itr, scope); } else if (obj.length === obj.length + 0) { // Is number but not NaN for (var key = 0, l = obj.length; key < l; key++) if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) return; } else { for (var key in obj) if (itr.call(scope, obj[key], key) === this.BREAK) return; } }, defer: function(fnc) { setTimeout(fnc, 0); }, toArray: function(obj) { if (obj.toArray) return obj.toArray(); return ARR_SLICE.call(obj); }, isUndefined: function(obj) { return obj === undefined; }, isNull: function(obj) { return obj === null; }, isNaN: function(obj) { return obj !== obj; }, isArray: Array.isArray || function(obj) { return obj.constructor === Array; }, isObject: function(obj) { return obj === Object(obj); }, isNumber: function(obj) { return obj === obj+0; }, isString: function(obj) { return obj === obj+''; }, isBoolean: function(obj) { return obj === false || obj === true; }, isFunction: function(obj) { return Object.prototype.toString.call(obj) === '[object Function]'; } }; })(); dat.color.toString = (function (common) { return function(color) { if (color.a == 1 || common.isUndefined(color.a)) { var s = color.hex.toString(16); while (s.length < 6) { s = '0' + s; } return '#' + s; } else { return 'rgba(' + Math.round(color.r) + ',' + Math.round(color.g) + ',' + Math.round(color.b) + ',' + color.a + ')'; } } })(dat.utils.common); dat.Color = dat.color.Color = (function (interpret, math, toString, common) { var Color = function() { this.__state = interpret.apply(this, arguments); if (this.__state === false) { throw 'Failed to interpret color arguments'; } this.__state.a = this.__state.a || 1; }; Color.COMPONENTS = ['r','g','b','h','s','v','hex','a']; common.extend(Color.prototype, { toString: function() { return toString(this); }, toOriginal: function() { return this.__state.conversion.write(this); } }); defineRGBComponent(Color.prototype, 'r', 2); defineRGBComponent(Color.prototype, 'g', 1); defineRGBComponent(Color.prototype, 'b', 0); defineHSVComponent(Color.prototype, 'h'); defineHSVComponent(Color.prototype, 's'); defineHSVComponent(Color.prototype, 'v'); Object.defineProperty(Color.prototype, 'a', { get: function() { return this.__state.a; }, set: function(v) { this.__state.a = v; } }); Object.defineProperty(Color.prototype, 'hex', { get: function() { if (!this.__state.space !== 'HEX') { this.__state.hex = math.rgb_to_hex(this.r, this.g, this.b); } return this.__state.hex; }, set: function(v) { this.__state.space = 'HEX'; this.__state.hex = v; } }); function defineRGBComponent(target, component, componentHexIndex) { Object.defineProperty(target, component, { get: function() { if (this.__state.space === 'RGB') { return this.__state[component]; } recalculateRGB(this, component, componentHexIndex); return this.__state[component]; }, set: function(v) { if (this.__state.space !== 'RGB') { recalculateRGB(this, component, componentHexIndex); this.__state.space = 'RGB'; } this.__state[component] = v; } }); } function defineHSVComponent(target, component) { Object.defineProperty(target, component, { get: function() { if (this.__state.space === 'HSV') return this.__state[component]; recalculateHSV(this); return this.__state[component]; }, set: function(v) { if (this.__state.space !== 'HSV') { recalculateHSV(this); this.__state.space = 'HSV'; } this.__state[component] = v; } }); } function recalculateRGB(color, component, componentHexIndex) { if (color.__state.space === 'HEX') { color.__state[component] = math.component_from_hex(color.__state.hex, componentHexIndex); } else if (color.__state.space === 'HSV') { common.extend(color.__state, math.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v)); } else { throw 'Corrupted color state'; } } function recalculateHSV(color) { var result = math.rgb_to_hsv(color.r, color.g, color.b); common.extend(color.__state, { s: result.s, v: result.v } ); if (!common.isNaN(result.h)) { color.__state.h = result.h; } else if (common.isUndefined(color.__state.h)) { color.__state.h = 0; } } return Color; })(dat.color.interpret = (function (toString, common) { var result, toReturn; var interpret = function() { toReturn = false; var original = arguments.length > 1 ? common.toArray(arguments) : arguments[0]; common.each(INTERPRETATIONS, function(family) { if (family.litmus(original)) { common.each(family.conversions, function(conversion, conversionName) { result = conversion.read(original); if (toReturn === false && result !== false) { toReturn = result; result.conversionName = conversionName; result.conversion = conversion; return common.BREAK; } }); return common.BREAK; } }); return toReturn; }; var INTERPRETATIONS = [ // Strings { litmus: common.isString, conversions: { THREE_CHAR_HEX: { read: function(original) { var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i); if (test === null) return false; return { space: 'HEX', hex: parseInt( '0x' + test[1].toString() + test[1].toString() + test[2].toString() + test[2].toString() + test[3].toString() + test[3].toString()) }; }, write: toString }, SIX_CHAR_HEX: { read: function(original) { var test = original.match(/^#([A-F0-9]{6})$/i); if (test === null) return false; return { space: 'HEX', hex: parseInt('0x' + test[1].toString()) }; }, write: toString }, CSS_RGB: { read: function(original) { var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/); if (test === null) return false; return { space: 'RGB', r: parseFloat(test[1]), g: parseFloat(test[2]), b: parseFloat(test[3]) }; }, write: toString }, CSS_RGBA: { read: function(original) { var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\,\s*(.+)\s*\)/); if (test === null) return false; return { space: 'RGB', r: parseFloat(test[1]), g: parseFloat(test[2]), b: parseFloat(test[3]), a: parseFloat(test[4]) }; }, write: toString } } }, // Numbers { litmus: common.isNumber, conversions: { HEX: { read: function(original) { return { space: 'HEX', hex: original, conversionName: 'HEX' } }, write: function(color) { return color.hex; } } } }, // Arrays { litmus: common.isArray, conversions: { RGB_ARRAY: { read: function(original) { if (original.length != 3) return false; return { space: 'RGB', r: original[0], g: original[1], b: original[2] }; }, write: function(color) { return [color.r, color.g, color.b]; } }, RGBA_ARRAY: { read: function(original) { if (original.length != 4) return false; return { space: 'RGB', r: original[0], g: original[1], b: original[2], a: original[3] }; }, write: function(color) { return [color.r, color.g, color.b, color.a]; } } } }, // Objects { litmus: common.isObject, conversions: { RGBA_OBJ: { read: function(original) { if (common.isNumber(original.r) && common.isNumber(original.g) && common.isNumber(original.b) && common.isNumber(original.a)) { return { space: 'RGB', r: original.r, g: original.g, b: original.b, a: original.a } } return false; }, write: function(color) { return { r: color.r, g: color.g, b: color.b, a: color.a } } }, RGB_OBJ: { read: function(original) { if (common.isNumber(original.r) && common.isNumber(original.g) && common.isNumber(original.b)) { return { space: 'RGB', r: original.r, g: original.g, b: original.b } } return false; }, write: function(color) { return { r: color.r, g: color.g, b: color.b } } }, HSVA_OBJ: { read: function(original) { if (common.isNumber(original.h) && common.isNumber(original.s) && common.isNumber(original.v) && common.isNumber(original.a)) { return { space: 'HSV', h: original.h, s: original.s, v: original.v, a: original.a } } return false; }, write: function(color) { return { h: color.h, s: color.s, v: color.v, a: color.a } } }, HSV_OBJ: { read: function(original) { if (common.isNumber(original.h) && common.isNumber(original.s) && common.isNumber(original.v)) { return { space: 'HSV', h: original.h, s: original.s, v: original.v } } return false; }, write: function(color) { return { h: color.h, s: color.s, v: color.v } } } } } ]; return interpret; })(dat.color.toString, dat.utils.common), dat.color.math = (function () { var tmpComponent; return { hsv_to_rgb: function(h, s, v) { var hi = Math.floor(h / 60) % 6; var f = h / 60 - Math.floor(h / 60); var p = v * (1.0 - s); var q = v * (1.0 - (f * s)); var t = v * (1.0 - ((1.0 - f) * s)); var c = [ [v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q] ][hi]; return { r: c[0] * 255, g: c[1] * 255, b: c[2] * 255 }; }, rgb_to_hsv: function(r, g, b) { var min = Math.min(r, g, b), max = Math.max(r, g, b), delta = max - min, h, s; if (max != 0) { s = delta / max; } else { return { h: NaN, s: 0, v: 0 }; } if (r == max) { h = (g - b) / delta; } else if (g == max) { h = 2 + (b - r) / delta; } else { h = 4 + (r - g) / delta; } h /= 6; if (h < 0) { h += 1; } return { h: h * 360, s: s, v: max / 255 }; }, rgb_to_hex: function(r, g, b) { var hex = this.hex_with_component(0, 2, r); hex = this.hex_with_component(hex, 1, g); hex = this.hex_with_component(hex, 0, b); return hex; }, component_from_hex: function(hex, componentIndex) { return (hex >> (componentIndex * 8)) & 0xFF; }, hex_with_component: function(hex, componentIndex, value) { return value << (tmpComponent = componentIndex * 8) | (hex & ~ (0xFF << tmpComponent)); } } })(), dat.color.toString, dat.utils.common);