From 901ee7f942fd48795c423eb48f61e1561319dcf2 Mon Sep 17 00:00:00 2001 From: Stefan Kienzle Date: Wed, 14 Aug 2013 18:07:56 +0200 Subject: [PATCH] Update `fabric.Image.filters.Mask` - based on https://github.com/kangax/fabric.js/pull/667 (@aleph1) [BACK_INCOMPAT] `fabric.Image._initFilters` is now async, add parameter callback Add `fabric.util.resolveNamespace` - used to get correct klass in `fabric.util.enlivenObjects` Doc additions --- src/filters/mask_filter.class.js | 114 +++++++++++++++++++++++++++++++ src/shapes/image.class.js | 32 +++++---- src/util/misc.js | 34 +++++++-- 3 files changed, 160 insertions(+), 20 deletions(-) create mode 100644 src/filters/mask_filter.class.js diff --git a/src/filters/mask_filter.class.js b/src/filters/mask_filter.class.js new file mode 100644 index 00000000..279ab31d --- /dev/null +++ b/src/filters/mask_filter.class.js @@ -0,0 +1,114 @@ +(function(global) { + + "use strict"; + + var fabric = global.fabric || (global.fabric = { }), + extend = fabric.util.object.extend; + + /** + * Mask filter class + * @class fabric.Image.filters.Mask + * @memberOf fabric.Image.filters + * @extends fabric.Image.filters.BaseFilter + */ + fabric.Image.filters.Mask = fabric.util.createClass(fabric.Image.filters.BaseFilter, /** @lends fabric.Image.filters.Mask.prototype */ { + + /** + * Filter type + * @param {String} type + * @default + */ + type: 'Mask', + + /** + * Constructor + * @memberOf fabric.Image.filters.Mask.prototype + * @param {Object} [options] Options object + */ + initialize: function(options) { + options = options || { }; + + this.mask = options.mask; + this.channel = [ 0, 1, 2, 3 ].indexOf(options.channel) > -1 ? options.channel : 0; + }, + + /** + * Applies filter to canvas element + * @param {Object} canvasEl Canvas element to apply filter to + */ + applyTo: function(canvasEl) { + if (!this.mask) return; + + var context = canvasEl.getContext('2d'), + imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height), + data = imageData.data, + maskEl = this.mask.getElement(), + maskCanvasEl = fabric.util.createCanvasElement(), + channel = this.channel, + i, + iLen = imageData.width * imageData.height * 4; + + maskCanvasEl.width = maskEl.width; + maskCanvasEl.height = maskEl.height; + + maskCanvasEl.getContext('2d').drawImage(maskEl, 0, 0, maskEl.width, maskEl.height); + + var maskImageData = maskCanvasEl.getContext('2d').getImageData(0, 0, maskEl.width, maskEl.height), + maskData = maskImageData.data; + + for (i = 0; i < iLen; i += 4) { + data[i + 3] = maskData[i + channel]; + } + + context.putImageData(imageData, 0, 0); + }, + + /** + * Returns object representation of an instance + * @return {Object} Object representation of an instance + */ + toObject: function() { + return extend(this.callSuper('toObject'), { + mask: this.mask.toObject(), + channel: this.channel + }); + } + }); + + /** + * Returns filter instance from an object representation + * @static + * @param {Object} object Object to create an instance from + * @param {Function} [callback] Callback to invoke when a mask filter instance is created + */ + fabric.Image.filters.Mask.fromObject = function(object, callback) { + var img = fabric.document.createElement('img'), + src = object.mask.src; + + /** @ignore */ + img.onload = function() { + object.mask = new fabric.Image(img, object.mask); + + callback && callback(new fabric.Image.filters.Mask(object)); + img = img.onload = img.onerror = null; + }; + + /** @ignore */ + img.onerror = function() { + fabric.log('Error loading ' + img.src); + callback && callback(null, true); + img = img.onload = img.onerror = null; + }; + + img.src = src; + }; + + /** + * Indicates that instances of this type are async + * @static + * @type Boolean + * @default + */ + fabric.Image.filters.Mask.async = true; + +})(typeof exports !== 'undefined' ? exports : this); diff --git a/src/shapes/image.class.js b/src/shapes/image.class.js index 7f363a01..26079fc2 100644 --- a/src/shapes/image.class.js +++ b/src/shapes/image.class.js @@ -79,7 +79,7 @@ /** * Returns original size of an image - * @return {Object} object with "width" and "height" properties + * @return {Object} Object with "width" and "height" properties */ getOriginalSize: function() { var element = this.getElement(); @@ -181,7 +181,7 @@ /** * Returns object representation of an instance - * @param {Array} propertiesToInclude + * @param {Array} propertiesToInclude Any properties that you might want to additionally include in the output * @return {Object} Object representation of an instance */ toObject: function(propertiesToInclude) { @@ -252,7 +252,7 @@ /** * Returns a clone of an instance * @param {Function} callback Callback is invoked with a clone as a first argument - * @param {Array} propertiesToInclude + * @param {Array} propertiesToInclude Any properties that you might want to additionally include in the output */ clone: function(callback, propertiesToInclude) { this.constructor.fromObject(this.toObject(propertiesToInclude), callback); @@ -313,7 +313,7 @@ /** * @private - * @param ctx + * @param {CanvasRenderingContext2D} ctx Context to render on */ _render: function(ctx) { ctx.drawImage( @@ -339,7 +339,7 @@ * The Image class's initialization method. This method is automatically * called by the constructor. * @private - * @param {HTMLImageElement|String} el The element representing the image + * @param {HTMLImageElement|String} element The element representing the image */ _initElement: function(element) { this.setElement(fabric.util.getById(element)); @@ -359,12 +359,13 @@ /** * @private * @param {Object} object Object with filters property + * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created */ - _initFilters: function(object) { + _initFilters: function(object, callback) { if (object.filters && object.filters.length) { - this.filters = object.filters.map(function(filterObj) { - return filterObj && fabric.Image.filters[filterObj.type].fromObject(filterObj); - }); + fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { + callback(enlivenedObjects); + }, 'fabric.Image.filters'); } }, @@ -395,6 +396,7 @@ * Default CSS class name for canvas * @static * @type String + * @default */ fabric.Image.CSS_CANVAS = "canvas-img"; @@ -416,11 +418,13 @@ /** @ignore */ img.onload = function() { - fabric.Image.prototype._initFilters.call(object, object); + fabric.Image.prototype._initFilters.call(object, object, function(filters) { + object.filters = filters; - var instance = new fabric.Image(img, object); - callback && callback(instance); - img = img.onload = img.onerror = null; + var instance = new fabric.Image(img, object); + callback && callback(instance); + img = img.onload = img.onerror = null; + }); }; /** @ignore */ @@ -481,8 +485,8 @@ /** * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 * @static - * @default * @type Number + * @default */ fabric.Image.pngCompression = 1; diff --git a/src/util/misc.js b/src/util/misc.js index d1208331..6992983d 100644 --- a/src/util/misc.js +++ b/src/util/misc.js @@ -105,13 +105,34 @@ } /** - * Returns klass "Class" object of given fabric.Object type + * Returns klass "Class" object of given namespace * @memberOf fabric.util * @param {String} type Type of object (eg. 'circle') + * @param {String} namespace Namespace to get klass "Class" object from * @return {Object} klass "Class" */ - function getKlass(type) { - return fabric[fabric.util.string.camelize(fabric.util.string.capitalize(type))]; + function getKlass(type, namespace) { + return resolveNamespace(namespace)[fabric.util.string.camelize(fabric.util.string.capitalize(type))]; + } + + /** + * Returns object of given namespace + * @memberOf fabric.util + * @param {String} namespace Namespace string e.g. 'fabric.Image.filter' or 'fabric' + * @return {Object} Object for given namespace (default fabric) + */ + function resolveNamespace(namespace) { + namespace = namespace || 'fabric'; + + var parts = namespace.split('.'), + len = parts.length, + obj = fabric.window; + + for (var i = 0; i < len; ++i) { + obj = obj[parts[i]]; + } + + return obj; } /** @@ -143,7 +164,7 @@ * @param {Array} objects Objects to enliven * @param {Function} callback Callback to invoke when all objects are created */ - function enlivenObjects(objects, callback) { + function enlivenObjects(objects, callback, namespace) { function onLoaded() { if (++numLoadedObjects === numTotalObjects) { @@ -161,7 +182,7 @@ if (!o.type) { return; } - var klass = fabric.util.getKlass(o.type); + var klass = fabric.util.getKlass(o.type, namespace); if (klass.async) { klass.fromObject(o, function (o, error) { if (!error) { @@ -278,7 +299,7 @@ * Creates image element (works on client and node) * @static * @memberOf fabric.util - * @return {Image} image element + * @return {HTMLImageElement} HTML image element */ function createImage() { return fabric.isLikelyNode @@ -495,6 +516,7 @@ fabric.util.getRandomInt = getRandomInt; fabric.util.falseFunction = falseFunction; fabric.util.getKlass = getKlass; + fabric.util.resolveNamespace = resolveNamespace; fabric.util.loadImage = loadImage; fabric.util.enlivenObjects = enlivenObjects; fabric.util.groupSVGElements = groupSVGElements;