text: (function (global) {
  
    "use strict";
  
    if (fabric.StaticCanvas) {
      fabric.warn('fabric.StaticCanvas is already defined.');
      return;
    }
  
    // aliases for faster resolution
    var extend = fabric.util.object.extend,
        getElementOffset = fabric.util.getElementOffset,
        removeFromArray = fabric.util.removeFromArray,
        removeListener = fabric.util.removeListener,
  
        CANVAS_INIT_ERROR = new Error('Could not initialize `canvas` element');
  
    /**
     * @class fabric.StaticCanvas
     * @constructor
     * @param {HTMLElement | String} el &lt;canvas> element to initialize instance on
     * @param {Object} [options] Options object
     */
    fabric.StaticCanvas = function (el, options) {
      options || (options = { });
  
      this._initStatic(el, options);
      fabric.StaticCanvas.activeInstance = this;
    };
  
    extend(fabric.StaticCanvas.prototype, fabric.Observable);
  
    extend(fabric.StaticCanvas.prototype, /** @scope fabric.StaticCanvas.prototype */ {
  
      /**
       * Background color of canvas instance
       * @property
       * @type String
       */
      backgroundColor:        'rgba(0, 0, 0, 0)',
  
      /**
       * Background image of canvas instance
       * Should be set via `setBackgroundImage`
       * @property
       * @type String
       */
      backgroundImage:        '',
  
      /**
       * Opacity of the background image of the canvas instance
       * @property
       * @type Float
       */
      backgroundImageOpacity:      1.0,
  
      /**
       * Indicatus whether the background image should be stretched to fit the
       * dimensions of the canvas instance.
       * @property
       * @type Boolean
       */
      backgroundImageStretch:      true,
  
      /**
       * Indicates whether toObject/toDatalessObject should include default values
       * @property
       * @type Boolean
       */
      includeDefaultValues:   true,
  
      /**
       * Indicates whether objects' state should be saved
       * @property
       * @type Boolean
       */
      stateful:               true,
  
      /**
       * Indicates whether fabric.Canvas#add should also re-render canvas.
       * Disabling this option could give a great performance boost when adding a lot of objects to canvas at once
       * (followed by a manual rendering after addition)
       */
      renderOnAddition:       true,
  
      /**
       * Function that determines clipping of entire canvas area
       * Being passed context as first argument. See clipping canvas area in https://github.com/kangax/fabric.js/wiki/FAQ
       * @property
       * @type Function
       */
      clipTo:                 null,
  
      /**
       * Default canvas width
       * @constant
       * @type Number
       */
      CANVAS_WIDTH:           600,
  
      /**
       * Default canvas height
       * @constant
       * @type Number
       */
      CANVAS_HEIGHT:          600,
  
      /**
       * Callback; invoked right before object is about to be scaled/rotated
       * @method onBeforeScaleRotate
       * @param {fabric.Object} target Object that's about to be scaled/rotated
       */
      onBeforeScaleRotate: function (target) {
        /* NOOP */
      },
  
      /**
       * Callback; invoked on every redraw of canvas and is being passed a number indicating current fps
       * @method onFpsUpdate
       * @param {Number} fps
       */
      onFpsUpdate: null,
  
      _initStatic: function(el, options) {
        this._objects = [];
  
        this._createLowerCanvas(el);
        this._initOptions(options);
  
        if (options.overlayImage) {
          this.setOverlayImage(options.overlayImage, this.renderAll.bind(this));
        }
        if (options.backgroundImage) {
          this.setBackgroundImage(options.backgroundImage, this.renderAll.bind(this));
        }
        this.calcOffset();
      },
  
      /**
       * Calculates canvas element offset relative to the document
       * This method is also attached as "resize" event handler of window
       * @method calcOffset
       * @return {fabric.Canvas} instance
       * @chainable
       */
      calcOffset: function () {
        this._offset = getElementOffset(this.lowerCanvasEl);
        return this;
      },
  
      /**
       * Sets overlay image for this canvas
       * @method setOverlayImage
       * @param {String} url url of an image to set overlay to
       * @param {Function} callback callback to invoke when image is loaded and set as an overlay
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setOverlayImage: function (url, callback) { // TODO (kangax): test callback
        return fabric.util.loadImage(url, function(img) {
          this.overlayImage = img;
          callback && callback();
        }, this);
      },
  
      /**
       * Sets background image for this canvas
       * @method setBackgroundImage
       * @param {String} url url of an image to set background to
       * @param {Function} callback callback to invoke when image is loaded and set as background
       * @param {Object} options optional options to set for the background image
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setBackgroundImage: function (url, callback, options) {
        return fabric.util.loadImage(url, function(img) {
          this.backgroundImage = img;
          if (options && options.backgroundOpacity) {
              this.backgroundOpacity = options.backgroundOpacity;
          }
          if (options && options.backgroundStretch) {
              this.backgroundStretch = options.backgroundStretch;
          }
          callback && callback();
        }, this);
      },
  
      /**
       * @private
       * @method _createCanvasElement
       * @param {Element} element
       */
      _createCanvasElement: function() {
        var element = fabric.document.createElement('canvas');
        if (!element.style) {
          element.style = { };
        }
        if (!element) {
          throw CANVAS_INIT_ERROR;
        }
        this._initCanvasElement(element);
        return element;
      },
  
      _initCanvasElement: function(element) {
        if (typeof element.getContext === 'undefined' &&
            typeof G_vmlCanvasManager !== 'undefined' &&
            G_vmlCanvasManager.initElement) {
  
          G_vmlCanvasManager.initElement(element);
        }
        if (typeof element.getContext === 'undefined') {
          throw CANVAS_INIT_ERROR;
        }
      },
  
      /**
       * @method _initOptions
       * @param {Object} options
       */
      _initOptions: function (options) {
        for (var prop in options) {
          this[prop] = options[prop];
        }
  
        this.width = parseInt(this.lowerCanvasEl.width, 10) || 0;
        this.height = parseInt(this.lowerCanvasEl.height, 10) || 0;
  
        this.lowerCanvasEl.style.width = this.width + 'px';
        this.lowerCanvasEl.style.height = this.height + 'px';
      },
  
      /**
       * Creates a secondary canvas
       * @method _createLowerCanvas
       */
      _createLowerCanvas: function (canvasEl) {
        this.lowerCanvasEl = fabric.util.getById(canvasEl) || this._createCanvasElement();
        this._initCanvasElement(this.lowerCanvasEl);
  
        fabric.util.addClass(this.lowerCanvasEl, 'lower-canvas');
  
        if (this.interactive) {
          this._applyCanvasStyle(this.lowerCanvasEl);
        }
  
        this.contextContainer = this.lowerCanvasEl.getContext('2d');
      },
  
      /**
       * Returns canvas width
       * @method getWidth
       * @return {Number}
       */
      getWidth: function () {
        return this.width;
      },
  
      /**
       * Returns canvas height
       * @method getHeight
       * @return {Number}
       */
      getHeight: function () {
        return this.height;
      },
  
      /**
       * Sets width of this canvas instance
       * @method setWidth
       * @param {Number} width value to set width to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setWidth: function (value) {
        return this._setDimension('width', value);
      },
  
      /**
       * Sets height of this canvas instance
       * @method setHeight
       * @param {Number} height value to set height to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      setHeight: function (value) {
        return this._setDimension('height', value);
      },
  
      /**
       * Sets dimensions (width, height) of this canvas instance
       * @method setDimensions
       * @param {Object} dimensions
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      setDimensions: function(dimensions) {
        for (var prop in dimensions) {
          this._setDimension(prop, dimensions[prop]);
        }
        return this;
      },
  
      /**
       * Helper for setting width/height
       * @private
       * @method _setDimensions
       * @param {String} prop property (width|height)
       * @param {Number} value value to set property to
       * @return {fabric.Canvas} instance
       * @chainable true
       */
      _setDimension: function (prop, value) {
        this.lowerCanvasEl[prop] = value;
        this.lowerCanvasEl.style[prop] = value + 'px';
  
        if (this.upperCanvasEl) {
          this.upperCanvasEl[prop] = value;
          this.upperCanvasEl.style[prop] = value + 'px';
        }
  
        if (this.wrapperEl) {
          this.wrapperEl.style[prop] = value + 'px';
        }
  
        this[prop] = value;
  
        this.calcOffset();
        this.renderAll();
  
        return this;
      },
  
      /**
       * Returns &lt;canvas> element corresponding to this instance
       * @method getElement
       * @return {HTMLCanvasElement}
       */
      getElement: function () {
        return this.lowerCanvasEl;
      },
  
      // placeholder
      getActiveObject: function() {
        return null;
      },
  
      // placeholder
      getActiveGroup: function() {
        return null;
      },
  
      /**
       * Given a context, renders an object on that context
       * @param ctx {Object} context to render object on
       * @param object {Object} object to render
       * @private
       */
      _draw: function (ctx, object) {
        object && object.render(ctx);
      },
  
      /**
       * Adds objects to canvas, then renders canvas;
       * Objects should be instances of (or inherit from) fabric.Object
       * @method add
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      add: function () {
        this._objects.push.apply(this._objects, arguments);
        for (var i = arguments.length; i--; ) {
          this.stateful && arguments[i].setupState();
          arguments[i].setCoords();
        }
        this.renderOnAddition && this.renderAll();
        return this;
      },
  
      /**
       * Inserts an object to canvas at specified index and renders canvas.
       * An object should be an instance of (or inherit from) fabric.Object
       * @method insertAt
       * @param object {Object} Object to insert
       * @param index {Number} index to insert object at
       * @param nonSplicing {Boolean} when `true`, no splicing (shifting) of objects occurs
       * @return {fabric.Canvas} instance
       */
      insertAt: function (object, index, nonSplicing) {
        if (nonSplicing) {
          this._objects[index] = object;
        }
        else {
          this._objects.splice(index, 0, object);
        }
        this.stateful && object.setupState();
        object.setCoords();
        this.renderAll();
        return this;
      },
  
      /**
       * Returns an array of objects this instance has
       * @method getObjects
       * @return {Array}
       */
      getObjects: function () {
        return this._objects;
      },
  
      /**
       * Clears specified context of canvas element
       * @method clearContext
       * @param context {Object} ctx context to clear
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      clearContext: function(ctx) {
        ctx.clearRect(0, 0, this.width, this.height);
        return this;
      },
  
      /**
       * Clears all contexts (background, main, top) of an instance
       * @method clear
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      clear: function () {
        this._objects.length = 0;
        this.clearContext(this.contextContainer);
        if (this.contextTop) {
          this.clearContext(this.contextTop);
        }
        this.renderAll();
        return this;
      },
  
      /**
       * Renders both the top canvas and the secondary container canvas.
       * @method renderAll
       * @param allOnTop {Boolean} optional Whether we want to force all images to be rendered on the top canvas
       * @return {fabric.Canvas} instance
       * @chainable
       */
      renderAll: function (allOnTop) {
  
        var canvasToDrawOn = this[(allOnTop === true && this.interactive) ? 'contextTop' : 'contextContainer'];
  
        if (this.contextTop) {
          this.clearContext(this.contextTop);
        }
  
        if (allOnTop === false || (typeof allOnTop === 'undefined')) {
          this.clearContext(canvasToDrawOn);
        }
  
        var length = this._objects.length,
            activeGroup = this.getActiveGroup(),
            startTime = new Date();
  
        if (this.clipTo) {
          canvasToDrawOn.save();
          canvasToDrawOn.beginPath();
          this.clipTo(canvasToDrawOn);
          canvasToDrawOn.clip();
        }
  
        canvasToDrawOn.fillStyle = this.backgroundColor;
        canvasToDrawOn.fillRect(0, 0, this.width, this.height);
  
        if (typeof this.backgroundImage == 'object') {
          canvasToDrawOn.save();
          canvasToDrawOn.globalAlpha = this.backgroundImageOpacity;
  
          if (this.backgroundImageStretch) {
              canvasToDrawOn.drawImage(this.backgroundImage, 0, 0, this.width, this.height);
          }
          else {
              canvasToDrawOn.drawImage(this.backgroundImage, 0, 0);
          }
          canvasToDrawOn.restore();
        }
  
        if (length) {
          for (var i = 0; i < length; ++i) {
            if (!activeGroup ||
                (activeGroup && this._objects[i] && !activeGroup.contains(this._objects[i]))) {
              this._draw(canvasToDrawOn, this._objects[i]);
            }
          }
        }
  
        if (this.clipTo) {
          canvasToDrawOn.restore();
        }
  
        // delegate rendering to group selection (if one exists)
        if (activeGroup) {
          this._draw(this.contextTop, activeGroup);
        }
  
        if (this.overlayImage) {
          this.contextTop.drawImage(this.overlayImage, 0, 0);
        }
  
        if (this.onFpsUpdate) {
          var elapsedTime = new Date() - startTime;
          this.onFpsUpdate(~~(1000 / elapsedTime));
        }
  
        this.fire('after:render');
  
        return this;
      },
  
      /**
       * Method to render only the top canvas.
       * Also used to render the group selection box.
       * @method renderTop
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      renderTop: function () {
        this.clearContext(this.contextTop || this.contextContainer);
  
        if (this.overlayImage) {
          (this.contextTop || this.contextContainer).drawImage(this.overlayImage, 0, 0);
        }
  
        // we render the top context - last object
        if (this.selection && this._groupSelector) {
          this._drawSelection();
        }
  
        // delegate rendering to group selection if one exists
        // used for drawing selection borders/corners
        var activeGroup = this.getActiveGroup();
        if (activeGroup) {
          activeGroup.render(this.contextTop);
        }
  
        this.fire('after:render');
  
        return this;
      },
  
      /**
       * Exports canvas element to a dataurl image.
       * @method toDataURL
       * @param {String} format the format of the output image. Either "jpeg" or "png".
       * @return {String}
       */
      toDataURL: function (format) {
        this.renderAll(true);
        var data = (this.upperCanvasEl || this.lowerCanvasEl).toDataURL('image/' + format);
        this.renderAll();
        return data;
      },
  
      /**
       * Exports canvas element to a dataurl image (allowing to change image size via multiplier).
       * @method toDataURLWithMultiplier
       * @param {String} format (png|jpeg)
       * @param {Number} multiplier
       * @return {String}
       */
      toDataURLWithMultiplier: function (format, multiplier) {
  
        var origWidth = this.getWidth(),
            origHeight = this.getHeight(),
            scaledWidth = origWidth * multiplier,
            scaledHeight = origHeight * multiplier,
            activeObject = this.getActiveObject();
  
        this.setWidth(scaledWidth).setHeight(scaledHeight);
        this.contextTop.scale(multiplier, multiplier);
  
        if (activeObject) {
          this.deactivateAll();
        }
  
        // restoring width, height for `renderAll` to draw
        // background properly (while context is scaled)
        this.width = origWidth;
        this.height = origHeight;
  
        this.renderAll(true);
  
        var dataURL = this.toDataURL(format);
  
        this.contextTop.scale(1 / multiplier,  1 / multiplier);
        this.setWidth(origWidth).setHeight(origHeight);
  
        if (activeObject) {
          this.setActiveObject(activeObject);
        }
        this.renderAll();
  
        return dataURL;
      },
  
      /**
       * Returns coordinates of a center of canvas.
       * Returned value is an object with top and left properties
       * @method getCenter
       * @return {Object} object with "top" and "left" number values
       */
      getCenter: function () {
        return {
          top: this.getHeight() / 2,
          left: this.getWidth() / 2
        };
      },
  
      /**
       * Centers object horizontally.
       * @method centerObjectH
       * @param {fabric.Object} object Object to center
       * @return {fabric.Canvas} thisArg
       */
      centerObjectH: function (object) {
        object.set('left', this.getCenter().left);
        this.renderAll();
        return this;
      },
  
      /**
       * Centers object vertically.
       * @method centerObjectH
       * @param {fabric.Object} object Object to center
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      centerObjectV: function (object) {
        object.set('top', this.getCenter().top);
        this.renderAll();
        return this;
      },
  
      /**
       * Centers object vertically and horizontally.
       * @method centerObject
       * @param {fabric.Object} object Object to center
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      centerObject: function (object) {
        return this.centerObjectH(object).centerObjectV(object);
      },
  
      /**
       * Straightens object, then rerenders canvas
       * @method straightenObject
       * @param {fabric.Object} object Object to straighten
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      straightenObject: function (object) {
        object.straighten();
        this.renderAll();
        return this;
      },
  
      /**
       * Returs dataless JSON representation of canvas
       * @method toDatalessJSON
       * @return {String} json string
       */
      toDatalessJSON: function () {
        return this.toDatalessObject();
      },
  
      /**
       * Returns object representation of canvas
       * @method toObject
       * @return {Object}
       */
      toObject: function () {
        return this._toObjectMethod('toObject');
      },
  
      /**
       * Returns dataless object representation of canvas
       * @method toDatalessObject
       * @return {Object}
       */
      toDatalessObject: function () {
        return this._toObjectMethod('toDatalessObject');
      },
  
      /**
       * @private
       * @method _toObjectMethod
       */
      _toObjectMethod: function (methodName) {
        return {
          objects: this._objects.map(function (instance){
            // TODO (kangax): figure out how to clean this up
            if (!this.includeDefaultValues) {
              var originalValue = instance.includeDefaultValues;
              instance.includeDefaultValues = false;
            }
            var object = instance[methodName]();
            if (!this.includeDefaultValues) {
              instance.includeDefaultValues = originalValue;
            }
            return object;
          }, this),
          background: this.backgroundColor
        }
      },
  
      /**
       * Returns SVG representation of canvas
       * @function
       * @method toSVG
       * @return {String}
       */
      toSVG: function() {
        var markup = [
          '<?xml version="1.0" standalone="no" ?>',
            '<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" ',
              '"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">',
            '<svg ',
              'xmlns="http://www.w3.org/2000/svg" ',
              'xmlns:xlink="http://www.w3.org/1999/xlink" ',
              'version="1.1" ',
              'width="', this.width, '" ',
              'height="', this.height, '" ',
              'xml:space="preserve">',
            '<desc>Created with Fabric.js ', fabric.version, '</desc>',
            fabric.createSVGFontFacesMarkup(this.getObjects())
        ];
  
        for (var i = 0, objects = this.getObjects(), len = objects.length; i < len; i++) {
          markup.push(objects[i].toSVG());
        }
        markup.push('</svg>');
  
        return markup.join('');
      },
  
      /**
       * Returns true if canvas contains no objects
       * @method isEmpty
       * @return {Boolean} true if canvas is empty
       */
      isEmpty: function () {
        return this._objects.length === 0;
      },
  
      /**
       * Removes an object from canvas and returns it
       * @method remove
       * @param object {Object} Object to remove
       * @return {Object} removed object
       */
      remove: function (object) {
        removeFromArray(this._objects, object);
        if (this.getActiveObject() === object) {
          this.discardActiveObject();
        }
        this.renderAll();
        return object;
      },
  
      /**
       * Moves an object to the bottom of the stack of drawn objects
       * @method sendToBack
       * @param object {fabric.Object} Object to send to back
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      sendToBack: function (object) {
        removeFromArray(this._objects, object);
        this._objects.unshift(object);
        return this.renderAll();
      },
  
      /**
       * Moves an object to the top of the stack of drawn objects
       * @method bringToFront
       * @param object {fabric.Object} Object to send
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      bringToFront: function (object) {
        removeFromArray(this._objects, object);
        this._objects.push(object);
        return this.renderAll();
      },
  
      /**
       * Moves an object one level down in stack of drawn objects
       * @method sendBackwards
       * @param object {fabric.Object} Object to send
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      sendBackwards: function (object) {
        var idx = this._objects.indexOf(object),
            nextIntersectingIdx = idx;
  
        // if object is not on the bottom of stack
        if (idx !== 0) {
  
          // traverse down the stack looking for the nearest intersecting object
          for (var i=idx-1; i>=0; --i) {
            if (object.intersectsWithObject(this._objects[i]) || object.isContainedWithinObject(this._objects[i])) {
              nextIntersectingIdx = i;
              break;
            }
          }
          removeFromArray(this._objects, object);
          this._objects.splice(nextIntersectingIdx, 0, object);
        }
        return this.renderAll();
      },
  
      /**
       * Moves an object one level up in stack of drawn objects
       * @method bringForward
       * @param object {fabric.Object} Object to send
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      bringForward: function (object) {
        var objects = this.getObjects(),
            idx = objects.indexOf(object),
            nextIntersectingIdx = idx;
  
  
        // if object is not on top of stack (last item in an array)
        if (idx !== objects.length-1) {
  
          // traverse up the stack looking for the nearest intersecting object
          for (var i = idx + 1, l = this._objects.length; i < l; ++i) {
            if (object.intersectsWithObject(objects[i]) || object.isContainedWithinObject(this._objects[i])) {
              nextIntersectingIdx = i;
              break;
            }
          }
          removeFromArray(objects, object);
          objects.splice(nextIntersectingIdx, 0, object);
        }
        this.renderAll();
      },
  
      /**
       * Returns object at specified index
       * @method item
       * @param {Number} index
       * @return {fabric.Object}
       */
      item: function (index) {
        return this.getObjects()[index];
      },
  
      /**
       * Returns number representation of an instance complexity
       * @method complexity
       * @return {Number} complexity
       */
      complexity: function () {
        return this.getObjects().reduce(function (memo, current) {
          memo += current.complexity ? current.complexity() : 0;
          return memo;
        }, 0);
      },
  
      /**
       * Iterates over all objects, invoking callback for each one of them
       * @method forEachObject
       * @return {fabric.Canvas} thisArg
       */
      forEachObject: function(callback, context) {
        var objects = this.getObjects(),
            i = objects.length;
        while (i--) {
          callback.call(context, objects[i], i, objects);
        }
        return this;
      },
  
      /**
       * Clears a canvas element and removes all event handlers.
       * @method dispose
       * @return {fabric.Canvas} thisArg
       * @chainable
       */
      dispose: function () {
        this.clear();
        if (this.interactive) {
          removeListener(this.upperCanvasEl, 'mousedown', this._onMouseDown);
          removeListener(this.upperCanvasEl, 'mousemove', this._onMouseMove);
          removeListener(fabric.window, 'resize', this._onResize);
        }
        return this;
      },
  
      /**
       * @private
       * @method _resizeImageToFit
       * @param {HTMLImageElement} imgEl
       */
      _resizeImageToFit: function (imgEl) {
  
        var imageWidth = imgEl.width || imgEl.offsetWidth,
            widthScaleFactor = this.getWidth() / imageWidth;
  
        // scale image down so that it has original dimensions when printed in large resolution
        if (imageWidth) {
          imgEl.width = imageWidth * widthScaleFactor;
        }
      }
    });
  
    /**
     * Returns a string representation of an instance
     * @method toString
     * @return {String} string representation of an instance
     */
    fabric.StaticCanvas.prototype.toString = function () { // Assign explicitly since `extend` doesn't take care of DontEnum bug yet
      return '#<fabric.Canvas (' + this.complexity() + '): '+
             '{ objects: ' + this.getObjects().length + ' }>';
    };
  
    extend(fabric.StaticCanvas, /** @scope fabric.StaticCanvas */ {
  
      /**
       * @static
       * @property EMPTY_JSON
       * @type String
       */
      EMPTY_JSON: '{"objects": [], "background": "white"}',
  
      /**
       * Takes &lt;canvas> element and transforms its data in such way that it becomes grayscale
       * @static
       * @method toGrayscale
       * @param {HTMLCanvasElement} canvasEl
       */
      toGrayscale: function (canvasEl) {
         var context = canvasEl.getContext('2d'),
             imageData = context.getImageData(0, 0, canvasEl.width, canvasEl.height),
             data = imageData.data,
             iLen = imageData.width,
             jLen = imageData.height,
             index, average, i, j;
  
         for (i = 0; i < iLen; i++) {
           for (j = 0; j < jLen; j++) {
  
             index = (i * 4) * jLen + (j * 4);
             average = (data[index] + data[index + 1] + data[index + 2]) / 3;
  
             data[index]     = average;
             data[index + 1] = average;
             data[index + 2] = average;
           }
         }
  
         context.putImageData(imageData, 0, 0);
       },
  
      /**
       * Provides a way to check support of some of the canvas methods
       * (either those of HTMLCanvasElement itself, or rendering context)
       *
       * @method supports
       * @param methodName {String} Method to check support for;
       *                            Could be one of "getImageData" or "toDataURL"
       * @return {Boolean | null} `true` if method is supported (or at least exists),
       *                          `null` if canvas element or context can not be initialized
       */
      supports: function (methodName) {
        var el = fabric.document.createElement('canvas');
  
        if (typeof G_vmlCanvasManager !== 'undefined') {
          G_vmlCanvasManager.initElement(el);
        }
        if (!el || !el.getContext) {
          return null;
        }
  
        var ctx = el.getContext('2d');
        if (!ctx) {
          return null;
        }
  
        switch (methodName) {
  
          case 'getImageData':
            return typeof ctx.getImageData !== 'undefined';
  
          case 'toDataURL':
            return typeof el.toDataURL !== 'undefined';
  
          default:
            return null;
        }
      }
    });
  
    /**
     * Returs JSON representation of canvas
     * @function
     * @method toJSON
     * @return {String} json string
     */
    fabric.StaticCanvas.prototype.toJSON = fabric.StaticCanvas.prototype.toObject;
  
  })(typeof exports != 'undefined' ? exports : this);
  