text:   function isNumber(n) {
      return typeof n === 'number' && !isNaN(n);
    }
  
    function isUndefined(n) {
      return typeof n === 'undefined';
    }
  
    function toArray(obj, offset) {
      var args = [];
  
      // This is necessary for IE8
      if (isNumber(offset)) {
        args.push(offset);
      }
  
      return args.slice.apply(obj, args);
    }
  
    // Custom proxy to avoid jQuery's guid
    function proxy(fn, context) {
      var args = toArray(arguments, 2);
  
      return function () {
        return fn.apply(context, args.concat(toArray(arguments)));
      };
    }
  
    function isCrossOriginURL(url) {
      var parts = url.match(/^(https?:)\/\/([^\:\/\?#]+):?(\d*)/i);
  
      return parts && (
        parts[1] !== location.protocol ||
        parts[2] !== location.hostname ||
        parts[3] !== location.port
      );
    }
  
    function addTimestamp(url) {
      var timestamp = 'timestamp=' + (new Date()).getTime();
  
      return (url + (url.indexOf('?') === -1 ? '?' : '&') + timestamp);
    }
  
    function getCrossOrigin(crossOrigin) {
      return crossOrigin ? ' crossOrigin="' + crossOrigin + '"' : '';
    }
  
    function getImageSize(image, callback) {
      var newImage;
  
      // Modern browsers
      if (image.naturalWidth) {
        return callback(image.naturalWidth, image.naturalHeight);
      }
  
      // IE8: Don't use `new Image()` here (#319)
      newImage = document.createElement('img');
  
      newImage.onload = function () {
        callback(this.width, this.height);
      };
  
      newImage.src = image.src;
    }
  
    function getTransform(options) {
      var transforms = [];
      var rotate = options.rotate;
      var scaleX = options.scaleX;
      var scaleY = options.scaleY;
  
      if (isNumber(rotate)) {
        transforms.push('rotate(' + rotate + 'deg)');
      }
  
      if (isNumber(scaleX) && isNumber(scaleY)) {
        transforms.push('scale(' + scaleX + ',' + scaleY + ')');
      }
  
      return transforms.length ? transforms.join(' ') : 'none';
    }
  
    function getRotatedSizes(data, isReversed) {
      var deg = abs(data.degree) % 180;
      var arc = (deg > 90 ? (180 - deg) : deg) * Math.PI / 180;
      var sinArc = sin(arc);
      var cosArc = cos(arc);
      var width = data.width;
      var height = data.height;
      var aspectRatio = data.aspectRatio;
      var newWidth;
      var newHeight;
  
      if (!isReversed) {
        newWidth = width * cosArc + height * sinArc;
        newHeight = width * sinArc + height * cosArc;
      } else {
        newWidth = width / (cosArc + sinArc / aspectRatio);
        newHeight = newWidth / aspectRatio;
      }
  
      return {
        width: newWidth,
        height: newHeight
      };
    }
  
    function getSourceCanvas(image, data) {
      var canvas = $('<canvas>')[0];
      var context = canvas.getContext('2d');
      var x = 0;
      var y = 0;
      var width = data.naturalWidth;
      var height = data.naturalHeight;
      var rotate = data.rotate;
      var scaleX = data.scaleX;
      var scaleY = data.scaleY;
      var scalable = isNumber(scaleX) && isNumber(scaleY) && (scaleX !== 1 || scaleY !== 1);
      var rotatable = isNumber(rotate) && rotate !== 0;
      var advanced = rotatable || scalable;
      var canvasWidth = width;
      var canvasHeight = height;
      var translateX;
      var translateY;
      var rotated;
  
      if (scalable) {
        translateX = width / 2;
        translateY = height / 2;
      }
  
      if (rotatable) {
        rotated = getRotatedSizes({
          width: width,
          height: height,
          degree: rotate
        });
  
        canvasWidth = rotated.width;
        canvasHeight = rotated.height;
        translateX = rotated.width / 2;
        translateY = rotated.height / 2;
      }
  
      canvas.width = canvasWidth;
      canvas.height = canvasHeight;
  
      if (advanced) {
        x = -width / 2;
        y = -height / 2;
  
        context.save();
        context.translate(translateX, translateY);
      }
  
      if (rotatable) {
        context.rotate(rotate * Math.PI / 180);
      }
  
      // Should call `scale` after rotated
      if (scalable) {
        context.scale(scaleX, scaleY);
      }
  
      context.drawImage(image, floor(x), floor(y), floor(width), floor(height));
  
      if (advanced) {
        context.restore();
      }
  
      return canvas;
    }
  
    function getTouchesCenter(touches) {
      var length = touches.length;
      var pageX = 0;
      var pageY = 0;
  
      if (length) {
        $.each(touches, function (i, touch) {
          pageX += touch.pageX;
          pageY += touch.pageY;
        });
  
        pageX /= length;
        pageY /= length;
      }
  
      return {
        pageX: pageX,
        pageY: pageY
      };
    }
  
    function getStringFromCharCode(dataView, start, length) {
      var str = '';
      var i;
  
      for (i = start, length += start; i < length; i++) {
        str += fromCharCode(dataView.getUint8(i));
      }
  
      return str;
    }
  
    function getOrientation(arrayBuffer) {
      var dataView = new DataView(arrayBuffer);
      var length = dataView.byteLength;
      var orientation;
      var exifIDCode;
      var tiffOffset;
      var firstIFDOffset;
      var littleEndian;
      var endianness;
      var app1Start;
      var ifdStart;
      var offset;
      var i;
  
      // Only handle JPEG image (start by 0xFFD8)
      if (dataView.getUint8(0) === 0xFF && dataView.getUint8(1) === 0xD8) {
        offset = 2;
  
        while (offset < length) {
          if (dataView.getUint8(offset) === 0xFF && dataView.getUint8(offset + 1) === 0xE1) {
            app1Start = offset;
            break;
          }
  
          offset++;
        }
      }
  
      if (app1Start) {
        exifIDCode = app1Start + 4;
        tiffOffset = app1Start + 10;
  
        if (getStringFromCharCode(dataView, exifIDCode, 4) === 'Exif') {
          endianness = dataView.getUint16(tiffOffset);
          littleEndian = endianness === 0x4949;
  
          if (littleEndian || endianness === 0x4D4D /* bigEndian */) {
            if (dataView.getUint16(tiffOffset + 2, littleEndian) === 0x002A) {
              firstIFDOffset = dataView.getUint32(tiffOffset + 4, littleEndian);
  
              if (firstIFDOffset >= 0x00000008) {
                ifdStart = tiffOffset + firstIFDOffset;
              }
            }
          }
        }
      }
  
      if (ifdStart) {
        length = dataView.getUint16(ifdStart, littleEndian);
  
        for (i = 0; i < length; i++) {
          offset = ifdStart + i * 12 + 2;
  
          if (dataView.getUint16(offset, littleEndian) === 0x0112 /* Orientation */) {
  
            // 8 is the offset of the current tag's value
            offset += 8;
  
            // Get the original orientation value
            orientation = dataView.getUint16(offset, littleEndian);
  
            // Override the orientation with the default value: 1
            dataView.setUint16(offset, 1, littleEndian);
            break;
          }
        }
      }
  
      return orientation;
    }
  
    function dataURLToArrayBuffer(dataURL) {
      var base64 = dataURL.replace(REGEXP_DATA_URL_HEAD, '');
      var binary = atob(base64);
      var length = binary.length;
      var arrayBuffer = new ArrayBuffer(length);
      var dataView = new Uint8Array(arrayBuffer);
      var i;
  
      for (i = 0; i < length; i++) {
        dataView[i] = binary.charCodeAt(i);
      }
  
      return arrayBuffer;
    }
  
    // Only available for JPEG image
    function arrayBufferToDataURL(arrayBuffer) {
      var dataView = new Uint8Array(arrayBuffer);
      var length = dataView.length;
      var base64 = '';
      var i;
  
      for (i = 0; i < length; i++) {
        base64 += fromCharCode(dataView[i]);
      }
  
      return 'data:image/jpeg;base64,' + btoa(base64);
    }
  