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);