Skip to content
Browse files

Fix some bugs related to SVG Filters.

  • Loading branch information...
1 parent a9b972f commit a7694eb2004ee7eb6a5d2b9d157fd65297b30975 @basecode basecode committed
Showing with 169 additions and 50 deletions.
  1. +3 −1 CHANGELOG
  2. +8 −19 src/renderer/svg/svg.js
  3. +108 −29 src/renderer/svg/svg_filters.js
  4. +50 −1 test/renderer/svg_filters-spec.js
View
4 CHANGELOG
@@ -1,6 +1,8 @@
v0.3.2
-------------------
-
+* Fix a bug where SVG Filters are applied in wrong order.
+* Fix a bug where SVG Filter's region was too big.
+* Make use of SVG's feDropShadow Filter when supported by user-agent.
v0.3.1 / 2012-08-01
-------------------
View
27 src/renderer/svg/svg.js
@@ -28,7 +28,8 @@ define([
// svgFilters
var isFEColorMatrixEnabled = svgFilters.isFEColorMatrixEnabled,
- colorApplyColorMatrix = svgFilters.colorApplyColorMatrix;
+ colorApplyColorMatrix = svgFilters.colorApplyColorMatrix,
+ filterElementsFromList = svgFilters.filterElementsFromList;
// AssetController
var fontIDs = AssetController.handlers.Font.fontIDs,
@@ -1277,10 +1278,12 @@ define([
var filterDef;
var signature = 'filter:';
+ // create signature
signature += list.map(function(filter) {
return filterToSignature(filter);
}).join();
+ // compare signature with existing signatures
if (signature in this.definitions) {
// We already have an identical filter -- use it:
filterDef = this.definitions[signature];
@@ -1314,24 +1317,10 @@ define([
filterContainer.id = this._genDefUID();
// handle filter specific stuff and get an array of <filter> elements back
- list.forEach(function(item) {
-
- // TODO: Have a attr on DisplayObjects to determine filter region
- // For now, we provide a region *3 of the bounding box
- filterContainer.setAttribute('height', 10);
- filterContainer.setAttribute('width', 10);
- filterContainer.setAttribute('x', -5);
- filterContainer.setAttribute('y', -5);
-
- var filter = svgFilters.create(item.type, item.value);
- if (tools.isArray(filter)) {
- for (var i = 0, m = filter.length; i < m; i++) {
- filterContainer.appendChild(filter[i]);
- }
- } else {
- filterContainer.appendChild(filter);
- }
- });
+ var filterElements = filterElementsFromList(list);
+ for (var i = 0, len = filterElements.length; i < len; i += 1) {
+ filterContainer.appendChild(filterElements[i]);
+ }
this.svg.defs.appendChild(filterContainer);
element.setAttribute('filter', 'url(#' + filterContainer.id + ')');
View
137 src/renderer/svg/svg_filters.js
@@ -1,5 +1,7 @@
/**
* This module contains filters used by svg renderers.
+ * Implementation details can be found here
+ * https://dvcs.w3.org/hg/FXTF/raw-file/916184271e55/filters/master/SVGFilter.html
*
* @exports svg_filters
*/
@@ -8,7 +10,17 @@ define([
], function(color) {
'use strict';
+ // Array prototype caching
+ var unshift = [].unshift;
+ var push = [].push;
+ var concat = [].concat;
+
var isFEColorMatrixEnabled = typeof window !== 'undefined' && 'SVGFEColorMatrixElement' in window;
+ var isFEDropShadowEnabled = typeof window !== 'undefined' && 'SVGFEDropShadowElement' in window;
+ var isFEMergeEnabled = typeof window !== 'undefined' && 'SVGFEMergeElement' in window;
+ var isFEBlendEnabled = typeof window !== 'undefined' && 'SVGFEBlendElement' in window;
+ var isFECompositeEnabled = typeof window !== 'undefined' && 'SVGFECompositeElement' in window;
+ var isCSSDropShadowEnabled = typeof window !== 'undefined' && 'webkitSvgShadow' in window.document.body.style;
function range(value, min, max) {
return Math.min(max, Math.max(min, value));
@@ -80,50 +92,69 @@ define([
]);
},
- /**
- *
- * @TODO: Use feDropShadow instead? Where is it implemented?
- */
dropShadowByOffset: function(values) {
var offsetX = values[0];
var offsetY = values[1];
var blurRadius = values[2];
var bsColor = color(values[3]);
- var filter = [];
+ var isBlack = bsColor.int32() === 255;
+ var filters = [];
+
+ // use `feDropShadow` filter primitive in case it's supported.
+ // The expectation is that user agents can optimize the handling
+ // by not having to do all the steps separately.
+ if (isFEDropShadowEnabled) {
+ return createElement('feDropShadow', {
+ stdDeviation: blurRadius,
+ dx: offsetX,
+ dy: offsetY,
+ 'flood-color': bsColor.rgb(),
+ 'flood-opacity': bsColor.alpha()
+ });
+ }
var blur = createElement('feGaussianBlur', {
- stdDeviation: blurRadius
+ 'stdDeviation': blurRadius,
+ 'in': 'SourceAlpha',
+ 'result': 'blur'
+ });
+ filters.push(blur);
+
+ // optimize performance by dropping a flood-color of rgba(0,0,0,1)
+ if (!isBlack) {
+ var flood = createElement('feFlood', {
+ 'flood-color': bsColor.rgb(),
+ 'flood-opacity': bsColor.alpha(),
+ 'in': 'blur',
+ 'result': 'flood'
+ });
+ filters.push(flood);
+ }
+
+ var composite1 = createElement('feComposite', {
+ 'in': isBlack ? 'blur' : 'flood',
+ 'in2': 'blur',
+ 'operator': 'in',
+ 'result': 'composite1'
});
- filter.push(blur);
+ filters.push(composite1);
var offset = createElement('feOffset', {
dx: offsetX,
dy: offsetY,
- result: 'offsetblur'
- });
- filter.push(offset);
-
- var flood = createElement('feFlood', {
- 'flood-color': bsColor.rgb(),
- 'flood-opacity': bsColor.alpha(),
- result: 'flood'
+ result: 'offset'
});
- filter.push(flood);
+ filters.push(offset);
- var comp = createElement('feComposite', {
- in2: 'offsetblur',
- operator: 'in'
+ var composite2 = createElement('feComposite', {
+ 'in': 'SourceGraphic',
+ 'in2': 'offset',
+ 'operator': 'over',
+ 'result': 'composite2'
});
- filter.push(comp);
-
- var merge = createElement('feMerge');
- createElement('feMergeNode', null, merge);
- createElement('feMergeNode', {
- 'in': 'SourceGraphic'
- }, merge);
- filter.push(merge);
+ filters.push(composite2);
- return filter;
+ return filters;
},
grayscale: function(value) {
@@ -202,6 +233,53 @@ define([
};
+ /*
+ * Checks whether a filterList contains a flatten filter.
+ */
+ function containsFlattenFilter(filters) {
+ filters = filters || [];
+ for (var filter, i = 0, len = filters.length; i < len; i += 1) {
+ filter = filters[i];
+ if (isFEMergeEnabled && filter instanceof window.SVGFEMergeElement) {
+ return true;
+ }
+ if (isFEBlendEnabled && filter instanceof window.SVGFEBlendElement) {
+ return true;
+ }
+ if (isFECompositeEnabled && filter instanceof window.SVGFECompositeElement) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Creates an array of SVG Filters (filterList)
+ *
+ * @param {Array} list A list of instructions
+ * @returns {Array} The SVG Filters
+ */
+ function filterElementsFromList(list) {
+ list = list || [];
+ var item, filters, filterList = [];
+ for (var i = 0, len = list.length; i < len; i += 1) {
+ item = list[i];
+ if (typeof filterDefs[item.type] !== 'function') {
+ continue;
+ }
+ // force `filters` to be of type `array`
+ filters = concat.call([], filterDefs[item.type](item.value));
+ // Check for flatten filters and put them at the start of the filterList.
+ // Otherwise other filters would be "eaten" by "SourceGraphic".
+ if (containsFlattenFilter(filters) && i > 0) {
+ unshift.apply(filterList, filters);
+ } else {
+ push.apply(filterList, filters);
+ }
+ }
+ return filterList;
+ }
+
var svgFilters = {
/**
* Creates a filter
@@ -214,7 +292,8 @@ define([
create: function(name, value) {
return filterDefs[name](value);
},
-
+ containsFlattenFilter: containsFlattenFilter,
+ filterElementsFromList: filterElementsFromList,
isFEColorMatrixEnabled: isFEColorMatrixEnabled
};
View
51 test/renderer/svg_filters-spec.js
@@ -4,7 +4,56 @@ require([
], function(svgFilters) {
describe('svgFilters', function() {
- /* tests are missing */
+ // the real typeof
+ var toString = {}.toString;
+ // a SVGElement
+ function createSVGElement(name) {
+ return document.createElementNS('http://www.w3.org/2000/svg', name);
+ }
+
+ describe('has a property `filterElementsFromList`', function() {
+ it('is a function', function() {
+ expect(typeof svgFilters.filterElementsFromList).toBe('function');
+ });
+ it('returns an empty array by default', function() {
+ expect(svgFilters.filterElementsFromList() instanceof Array).toBeTruthy();
+ });
+ it('returns an empty array when the instructions are unknown', function() {
+ var instructions = [{ type: 'megaFilter', value: 'big-number' }];
+ expect(svgFilters.filterElementsFromList(instructions)).toEqual([]);
+ });
+ it('returns an empty array when the instructions are invalid', function() {
+ var instructions = [{ typo: 'typo' }];
+ expect(svgFilters.filterElementsFromList(instructions)).toEqual([]);
+ });
+ it('returns an SVGFE…Element at index 0 when instructions are valid', function() {
+ var instructions = [{ type: 'sepia', value: 1 }];
+ var filter = svgFilters.filterElementsFromList(instructions)[0];
+ expect(toString.call(filter)).toMatch(/^\[object SVGFE\w+Element\]/);
+ });
+ it('returns an SVGFE…Element at index 1 when multiple instructions are passed', function() {
+ var instructions = [{ type: 'sepia', value: 1 }, { type: 'sepia', value: 1 }];
+ var filter = svgFilters.filterElementsFromList(instructions)[1];
+ expect(toString.call(filter)).toMatch(/^\[object SVGFE\w+Element\]/);
+ });
+ });
+
+ describe('has a property `containsFlattenFilter`', function() {
+ it('is a function', function() {
+ expect(typeof svgFilters.containsFlattenFilter).toBe('function');
+ });
+ it('returns false by default', function() {
+ expect(svgFilters.containsFlattenFilter()).toBeFalsy();
+ });
+ it('returns false when the passed filters do not flatten.', function() {
+ var filterList = ['aFilter'];
+ expect(svgFilters.containsFlattenFilter(filterList)).toBeFalsy();
+ });
+ it('returns true when the passed filters contain a flatten filter.', function() {
+ var filterList = [createSVGElement('feMerge')];
+ expect(svgFilters.containsFlattenFilter(filterList)).toBeTruthy();
+ });
+ });
});
});

0 comments on commit a7694eb

Please sign in to comment.
Something went wrong with that request. Please try again.