(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 * @default */ type: 'image', /** * Constructor * @param {HTMLImageElement | String} element Image element * @param {Object} [options] Options object * @return {fabric.Image} thisArg */ initialize: function(element, options) { options || (options = { }); this.filters = [ ]; this.callSuper('initialize', options); this._initElement(element); this._initConfig(options); 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. * If filters defined they are applied to new image. * You might need to call `canvas.renderAll` and `object.setCoords` after replacing, to render new image and update controls area. * @param {HTMLImageElement} element * @param {Function} [callback] Callback is invoked when all filters have been applied and new image is generated * @return {fabric.Image} thisArg * @chainable */ setElement: function(element, callback) { this._element = element; this._originalElement = element; this._initConfig(); if (this.filters.length !== 0) { this.applyFilters(callback); } 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); } ctx.save(); 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(); 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.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; ctx.miterLimit = this.strokeMiterLimit; ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : this.stroke; ctx.beginPath(); ctx.strokeRect(-this.width / 2, -this.height / 2, this.width, this.height); ctx.closePath(); 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.save(); ctx.lineWidth = this.strokeWidth; ctx.lineCap = this.strokeLineCap; ctx.lineJoin = this.strokeLineJoin; ctx.miterLimit = this.strokeMiterLimit; ctx.strokeStyle = this.stroke.toLive ? this.stroke.toLive(ctx) : 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(); ctx.restore(); }, /** * Returns object representation of an instance * @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) { return extend(this.callSuper('toObject', propertiesToInclude), { src: this._originalElement.src || this._originalElement._src, filters: this.filters.map(function(filterObj) { return filterObj && filterObj.toObject(); }) }); }, /* _TO_SVG_START_ */ /** * 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(''); }, /* _TO_SVG_END_ */ /** * 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 Any properties that you might want to additionally include in the output */ 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._element = this._originalElement; callback && callback(); return; } var imgEl = this._originalElement, 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) { replacement.src = canvasEl.toBuffer(undefined, fabric.Image.pngCompression); // 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 {CanvasRenderingContext2D} ctx Context to render on */ _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} element 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 * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created */ _initFilters: function(object, callback) { if (object.filters && object.filters.length) { fabric.util.enlivenObjects(object.filters, function(enlivenedObjects) { callback && callback(enlivenedObjects); }, 'fabric.Image.filters'); } else { callback && callback(); } }, /** * @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 of this instance */ complexity: function() { return 1; } }); /** * Default CSS class name for canvas * @static * @type String * @default */ 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 Object to create an instance from * @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; /** @ignore */ img.onload = function() { 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; }); }; /** @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)); }); }; /* _FROM_SVG_START_ */ /** * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement}) * @static * @see {@link http://www.w3.org/TR/SVG/struct.html#ImageElement} */ fabric.Image.ATTRIBUTE_NAMES = fabric.SHARED_ATTRIBUTES.concat('x y width height 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} Instance of 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)); }; /* _FROM_SVG_END_ */ /** * Indicates that instances of this type are async * @static * @type Boolean * @default */ fabric.Image.async = true; /** * Indicates compression level used when generating PNG under Node (in applyFilters). Any of 0-9 * @static * @type Number * @default */ fabric.Image.pngCompression = 1; })(typeof exports !== 'undefined' ? exports : this);