mirror of
https://github.com/Hopiu/fabric.js.git
synced 2026-04-08 16:30:59 +00:00
508 lines
15 KiB
JavaScript
508 lines
15 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 fabric.Image
|
|
* @extends fabric.Object
|
|
* @tutorial {@link http://fabricjs.com/fabric-intro-part-1/#images}
|
|
* @see {@link fabric.Image#initialize} for constructor definition
|
|
*/
|
|
fabric.Image = fabric.util.createClass(fabric.Object, /** @lends fabric.Image.prototype */ {
|
|
|
|
/**
|
|
* Type of an object
|
|
* @type String
|
|
* @default
|
|
*/
|
|
type: 'image',
|
|
|
|
/**
|
|
* crossOrigin value (one of "", "anonymous", "allow-credentials")
|
|
* @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
|
|
* @type String
|
|
* @default
|
|
*/
|
|
crossOrigin: '',
|
|
|
|
/**
|
|
* 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, options);
|
|
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;
|
|
},
|
|
|
|
/**
|
|
* Sets crossOrigin value (on an instance and corresponding image element)
|
|
* @return {fabric.Image} thisArg
|
|
* @chainable
|
|
*/
|
|
setCrossOrigin: function(value) {
|
|
this.crossOrigin = value;
|
|
this._element.crossOrigin = value;
|
|
|
|
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,
|
|
isInPathGroup = this.group && this.group.type === 'path-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();
|
|
this._setStrokeStyles(ctx);
|
|
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();
|
|
this._setStrokeStyles(ctx);
|
|
|
|
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();
|
|
}),
|
|
crossOrigin: this.crossOrigin
|
|
});
|
|
},
|
|
|
|
/* _TO_SVG_START_ */
|
|
/**
|
|
* Returns SVG representation of an instance
|
|
* @param {Function} [reviver] Method for further parsing of svg representation.
|
|
* @return {String} svg representation of an instance
|
|
*/
|
|
toSVG: function(reviver) {
|
|
var markup = [];
|
|
|
|
markup.push(
|
|
'<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,
|
|
'" preserveAspectRatio="none"',
|
|
'></image>'
|
|
);
|
|
|
|
if (this.stroke || this.strokeDashArray) {
|
|
var origFill = this.fill;
|
|
this.fill = null;
|
|
markup.push(
|
|
'<rect ',
|
|
'x="', (-1 * this.width / 2), '" y="', (-1 * this.height / 2),
|
|
'" width="', this.width, '" height="', this.height,
|
|
'" style="', this.getSvgStyles(),
|
|
'"/>'
|
|
);
|
|
this.fill = origFill;
|
|
}
|
|
|
|
markup.push('</g>');
|
|
|
|
return reviver ? reviver(markup.join('')) : markup.join('');
|
|
},
|
|
/* _TO_SVG_END_ */
|
|
|
|
/**
|
|
* Returns source of an image
|
|
* @return {String} Source of an image
|
|
*/
|
|
getSrc: function() {
|
|
if (this.getElement()) {
|
|
return this.getElement().src || this.getElement()._src;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Returns string representation of an instance
|
|
* @return {String} String representation of an instance
|
|
*/
|
|
toString: function() {
|
|
return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
|
|
},
|
|
|
|
/**
|
|
* 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._originalElement) {
|
|
return;
|
|
}
|
|
|
|
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) {
|
|
this._element && 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);
|
|
if (this._element) {
|
|
this._element.crossOrigin = this.crossOrigin;
|
|
}
|
|
},
|
|
|
|
/**
|
|
* @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()
|
|
? this.getElement().width || 0
|
|
: 0);
|
|
|
|
this.height = 'height' in options
|
|
? options.height
|
|
: (this.getElement()
|
|
? this.getElement().height || 0
|
|
: 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) {
|
|
fabric.util.loadImage(object.src, function(img) {
|
|
fabric.Image.prototype._initFilters.call(object, object, function(filters) {
|
|
object.filters = filters || [ ];
|
|
var instance = new fabric.Image(img, object);
|
|
callback && callback(instance);
|
|
});
|
|
}, null, object.crossOrigin);
|
|
};
|
|
|
|
/**
|
|
* 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));
|
|
}, null, imgOptions && imgOptions.crossOrigin);
|
|
};
|
|
|
|
/* _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);
|