text: (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: '',
  
      /**
       * AlignX value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
       * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
       * This parameter defines how the picture is aligned to its viewport when image element width differs from image width.
       * @type String
       * @default
       */
      alignX: 'none',
  
      /**
       * AlignY value, part of preserveAspectRatio (one of "none", "mid", "min", "max")
       * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
       * This parameter defines how the picture is aligned to its viewport when image element height differs from image height.
       * @type String
       * @default
       */
      alignY: 'none',
  
      /**
       * meetOrSlice value, part of preserveAspectRatio  (one of "meet", "slice").
       * if meet the image is always fully visibile, if slice the viewport is always filled with image.
       * @see http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute
       * @type String
       * @default
       */
      meetOrSlice: 'meet',
  
      /**
       * private
       * contains last value of scaleX to detect
       * if the Image got resized after the last Render
       * @type Number
       */
      _lastScaleX: 1,
  
      /**
       * private
       * contains last value of scaleY to detect
       * if the Image got resized after the last Render
       * @type Number
       */
      _lastScaleY: 1,
  
      /**
       * 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.resizeFilters = [ ];
        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
       * @param {Object} [options] Options object
       * @return {fabric.Image} thisArg
       * @chainable
       */
      setElement: function(element, callback, options) {
        this._element = element;
        this._originalElement = element;
        this._initConfig(options);
  
        if (this.filters.length !== 0) {
          this.applyFilters(callback);
        }
        else if (callback) {
          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
        };
      },
  
      /**
       * @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,
          alignX: this.alignX,
          alignY: this.alignY,
          meetOrSlice: this.meetOrSlice
        });
      },
  
      /* _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 = [], x = -this.width / 2, y = -this.height / 2,
            preserveAspectRatio = 'none';
        if (this.group && this.group.type === 'path-group') {
          x = this.left;
          y = this.top;
        }
        if (this.alignX !== 'none' && this.alignY !== 'none') {
          preserveAspectRatio = 'x' + this.alignX + 'Y' + this.alignY + ' ' + this.meetOrSlice;
        }
        markup.push(
          '<g transform="', this.getSvgTransform(), this.getSvgTransformMatrix(), '">\n',
            '<image xlink:href="', this.getSvgSrc(),
              '" x="', x, '" y="', y,
              '" 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
              '" width="', this.width,
              '" height="', this.height,
              '" preserveAspectRatio="', preserveAspectRatio, '"',
            '></image>\n'
        );
  
        if (this.stroke || this.strokeDashArray) {
          var origFill = this.fill;
          this.fill = null;
          markup.push(
            '<rect ',
              'x="', x, '" y="', y,
              '" width="', this.width, '" height="', this.height,
              '" style="', this.getSvgStyles(),
            '"/>\n'
          );
          this.fill = origFill;
        }
  
        markup.push('</g>\n');
  
        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;
        }
      },
  
      /**
       * Sets source of an image
       * @param {String} src Source string (URL)
       * @param {Function} [callback] Callback is invoked when image has been loaded (and all filters have been applied)
       * @param {Object} [options] Options object
       * @return {fabric.Image} thisArg
       * @chainable
       */
      setSrc: function(src, callback, options) {
        fabric.util.loadImage(src, function(img) {
          return this.setElement(img, callback, options);
        }, this, options && options.crossOrigin);
      },
  
      /**
       * 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)
       * @method 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, filters, imgElement, forResizing) {
  
        filters = filters || this.filters;
        imgElement = imgElement || this._originalElement;
  
        if (!imgElement) {
          return;
        }
  
        var imgEl = imgElement,
            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);
  
        if (filters.length === 0) {
          this._element = imgElement;
          callback && callback();
          return canvasEl;
        }
        filters.forEach(function(filter) {
          filter && filter.applyTo(canvasEl, filter.scaleX || _this.scaleX, filter.scaleY || _this.scaleY);
          if (!forResizing && filter && filter.type === 'Resize') {
            _this.width *= filter.scaleX;
            _this.height *= filter.scaleY;
          }
        });
  
        /** @ignore */
        replacement.width = canvasEl.width;
        replacement.height = canvasEl.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;
          !forResizing && (_this._filteredEl = replacement);
          callback && callback();
        }
        else {
          replacement.onload = function() {
            _this._element = replacement;
            !forResizing && (_this._filteredEl = replacement);
            callback && callback();
            replacement.onload = canvasEl = imgEl = null;
          };
          replacement.src = canvasEl.toDataURL('image/png');
        }
        return canvasEl;
      },
  
      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function(ctx, noTransform) {
        var x, y, imageMargins = this._findMargins(), elementToDraw;
  
        x = (noTransform ? this.left : -this.width / 2);
        y = (noTransform ? this.top : -this.height / 2);
  
        if (this.meetOrSlice === 'slice') {
          ctx.beginPath();
          ctx.rect(x, y, this.width, this.height);
          ctx.clip();
        }
  
        if (this.isMoving === false && this.resizeFilters.length && this._needsResize()) {
          this._lastScaleX = this.scaleX;
          this._lastScaleY = this.scaleY;
          elementToDraw = this.applyFilters(null, this.resizeFilters, this._filteredEl || this._originalElement, true);
        }
        else {
          elementToDraw = this._element;
        }
        elementToDraw && ctx.drawImage(elementToDraw,
                                       x + imageMargins.marginX,
                                       y + imageMargins.marginY,
                                       imageMargins.width,
                                       imageMargins.height
                                      );
  
        this._renderStroke(ctx);
      },
  
      /**
       * @private, needed to check if image needs resize
       */
      _needsResize: function() {
        return (this.scaleX !== this._lastScaleX || this.scaleY !== this._lastScaleY);
      },
  
      /**
       * @private
       */
      _findMargins: function() {
        var width = this.width, height = this.height, scales,
            scale, marginX = 0, marginY = 0;
  
        if (this.alignX !== 'none' || this.alignY !== 'none') {
          scales = [this.width / this._element.width, this.height / this._element.height];
          scale = this.meetOrSlice === 'meet'
                  ? Math.min.apply(null, scales) : Math.max.apply(null, scales);
          width = this._element.width * scale;
          height = this._element.height * scale;
          if (this.alignX === 'Mid') {
            marginX = (this.width - width) / 2;
          }
          if (this.alignX === 'Max') {
            marginX = this.width - width;
          }
          if (this.alignY === 'Mid') {
            marginY = (this.height - height) / 2;
          }
          if (this.alignY === 'Max') {
            marginY = this.height - height;
          }
        }
        return {
          width:  width,
          height: height,
          marginX: marginX,
          marginY: marginY
        };
      },
  
      /**
       * @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.crossOrigin) {
          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 && 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 preserveAspectRatio 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),
          align = 'xMidYMid', meetOrSlice = 'meet', alignX, alignY, aspectRatioAttrs;
  
      if (parsedAttributes.preserveAspectRatio) {
        aspectRatioAttrs = parsedAttributes.preserveAspectRatio.split(' ');
      }
  
      if (aspectRatioAttrs && aspectRatioAttrs.length) {
        meetOrSlice = aspectRatioAttrs.pop();
        if (meetOrSlice !== 'meet' && meetOrSlice !== 'slice') {
          align = meetOrSlice;
          meetOrSlice = 'meet';
        }
        else if (aspectRatioAttrs.length) {
          align = aspectRatioAttrs.pop();
        }
      }
      //divide align in alignX and alignY
      alignX = align !== 'none' ? align.slice(1, 4) : 'none';
      alignY = align !== 'none' ? align.slice(5, 8) : 'none';
      parsedAttributes.alignX = alignX;
      parsedAttributes.alignY = alignY;
      parsedAttributes.meetOrSlice = meetOrSlice;
      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);
  