diff --git a/examples/JSON/Gradients.html b/examples/JSON/Gradients.html index b7882742..ce2c961e 100644 --- a/examples/JSON/Gradients.html +++ b/examples/JSON/Gradients.html @@ -10,7 +10,7 @@ var gradient = new Gradient(['yellow', 'red', 'black'], true); var from = path.position; var to = path.bounds.rightCenter; - var gradientColor = new GradientColor(gradient, from, to); + var gradientColor = new Color(gradient, from, to); path.fillColor = gradientColor; path.strokeColor = 'black'; window._json = project.exportJson(); diff --git a/examples/Paperjs.org/BouncingBalls.html b/examples/Paperjs.org/BouncingBalls.html index 3d2a69f4..2b306c05 100644 --- a/examples/Paperjs.org/BouncingBalls.html +++ b/examples/Paperjs.org/BouncingBalls.html @@ -35,7 +35,7 @@ radius: radius / 3 }) ], - fillColor: new GradientColor(gradient, 0, radius, radius / 8), + fillColor: new Color(gradient, 0, radius, radius / 8), position: this.point }); } diff --git a/examples/Paperjs.org/RadialRainbows.html b/examples/Paperjs.org/RadialRainbows.html index 51874024..a480b7d1 100644 --- a/examples/Paperjs.org/RadialRainbows.html +++ b/examples/Paperjs.org/RadialRainbows.html @@ -24,7 +24,7 @@ var path = new Path.Rectangle(view.bounds); var gradient = new Gradient(colors, true); var radius = Math.max(view.size.width, view.size.height) * 0.75; - path.fillColor = new GradientColor(gradient, point, point + [radius, 0]); + path.fillColor = new Color(gradient, point, point + [radius, 0]); var gradientColor = path.fillColor; var mouseDown = false, diff --git a/examples/SVG Export/Gradients.html b/examples/SVG Export/Gradients.html index 85b3dd98..dbde5c82 100644 --- a/examples/SVG Export/Gradients.html +++ b/examples/SVG Export/Gradients.html @@ -16,7 +16,7 @@ var circle = new Path.Circle({ center: from, radius: radius, - fillColor: new GradientColor(radial, from, to), + fillColor: new Color(radial, from, to), strokeColor: 'black' }); @@ -26,7 +26,7 @@ var rect = new Path.Rectangle({ from: from, to: to, - fillColor: new GradientColor(linear, from, to), + fillColor: new Color(linear, from, to), strokeColor: 'black' }); diff --git a/examples/Scripts/HslColor.html b/examples/Scripts/HslColor.html index 38a4a403..b3b53cb0 100644 --- a/examples/Scripts/HslColor.html +++ b/examples/Scripts/HslColor.html @@ -41,7 +41,7 @@ var gradient = new Gradient(colors, true); var from = center; var to = center + vector; - var gradientColor = new GradientColor(gradient, from, to); + var gradientColor = new Color(gradient, from, to); path.fillColor = path.strokeColor = gradientColor; } } diff --git a/src/color/Color.js b/src/color/Color.js index ae70be8b..50cc931f 100644 --- a/src/color/Color.js +++ b/src/color/Color.js @@ -47,7 +47,8 @@ var Color = this.Color = Base.extend(new function() { gray: ['gray'], rgb: ['red', 'green', 'blue'], hsb: ['hue', 'saturation', 'brightness'], - hsl: ['hue', 'saturation', 'lightness'] + hsl: ['hue', 'saturation', 'lightness'], + gradient: ['gradient', 'origin', 'destination', 'hilite'] }; var colorCache = {}, @@ -192,7 +193,18 @@ var Color = this.Color = Base.extend(new function() { 'gray-hsl': function(g) { // TODO: Is lightness really the same as brightness for gray? return [0, 0, g]; + }, + + 'gradient-rgb': function(gradient) { + // TODO: Implement + return []; + }, + + 'rgb-gradient': function(r, g, b) { + // TODO: Implement + return []; } + }; // Produce getters and setter methods for the various color components known @@ -201,12 +213,15 @@ var Color = this.Color = Base.extend(new function() { // its component. return Base.each(types, function(properties, type) { Base.each(properties, function(name, index) { - var isHue = name === 'hue', + var part = Base.capitalize(name), + isHue = name === 'hue', + isGradient = name === 'gradient', + isPoint = type === 'gradient' && !isGradient, + isHilite = name === 'hilite', // Both hue and saturation have overlapping properties between // hsb and hsl. Handle this here separately, by testing for // overlaps and skipping conversion if the type is /hs[bl]/ - hasOverlap = /^(hue|saturation)$/.test(name), - part = Base.capitalize(name); + hasOverlap = /^(hue|saturation)$/.test(name); this['get' + part] = function() { return this._type === type @@ -222,11 +237,20 @@ var Color = this.Color = Base.extend(new function() { this._components = this._convert(type); this._type = type; } - this._components[index] = isHue + if (!isGradient) + value = isHue // Keep negative values within modulo 360 too: ? ((value % 360) + 360) % 360 - // All other values are 0..1 - : Math.min(Math.max(value, 0), 1); + : isPoint + ? Point.read(arguments, 0, 0, true, isHilite) // clone, readNull + // All other values are 0..1 + : Math.min(Math.max(value, 0), 1); + if (value != null) { + this._components[index] = value; + if (isGradient) + value._addOwner(this); + this._changed(); + } }; }, this); }, /** @lends Color# */{ @@ -242,6 +266,10 @@ var Color = this.Color = Base.extend(new function() { args = arguments, read = 0, type; + if (Array.isArray(arg)) { + args = arg; + arg = args[0]; + } // Try type arg first if (typeof arg === 'string' && arg in types) { type = arg; @@ -286,17 +314,19 @@ var Color = this.Color = Base.extend(new function() { components = arg._components.slice(); alpha = arg._alpha; } else if (arg._class === 'Gradient') { - // TODO: Construct gradient type = 'gradient'; + components = slice.call(args); } else { // Determine type by presence of object property names type = 'hue' in arg ? 'lightness' in arg ? 'hsl' : 'hsb' - : 'gray' in arg - ? 'gray' - : 'rgb'; + : 'gradient' in arg + ? 'gradient' + : 'gray' in arg + ? 'gray' + : 'rgb'; var properties = types[type]; components = []; for (var i = 0, l = properties.length; i < l; i++) @@ -307,19 +337,31 @@ var Color = this.Color = Base.extend(new function() { if (this._read && type) read = 1; } + // Define this GradientColor's unique id. + if (type === 'gradient') + this._id = ++Base._uid; // Default fallbacks: rgb, black this._type = type || 'rgb'; this._components = components || (type === 'gray' ? [0] : [0, 0, 0]); this._alpha = alpha; + // Trigger setters for each component by looping through properties + // and resseting component value. + // TODO: This is a hack and should implemented better, e.g. with + // value handlers. + var properties = types[this._type]; + for (var i = 0, l = properties.length; i < l; i++) + this[properties[i]] = this._components[i]; if (this._read) this._read = read; }, - _serialize: function(options) { - // We can ommit the type for gray and rgb: - return /^(gray|rgb)$/.test(this._type) - ? this._components - : [this._type].concat(this._components); + _serialize: function(options, dictionary) { + return Base.serialize( + // We can ommit the type for gray and rgb: + /^(gray|rgb)$/.test(this._type) + ? this._components + : [this._type].concat(this._components), + options, true, dictionary); }, /** @@ -477,8 +519,53 @@ var Color = this.Color = Base.extend(new function() { return css; }, - toCanvasStyle: function() { - return this.toCss(); + toCanvasStyle: function(ctx) { + if (this._type !== 'gradient') + return this.toCss(); + // Gradient code form here onwards, incudling caching + if (this._canvasGradient) + return this._canvasGradient; + var components = this._components, + gradient = components[0], + stops = gradient._stops, + origin = components[1], + destination = components[2], + canvasGradient; + if (gradient._radial) { + var radius = destination.getDistance(origin), + hilite = components[3]; + if (hilite) { + var vector = hilite.subtract(origin); + if (vector.getLength() > radius) + hilite = origin.add(vector.normalize(radius - 0.1)); + } + var start = hilite || origin; + canvasGradient = ctx.createRadialGradient(start.x, start.y, + 0, origin.x, origin.y, radius); + } else { + canvasGradient = ctx.createLinearGradient(origin.x, origin.y, + destination.x, destination.y); + } + for (var i = 0, l = stops.length; i < l; i++) { + var stop = stops[i]; + canvasGradient.addColorStop(stop._rampPoint, stop._color.toCss()); + } + return this._canvasGradient = canvasGradient; + }, + + /** + * Transform the gradient color by the specified matrix. + * + * @param {Matrix} matrix the matrix to transform the gradient color by + */ + transform: function(matrix) { + if (this._type === 'gradient') { + var components = this._components; + for (var i = 1, l = components.length; i < l; i++) { + var point = components[i]; + matrix._transformPoint(point, point, true); + } + } }, /** @@ -613,6 +700,16 @@ var Color = this.Color = Base.extend(new function() { color._type = type; color._components = components; color._alpha = alpha; + if (type === 'gradient') { + // Make sure gradients always have an id + color._id = ++Base._uid; + // Clone all points: + for (var i = 1, l = components.length; i < l; i++) { + var point = components[i]; + if (point) + components[i] = point.clone(); + } + } return color; }, @@ -626,16 +723,18 @@ var Color = this.Color = Base.extend(new function() { // Expose RgbColor, RGBColor, etc. constructors for backward compatibility. Base.each(Color._types, function(properties, type) { - this[Base.capitalize(type) + 'Color'] = this[type.toUpperCase() + 'Color'] = + var ctor = this[Base.capitalize(type) + 'Color'] = function(arg) { var argType = arg != null && typeof arg, - components = argType === 'number' - ? arguments - : argType === 'object' && arg.length != null - ? arg - : null; + components = argType === 'object' && arg.length != null + ? arg + : argType === 'string' + ? null + : arguments; return components ? new Color(type, components) : new Color(arg); }; + if (!/^(gray|gradient)$/.test(type)) + this[type.toUpperCase() + 'Color'] = ctor; }, this); diff --git a/src/color/Gradient.js b/src/color/Gradient.js index 07a11646..b2931c26 100644 --- a/src/color/Gradient.js +++ b/src/color/Gradient.js @@ -29,7 +29,7 @@ var Gradient = this.Gradient = Base.extend(/** @lends Gradient# */{ _serialize: function(options, dictionary) { return dictionary.add(this, function() { - return Base.serialize([this._type, this._stops], + return Base.serialize([this._stops, this._radial], options, true, dictionary); }); }, diff --git a/src/color/GradientColor.js b/src/color/GradientColor.js index 6edc90ef..4572670a 100644 --- a/src/color/GradientColor.js +++ b/src/color/GradientColor.js @@ -16,7 +16,6 @@ * @class The GradientColor object. */ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# */{ - _type: 'gradient', /** * Creates a gradient color object. @@ -45,7 +44,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# * * // Have the gradient color run between the topLeft and * // bottomRight points we defined earlier: - * var gradientColor = new GradientColor(gradient, topLeft, bottomRight); + * var gradientColor = new Color(gradient, topLeft, bottomRight); * * // Set the fill color of the path to the gradient color: * path.fillColor = gradientColor; @@ -77,7 +76,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# * var to = path.position + [80, 0]; * * // Create the gradient color: - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * * // Set the fill color of the path to the gradient color: * path.fillColor = gradientColor; @@ -145,7 +144,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# * // to the bottom right point of the view: * var from = view.bounds.topLeft; * var to = view.bounds.bottomRight; - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * path.fillColor = gradientColor; * * function onMouseMove(event) { @@ -185,7 +184,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# * var gradient = new Gradient(['yellow', 'red', 'black'], true); * var from = view.center; * var to = view.bounds.bottomRight; - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * path.fillColor = gradientColor; * * function onMouseMove(event) { @@ -221,7 +220,7 @@ var GradientColor = this.GradientColor = Color.extend(/** @lends GradientColor# * var gradient = new Gradient(['yellow', 'red', 'black'], true); * var from = path.position; * var to = path.bounds.rightCenter; - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * * path.fillColor = gradientColor; * diff --git a/src/color/GradientStop.js b/src/color/GradientStop.js index a68d0c88..91aa1dd9 100644 --- a/src/color/GradientStop.js +++ b/src/color/GradientStop.js @@ -54,7 +54,7 @@ var GradientStop = this.GradientStop = Base.extend(/** @lends GradientStop# */{ }, _serialize: function(options, dictionary) { - return Base.serialize([this._color, this._rampPoint], options, false, + return Base.serialize([this._color, this._rampPoint], options, true, dictionary); }, @@ -92,7 +92,7 @@ var GradientStop = this.GradientStop = Base.extend(/** @lends GradientStop# */{ * var gradient = new Gradient(colors, true); * var from = path.position; * var to = path.bounds.rightCenter; - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * * path.fillColor = gradientColor; * @@ -141,7 +141,7 @@ var GradientStop = this.GradientStop = Base.extend(/** @lends GradientStop# */{ * // to the right center of its bounding rectangle: * var from = path.position; * var to = path.bounds.rightCenter; - * var gradientColor = new GradientColor(gradient, from, to); + * var gradientColor = new Color(gradient, from, to); * path.fillColor = gradientColor; * * // This function is called each frame of the animation: diff --git a/src/core/Base.js b/src/core/Base.js index 3c384085..c53ae2e1 100644 --- a/src/core/Base.js +++ b/src/core/Base.js @@ -290,7 +290,12 @@ this.Base = Base.inject(/** @lends Base# */{ ref = this.references[id]; if (!ref) { this.length++; - this.definitions[id] = create.call(item); + var res = create.call(item); + // Also automatically insert class for dictionary + // entries. + if (item._class && res[0] !== item._class) + res.unshift(item._class); + this.definitions[id] = res; ref = this.references[id] = [id]; } return ref; @@ -300,8 +305,8 @@ this.Base = Base.inject(/** @lends Base# */{ if (obj && obj._serialize) { res = obj._serialize(options, dictionary); // If we don't serialize to compact form (meaning no type - // identifier), see if _serialize didn't already add the type, - // e.g. for types that do not support compact form. + // identifier), see if _serialize didn't already add the class, + // e.g. for classes that do not support compact form. if (obj._class && !compact && res[0] !== obj._class) res.unshift(obj._class); } else if (Array.isArray(obj)) { diff --git a/src/paper.js b/src/paper.js index 871887d3..fc500e0d 100644 --- a/src/paper.js +++ b/src/paper.js @@ -87,7 +87,6 @@ var paper = new function() { /*#*/ include('style/CharacterStyle.js'); /*#*/ include('color/Color.js'); -/*#*/ include('color/GradientColor.js'); /*#*/ include('color/Gradient.js'); /*#*/ include('color/GradientStop.js'); diff --git a/src/svg/SvgExport.js b/src/svg/SvgExport.js index d92c48a8..a8f6556c 100644 --- a/src/svg/SvgExport.js +++ b/src/svg/SvgExport.js @@ -341,12 +341,11 @@ new function() { // TODO: Implement gradient merging in SvgImport var gradientNode = getDefinition(color); if (!gradientNode) { - var gradient = color.gradient, + var gradient = color.getGradient(), radial = gradient._radial, matrix = item._gradientMatrix, - origin = color._origin.transform(matrix), - destination = color._destination.transform(matrix), - highlight = color._hilite && color._hilite.transform(matrix), + origin = color.getOrigin().transform(matrix), + destination = color.getDestination().transform(matrix), attrs; if (radial) { attrs = { @@ -354,7 +353,9 @@ new function() { cy: origin.y, r: origin.getDistance(destination) }; + var highlight = color.getHilite(); if (highlight) { + highlight = highlight.transform(matrix); attrs.fx = highlight.x; attrs.fy = highlight.y; } diff --git a/src/svg/SvgImport.js b/src/svg/SvgImport.js index 0dc9edcb..fa90d84f 100644 --- a/src/svg/SvgImport.js +++ b/src/svg/SvgImport.js @@ -156,7 +156,7 @@ new function() { // We don't return the GradientColor, since we only need a reference to // it in definitions, which is created in applyAttributes() applyAttributes( - new GradientColor(gradient, origin, destination, highlight), node); + new Color(gradient, origin, destination, highlight), node); return null; } diff --git a/test/tests/Item_Cloning.js b/test/tests/Item_Cloning.js index 7310592b..3ca3218c 100644 --- a/test/tests/Item_Cloning.js +++ b/test/tests/Item_Cloning.js @@ -54,7 +54,7 @@ test('Path#clone()', function() { test('Path#clone() with GradientColor', function() { var colors = ['red', 'green', 'black']; var gradient = new Gradient(colors, true); - var color = new GradientColor(gradient, [0, 0], [20, 20], [10, 10]); + var color = new Color(gradient, [0, 0], [20, 20], [10, 10]); var path = new Path([10, 20], [30, 40]); path.fillColor = color; diff --git a/test/tests/JSON.js b/test/tests/JSON.js index ab7d8943..9f01a04d 100644 --- a/test/tests/JSON.js +++ b/test/tests/JSON.js @@ -61,7 +61,7 @@ test('Gradients', function() { var gradient = new Gradient(['yellow', 'red', 'black'], true); var from = path.position; var to = path.bounds.rightCenter; - var gradientColor = new GradientColor(gradient, from, to); + var gradientColor = new Color(gradient, from, to); path.fillColor = gradientColor; path.strokeColor = 'black'; testExportImportJson(paper.project);