fabric.js/src/image.class.js

420 lines
12 KiB
JavaScript

(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 Image
* @extends fabric.Object
*/
fabric.Image = fabric.util.createClass(fabric.Object, /** @scope fabric.Image.prototype */ {
/**
* Type of an object
* @property
* @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
* @method getElement
* @return {HTMLImageElement} image element
*/
getElement: function() {
return this._element;
},
/**
* Sets image element for this instance to a specified one
* @method setElement
* @param {HTMLImageElement} element
* @return {fabric.Image} thisArg
* @chainable
*/
setElement: function(element) {
this._element = element;
this._initConfig();
return this;
},
/**
* Returns original size of an image
* @method getOriginalSize
* @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
* @method render
* @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;
// this._resetWidthHeight();
if (this.group) {
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);
this.clipTo && ctx.restore();
this._removeShadow(ctx);
if (this.active && !noTransform) {
this.drawBorders(ctx);
this.drawControls(ctx);
}
ctx.restore();
},
/**
* Returns object representation of an instance
* @method toObject
* @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
* @method toSVG
* @return {String} svg representation of an instance
*/
toSVG: function() {
return '<g transform="' + this.getSvgTransform() + '">'+
'<image xlink:href="' + this.getSvgSrc() + '" '+
'style="' + this.getSvgStyles() + '" ' +
// we're essentially moving origin of transformation from top/left corner to the center of the shape
// by wrapping it in container <g> element with actual transformation, then offsetting object to the top/left
// so that object's center aligns with container's left/top
'transform="translate('+ (-this.width/2) + ' ' + (-this.height/2) + ')" ' +
'width="' + this.width + '" ' +
'height="' + this.height + '"' + '></image>' +
'</g>';
},
/**
* Returns source of an image
* @method getSrc
* @return {String} Source of an image
*/
getSrc: function() {
return this.getElement().src || this.getElement()._src;
},
/**
* Returns string representation of an instance
* @method toString
* @return {String} String representation of an instance
*/
toString: function() {
return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
},
/**
* Returns a clone of an instance
* @method clone
* @param {Array} propertiesToInclude
* @param {Function} callback Callback is invoked with a clone as a first argument
*/
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 isLikelyNode = typeof Buffer !== 'undefined' && typeof window === 'undefined',
imgEl = this._originalImage,
canvasEl = fabric.util.createCanvasElement(),
replacement = isLikelyNode
? new (require('canvas').Image)()
: fabric.document.createElement('img'),
_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.onload = function() {
_this._element = replacement;
callback && callback();
replacement.onload = canvasEl = imgEl = null;
};
replacement.width = imgEl.width;
replacement.height = imgEl.height;
if (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');
_this._element = replacement;
// onload doesn't fire in node, so we invoke callback manually
callback && callback();
}
else {
replacement.src = canvasEl.toDataURL('image/png');
}
return this;
},
/**
* @private
* @method _render
* @param ctx
*/
_render: function(ctx) {
ctx.drawImage(
this._element,
-this.width / 2,
-this.height / 2,
this.width,
this.height
);
},
/**
* @private
* @method _resetWidthHeight
*/
_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
* @method _initElement
* @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
* @method _initConfig
* @param {Object} [options] Options object
*/
_initConfig: function(options) {
options || (options = { });
this.setOptions(options);
this._setWidthHeight(options);
},
/**
* @private
* @method _initFilters
* @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
* @method _setWidthHeight
* @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
* @method complexity
* @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
* @method getSvgSrc
*/
fabric.Image.prototype.getSvgSrc = fabric.Image.prototype.getSrc;
/**
* Creates an instance of fabric.Image from its object representation
* @static
* @method fromObject
* @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
* @method fromURL
* @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
* @method fabric.Image.fromElement
* @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);