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