Refactor GradientStop: Improve handling of optionally defined color and rampPoint.

Relates to https://github.com/paperjs/paper.js/issues/1001#issuecomment-197557990
This commit is contained in:
Jürg Lehni 2016-03-17 09:38:42 +01:00
parent 0e658da104
commit d93aca6b5c
6 changed files with 83 additions and 53 deletions

View file

@ -607,7 +607,7 @@ var Color = Base.extend(new function() {
// Default fallbacks: rgb, black // Default fallbacks: rgb, black
this._type = type || 'rgb'; this._type = type || 'rgb';
// Define this Color's unique id in its own private id pool. // Define this Color's unique id in its own private id pool.
// NOTE: This is required by SVG Export code! // NOTE: This is only required by SVG Export code!
this._id = UID.get(Color); this._id = UID.get(Color);
if (!components) { if (!components) {
// Produce a components array now, and parse values. Even if no // Produce a components array now, and parse values. Even if no
@ -856,7 +856,11 @@ var Color = Base.extend(new function() {
} }
for (var i = 0, l = stops.length; i < l; i++) { for (var i = 0, l = stops.length; i < l; i++) {
var stop = stops[i]; var stop = stops[i];
canvasGradient.addColorStop(stop._rampPoint, // Use the defined offset, and fall back to automatic linear
// calculation.
// NOTE: that if _rampPoint is 0 for the first entry, the fall
// back will be so too.
canvasGradient.addColorStop(stop._rampPoint || i / (l - 1),
stop._color.toCanvasStyle()); stop._color.toCanvasStyle());
} }
return this._canvasStyle = canvasGradient; return this._canvasStyle = canvasGradient;

View file

@ -72,10 +72,11 @@ var Gradient = Base.extend(/** @lends Gradient# */{
stops = radial = null; stops = radial = null;
if (!this._stops) if (!this._stops)
this.setStops(stops || ['white', 'black']); this.setStops(stops || ['white', 'black']);
if (this._radial == null) if (this._radial == null) {
// Support old string type argument and new radial boolean. // Support old string type argument and new radial boolean.
this.setRadial(typeof radial === 'string' && radial === 'radial' this.setRadial(typeof radial === 'string' && radial === 'radial'
|| radial || false); || radial || false);
}
}, },
_serialize: function(options, dictionary) { _serialize: function(options, dictionary) {
@ -91,8 +92,9 @@ var Gradient = Base.extend(/** @lends Gradient# */{
_changed: function() { _changed: function() {
// Loop through the gradient-colors that use this gradient and notify // Loop through the gradient-colors that use this gradient and notify
// them, so they can notify the items they belong to. // them, so they can notify the items they belong to.
for (var i = 0, l = this._owners && this._owners.length; i < l; i++) for (var i = 0, l = this._owners && this._owners.length; i < l; i++) {
this._owners[i]._changed(); this._owners[i]._changed();
}
}, },
/** /**
@ -122,8 +124,9 @@ var Gradient = Base.extend(/** @lends Gradient# */{
*/ */
clone: function() { clone: function() {
var stops = []; var stops = [];
for (var i = 0, l = this._stops.length; i < l; i++) for (var i = 0, l = this._stops.length; i < l; i++) {
stops[i] = this._stops[i].clone(); stops[i] = this._stops[i].clone();
}
return new Gradient(stops, this._radial); return new Gradient(stops, this._radial);
}, },
@ -138,23 +141,20 @@ var Gradient = Base.extend(/** @lends Gradient# */{
}, },
setStops: function(stops) { setStops: function(stops) {
// If this gradient already contains stops, first remove if (stops.length < 2) {
// this gradient as their owner.
if (this.stops) {
for (var i = 0, l = this._stops.length; i < l; i++)
this._stops[i]._owner = undefined;
}
if (stops.length < 2)
throw new Error( throw new Error(
'Gradient stop list needs to contain at least two stops.'); 'Gradient stop list needs to contain at least two stops.');
this._stops = GradientStop.readAll(stops, 0, { clone: true });
// Now reassign ramp points if they were not specified.
for (var i = 0, l = this._stops.length; i < l; i++) {
var stop = this._stops[i];
stop._owner = this;
if (stop._defaultRamp)
stop.setRampPoint(i / (l - 1));
} }
// If this gradient already contains stops, first remove their owner.
var _stops = this._stops;
if (_stops) {
for (var i = 0, l = _stops.length; i < l; i++)
_stops[i]._owner = undefined;
}
_stops = this._stops = GradientStop.readAll(stops, 0, { clone: true });
// Now assign this gradient as the new gradients' owner.
for (var i = 0, l = _stops.length; i < l; i++)
_stops[i]._owner = this;
this._changed(); this._changed();
}, },
@ -182,13 +182,17 @@ var Gradient = Base.extend(/** @lends Gradient# */{
equals: function(gradient) { equals: function(gradient) {
if (gradient === this) if (gradient === this)
return true; return true;
if (gradient && this._class === gradient._class if (gradient && this._class === gradient._class) {
&& this._stops.length === gradient._stops.length) { var stops1 = this._stops,
for (var i = 0, l = this._stops.length; i < l; i++) { stops2 = gradient._stops,
if (!this._stops[i].equals(gradient._stops[i])) length = stops1.length;
return false; if (length === stops2.length) {
for (var i = 0; i < length; i++) {
if (!stops1[i].equals(stops2[i]))
return false;
}
return true;
} }
return true;
} }
return false; return false;
} }

View file

@ -23,28 +23,30 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
* Creates a GradientStop object. * Creates a GradientStop object.
* *
* @param {Color} [color=new Color(0, 0, 0)] the color of the stop * @param {Color} [color=new Color(0, 0, 0)] the color of the stop
* @param {Number} [rampPoint=0] the position of the stop on the gradient * @param {Number} [rampPoint=null] the position of the stop on the gradient
* ramp as a value between 0 and 1 * ramp as a value between `0` and `1`; `null` or `undefined` for automatic
* assignment.
*/ */
initialize: function GradientStop(arg0, arg1) { initialize: function GradientStop(arg0, arg1) {
if (arg0) { // (color, rampPoint)
var color, rampPoint; var color = arg0,
if (arg1 === undefined && Array.isArray(arg0)) { rampPoint = arg1;
// [color, rampPoint] if (typeof arg0 === 'object' && arg1 === undefined) {
// Make sure the first entry in the array is not a number, in which
// case the whole array would be a color, and the assignments would
// already have occurred correctly above.
if (Array.isArray(arg0) && typeof arg0[0] !== 'number') {
// ([color, rampPoint])
color = arg0[0]; color = arg0[0];
rampPoint = arg0[1]; rampPoint = arg0[1];
} else if (arg0.color) { } else if ('color' in arg0 || 'rampPoint' in arg0) {
// stop // (stop)
color = arg0.color; color = arg0.color;
rampPoint = arg0.rampPoint; rampPoint = arg0.rampPoint;
} else {
// color, rampPoint
color = arg0;
rampPoint = arg1;
} }
this.setColor(color);
this.setRampPoint(rampPoint);
} }
this.setColor(color);
this.setRampPoint(rampPoint);
}, },
// TODO: Do we really need to also clone the color here? // TODO: Do we really need to also clone the color here?
@ -56,8 +58,10 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
}, },
_serialize: function(options, dictionary) { _serialize: function(options, dictionary) {
return Base.serialize([this._color, this._rampPoint], options, true, var color = this._color,
dictionary); rampPoint = this._rampPoint;
return Base.serialize(rampPoint == null ? [color] : [color, rampPoint],
options, true, dictionary);
}, },
/** /**
@ -115,8 +119,7 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
}, },
setRampPoint: function(rampPoint) { setRampPoint: function(rampPoint) {
this._defaultRamp = rampPoint == null; this._rampPoint = rampPoint;
this._rampPoint = rampPoint || 0;
this._changed(); this._changed();
}, },
@ -162,13 +165,13 @@ var GradientStop = Base.extend(/** @lends GradientStop# */{
return this._color; return this._color;
}, },
setColor: function(color) { setColor: function(/* color */) {
// Make sure newly set colors are cloned, since they can only have // Make sure newly set colors are cloned, since they can only have
// one owner. // one owner.
this._color = Color.read(arguments); var color = Color.read(arguments, 0, { clone: true });
if (this._color === color) if (color)
this._color = color.clone(); color._owner = this;
this._color._owner = this; this._color = color;
this._changed(); this._changed();
}, },

View file

@ -231,12 +231,14 @@ new function() {
var stops = gradient._stops; var stops = gradient._stops;
for (var i = 0, l = stops.length; i < l; i++) { for (var i = 0, l = stops.length; i < l; i++) {
var stop = stops[i], var stop = stops[i],
offset = stop._rampPoint,
stopColor = stop._color, stopColor = stop._color,
alpha = stopColor.getAlpha(); alpha = stopColor.getAlpha();
attrs = { attrs = {};
offset: stop._rampPoint, if (offset != null)
'stop-color': stopColor.toCSS(true) attrs.offset = offset;
}; if (stopColor)
attrs['stop-color'] = stopColor.toCSS(true);
// See applyStyle for an explanation of why there are separated // See applyStyle for an explanation of why there are separated
// opacity / color attributes. // opacity / color attributes.
if (alpha < 1) if (alpha < 1)

View file

@ -222,3 +222,16 @@ test('Color#divide', function() {
var color = new Color(1, 1, 1); var color = new Color(1, 1, 1);
equals(color.divide(4), new Color([0.25, 0.25, 0.25])); equals(color.divide(4), new Color([0.25, 0.25, 0.25]));
}); });
test('Gradient', function() {
var stop1 = new GradientStop({ rampPoint: 0 });
var stop2 = new GradientStop('red', 0.75);
var stop3 = new GradientStop(['white', 1]);
var gradient = new Gradient([stop1, stop2, stop3], true);
equals(function() { return stop1.color; }, new Color(0, 0, 0));
equals(function() { return stop2.color; }, new Color(1, 0, 0));
equals(function() { return stop3.color; }, new Color(1, 1, 1));
equals(function() { return stop1.rampPoint; }, 0);
equals(function() { return stop2.rampPoint; }, 0.75);
equals(function() { return stop3.rampPoint; }, 1);
});

View file

@ -219,7 +219,11 @@ test('Color#importJSON()', function() {
// that runs between the two points we defined earlier: // that runs between the two points we defined earlier:
fillColor: { fillColor: {
gradient: { gradient: {
stops: ['yellow', 'red', 'blue'] stops: [
['yellow', 0],
['red', 0.5],
['blue', 1]
]
}, },
origin: topLeft, origin: topLeft,
destination: bottomRight destination: bottomRight