Remove internal clamping of color values to facilitate proper mathematical calculations with colors.

Clamp only when producing CSS values. Closes #271.
This commit is contained in:
Jürg Lehni 2014-01-04 18:16:52 +01:00
parent bb77da22a8
commit 0dddd897ab
4 changed files with 45 additions and 63 deletions

View file

@ -179,9 +179,6 @@ Base.inject(/** @lends Base# */{
obj = Base.create(this.prototype); obj = Base.create(this.prototype);
if (readIndex) if (readIndex)
obj.__read = true; obj.__read = true;
// If options were provided, pass them on to the constructed object
if (options)
obj.__options = options;
obj = obj.initialize.apply(obj, index > 0 || length < list.length obj = obj.initialize.apply(obj, index > 0 || length < list.length
? Array.prototype.slice.call(list, index, index + length) ? Array.prototype.slice.call(list, index, index + length)
: list) || obj; : list) || obj;
@ -191,8 +188,6 @@ Base.inject(/** @lends Base# */{
// last read() call // last read() call
list.__read = obj.__read; list.__read = obj.__read;
delete obj.__read; delete obj.__read;
if (options)
delete obj.__options;
} }
return obj; return obj;
}, },

View file

@ -247,23 +247,20 @@ var Color = Base.extend(new function() {
} }
return value; return value;
} }
: name === 'hue' : type === 'gradient'
? function(value) { ? function(/* value */) {
// Keep negative values within modulo 360 too: return Point.read(arguments, 0, 0, {
return isNaN(value) ? 0 readNull: name === 'highlight',
: ((value % 360) + 360) % 360; clone: true
});
} }
: type === 'gradient' : function(value) {
? function(/* value */) { // NOTE: We don't clamp values here, they're only
return Point.read(arguments, 0, 0, { // clamped once the actual CSS values are produced.
readNull: name === 'highlight', // Gotta love the fact that isNaN(null) is false,
clone: true // while isNaN(undefined) is true.
}); return value == null || isNaN(value) ? 0 : value;
} };
: function(value) {
return isNaN(value) ? 0
: Math.min(Math.max(value, 0), 1);
};
this['get' + part] = function() { this['get' + part] = function() {
return this._type === type return this._type === type
@ -401,7 +398,7 @@ var Color = Base.extend(new function() {
* // the path and to position the gradient color: * // the path and to position the gradient color:
* var topLeft = view.center - [80, 80]; * var topLeft = view.center - [80, 80];
* var bottomRight = view.center + [80, 80]; * var bottomRight = view.center + [80, 80];
* *
* var path = new Path.Rectangle({ * var path = new Path.Rectangle({
* topLeft: topLeft, * topLeft: topLeft,
* bottomRight: bottomRight, * bottomRight: bottomRight,
@ -489,7 +486,6 @@ var Color = Base.extend(new function() {
var slice = Array.prototype.slice, var slice = Array.prototype.slice,
args = arguments, args = arguments,
read = 0, read = 0,
parse = true,
type, type,
components, components,
alpha, alpha,
@ -520,8 +516,6 @@ var Color = Base.extend(new function() {
} }
} }
if (!components) { if (!components) {
// Only parse values if we're not told to not do so
parse = !(this.__options && this.__options.dontParse);
// Determine if there is a values array // Determine if there is a values array
values = argType === 'number' values = argType === 'number'
? args ? args
@ -586,7 +580,7 @@ var Color = Base.extend(new function() {
: 'rgb'; : 'rgb';
// Convert to array and parse in one loop, for efficiency // Convert to array and parse in one loop, for efficiency
var properties = types[type]; var properties = types[type];
parsers = parse && componentParsers[type]; parsers = componentParsers[type];
this._components = components = []; this._components = components = [];
for (var i = 0, l = properties.length; i < l; i++) { for (var i = 0, l = properties.length; i < l; i++) {
var value = arg[properties[i]]; var value = arg[properties[i]];
@ -600,8 +594,7 @@ var Color = Base.extend(new function() {
radial: arg.radial radial: arg.radial
}; };
} }
if (parse) value = parsers[i].call(this, value);
value = parsers[i].call(this, value);
if (value != null) if (value != null)
components[i] = value; components[i] = value;
} }
@ -623,9 +616,7 @@ var Color = Base.extend(new function() {
this._components = components = []; this._components = components = [];
var parsers = componentParsers[this._type]; var parsers = componentParsers[this._type];
for (var i = 0, l = parsers.length; i < l; i++) { for (var i = 0, l = parsers.length; i < l; i++) {
var value = values && values[i]; var value = parsers[i].call(this, values && values[i]);
if (parse)
value = parsers[i].call(this, value);
if (value != null) if (value != null)
components[i] = value; components[i] = value;
} }
@ -815,13 +806,16 @@ var Color = Base.extend(new function() {
// TODO: Support HSL / HSLA CSS3 colors directly, without conversion // TODO: Support HSL / HSLA CSS3 colors directly, without conversion
var components = this._convert('rgb'), var components = this._convert('rgb'),
alpha = hex || this._alpha == null ? 1 : this._alpha; alpha = hex || this._alpha == null ? 1 : this._alpha;
function convert(val) {
return Math.round((val < 0 ? 0 : val > 1 ? 1 : val) * 255);
}
components = [ components = [
Math.round(components[0] * 255), convert(components[0]),
Math.round(components[1] * 255), convert(components[1]),
Math.round(components[2] * 255) convert(components[2])
]; ];
if (alpha < 1) if (alpha < 1)
components.push(alpha); components.push(val < 0 ? 0 : val);
return hex return hex
? '#' + ((1 << 24) + (components[0] << 16) ? '#' + ((1 << 24) + (components[0] << 16)
+ (components[1] << 8) + (components[1] << 8)
@ -1119,48 +1113,33 @@ var Color = Base.extend(new function() {
} }
}); });
}, new function() { }, new function() {
function clamp(value, hue) {
return value < 0
? 0
: hue && value > 360
? 360
: !hue && value > 1
? 1
: value;
}
var operators = { var operators = {
add: function(a, b, hue) { add: function(a, b) {
return clamp(a + b, hue); return a + b;
}, },
subtract: function(a, b, hue) { subtract: function(a, b) {
return clamp(a - b, hue); return a - b;
}, },
multiply: function(a, b, hue) { multiply: function(a, b) {
return clamp(a * b, hue); return a * b;
}, },
divide: function(a, b, hue) { divide: function(a, b) {
return clamp(a / b, hue); return a / b;
} }
}; };
return Base.each(operators, function(operator, name) { return Base.each(operators, function(operator, name) {
// Tell the argument reader not to parse values for multiply and divide,
// so the are not clamped yet.
var options = { dontParse: /^(multiply|divide)$/.test(name) };
this[name] = function(color) { this[name] = function(color) {
color = Color.read(arguments, 0, 0, options); color = Color.read(arguments, 0, 0);
var type = this._type, var type = this._type,
properties = this._properties, properties = this._properties,
components1 = this._components, components1 = this._components,
components2 = color._convert(type); components2 = color._convert(type);
for (var i = 0, l = components1.length; i < l; i++) for (var i = 0, l = components1.length; i < l; i++)
components2[i] = operator(components1[i], components2[i], components2[i] = operator(components1[i], components2[i]);
properties[i] === 'hue');
return new Color(type, components2, return new Color(type, components2,
this._alpha != null this._alpha != null
? operator(this._alpha, color.getAlpha()) ? operator(this._alpha, color.getAlpha())

View file

@ -51,6 +51,14 @@ test('Set color to array', function() {
}); });
test('Creating Colors', function() { test('Creating Colors', function() {
compareColors(new Color(), new Color(0, 0, 0),
'Color with no arguments should be black');
compareColors(new Color('black'), new Color(0, 0, 0),
'Color from name (black)');
compareColors(new Color('red'), new Color(1, 0, 0),
'Color from name (red)');
compareColors(new Color('#ff0000'), new Color(1, 0, 0), compareColors(new Color('#ff0000'), new Color(1, 0, 0),
'Color from hex code'); 'Color from hex code');
@ -203,7 +211,7 @@ test('Saturation from black rgb', function() {
test('Color#add', function() { test('Color#add', function() {
var color = new Color(0, 1, 1); var color = new Color(0, 1, 1);
compareColors(color.add([1, 0, 0]), [1, 1, 1]); compareColors(color.add([1, 0, 0]), [1, 1, 1]);
compareColors(color.add([1, 0.5, 0]), [1, 1, 1]); compareColors(color.add([1, 0.5, 0]), [1, 1.5, 1]);
var color = new Color(0, 0.5, 0); var color = new Color(0, 0.5, 0);
compareColors(color.add(0.5), [0.5, 1, 0.5]); compareColors(color.add(0.5), [0.5, 1, 0.5]);
}); });
@ -229,7 +237,7 @@ test('Color#divide', function() {
var color = new Color(1, 1, 1); var color = new Color(1, 1, 1);
compareColors(color.divide([1, 2, 4]), [1, 0.5, 0.25]); compareColors(color.divide([1, 2, 4]), [1, 0.5, 0.25]);
var color = new Color(1, 0.5, 0.25); var color = new Color(1, 0.5, 0.25);
compareColors(color.divide(0.25), [1, 1, 1]); compareColors(color.divide(0.25), [4, 2, 1]);
var color = new Color(1, 1, 1); var color = new Color(1, 1, 1);
compareColors(color.divide(4), [0.25, 0.25, 0.25]); compareColors(color.divide(4), [0.25, 0.25, 0.25]);
}); });

View file

@ -19,7 +19,7 @@ test('PointText', function() {
point: [100, 100], point: [100, 100],
content: 'Hello World!' content: 'Hello World!'
}); });
equals(text.fillColor, { red: 0, green: 0, blue: 0 }, 'text.fillColor should be black by default'); compareColors(text.fillColor, new Color(0, 0, 0), 'text.fillColor should be black by default');
comparePoints(text.point, { x: 100, y: 100 }); comparePoints(text.point, { x: 100, y: 100 });
compareRectangles(text.bounds, { x: 100, y: 87.4, width: 77, height: 16.8 }); compareRectangles(text.bounds, { x: 100, y: 87.4, width: 77, height: 16.8 });
equals(function() { equals(function() {