1 //= require "object.class"
  2 
  3 (function(global) {
  4   
  5   "use strict";
  6   
  7   var extend = fabric.util.object.extend;
  8   
  9   if (!global.fabric) {
 10     global.fabric = { };
 11   }
 12   
 13   if (global.fabric.Image) {
 14     fabric.warn('fabric.Image is already defined.');
 15     return;
 16   };
 17   
 18   if (!fabric.Object) {
 19     fabric.warn('fabric.Object is required for fabric.Image initialization');
 20     return;
 21   }
 22   
 23   /** 
 24    * @class Image
 25    * @extends fabric.Object
 26    */
 27   fabric.Image = fabric.util.createClass(fabric.Object, /** @scope fabric.Image.prototype */ {
 28     
 29     /**
 30      * @property
 31      * @type Number
 32      */
 33     maxwidth: null,
 34     
 35     /**
 36      * @property
 37      * @type Number
 38      */
 39     maxheight: null,
 40     
 41     /**
 42      * @property
 43      * @type Boolean
 44      */
 45     active: false,
 46     
 47     /**
 48      * @property
 49      * @type Boolean
 50      */
 51     bordervisibility: false,
 52     
 53     /**
 54      * @property
 55      * @type Boolean
 56      */
 57     cornervisibility: false,
 58     
 59     /**
 60      * @property
 61      * @type String
 62      */
 63     type: 'image',
 64     
 65     __isGrayscaled: false,
 66     
 67     /**
 68      * Constructor
 69      * @param {HTMLImageElement | String} element Image element
 70      * @param {Object} options optional
 71      */
 72     initialize: function(element, options) {
 73       this.callSuper('initialize', options);
 74       this._initElement(element);
 75       this._initConfig(options || { });
 76     },
 77     
 78     /**
 79      * Returns image element which this instance if based on
 80      * @method getElement
 81      * @return {HTMLImageElement} image element
 82      */
 83     getElement: function() {
 84       return this._element;
 85     },
 86     
 87     /**
 88      * Sets image element for this instance to a specified one
 89      * @method setElement
 90      * @param {HTMLImageElement} element
 91      * @return {fabric.Image} thisArg
 92      * @chainable
 93      */
 94     setElement: function(element) {
 95       this._element = element;
 96       return this;
 97     },
 98     
 99     /**
100      * Resizes an image depending on whether maxwidth and maxheight are set up;
101      * Width and height have to mantain the same proportion in the final image as it was in the initial one.
102      * @method getNormalizedSize
103      * @param {Object} oImg
104      * @param {Number} maxwidth maximum width of the image (in px)
105      * @param {Number} maxheight maximum height of the image (in px)
106      */ 
107     getNormalizedSize: function(oImg, maxwidth, maxheight) {
108       if (maxheight && maxwidth && (oImg.width > oImg.height && (oImg.width / oImg.height) < (maxwidth / maxheight))) {
109         // height is the constraining dimension.
110         normalizedWidth = ~~((oImg.width * maxheight) / oImg.height);
111         normalizedHeight = maxheight;
112       }
113       else if (maxheight && ((oImg.height == oImg.width) || (oImg.height > oImg.width) || (oImg.height > maxheight))) {
114         // height is the constraining dimension.
115         normalizedWidth = ~~((oImg.width * maxheight) / oImg.height);
116         normalizedHeight = maxheight;
117       }
118       else if (maxwidth && (maxwidth < oImg.width)) {
119         // width is the constraining dimension.
120         normalizedHeight = ~~((oImg.height * maxwidth) / oImg.width);
121         normalizedWidth = maxwidth;
122       }
123       else {
124         normalizedWidth = oImg.width;
125         normalizedHeight = oImg.height;
126       }
127       
128       return { 
129         width: normalizedWidth, 
130         height: normalizedHeight 
131       };
132     },
133     
134     /**
135      * Returns original size of an image
136      * @method getOriginalSize
137      * @return {Object} object with "width" and "height" properties
138      */
139     getOriginalSize: function() {
140       var element = this.getElement();
141       return { 
142         width: element.width, 
143         height: element.height
144       };
145     },
146     
147     /**
148      * Sets border visibility
149      * @method setBorderVisibility
150      * @param {Boolean} visible When true, border is set to be visible
151      */
152     setBorderVisibility: function(visible) {
153       this._resetWidthHeight();
154       this._adjustWidthHeightToBorders(showBorder);
155       this.setCoords();
156     },
157     
158     /**
159      * Sets corner visibility
160      * @method setCornersVisibility
161      * @param {Boolean} visible When true, corners are set to be visible
162      */
163     setCornersVisibility: function(visible) {
164       this.cornervisibility = !!visible;
165     },
166     
167     /**
168      * Renders image on a specified context
169      * @method render
170      * @param {CanvasRenderingContext2D} ctx Context to render on
171      */
172     render: function(ctx, noTransform) {
173       ctx.save();
174       if (!noTransform) {
175         this.transform(ctx);
176       }
177       this._render(ctx);
178       if (this.active && !noTransform) {
179         this.drawBorders(ctx);
180         this.hideCorners || this.drawCorners(ctx);
181       }
182       ctx.restore();
183     },
184     
185     /**
186      * Returns object representation of an instance
187      * @method toObject
188      * @return {Object} Object representation of an instance
189      */
190     toObject: function() {
191       return extend(this.callSuper('toObject'), {
192         src: this.getSrc()
193       });
194     },
195     
196     /**
197      * Returns source of an image
198      * @method getSrc
199      * @return {String} Source of an image
200      */
201     getSrc: function() {
202       return this.getElement().src;
203     },
204     
205     /**
206      * Returns string representation of an instance
207      * @method toString
208      * @return {String} String representation of an instance
209      */
210     toString: function() {        
211       return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
212     },
213     
214     /**
215      * Returns a clone of an instance
216      * @mthod clone
217      * @param {Function} callback Callback is invoked with a clone as a first argument
218      */
219     clone: function(callback) {
220       this.constructor.fromObject(this.toObject(), callback);
221     },
222     
223     /**
224      * Makes image grayscale
225      * @mthod toGrayscale
226      * @param {Function} callback
227      */
228     toGrayscale: function(callback) {
229       
230       if (this.__isGrayscaled) {
231         return;
232       }
233       
234       var imgEl = this.getElement(),
235           canvasEl = document.createElement('canvas'),
236           replacement = document.createElement('img'),
237           _this = this;
238 
239       canvasEl.width = imgEl.width;
240       canvasEl.height = imgEl.height;
241 
242       canvasEl.getContext('2d').drawImage(imgEl, 0, 0);
243       fabric.Canvas.toGrayscale(canvasEl);
244       
245       /** @ignore */
246       replacement.onload = function() {
247         _this.setElement(replacement);
248         callback && callback();
249         replacement.onload = canvasEl = imgEl = imageData = null;
250       };
251       replacement.width = imgEl.width;
252       replacement.height = imgEl.height;
253       
254       replacement.src = canvasEl.toDataURL('image/png');
255       
256       this.__isGrayscaled = true;
257       
258       return this;
259     },
260     
261     /**
262      * @private
263      */
264     _render: function(ctx) {
265       var originalImgSize = this.getOriginalSize();
266       ctx.drawImage(
267         this.getElement(),
268         - originalImgSize.width / 2,
269         - originalImgSize.height / 2,
270         originalImgSize.width,
271         originalImgSize.height
272       );
273     },
274     
275     /**
276      * @private
277      */
278     _adjustWidthHeightToBorders: function(showBorder) {
279       if (showBorder) {
280         this.currentBorder = this.borderwidth;
281         this.width += (2 * this.currentBorder);
282         this.height += (2 * this.currentBorder);
283       }
284       else {
285         this.currentBorder = 0;
286       }
287     },
288     
289     /**
290      * @private
291      */
292     _resetWidthHeight: function() {
293       var element = this.getElement();
294       
295       this.set('width', element.width);
296       this.set('height', element.height);
297     },
298     
299     /**
300      * The Image class's initialization method. This method is automatically 
301      * called by the constructor.
302      * @method _initElement
303      * @param {HTMLImageElement|String} el The element representing the image
304      */
305     _initElement: function(element) {
306       this.setElement(fabric.util.getById(element));
307       fabric.util.addClass(this.getElement(), fabric.Image.CSS_CANVAS);
308     },
309     
310     /**
311      * @method _initConfig
312      * @param {Object} options Options object
313      */
314     _initConfig: function(options) {
315       this.setOptions(options);
316       this._setBorder();
317       this._setWidthHeight(options);
318     },
319     
320     /**
321      * @private
322      */
323     _setBorder: function() {
324       if (this.bordervisibility) {
325         this.currentBorder = this.borderwidth;
326       }
327       else {
328         this.currentBorder = 0;
329       }
330     },
331     
332     /**
333      * @private
334      */
335     _setWidthHeight: function(options) {
336       var sidesBorderWidth = 2 * this.currentBorder;
337       this.width = (this.getElement().width || 0) + sidesBorderWidth;
338       this.height = (this.getElement().height || 0) + sidesBorderWidth;
339     },
340     
341     /**
342      * Returns complexity of an instance
343      * @method complexity
344      * @return {Number} complexity
345      */
346     complexity: function() {
347       return 1;
348     }
349   });
350   
351   /**
352    * Default CSS class name for canvas
353    * @static
354    * @type String
355    */
356   fabric.Image.CSS_CANVAS = "canvas-img";
357   
358   /**
359    * Creates an instance of fabric.Image from its object representation
360    * @static
361    * @method fromObject
362    * @param object {Object}
363    * @param callback {Function} optional
364    */
365   fabric.Image.fromObject = function(object, callback) {
366     var img = document.createElement('img'),
367         src = object.src;
368         
369     if (object.width) {
370       img.width = object.width;
371     }
372     if (object.height) {
373       img.height = object.height;
374     }
375     
376     /** @ignore */
377     img.onload = function() {
378       if (callback) {
379         callback(new fabric.Image(img, object));
380       }
381       img = img.onload = null;
382     };
383     img.src = src;
384   };
385   
386   /**
387    * Creates an instance of fabric.Image from an URL string
388    * @static
389    * @method fromURL
390    * @param {String} url URL to create an image from
391    * @param {Function} [callback] Callback to invoke when image is created (newly created image is passed as a first argument)
392    * @param {Object} [imgOptions] Options object
393    */
394   fabric.Image.fromURL = function(url, callback, imgOptions) {
395     var img = document.createElement('img');
396     
397     /** @ignore */
398     img.onload = function() {
399       if (callback) {
400         callback(new fabric.Image(img, imgOptions));
401       }
402       img = img.onload = null;
403     };
404     img.src = url;
405   };
406   
407   /**
408    * List of attribute names to account for when parsing SVG element (used by {@link fabric.Image.fromElement})
409    * @static
410    * @see http://www.w3.org/TR/SVG/struct.html#ImageElement
411    */
412   fabric.Image.ATTRIBUTE_NAMES = 'x y width height fill fill-opacity opacity stroke stroke-width transform xlink:href'.split(' ');
413   
414   /**
415    * Returns {@link fabric.Image} instance from an SVG element
416    * @static
417    * @method fabric.Image.fromElement
418    * @param {SVGElement} element Element to parse
419    * @param {Function} callback Callback to execute when fabric.Image object is created
420    * @param {Object} [options] Options object
421    * @return {fabric.Image}
422    */
423   fabric.Image.fromElement = function(element, callback, options) {
424     options || (options = { });
425     
426     var parsedAttributes = fabric.parseAttributes(element, fabric.Image.ATTRIBUTE_NAMES);
427     
428     fabric.Image.fromURL(parsedAttributes['xlink:href'], callback, extend(parsedAttributes, options));
429   };
430   
431   fabric.Image.fromElement.async = true;
432   
433 })(this);