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.

This commit is contained in:
kangax 2011-01-09 01:38:54 -05:00
parent 6fa84aba5e
commit a40e9cd610
7 changed files with 341 additions and 2 deletions

152
dist/all.js vendored
View file

@ -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:
*
* <linearGradient id="grad1">
* <stop offset="0%" stop-color="white"/>
* <stop offset="100%" stop-color="black"/>
* </linearGradient>
*
*/
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));
}
});

View file

@ -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"

129
src/gradient.js Normal file
View file

@ -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:
*
* <linearGradient id="grad1">
* <stop offset="0%" stop-color="white"/>
* <stop offset="100%" stop-color="black"/>
* </linearGradient>
*
*/
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;
})();

View file

@ -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));
}
});

View file

@ -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) {

View file

@ -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);

View file

@ -50,17 +50,25 @@
<p>Add <strong>SVG shapes</strong> to canvas:</p>
<ul class="svg-shapes">
<li><button class="shape" id="shape54"><strong>1</strong> path</button></li>
<li><button class="shape" id="shape25"><strong>36</strong> paths</button></li>
<li><button class="shape" id="shape36"><strong>41</strong> paths</button></li>
<li><button class="shape" id="shape58"><strong>54</strong> paths</button></li>
<li><button class="shape" id="shape59"><strong>57</strong> paths</button></li>
<li><button class="shape" id="shape8"><strong>65</strong> paths</button></li>
<li><button class="shape" id="shape17"><strong>87</strong> paths</button></li>
<li><button class="shape" id="shape2"><strong>90</strong> paths</button></li>
<li><button class="shape" id="shape47"><strong>133</strong> paths</button></li>
<li><button class="shape" id="shape51"><strong>141</strong> paths</button></li>
<li><button class="shape" id="shape50"><strong>167</strong> paths</button></li>
<li><button class="shape" id="shape63"><strong>202</strong> paths</button></li>
<li><button class="shape" id="shape64"><strong>224</strong> paths</button></li>
<li><button class="shape" id="shape14"><strong>226</strong> paths</button></li>
<li><button class="shape" id="shape62"><strong>237</strong> paths</button></li>
<li><button class="shape" id="shape57"><strong>321</strong> paths</button></li>
<li><button class="shape" id="shape9"><strong>404</strong> paths</button></li>
<li><button class="shape" id="shape45"><strong>404</strong> paths</button></li>
<li><button class="shape" id="shape65"><strong>444</strong> paths</button></li>
<li><button class="shape" id="shape1"><strong>448</strong> paths</button></li>
<li><button class="shape" id="shape38"><strong>563</strong> paths</button></li>
<li><button class="shape" id="shape37"><strong>758</strong> paths</button></li>
@ -75,16 +83,19 @@
<li><button class="shape" id="shape21"><strong>1972</strong> paths</button></li>
<li><button class="shape" id="shape5"><strong>2208</strong> paths</button></li>
<li><button class="shape" id="shape40"><strong>2394</strong> paths</button></li>
<li><button class="shape" id="shape55"><strong>2499</strong> paths</button></li>
<li><button class="shape" id="shape4"><strong>2742</strong> paths</button></li>
<li><button class="shape" id="shape29"><strong>3103</strong> paths</button></li>
<li><button class="shape" id="shape30"><strong>3566</strong> paths</button></li>
<li><button class="shape" id="shape61"><strong>3685</strong> paths</button></li>
<li><button class="shape" id="shape6"><strong>3921</strong> paths</button></li>
<li><button class="shape" id="shape23"><strong>4418</strong> paths</button></li>
<li><button class="shape" id="shape42"><strong>4583</strong> paths</button></li>
<li><button class="shape" id="shape31"><strong>4768</strong> paths</button></li>
<!-- <li><button class="shape" id="shape28"><strong>5378</strong> paths</button></li> -->
<li><button class="shape" id="shape15"><strong>8325</strong> paths</button></li>
<li><button class="shape" id="shape22"><strong>9663</strong> paths</button></li>
<li><button class="shape" id="shape52"><strong>11285</strong> paths <em>(broken)</em></button></li>
<!-- <li><button class="shape" id="shape52"><strong>11285</strong> paths</button></li> -->
<li><button class="shape" id="shape41"><strong>12361</strong> paths</button></li>
<li><button class="shape" id="shape24"><strong>12866</strong> paths</button></li>
<li><button class="shape" id="shape27"><strong>13905</strong> paths</button></li>
@ -94,7 +105,9 @@
<li><button class="shape" id="shape35"><strong>19271</strong> paths</button></li>
<li><button class="shape" id="shape44"><strong>22375</strong> paths</button></li>
<li><button class="shape" id="shape48"><strong>41787</strong> paths</button></li>
<li><button class="shape" id="shape54"><strong>xx</strong> paths</button></li>
<!--<li><button class="shape" id="shape56"><strong>xxx</strong> paths</button></li>-->
<!-- <li><button class="shape" id="shape60"><strong>xxx</strong> paths</button></li> -->
<li><button class="shape" id="shape66"><strong>xxx</strong> paths</button></li>
</ul>
<ul>
@ -115,6 +128,10 @@
<li>
<button id="lock-rotation">Lock rotation</button>
</li>
<li style="margin-top:10px;">
<button id="gradientify">Gradientify</button>
</li>
<li style="margin-top:10px;">
<button id="drawing-mode">Enter drawing mode</button>