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", "use-credentials")
       * @see https://developer.mozilla.org/en-US/docs/HTML/CORS_settings_attributes
       * @type String
       * @default
       */
      crossOrigin: '',
  
      /**
       * Width of a stroke.
       * For image quality a stroke multiple of 2 gives better results.
       * @type Number
       * @default
       */
      strokeWidth: 0,
  
      /**
       * When calling {@link fabric.Image.getSrc}, return value from element src with `element.getAttribute('src')`.
       * This allows for relative urls as image src.
       * @since 2.7.0
       * @type Boolean
       * @default
       */
      srcFromAttribute: false,
  
      /**
       * 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,
  
      /**
       * private
       * contains last value of scaling applied by the apply filter chain
       * @type Number
       */
      _filterScalingX: 1,
  
      /**
       * private
       * contains last value of scaling applied by the apply filter chain
       * @type Number
       */
      _filterScalingY: 1,
  
      /**
       * minimum scale factor under which any resizeFilter is triggered to resize the image
       * 0 will disable the automatic resize. 1 will trigger automatically always.
       * number bigger than 1 are not implemented yet.
       * @type Number
       */
      minimumScaleTrigger: 0.5,
  
      /**
       * List of properties to consider when checking if
       * state of an object is changed ({@link fabric.Object#hasStateChanged})
       * as well as for history (undo/redo) purposes
       * @type Array
       */
      stateProperties: fabric.Object.prototype.stateProperties.concat('cropX', 'cropY'),
  
      /**
       * key used to retrieve the texture representing this image
       * @since 2.0.0
       * @type String
       * @default
       */
      cacheKey: '',
  
      /**
       * Image crop in pixels from original image size.
       * @since 2.0.0
       * @type Number
       * @default
       */
      cropX: 0,
  
      /**
       * Image crop in pixels from original image size.
       * @since 2.0.0
       * @type Number
       * @default
       */
      cropY: 0,
  
      /**
       * Constructor
       * @param {HTMLImageElement | String} element Image element
       * @param {Object} [options] Options object
       * @param {function} [callback] callback function to call after eventual filters applied.
       * @return {fabric.Image} thisArg
       */
      initialize: function(element, options) {
        options || (options = { });
        this.filters = [];
        this.cacheKey = 'texture' + fabric.Object.__uid++;
        this.callSuper('initialize', options);
        this._initElement(element, options);
      },
  
      /**
       * 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 {Object} [options] Options object
       * @return {fabric.Image} thisArg
       * @chainable
       */
      setElement: function(element, options) {
        this.removeTexture(this.cacheKey);
        this.removeTexture(this.cacheKey + '_filtered');
        this._element = element;
        this._originalElement = element;
        this._initConfig(options);
        if (this.filters.length !== 0) {
          this.applyFilters();
        }
        // resizeFilters work on the already filtered copy.
        // we need to apply resizeFilters AFTER normal filters.
        // applyResizeFilters is run more often than normal fiters
        // and is triggered by user interactions rather than dev code
        if (this.resizeFilter) {
          this.applyResizeFilters();
        }
        return this;
      },
  
      /**
       * Delete a single texture if in webgl mode
       */
      removeTexture: function(key) {
        var backend = fabric.filterBackend;
        if (backend && backend.evictCachesForKey) {
          backend.evictCachesForKey(key);
        }
      },
  
      /**
       * Delete textures, reference to elements and eventually JSDOM cleanup
       */
      dispose: function() {
        this.removeTexture(this.cacheKey);
        this.removeTexture(this.cacheKey + '_filtered');
        this._cacheContext = undefined;
        ['_originalElement', '_element', '_filteredEl', '_cacheCanvas'].forEach((function(element) {
          fabric.util.cleanUpJsdomNode(this[element]);
          this[element] = undefined;
        }).bind(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.naturalWidth || element.width,
          height: element.naturalHeight || element.height
        };
      },
  
      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _stroke: function(ctx) {
        if (!this.stroke || this.strokeWidth === 0) {
          return;
        }
        var w = this.width / 2, h = this.height / 2;
        ctx.beginPath();
        ctx.moveTo(-w, -h);
        ctx.lineTo(w, -h);
        ctx.lineTo(w, h);
        ctx.lineTo(-w, h);
        ctx.lineTo(-w, -h);
        ctx.closePath();
      },
  
      /**
       * @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, this);
  
        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) {
        var filters = [];
  
        this.filters.forEach(function(filterObj) {
          if (filterObj) {
            filters.push(filterObj.toObject());
          }
        });
        var object = extend(
          this.callSuper(
            'toObject',
            ['crossOrigin', 'cropX', 'cropY'].concat(propertiesToInclude)
          ), {
            src: this.getSrc(),
            filters: filters,
          });
        if (this.resizeFilter) {
          object.resizeFilter = this.resizeFilter.toObject();
        }
        return object;
      },
  
      /**
       * Returns true if an image has crop applied, inspecting values of cropX,cropY,width,hight.
       * @return {Boolean}
       */
      hasCrop: function() {
        return this.cropX || this.cropY || this.width < this._element.width || this.height < this._element.height;
      },
  
      /* _TO_SVG_START_ */
      /**
       * Returns svg representation of an instance
       * @return {Array} an array of strings with the specific svg representation
       * of the instance
       */
      _toSVG: function() {
        var svgString = [], imageMarkup = [], strokeSvg,
            x = -this.width / 2, y = -this.height / 2, clipPath = '';
        if (this.hasCrop()) {
          var clipPathId = fabric.Object.__uid++;
          svgString.push(
            '<clipPath id="imageCrop_' + clipPathId + '">\n',
            '\t<rect x="' + x + '" y="' + y + '" width="' + this.width + '" height="' + this.height + '" />\n',
            '</clipPath>\n'
          );
          clipPath = ' clip-path="url(#imageCrop_' + clipPathId + ')" ';
        }
        imageMarkup.push('\t<image ', 'COMMON_PARTS', 'xlink:href="', this.getSvgSrc(true),
          '" x="', x - this.cropX, '" y="', y - this.cropY,
          // 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._element.width || this._element.naturalWidth,
          '" height="', this._element.height || this._element.height,
          '"', clipPath,
          '></image>\n');
  
        if (this.stroke || this.strokeDashArray) {
          var origFill = this.fill;
          this.fill = null;
          strokeSvg = [
            '\t<rect ',
            'x="', x, '" y="', y,
            '" width="', this.width, '" height="', this.height,
            '" style="', this.getSvgStyles(),
            '"/>\n'
          ];
          this.fill = origFill;
        }
        if (this.paintFirst !== 'fill') {
          svgString = svgString.concat(strokeSvg, imageMarkup);
        }
        else {
          svgString = svgString.concat(imageMarkup, strokeSvg);
        }
        return svgString;
      },
      /* _TO_SVG_END_ */
  
      /**
       * Returns source of an image
       * @param {Boolean} filtered indicates if the src is needed for svg
       * @return {String} Source of an image
       */
      getSrc: function(filtered) {
        var element = filtered ? this._element : this._originalElement;
        if (element) {
          if (element.toDataURL) {
            return element.toDataURL();
          }
  
          if (this.srcFromAttribute) {
            return element.getAttribute('src');
          }
          else {
            return element.src;
          }
        }
        else {
          return this.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) {
          this.setElement(img, options);
          this._setWidthHeight();
          callback && callback(this);
        }, this, options && options.crossOrigin);
        return this;
      },
  
      /**
       * Returns string representation of an instance
       * @return {String} String representation of an instance
       */
      toString: function() {
        return '#<fabric.Image: { src: "' + this.getSrc() + '" }>';
      },
  
      applyResizeFilters: function() {
        var filter = this.resizeFilter,
            minimumScale = this.minimumScaleTrigger,
            objectScale = this.getTotalObjectScaling(),
            scaleX = objectScale.scaleX,
            scaleY = objectScale.scaleY,
            elementToFilter = this._filteredEl || this._originalElement;
        if (this.group) {
          this.set('dirty', true);
        }
        if (!filter || (scaleX > minimumScale && scaleY > minimumScale)) {
          this._element = elementToFilter;
          this._filterScalingX = 1;
          this._filterScalingY = 1;
          this._lastScaleX = scaleX;
          this._lastScaleY = scaleY;
          return;
        }
        if (!fabric.filterBackend) {
          fabric.filterBackend = fabric.initFilterBackend();
        }
        var canvasEl = fabric.util.createCanvasElement(),
            cacheKey = this._filteredEl ? (this.cacheKey + '_filtered') : this.cacheKey,
            sourceWidth = elementToFilter.width, sourceHeight = elementToFilter.height;
        canvasEl.width = sourceWidth;
        canvasEl.height = sourceHeight;
        this._element = canvasEl;
        this._lastScaleX = filter.scaleX = scaleX;
        this._lastScaleY = filter.scaleY = scaleY;
        fabric.filterBackend.applyFilters(
          [filter], elementToFilter, sourceWidth, sourceHeight, this._element, cacheKey);
        this._filterScalingX = canvasEl.width / this._originalElement.width;
        this._filterScalingY = canvasEl.height / this._originalElement.height;
      },
  
      /**
       * Applies filters assigned to this image (from "filters" array) or from filter param
       * @method applyFilters
       * @param {Array} filters to be applied
       * @param {Boolean} forResizing specify if the filter operation is a resize operation
       * @return {thisArg} return the fabric.Image object
       * @chainable
       */
      applyFilters: function(filters) {
  
        filters = filters || this.filters || [];
        filters = filters.filter(function(filter) { return filter && !filter.isNeutralState(); });
        this.set('dirty', true);
  
        // needs to clear out or WEBGL will not resize correctly
        this.removeTexture(this.cacheKey + '_filtered');
  
        if (filters.length === 0) {
          this._element = this._originalElement;
          this._filteredEl = null;
          this._filterScalingX = 1;
          this._filterScalingY = 1;
          return this;
        }
  
        var imgElement = this._originalElement,
            sourceWidth = imgElement.naturalWidth || imgElement.width,
            sourceHeight = imgElement.naturalHeight || imgElement.height;
  
        if (this._element === this._originalElement) {
          // if the element is the same we need to create a new element
          var canvasEl = fabric.util.createCanvasElement();
          canvasEl.width = sourceWidth;
          canvasEl.height = sourceHeight;
          this._element = canvasEl;
          this._filteredEl = canvasEl;
        }
        else {
          // clear the existing element to get new filter data
          // also dereference the eventual resized _element
          this._element = this._filteredEl;
          this._filteredEl.getContext('2d').clearRect(0, 0, sourceWidth, sourceHeight);
          // we also need to resize again at next renderAll, so remove saved _lastScaleX/Y
          this._lastScaleX = 1;
          this._lastScaleY = 1;
        }
        if (!fabric.filterBackend) {
          fabric.filterBackend = fabric.initFilterBackend();
        }
        fabric.filterBackend.applyFilters(
          filters, this._originalElement, sourceWidth, sourceHeight, this._element, this.cacheKey);
        if (this._originalElement.width !== this._element.width ||
          this._originalElement.height !== this._element.height) {
          this._filterScalingX = this._element.width / this._originalElement.width;
          this._filterScalingY = this._element.height / this._originalElement.height;
        }
        return this;
      },
  
      /**
       * @private
       * @param {CanvasRenderingContext2D} ctx Context to render on
       */
      _render: function(ctx) {
        if (this.isMoving !== true && this.resizeFilter && this._needsResize()) {
          this.applyResizeFilters();
        }
        this._stroke(ctx);
        this._renderPaintInOrder(ctx);
      },
  
      /**
       * Decide if the object should cache or not. Create its own cache level
       * needsItsOwnCache should be used when the object drawing method requires
       * a cache step. None of the fabric classes requires it.
       * Generally you do not cache objects in groups because the group outside is cached.
       * This is the special image version where we would like to avoid caching where possible.
       * Essentially images do not benefit from caching. They may require caching, and in that
       * case we do it. Also caching an image usually ends in a loss of details.
       * A full performance audit should be done.
       * @return {Boolean}
       */
      shouldCache: function() {
        return this.needsItsOwnCache();
      },
  
      _renderFill: function(ctx) {
        var elementToDraw = this._element,
            w = this.width, h = this.height,
            sW = Math.min(elementToDraw.naturalWidth || elementToDraw.width, w * this._filterScalingX),
            sH = Math.min(elementToDraw.naturalHeight || elementToDraw.height, h * this._filterScalingY),
            x = -w / 2, y = -h / 2,
            sX = Math.max(0, this.cropX * this._filterScalingX),
            sY = Math.max(0, this.cropY * this._filterScalingY);
  
        elementToDraw && ctx.drawImage(elementToDraw, sX, sY, sW, sH, x, y, w, h);
      },
  
      /**
       * needed to check if image needs resize
       * @private
       */
      _needsResize: function() {
        var scale = this.getTotalObjectScaling();
        return (scale.scaleX !== this._lastScaleX || scale.scaleY !== this._lastScaleY);
      },
  
      /**
       * @private
       */
      _resetWidthHeight: function() {
        this.set(this.getOriginalSize());
      },
  
      /**
       * The Image class's initialization method. This method is automatically
       * called by the constructor.
       * @private
       * @param {HTMLImageElement|String} element The element representing the image
       * @param {Object} [options] Options object
       */
      _initElement: function(element, options) {
        this.setElement(fabric.util.getById(element), options);
        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 {Array} filters to be initialized
       * @param {Function} callback Callback to invoke when all fabric.Image.filters instances are created
       */
      _initFilters: function(filters, callback) {
        if (filters && filters.length) {
          fabric.util.enlivenObjects(filters, function(enlivenedObjects) {
            callback && callback(enlivenedObjects);
          }, 'fabric.Image.filters');
        }
        else {
          callback && callback();
        }
      },
  
      /**
       * @private
       * Set the width and the height of the image object, using the element or the
       * options.
       * @param {Object} [options] Object with width/height properties
       */
      _setWidthHeight: function(options) {
        options || (options = { });
        var el = this.getElement();
        this.width = options.width || el.naturalWidth || el.width || 0;
        this.height = options.height || el.naturalHeight || el.height || 0;
      },
  
      /**
       * Calculate offset for center and scale factor for the image in order to respect
       * the preserveAspectRatio attribute
       * @private
       * @return {Object}
       */
      parsePreserveAspectRatioAttribute: function() {
        var pAR = fabric.util.parsePreserveAspectRatioAttribute(this.preserveAspectRatio || ''),
            rWidth = this._element.width, rHeight = this._element.height,
            scaleX = 1, scaleY = 1, offsetLeft = 0, offsetTop = 0, cropX = 0, cropY = 0,
            offset, pWidth = this.width, pHeight = this.height, parsedAttributes = { width: pWidth, height: pHeight };
        if (pAR && (pAR.alignX !== 'none' || pAR.alignY !== 'none')) {
          if (pAR.meetOrSlice === 'meet') {
            scaleX = scaleY = fabric.util.findScaleToFit(this._element, parsedAttributes);
            offset = (pWidth - rWidth * scaleX) / 2;
            if (pAR.alignX === 'Min') {
              offsetLeft = -offset;
            }
            if (pAR.alignX === 'Max') {
              offsetLeft = offset;
            }
            offset = (pHeight - rHeight * scaleY) / 2;
            if (pAR.alignY === 'Min') {
              offsetTop = -offset;
            }
            if (pAR.alignY === 'Max') {
              offsetTop = offset;
            }
          }
          if (pAR.meetOrSlice === 'slice') {
            scaleX = scaleY = fabric.util.findScaleToCover(this._element, parsedAttributes);
            offset = rWidth - pWidth / scaleX;
            if (pAR.alignX === 'Mid') {
              cropX = offset / 2;
            }
            if (pAR.alignX === 'Max') {
              cropX = offset;
            }
            offset = rHeight - pHeight / scaleY;
            if (pAR.alignY === 'Mid') {
              cropY = offset / 2;
            }
            if (pAR.alignY === 'Max') {
              cropY = offset;
            }
            rWidth = pWidth / scaleX;
            rHeight = pHeight / scaleY;
          }
        }
        else {
          scaleX = pWidth / rWidth;
          scaleY = pHeight / rHeight;
        }
        return {
          width: rWidth,
          height: rHeight,
          scaleX: scaleX,
          scaleY: scaleY,
          offsetLeft: offsetLeft,
          offsetTop: offsetTop,
          cropX: cropX,
          cropY: cropY
        };
      }
    });
  
    /**
     * 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) {
      var object = fabric.util.object.clone(_object);
      fabric.util.loadImage(object.src, function(img, error) {
        if (error) {
          callback && callback(null, error);
          return;
        }
        fabric.Image.prototype._initFilters.call(object, object.filters, function(filters) {
          object.filters = filters || [];
          fabric.Image.prototype._initFilters.call(object, [object.resizeFilter], function(resizeFilters) {
            object.resizeFilter = resizeFilters[0];
            fabric.util.enlivenObjects([object.clipPath], function(enlivedProps) {
              object.clipPath = enlivedProps[0];
              var image = new fabric.Image(img, object);
              callback(image);
            });
          });
        });
      }, 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 crossOrigin'.split(' '));
  
    /**
     * Returns {@link fabric.Image} instance from an SVG element
     * @static
     * @param {SVGElement} element Element to parse
     * @param {Object} [options] Options object
     * @param {Function} callback Callback to execute when fabric.Image object is created
     * @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_ */
  
  })(typeof exports !== 'undefined' ? exports : this);
  