text: /*
  * jQuery.eraser v0.5.2
  * makes any image or canvas erasable by the user, using touch or mouse input
  * https://github.com/boblemarin/jQuery.eraser
  *
  * Usage:
  *
  * $('#myImage').eraser(); // simple way
  *
  * $('#canvas').eraser( {
  *   size: 20, // define brush size (default value is 40)
  *   completeRatio: .65, // allows to call function when a erased ratio is reached (between 0 and 1, default is .7 )
  *   completeFunction: myFunction // callback function when complete ratio is reached
  * } );
  *
  * $('#image').eraser( 'clear' ); // erases all canvas content
  *
  * $('#image').eraser( 'reset' ); // revert back to original content
  *
  * $('#image').eraser( 'size', 80 ); // change the eraser size
  *
  * $('#image').eraser( 'enable/disable' ); // enable or disable erasing
  *
  * $('#image').eraser( 'enabled' ); // returns whether the eraser is enabled
  *
  *
  * https://github.com/boblemarin/jQuery.eraser
  * http://minimal.be/lab/jQuery.eraser/
  *
  * Copyright (c) 2010 boblemarin emeric@minimal.be http://www.minimal.be
  *
  * Permission is hereby granted, free of charge, to any person
  * obtaining a copy of this software and associated documentation
  * files (the "Software"), to deal in the Software without
  * restriction, including without limitation the rights to use,
  * copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the
  * Software is furnished to do so, subject to the following
  * conditions:
  *
  * The above copyright notice and this permission notice shall be
  * included in all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  * OTHER DEALINGS IN THE SOFTWARE.
  */
  
  (function($){
    var methods = {
  
      init: function(options) {
        return this.each(function(){
          var $this = $(this),
              data = $this.data('eraser');
  
          if (!data) {
  
            var handleImage = function() {
              var $canvas = $('<canvas/>'),
                  canvas = $canvas.get(0),
                  ctx = canvas.getContext('2d'),
  
                  // calculate scale ratio for high DPI devices
                  // http://www.html5rocks.com/en/tutorials/canvas/hidpi/
                  devicePixelRatio = window.devicePixelRatio || 1,
                  backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
                      ctx.mozBackingStorePixelRatio ||
                      ctx.msBackingStorePixelRatio ||
                      ctx.oBackingStorePixelRatio ||
                      ctx.backingStorePixelRatio || 1,
                  scaleRatio = devicePixelRatio / backingStoreRatio,
  
                  realWidth = $this.width(),
                  realHeight = $this.height(),
                  width = realWidth * scaleRatio,
                  height = realHeight * scaleRatio,
                  pos = $this.offset(),
                  enabled = (options && options.enabled === false) ? false : true,
                  size = ((options && options.size) ? options.size : 40) * scaleRatio,
                  completeRatio = (options && options.completeRatio) ? options.completeRatio : .7,
                  completeFunction = (options && options.completeFunction) ? options.completeFunction : null,
                  progressFunction = (options && options.progressFunction) ? options.progressFunction : null,
                  zIndex = $this.css('z-index') == "auto"?1:$this.css('z-index'),
                  parts = [],
                  colParts = Math.floor(width / size),
                  numParts = colParts * Math.floor(height / size),
                  n = numParts,
                  that = $this[0];
  
              // replace target with canvas
              $this.after($canvas);
              canvas.id = that.id;
              canvas.className = that.className;
              canvas.width = width;
              canvas.height = height;
              canvas.style.width = realWidth.toString() + "px";
              canvas.style.height = realHeight.toString() + "px";
              ctx.drawImage(that, 0, 0, width, height);
              $this.remove();
  
              // prepare context for drawing operations
              ctx.globalCompositeOperation = 'destination-out';
              ctx.strokeStyle = 'rgba(255,0,0,255)';
              ctx.lineWidth = size;
  
              ctx.lineCap = 'round';
              // bind events
              $canvas.bind('mousedown.eraser', methods.mouseDown);
              $canvas.bind('touchstart.eraser', methods.touchStart);
              $canvas.bind('touchmove.eraser', methods.touchMove);
              $canvas.bind('touchend.eraser', methods.touchEnd);
  
              // reset parts
              while(n--) parts.push(1);
  
              // store values
              data = {
                posX: pos.left,
                posY: pos.top,
                touchDown: false,
                touchID: -999,
                touchX: 0,
                touchY: 0,
                ptouchX: 0,
                ptouchY: 0,
                canvas: $canvas,
                ctx: ctx,
                w: width,
                h: height,
                scaleRatio: scaleRatio,
                source: that,
                size: size,
                parts: parts,
                colParts: colParts,
                numParts: numParts,
                ratio: 0,
                enabled: enabled,
                complete: false,
                completeRatio: completeRatio,
                completeFunction: completeFunction,
                progressFunction: progressFunction,
                zIndex: zIndex
              };
              $canvas.data('eraser', data);
  
              // listen for resize event to update offset values
              $(window).resize(function() {
                var pos = $canvas.offset();
                data.posX = pos.left;
                data.posY = pos.top;
              });
            }
  
            if (this.complete && this.naturalWidth > 0) {
              handleImage();
            } else {
              //this.onload = handleImage;
              $this.on('load', handleImage);
            }
          }
        });
      },
  
      touchStart: function(event) {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (!data.touchDown) {
          var t = event.originalEvent.changedTouches[0],
              tx = t.pageX - data.posX,
              ty = t.pageY - data.posY;
          tx *= data.scaleRatio;
          ty *= data.scaleRatio;
  
          if (data.enabled) {
            methods.evaluatePoint(data, tx, ty);
          }
  
          data.touchDown = true;
          data.touchID = t.identifier;
          data.touchX = tx;
          data.touchY = ty;
          event.preventDefault();
        }
      },
  
      touchMove: function(event) {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data.touchDown) {
          var ta = event.originalEvent.changedTouches,
              n = ta.length;
          while (n--) {
            if (ta[n].identifier == data.touchID) {
              var tx = ta[n].pageX - data.posX,
                  ty = ta[n].pageY - data.posY;
              tx *= data.scaleRatio;
              ty *= data.scaleRatio;
  
              if (data.enabled) {
                methods.evaluatePoint(data, tx, ty);
                data.ctx.beginPath();
                data.ctx.moveTo(data.touchX, data.touchY);
                data.ctx.lineTo(tx, ty);
                data.ctx.stroke();
                $this.css({"z-index":$this.css('z-index')==data.zIndex?parseInt(data.zIndex)+1:data.zIndex});
              }
  
              data.touchX = tx;
              data.touchY = ty;
  
              event.preventDefault();
              break;
            }
          }
        }
      },
  
      touchEnd: function(event) {
        var $this = $(this),
          data = $this.data('eraser');
  
        if ( data.touchDown ) {
          var ta = event.originalEvent.changedTouches,
            n = ta.length;
          while( n-- ) {
            if ( ta[n].identifier == data.touchID ) {
              data.touchDown = false;
              event.preventDefault();
              break;
            }
          }
        }
      },
  
      evaluatePoint: function(data, tx, ty) {
        if (!data.enabled) return;
        var p = Math.floor(tx/data.size) + Math.floor( ty / data.size ) * data.colParts;
  
        if ( p >= 0 && p < data.numParts ) {
          data.ratio += data.parts[p];
          data.parts[p] = 0;
          if (!data.complete) {
            p = data.ratio/data.numParts;
            if ( p >= data.completeRatio ) {
              data.complete = true;
              if ( data.completeFunction != null ) data.completeFunction();
            } else {
              if ( data.progressFunction != null ) data.progressFunction(p);
            }
          }
        }
  
      },
  
      mouseDown: function(event) {
        var $this = $(this),
            data = $this.data('eraser'),
            tx = event.pageX - data.posX,
            ty = event.pageY - data.posY;
        tx *= data.scaleRatio;
        ty *= data.scaleRatio;
  
        data.touchDown = true;
        data.touchX = tx;
        data.touchY = ty;
  
        if (data.enabled) {
          methods.evaluatePoint( data, tx, ty );
  
          data.ctx.beginPath();
          data.ctx.moveTo(data.touchX-1, data.touchY);
          data.ctx.lineTo(data.touchX, data.touchY);
          data.ctx.stroke();
        }
  
        $this.bind('mousemove.eraser', methods.mouseMove);
        $(document).bind('mouseup.eraser', data, methods.mouseUp);
        event.preventDefault();
      },
  
      mouseMove: function(event) {
        var $this = $(this),
            data = $this.data('eraser'),
            tx = event.pageX - data.posX,
            ty = event.pageY - data.posY;
        tx *= data.scaleRatio;
        ty *= data.scaleRatio;
  
        if (data.enabled) {
          methods.evaluatePoint( data, tx, ty );
          data.ctx.beginPath();
          data.ctx.moveTo( data.touchX, data.touchY );
          data.ctx.lineTo( tx, ty );
          data.ctx.stroke();
          $this.css({"z-index":$this.css('z-index')==data.zIndex?parseInt(data.zIndex)+1:data.zIndex});
        }
  
        data.touchX = tx;
        data.touchY = ty;
  
        event.preventDefault();
      },
  
      mouseUp: function(event) {
        var data = event.data,
            $this = data.canvas;
  
        data.touchDown = false;
        $this.unbind('mousemove.eraser');
        $(document).unbind('mouseup.eraser');
        event.preventDefault();
      },
  
      clear: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data) {
          data.ctx.clearRect(0, 0, data.w, data.h);
          var n = data.numParts;
          while(n--) data.parts[n] = 0;
          data.ratio = data.numParts;
          data.complete = true;
          if (data.completeFunction != null) data.completeFunction();
        }
      },
  
      enabled: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data && data.enabled) {
          return true;
        }
        return false;
      },
  
      enable: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data) {
          data.enabled = true;
        }
      },
  
      disable: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data) {
          data.enabled = false;
        }
      },
  
      size: function(value) {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data && value) {
          data.size = value;
          data.ctx.lineWidth = value;
        }
      },
  
      reset: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data) {
          data.ctx.globalCompositeOperation = 'source-over';
          data.ctx.drawImage( data.source, 0, 0, data.w, data.h);
          data.ctx.globalCompositeOperation = 'destination-out';
          var n = data.numParts;
          while (n--) data.parts[n] = 1;
          data.ratio = 0;
          data.complete = false;
          data.touchDown = false;
        }
      },
  
      progress: function() {
        var $this = $(this),
            data = $this.data('eraser');
  
        if (data) {
          return data.ratio/data.numParts;
        }
        return 0;
      }
  
    };
  
    $.fn.eraser = function(method) {
      if (methods[method]) {
        return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
      } else if (typeof method === 'object' || ! method) {
        return methods.init.apply(this, arguments);
      } else {
        $.error('Method ' +  method + ' does not yet exist on jQuery.eraser');
      }
    };
  })(jQuery);
  