(function(global) { "use strict"; var extend = fabric.util.object.extend; if (!global.fabric) { global.fabric = { }; } if (global.fabric.Image) { fabric.warn('fabric.Image is already defined.'); return; } /** * Image class * @class fabric.Image * @extends fabric.Object */ fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ { /** * Type of an object * @type String */ type: 'image', /** * Constructor * @param {HTMLImageElement | String} element Image element * @param {Object} [options] Options object * @return {fabric.Image} */ initialize: function(element, options) { options || (options = { }); this.callSuper('initialize', options); this._initElement(element); this._originalImage = this.getElement(); this._initConfig(options); this.filters = [ ]; if (options.filters) { this.filters = options.filters; this.applyFilters(); } }, /** * Returns image element which this instance if based on * @return {HTMLImageElement} image element */ getElement: function() { return this._element; }, /** * Sets image element for this instance to a specified one * @param {HTMLImageElement} element * @return {fabric.Image} thisArg * @chainable */ setElement: function(element) { this._element = element; this._initConfig(); return this; }, /** * Returns original size of an image * @return {Object} object with "width" and "height" properties */ getOriginalSize: function() { var element = this.getElement(); return { width: element.width, height: element.height }; }, /** * Renders image on a specified context * @param {CanvasRenderingContext2D} ctx Context to render on * @param {Boolean} [noTransform] When true, context is not transformed */ render: function(ctx, noTransform) { // do not render if object is not visible if (!this.visible) return; ctx.save(); var m = this.transformMatrix; var isInPathGroup = this.group && this.group.type !== 'group'; // this._resetWidthHeight(); if (isInPathGroup) { ctx.translate(-this.group.width/2 + this.width/2, -this.group.height/2 + this.height/2); } if (m) { ctx.transform(m[0], m[1], m[2], m[3], m[4], m[5]); } if (!noTransform) { this.transform(ctx); } this._setShadow(ctx); this.clipTo && fabric.util.clipContext(this, ctx); this._render(ctx); if (this.shadow && !this.shadow.affectStroke) { this._removeShadow(ctx); } this._renderStroke(ctx); this.clipTo && ctx.restore(); if (this.active && !noTransform) { this.drawBorders(ctx); this.drawControls(ctx); } ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _stroke: function(ctx) { ctx.save(); ctx.lineWidth = this.strokeWidth; ctx.strokeStyle = this.stroke; ctx.beginPath(); ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); ctx.beginPath(); ctx.restore(); }, /** * @private * @param {CanvasRenderingContext2D} ctx Context to render on */ _renderDashedStroke: function(ctx) { var x = -this.width/2, y = -this.height/2, w = this.width, h = this.height; ctx.lineWidth = this.strokeWidth; ctx.strokeStyle = this.stroke; ctx.beginPath(); fabric.util.drawDashedLine(ctx, x, y, x+w, y, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x+w, y, x+w, y+h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x+w, y+h, x, y+h, this.strokeDashArray); fabric.util.drawDashedLine(ctx, x, y+h, x, y, this.strokeDashArray); ctx.closePath(); }, /** * Returns object representation of an instance * @param {Array} propertiesToInclude * @return {Object} propertiesToInclude Object representation of an instance */ toObject: function(propertiesToInclude) { return extend(this.callSuper('toObject', propertiesToInclude), { src: this._originalImage.src || this._originalImage._src, filters: this.filters.concat() }); }, /** * Returns SVG representation of an instance * @return {String} svg representation of an instance */ toSVG: function() { var markup = []; markup.push( '', '' ); if (this.stroke || this.strokeDashArray) { var origFill = this.fill; this.fill = null; markup.push( '' ); this.fill = origFill; } markup.push(''); return markup.join(''); }, /** * Returns source of an image * @return {String} Source of an image */ getSrc: function() { return this.getElement().src || this.getElement()._src; }, /** * Returns string representation of an instance * @return {String} String representation of an instance */ toString: function() { return '#'; }, /** * Returns a clone of an instance * @param {Function} callback Callback is invoked with a clone as a first argument * @param {Array} propertiesToInclude */ clone: function(callback, propertiesToInclude) { this.constructor.fromObject(this.toObject(propertiesToInclude), callback); }, /** * Applies filters assigned to this image (from "filters" array) * @mthod applyFilters * @param {Function} callback Callback is invoked when all filters have been applied and new image is generated * @return {fabric.Image} thisArg * @chainable */ applyFilters: function(callback) { if (this.filters.length === 0) { this.setElement(this._originalImage); callback && callback(); return; } var imgEl = this._originalImage, canvasEl = fabric.util.createCanvasElement(), replacement = fabric.util.createImage(), _this = this; canvasEl.width = imgEl.width; canvasEl.height = imgEl.height; canvasEl.getContext('2d').drawImage(imgEl, 0, 0, imgEl.width, imgEl.height); this.filters.forEach(function(filter) { filter && filter.applyTo(canvasEl); }); /** @ignore */ replacement.width = imgEl.width; replacement.height = imgEl.height; if (fabric.isLikelyNode) { // cut off data:image/png;base64, part in the beginning var base64str = canvasEl.toDataURL('image/png').substring(22); replacement.src = new Buffer(base64str, 'base64'); // onload doesn't fire in some node versions, so we invoke callback manually _this._element = replacement; callback && callback(); } else { replacement.onload = function() { _this._element = replacement; callback && callback(); replacement.onload = canvasEl = imgEl = null; }; replacement.src = canvasEl.toDataURL('image/png'); } return this; }, /** * @private * @param ctx */ _render: function(ctx) { ctx.drawImage( this._element, -this.width / 2, -this.height / 2, this.width, this.height ); }, /** * @private */ _resetWidthHeight: function() { var element = this.getElement(); this.set('width', element.width); this.set('height', element.height); }, /** * The Image class's initialization method. This method is automatically * called by the constructor. * @private * @param {HTMLImageElement|String} el The element representing the image */ _initElement: function(element) { this.setElement(fabric.util.getById(element)); fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS); }, /** * @private * @param {Object} [options] Options object */ _initConfig: function(options) { options || (options = { }); this.setOptions(options); this._setWidthHeight(options); }, /** * @private * @param {Object} object Object with filters property */ _initFilters: function(object) { if (object.filters && object.filters.length) { this.filters = object.filters.map(function(filterObj) { return filterObj && fabric.Image.filters[filterObj.type].fromObject(filterObj); }); } }, /** * @private * @param {Object} [options] Object with width/height properties */ _setWidthHeight: function(options) { this.width = 'width' in options ? options.width : (this.getElement().width || 0); this.height = 'height' in options ? options.height : (this.getElement().height || 0); }, /** * Returns complexity of an instance * @return {Number} complexity */ complexity: function() { return 1; } }); /** * Default CSS class name for canvas * @static * @type String */ fabric.Image.CSS_CANVAS = "canvas-img"; /** * Alias for getSrc * @static */ fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc; /** * Creates an instance of fabric.Image from its object representation * @static * @param {Object} object * @param {Function} [callback] Callback to invoke when an image instance is created */ fabric.Image.fromObject = function(object, callback) { var img = fabric.document.createElement('img'), src = object.src; if (object.width) { img.width = object.width; } if (object.height) { img.height = object.height; } /** @ignore */ img.onload = function() { fabric.Image.prototype._initFilters.call(object, object); var instance = new fabric.Image(img, object); callback && callback(instance); 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; }; /** * Creates an instance of fabric.Image from an URL string * @static * @param {String} url URL to create an image from * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument) * @param {Object} [imgOptions] Options object */ fabric.Image.fromURL = function(url, callback, imgOptions) { fabric.util.loadImage(url, function(img) { callback(new fabric.Image(img, imgOptions)); }); }; /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) * @static * @see http://www.w3.org/TR/SVG/struct.html#ImageElement */ fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href'.split(' '); /** * Returns {@link fabric.Image} instance from an SVG element * @static * @param {SVGElement} element Element to parse * @param {Function} callback Callback to execute when fabric.Image object is created * @param {Object} [options] Options object * @return {fabric.Image} */ fabric.Image.fromElement = function(element, callback, options) { var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES); fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend((options ? fabric.util.object.clone(options) : { }), parsedAttributes)); }; /** * Indicates that instances of this type are async * @static * @type Boolean */ fabric.Image.async = true; })(typeof exports !== 'undefined' ? exports : this);