2011-03-06 19:50:44 -05:00
|
|
|
/*
|
|
|
|
* Paper.js
|
|
|
|
*
|
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
2011-03-07 20:41:50 -05:00
|
|
|
* http://paperjs.org/
|
2011-03-06 19:50:44 -05:00
|
|
|
* http://scriptographer.org/
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* Distributed under the MIT license. See LICENSE file for details.
|
|
|
|
*
|
2011-03-06 19:50:44 -05:00
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
*
|
2011-03-07 20:41:50 -05:00
|
|
|
* All rights reserved.
|
2011-03-06 19:50:44 -05:00
|
|
|
*/
|
|
|
|
|
2011-03-08 20:23:39 -05:00
|
|
|
var Color = this.Color = Base.extend(new function() {
|
2011-05-18 16:21:28 -04:00
|
|
|
|
|
|
|
var components = {
|
|
|
|
gray: ['gray'],
|
|
|
|
rgb: ['red', 'green', 'blue'],
|
|
|
|
hsb: ['hue', 'saturation', 'brightness']
|
|
|
|
};
|
2011-03-13 17:55:20 -04:00
|
|
|
|
|
|
|
var colorCache = {},
|
|
|
|
colorContext;
|
|
|
|
|
|
|
|
function nameToRGBColor(name) {
|
|
|
|
var color = colorCache[name];
|
2011-03-13 17:57:01 -04:00
|
|
|
if (color)
|
2011-03-14 08:54:57 -04:00
|
|
|
return color; // TODO: return a clone of the color
|
|
|
|
// Use a canvas to draw to with the given name and then retrieve rgb
|
|
|
|
// values from. Build a cache for all the used colors.
|
2011-03-13 17:57:01 -04:00
|
|
|
if (!colorContext) {
|
|
|
|
var canvas = CanvasProvider.getCanvas(Size.create(1, 1));
|
|
|
|
colorContext = canvas.getContext('2d');
|
|
|
|
colorContext.globalCompositeOperation = 'copy';
|
2011-03-09 08:29:19 -05:00
|
|
|
}
|
2011-03-14 17:07:21 -04:00
|
|
|
// Set the current fillStyle to transparent, so that it will be
|
|
|
|
// transparent instead of the previously set color in case the new color
|
|
|
|
// can not be interpreted.
|
2011-03-14 08:54:57 -04:00
|
|
|
colorContext.fillStyle = 'rgba(0, 0, 0, 0)';
|
2011-03-13 18:00:01 -04:00
|
|
|
// Set the fillStyle of the context to the passed name and fill the
|
|
|
|
// canvas with it, then retrieve the data for the drawn pixel:
|
2011-03-13 17:57:01 -04:00
|
|
|
colorContext.fillStyle = name;
|
|
|
|
colorContext.fillRect(0, 0, 1, 1);
|
|
|
|
var data = colorContext.getImageData(0, 0, 1, 1).data,
|
2011-03-13 18:00:01 -04:00
|
|
|
rgb = [data[0] / 255, data[1] / 255, data[2] / 255];
|
2011-03-14 08:54:57 -04:00
|
|
|
return colorCache[name] = RGBColor.read(rgb); // TODO: return a clone
|
2011-03-13 15:14:16 -04:00
|
|
|
}
|
2011-03-13 17:53:32 -04:00
|
|
|
|
2011-03-13 15:14:16 -04:00
|
|
|
function hexToRGBColor(string) {
|
2011-03-09 08:29:19 -05:00
|
|
|
var hex = string.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
|
2011-03-09 08:23:18 -05:00
|
|
|
if (hex.length >= 4) {
|
|
|
|
var rgb = new Array(3);
|
|
|
|
for (var i = 0; i < 3; i++) {
|
|
|
|
var channel = hex[i + 1];
|
|
|
|
rgb[i] = parseInt(channel.length == 1
|
|
|
|
? channel + channel : channel, 16) / 255;
|
|
|
|
}
|
2011-03-13 15:14:16 -04:00
|
|
|
return RGBColor.read(rgb);
|
2011-03-09 08:23:18 -05:00
|
|
|
}
|
2011-05-18 15:35:18 -04:00
|
|
|
}
|
2011-03-08 20:23:39 -05:00
|
|
|
|
|
|
|
var converters = {
|
|
|
|
'rgb-hsb': function(color) {
|
2011-03-09 09:25:41 -05:00
|
|
|
var r = color._red,
|
|
|
|
g = color._green,
|
|
|
|
b = color._blue,
|
|
|
|
alpha = color._alpha,
|
|
|
|
max = Math.max(r, g, b),
|
|
|
|
min = Math.min(r, g, b),
|
|
|
|
delta = max - min,
|
|
|
|
hue,
|
|
|
|
saturation = (max != 0) ? delta / max : 0,
|
|
|
|
brightness = max;
|
|
|
|
if (saturation == 0) {
|
|
|
|
hue = 0;
|
|
|
|
} else {
|
|
|
|
var rr = (max - r) / delta,
|
|
|
|
gr = (max - g) / delta,
|
|
|
|
br = (max - b) / delta;
|
|
|
|
hue = r == max
|
|
|
|
? br - gr
|
|
|
|
: g == max
|
|
|
|
? 2 + rr - br
|
|
|
|
: 4 + gr - rr;
|
|
|
|
hue /= 6;
|
|
|
|
if (hue < 0)
|
|
|
|
hue++;
|
|
|
|
}
|
|
|
|
return new HSBColor(hue * 360, saturation, brightness, alpha);
|
2011-03-08 20:23:39 -05:00
|
|
|
},
|
2011-03-09 08:31:40 -05:00
|
|
|
|
2011-03-08 20:23:39 -05:00
|
|
|
'hsb-rgb': function(color) {
|
2011-03-09 09:25:41 -05:00
|
|
|
var h = color._hue,
|
|
|
|
s = color._saturation,
|
|
|
|
b = color._brightness,
|
2011-03-09 09:50:32 -05:00
|
|
|
a = color._alpha,
|
|
|
|
f = h % 60,
|
2011-03-09 09:25:41 -05:00
|
|
|
p = (b * (1 - s)) / 1,
|
|
|
|
q = (b * (60 - s * f)) / 60,
|
|
|
|
t = (b * (60 - s * (60 - f))) / 60;
|
|
|
|
switch (Math.floor(h / 60)) {
|
|
|
|
case 0: return new RGBColor(b, t, p, a);
|
|
|
|
case 1: return new RGBColor(q, b, p, a);
|
|
|
|
case 2: return new RGBColor(p, b, t, a);
|
|
|
|
case 3: return new RGBColor(p, q, b, a);
|
|
|
|
case 4: return new RGBColor(t, p, b, a);
|
|
|
|
case 5: return new RGBColor(b, p, q, a);
|
|
|
|
}
|
2011-03-08 20:23:39 -05:00
|
|
|
},
|
2011-03-09 08:31:40 -05:00
|
|
|
|
2011-03-13 13:41:32 -04:00
|
|
|
'rgb-gray': function(color) {
|
|
|
|
// Using the standard NTSC conversion formula that is used for
|
|
|
|
// calculating the effective luminance of an RGB color:
|
|
|
|
// http://www.mathworks.com/support/solutions/en/data/1-1ASCU/index.html?solution=1-1ASCU
|
|
|
|
return new GrayColor(1 -
|
|
|
|
(color._red * 0.2989
|
|
|
|
+ color._green * 0.587
|
|
|
|
+ color._blue * 0.114),
|
|
|
|
color._alpha
|
|
|
|
);
|
2011-03-08 20:23:39 -05:00
|
|
|
},
|
2011-03-09 08:31:40 -05:00
|
|
|
|
2011-03-08 20:23:39 -05:00
|
|
|
'gray-rgb': function(color) {
|
2011-05-18 15:40:52 -04:00
|
|
|
var comp = 1 - color.getGray();
|
|
|
|
return new RGBColor(comp, comp, comp, color._alpha);
|
2011-03-08 20:23:39 -05:00
|
|
|
},
|
2011-03-09 08:31:40 -05:00
|
|
|
|
2011-03-13 13:41:32 -04:00
|
|
|
'hsb-gray': function(color) {
|
2011-05-18 15:40:52 -04:00
|
|
|
return converters['rgb-gray'](converters['hsb-rgb'](color));
|
2011-03-13 13:41:32 -04:00
|
|
|
},
|
|
|
|
|
2011-03-08 20:23:39 -05:00
|
|
|
'gray-hsb': function(color) {
|
2011-03-09 09:25:41 -05:00
|
|
|
return new HSBColor(0, 0, 1 - color._gray, color._alpha);
|
2011-03-08 20:23:39 -05:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-05-18 15:35:18 -04:00
|
|
|
var fields = {
|
|
|
|
beans: true,
|
|
|
|
_readNull: true,
|
|
|
|
|
|
|
|
initialize: function(arg) {
|
|
|
|
var isArray = Array.isArray(arg);
|
|
|
|
if (typeof arg === 'object' && !isArray) {
|
|
|
|
if (!this._colorType) {
|
|
|
|
// Called on the abstract Color class. Guess color type
|
|
|
|
// from arg
|
|
|
|
return arg.red !== undefined
|
|
|
|
? new RGBColor(arg.red, arg.green, arg.blue, arg.alpha)
|
|
|
|
: arg.gray !== undefined
|
|
|
|
? new GrayColor(arg.gray, arg.alpha)
|
|
|
|
: arg.hue !== undefined
|
|
|
|
? new HSBColor(arg.hue, arg.saturation, arg.brightness,
|
|
|
|
arg.alpha)
|
|
|
|
: new RGBColor(); // Fallback
|
|
|
|
} else {
|
|
|
|
// Called on a subclass instance. Return the converted
|
|
|
|
// color.
|
|
|
|
var color = Color.read(arguments, 0, 1);
|
|
|
|
return this._colorType
|
|
|
|
? color.convert(this._colorType)
|
|
|
|
: color;
|
|
|
|
}
|
|
|
|
} else if (typeof arg === 'string') {
|
|
|
|
var rgbColor = arg.match(/^#[0-9a-f]{3,6}$/i)
|
|
|
|
? hexToRGBColor(arg)
|
|
|
|
: nameToRGBColor(arg);
|
|
|
|
return this._colorType
|
|
|
|
? rgbColor.convert(this._colorType)
|
|
|
|
: rgbColor;
|
|
|
|
} else {
|
|
|
|
var components = isArray ? arg
|
|
|
|
: Array.prototype.slice.call(arguments);
|
|
|
|
if (!this._colorType) {
|
|
|
|
// Called on the abstract Color class. Guess color type
|
|
|
|
// from arg
|
|
|
|
//if (components.length >= 4)
|
|
|
|
// return new CMYKColor(components);
|
|
|
|
if (components.length >= 3)
|
|
|
|
return new RGBColor(components);
|
|
|
|
return new GrayColor(components);
|
|
|
|
} else {
|
|
|
|
// Called on a subclass instance. Just copy over
|
|
|
|
// components.
|
|
|
|
Base.each(this._components,
|
|
|
|
function(name, i) {
|
|
|
|
var value = components[i];
|
|
|
|
// TODO: Should we call the setter?
|
|
|
|
// this['set' + name.capitalize()]
|
|
|
|
this[name] = value !== undefined
|
|
|
|
? value
|
|
|
|
// TODO: Is this correct?
|
|
|
|
// Shouldn't alpha be set to -1?
|
|
|
|
: name === 'alpha' ? 1 : null;
|
|
|
|
},
|
|
|
|
this);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-03-08 20:23:39 -05:00
|
|
|
convert: function(type) {
|
|
|
|
return this._colorType == type
|
|
|
|
? this
|
|
|
|
: converters[this._colorType + '-' + type](this);
|
2011-05-18 16:11:59 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
/**
|
|
|
|
* Override Color.extend() to produce getters and setters based
|
|
|
|
* on the component types defined in _components.
|
|
|
|
*/
|
|
|
|
extend: function(src) {
|
|
|
|
src.beans = true;
|
2011-05-18 16:21:28 -04:00
|
|
|
if (src._colorType) {
|
|
|
|
var comps = components[src._colorType];
|
|
|
|
// Automatically produce the _components field, adding alpha
|
|
|
|
src._components = comps.concat(['alpha']);
|
|
|
|
Base.each(comps, function(name) {
|
2011-05-18 16:11:59 -04:00
|
|
|
var part = Base.capitalize(name),
|
2011-05-19 14:00:36 -04:00
|
|
|
name = '_' + name;
|
2011-05-18 16:11:59 -04:00
|
|
|
this['get' + part] = function() {
|
|
|
|
return this[name];
|
|
|
|
};
|
2011-05-19 14:00:36 -04:00
|
|
|
this['set' + part] = function(value) {
|
|
|
|
this[name] = name === '_hue'
|
|
|
|
// Keep negative values within modulo 360 too:
|
|
|
|
? ((value % 360) + 360) % 360
|
|
|
|
// All other values are 0..1
|
|
|
|
: Math.min(Math.max(value, 0), 1);
|
|
|
|
this._cssString = null;
|
|
|
|
return this;
|
|
|
|
};
|
2011-05-18 16:11:59 -04:00
|
|
|
}, src)
|
2011-05-18 16:21:28 -04:00
|
|
|
}
|
|
|
|
return this.base(src);
|
2011-05-18 16:11:59 -04:00
|
|
|
}
|
2011-03-08 20:23:39 -05:00
|
|
|
}
|
|
|
|
};
|
2011-05-18 15:35:18 -04:00
|
|
|
|
|
|
|
// Produce conversion methods for the various color components known by the
|
|
|
|
// possible color types. Requesting any of these components on any color
|
|
|
|
// internally converts the color to the required type and then returns its
|
|
|
|
// component, using bean access.
|
2011-05-18 16:21:28 -04:00
|
|
|
Base.each(components, function(comps, colorType) {
|
|
|
|
Base.each(comps, function(component) {
|
2011-05-18 15:37:09 -04:00
|
|
|
var part = Base.capitalize(component);
|
|
|
|
fields['get' + part] = function() {
|
2011-03-08 20:23:39 -05:00
|
|
|
return this.convert(colorType)[component];
|
2011-03-07 09:59:35 -05:00
|
|
|
};
|
2011-05-18 15:37:09 -04:00
|
|
|
fields['set' + part] = function(value) {
|
2011-03-09 08:59:38 -05:00
|
|
|
var color = this.convert(colorType);
|
|
|
|
color[component] = value;
|
|
|
|
color = color.convert(this._colorType);
|
2011-03-08 20:23:39 -05:00
|
|
|
for (var i = 0, l = this._components.length; i < l; i++) {
|
|
|
|
this[this._components[i]] = color[this._components[i]];
|
|
|
|
}
|
2011-03-07 09:59:35 -05:00
|
|
|
};
|
2011-05-18 15:36:24 -04:00
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
return fields;
|
2011-05-18 15:35:18 -04:00
|
|
|
}, {
|
|
|
|
beans: true,
|
|
|
|
|
|
|
|
getType: function() {
|
|
|
|
return this._colorType;
|
|
|
|
},
|
|
|
|
|
|
|
|
getComponents: function() {
|
2011-05-18 16:21:28 -04:00
|
|
|
var length = this._components.length;
|
|
|
|
var comps = new Array(length);
|
|
|
|
for (var i = 0; i < length; i++)
|
|
|
|
comps[i] = this['_' + this._components[i]];
|
|
|
|
return comps;
|
2011-05-18 15:35:18 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* A value between 0 and 1 that specifies the color's alpha value.
|
|
|
|
* All colors of the different subclasses support alpha values.
|
|
|
|
*/
|
|
|
|
getAlpha: function() {
|
|
|
|
return this._alpha != null ? this._alpha : 1;
|
|
|
|
},
|
|
|
|
|
|
|
|
setAlpha: function(alpha) {
|
|
|
|
this._alpha = Math.min(Math.max(alpha, 0), 1);
|
|
|
|
this._cssString = null;
|
2011-05-18 16:11:59 -04:00
|
|
|
return this;
|
2011-05-18 15:35:18 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the color has an alpha value.
|
|
|
|
*
|
|
|
|
* @return true if the color has an alpha value, false otherwise.
|
|
|
|
*/
|
|
|
|
hasAlpha: function() {
|
|
|
|
return this._alpha != null;
|
|
|
|
},
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Checks if the component color values of the color are the
|
|
|
|
* same as those of the supplied one.
|
|
|
|
*
|
|
|
|
* @param obj the GrayColor to compare with
|
|
|
|
* @return true if the GrayColor is the same, false otherwise.
|
|
|
|
*/
|
|
|
|
equals: function(color) {
|
|
|
|
if (color && color._colorType === this._colorType) {
|
|
|
|
for (var i = 0, l = this._components.length; i < l; i++) {
|
|
|
|
var component = '_' + this._components[i];
|
|
|
|
if (this[component] !== color[component])
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
},
|
|
|
|
|
|
|
|
toString: function() {
|
|
|
|
var parts = [],
|
|
|
|
format = Base.formatNumber;
|
|
|
|
for (var i = 0, l = this._components.length; i < l; i++) {
|
|
|
|
var component = this._components[i],
|
|
|
|
value = this['_' + component];
|
|
|
|
if (component === 'alpha' && value == null)
|
|
|
|
value = 1;
|
|
|
|
parts.push(component + ': ' + format(value));
|
|
|
|
}
|
|
|
|
return '{ ' + parts.join(', ') + ' }';
|
|
|
|
},
|
|
|
|
|
|
|
|
toCssString: function() {
|
|
|
|
if (!this._cssString) {
|
2011-05-19 15:19:36 -04:00
|
|
|
var color = this.convert('rgb');
|
2011-05-18 15:35:18 -04:00
|
|
|
var alpha = color.getAlpha();
|
|
|
|
var components = [
|
|
|
|
Math.round(color.getRed() * 255),
|
|
|
|
Math.round(color.getGreen() * 255),
|
|
|
|
Math.round(color.getBlue() * 255),
|
|
|
|
alpha != null ? this.alpha : 1
|
|
|
|
];
|
|
|
|
this._cssString = 'rgba(' + components.join(', ') + ')';
|
|
|
|
}
|
|
|
|
return this._cssString;
|
|
|
|
},
|
|
|
|
|
|
|
|
getCanvasStyle: function() {
|
|
|
|
return this.toCssString();
|
|
|
|
}
|
2011-03-09 08:31:40 -05:00
|
|
|
});
|
2011-05-19 14:00:36 -04:00
|
|
|
|
|
|
|
var GrayColor = this.GrayColor = Color.extend({
|
|
|
|
_colorType: 'gray'
|
|
|
|
});
|
|
|
|
|
|
|
|
var RGBColor = this.RGBColor = Color.extend({
|
|
|
|
_colorType: 'rgb'
|
|
|
|
});
|
|
|
|
|
|
|
|
var HSBColor = this.HSBColor = Color.extend({
|
|
|
|
_colorType: 'hsb'
|
|
|
|
});
|