From a40e9cd6101ffd4d2f033be55f3da945534f5630 Mon Sep 17 00:00:00 2001 From: kangax Date: Sun, 9 Jan 2011 01:38:54 -0500 Subject: [PATCH] Initial implementation of gradients. Work in progress (but demo page now has working "gradientify" button). Still need to add tests, and make sure gradient-based fills on SVG elements are parsed/set properly. --- dist/all.js | 152 +++++++++++++++++++++++++++++++++++++++++++ fabric.js | 1 + src/gradient.js | 129 ++++++++++++++++++++++++++++++++++++ src/object.class.js | 4 ++ src/parser.js | 20 ++++++ test/demo/demo.js | 16 +++++ test/demo/index.html | 21 +++++- 7 files changed, 341 insertions(+), 2 deletions(-) create mode 100644 src/gradient.js diff --git a/dist/all.js b/dist/all.js index 4e8fee00..a8a090c9 100644 --- a/dist/all.js +++ b/dist/all.js @@ -2706,6 +2706,21 @@ fabric.util.animate = animate; return oStyle; }; + function resolveGradients(instances) { + for (var i = instances.length; i--; ) { + var instanceFillValue = instances[i].get('fill'); + + if (/^url\(/.test(instanceFillValue)) { + + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + + if (fabric.gradientDefs[gradientId]) { + instances[i].set('fill', fabric.gradientDefs[gradientId]); + } + } + } + } + /** * Transforms an array of svg elements to corresponding fabric.* instances * @static @@ -2723,6 +2738,7 @@ fabric.util.animate = animate; instances = instances.filter(function(el) { return el != null; }); + resolveGradients(instances); callback(instances); } } @@ -2790,6 +2806,7 @@ fabric.util.animate = animate; return function(doc, callback) { if (!doc) return; + var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); var elements = descendants.filter(function(el) { @@ -2822,6 +2839,8 @@ fabric.util.animate = animate; height: height }; + fabric.gradientDefs = fabric.getGradientDefs(doc); + fabric.parseElements(elements, function(instances) { if (callback) { callback(instances, options); @@ -2838,6 +2857,135 @@ fabric.util.animate = animate; }); })(this); +(function() { + + /** @namespace */ + + fabric.Gradient = { + + /** + * @method create + * @static + */ + create: function(ctx, options) { + options || (options = { }); + + var x1 = options.x1 || 0, + y1 = options.y1 || 0, + x2 = options.x2 || ctx.canvas.width, + y2 = options.y2 || 0, + colorStops = options.colorStops; + + var gradient = ctx.createLinearGradient(x1, y1, x2, y2); + for (var position in colorStops) { + var colorValue = colorStops[position]; + gradient.addColorStop(position, colorValue); + } + return gradient; + }, + + /** + * @method fromElement + * @static + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + */ + fromElement: function(el, ctx) { + + /** + * @example: + * + * + * + * + * + * + */ + + var colorStopEls = el.getElementsByTagName('stop'), + el, + offset, + colorStops = { }; + + for (var i = colorStopEls.length; i--; ) { + el = colorStopEls[i]; + offset = parseInt(el.getAttribute('offset'), 10) / 100; + colorStops[offset] = el.getAttribute('stop-color'); + } + + return fabric.Gradient.create(ctx, { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0, + colorStops: colorStops + }); + }, + + /** + * @method forObject + * @static + */ + forObject: function(obj, ctx, options) { + options || (options = { }); + + _convertPercentUnitsToValues(obj, options); + + var gradient = fabric.Gradient.create(ctx, { + x1: options.x1 - (obj.width / 2), + y1: options.y1 - (obj.height / 2), + x2: options.x2 - (obj.width / 2), + y2: options.y2 - (obj.height / 2), + colorStops: options.colorStops + }); + + return gradient; + } + }; + + function _convertPercentUnitsToValues(object, options) { + for (var prop in options) { + if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { + var percents = parseFloat(options[prop], 10); + if (prop === 'x1' || prop === 'x2') { + options[prop] = object.width * percents / 100; + } + else if (prop === 'y1' || prop === 'y2') { + options[prop] = object.height * percents / 100; + } + } + } + } + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @method getGradientDefs + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition + */ + function getGradientDefs(doc) { + var linearGradientEls = doc.getElementsByTagName('linearGradient'), + radialGradientEls = doc.getElementsByTagName('radialGradient'), + el, + gradientDefs = { }; + + for (var i = linearGradientEls.length; i--; ) { + el = linearGradientEls[i]; + gradientDefs[el.id] = fabric.Gradient.fromElement(el); + } + for (var i = radialGradientEls.length; i--; ) { + el = radialGradientEls[i]; + gradientDefs[el.id] = fabric.Gradient.fromElement(el); + } + + return gradientDefs; + } + + fabric.getGradientDefs = getGradientDefs; + +})(); (function(global) { @@ -7024,6 +7172,10 @@ fabric.util.animate = animate; */ toJSON: function() { return this.toObject(); + }, + + setGradientFill: function(ctx, options) { + this.set('fill', fabric.Gradient.forObject(this, ctx, options)); } }); diff --git a/fabric.js b/fabric.js index aeca3c58..bd5b2a17 100644 --- a/fabric.js +++ b/fabric.js @@ -34,6 +34,7 @@ if (typeof console !== 'undefined') { //= require "src/util" //= require "src/parser" +//= require "src/gradient" //= require "src/point.class" //= require "src/intersection.class" diff --git a/src/gradient.js b/src/gradient.js new file mode 100644 index 00000000..be829646 --- /dev/null +++ b/src/gradient.js @@ -0,0 +1,129 @@ +(function() { + + /** @namespace */ + + fabric.Gradient = { + + /** + * @method create + * @static + */ + create: function(ctx, options) { + options || (options = { }); + + var x1 = options.x1 || 0, + y1 = options.y1 || 0, + x2 = options.x2 || ctx.canvas.width, + y2 = options.y2 || 0, + colorStops = options.colorStops; + + var gradient = ctx.createLinearGradient(x1, y1, x2, y2); + for (var position in colorStops) { + var colorValue = colorStops[position]; + gradient.addColorStop(position, colorValue); + } + return gradient; + }, + + /** + * @method fromElement + * @static + * @see http://www.w3.org/TR/SVG/pservers.html#LinearGradientElement + */ + fromElement: function(el, ctx) { + + /** + * @example: + * + * + * + * + * + * + */ + + var colorStopEls = el.getElementsByTagName('stop'), + el, + offset, + colorStops = { }; + + for (var i = colorStopEls.length; i--; ) { + el = colorStopEls[i]; + offset = parseInt(el.getAttribute('offset'), 10) / 100; + colorStops[offset] = el.getAttribute('stop-color'); + } + + return fabric.Gradient.create(ctx, { + x1: el.getAttribute('x1') || 0, + y1: el.getAttribute('y1') || 0, + x2: el.getAttribute('x2') || '100%', + y2: el.getAttribute('y2') || 0, + colorStops: colorStops + }); + }, + + /** + * @method forObject + * @static + */ + forObject: function(obj, ctx, options) { + options || (options = { }); + + _convertPercentUnitsToValues(obj, options); + + var gradient = fabric.Gradient.create(ctx, { + x1: options.x1 - (obj.width / 2), + y1: options.y1 - (obj.height / 2), + x2: options.x2 - (obj.width / 2), + y2: options.y2 - (obj.height / 2), + colorStops: options.colorStops + }); + + return gradient; + } + }; + + function _convertPercentUnitsToValues(object, options) { + for (var prop in options) { + if (typeof options[prop] === 'string' && /^\d+%$/.test(options[prop])) { + var percents = parseFloat(options[prop], 10); + if (prop === 'x1' || prop === 'x2') { + options[prop] = object.width * percents / 100; + } + else if (prop === 'y1' || prop === 'y2') { + options[prop] = object.height * percents / 100; + } + } + } + } + + /** + * Parses an SVG document, returning all of the gradient declarations found in it + * @static + * @function + * @memberOf fabric + * @method getGradientDefs + * @param {SVGDocument} doc SVG document to parse + * @return {Object} Gradient definitions; key corresponds to element id, value -- to gradient definition + */ + function getGradientDefs(doc) { + var linearGradientEls = doc.getElementsByTagName('linearGradient'), + radialGradientEls = doc.getElementsByTagName('radialGradient'), + el, + gradientDefs = { }; + + for (var i = linearGradientEls.length; i--; ) { + el = linearGradientEls[i]; + gradientDefs[el.id] = fabric.Gradient.fromElement(el); + } + for (var i = radialGradientEls.length; i--; ) { + el = radialGradientEls[i]; + gradientDefs[el.id] = fabric.Gradient.fromElement(el); + } + + return gradientDefs; + } + + fabric.getGradientDefs = getGradientDefs; + +})(); \ No newline at end of file diff --git a/src/object.class.js b/src/object.class.js index c8970d02..8aba1225 100644 --- a/src/object.class.js +++ b/src/object.class.js @@ -1270,6 +1270,10 @@ toJSON: function() { // delegate, not alias return this.toObject(); + }, + + setGradientFill: function(ctx, options) { + this.set('fill', fabric.Gradient.forObject(this, ctx, options)); } }); diff --git a/src/parser.js b/src/parser.js index c83b1e8b..d0c2bda5 100644 --- a/src/parser.js +++ b/src/parser.js @@ -285,6 +285,22 @@ } return oStyle; }; + + function resolveGradients(instances) { + for (var i = instances.length; i--; ) { + var instanceFillValue = instances[i].get('fill'); + + if (/^url\(/.test(instanceFillValue)) { + + // url(#grad1) --> grad1 + var gradientId = instanceFillValue.slice(5, instanceFillValue.length - 1); + + if (fabric.gradientDefs[gradientId]) { + instances[i].set('fill', fabric.gradientDefs[gradientId]); + } + } + } + } /** * Transforms an array of svg elements to corresponding fabric.* instances @@ -303,6 +319,7 @@ instances = instances.filter(function(el) { return el != null; }); + resolveGradients(instances); callback(instances); } } @@ -373,6 +390,7 @@ return function(doc, callback) { if (!doc) return; + var descendants = fabric.util.toArray(doc.getElementsByTagName('*')); var elements = descendants.filter(function(el) { @@ -405,6 +423,8 @@ width: width, height: height }; + + fabric.gradientDefs = fabric.getGradientDefs(doc); fabric.parseElements(elements, function(instances) { if (callback) { diff --git a/test/demo/demo.js b/test/demo/demo.js index 3e8bbc00..38acacf0 100644 --- a/test/demo/demo.js +++ b/test/demo/demo.js @@ -355,4 +355,20 @@ initAligningGuidelines(canvas); } + document.getElementById('gradientify').onclick = function() { + var obj = canvas.getActiveObject(); + if (obj) { + obj.setGradientFill(canvas.getContext(), { + x2: (getRandomInt(0, 1) ? 0 : obj.width), + y2: (getRandomInt(0, 1) ? 0 : obj.height), + colorStops: { + 0: '#' + getRandomColor(), + 1: '#' + getRandomColor() + } + }); + canvas.renderAll(); + console.log(getRandomNum(0, 1)); + } + }; + })(this); \ No newline at end of file diff --git a/test/demo/index.html b/test/demo/index.html index eae74457..2e3cc239 100644 --- a/test/demo/index.html +++ b/test/demo/index.html @@ -50,17 +50,25 @@

Add SVG shapes to canvas: