# modules/addimage.js

    /** @license
     * jsPDF addImage plugin
     * Copyright (c) 2012 Jason Siefken, https://github.com/siefkenj/
     *               2013 Chris Dowling, https://github.com/gingerchris
     *               2013 Trinh Ho, https://github.com/ineedfat
     *               2013 Edwin Alejandro Perez, https://github.com/eaparango
     *               2013 Norah Smith, https://github.com/burnburnrocket
     *               2014 Diego Casorran, https://github.com/diegocr
     *               2014 James Robb, https://github.com/jamesbrobb
     *
     * 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.
     */
    /**
     * @name addImage
     * @module
     */

    import { jsPDF } from "../jspdf.js";
    import { atob, btoa } from "../libs/AtobBtoa.js";

    (function(jsPDFAPI) {
      "use strict";

      var namespace = "addImage_";
      jsPDFAPI.__addimage__ = {};

      var UNKNOWN = "UNKNOWN";

      // Heuristic selection of a good batch for large array .apply. Not limiting make the call overflow.
      // With too small batch iteration will be slow as more calls are made,
      // higher values cause larger and slower garbage collection.
      var ARRAY_APPLY_BATCH = 8192;

      var imageFileTypeHeaders = {
        PNG: [[0x89, 0x50, 0x4e, 0x47]],
        TIFF: [
          [0x4d, 0x4d, 0x00, 0x2a], //Motorola
          [0x49, 0x49, 0x2a, 0x00] //Intel
        ],
        JPEG: [
          [
            0xff,
            0xd8,
            0xff,
            0xe0,
            undefined,
            undefined,
            0x4a,
            0x46,
            0x49,
            0x46,
            0x00
          ], //JFIF
          [
            0xff,
            0xd8,
            0xff,
            0xe1,
            undefined,
            undefined,
            0x45,
            0x78,
            0x69,
            0x66,
            0x00,
            0x00
          ], //Exif
          [0xff, 0xd8, 0xff, 0xdb], //JPEG RAW
          [0xff, 0xd8, 0xff, 0xee] //EXIF RAW
        ],
        JPEG2000: [[0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20]],
        GIF87a: [[0x47, 0x49, 0x46, 0x38, 0x37, 0x61]],
        GIF89a: [[0x47, 0x49, 0x46, 0x38, 0x39, 0x61]],
        WEBP: [
          [
            0x52,
            0x49,
            0x46,
            0x46,
            undefined,
            undefined,
            undefined,
            undefined,
            0x57,
            0x45,
            0x42,
            0x50
          ]
        ],
        BMP: [
          [0x42, 0x4d], //BM - Windows 3.1x, 95, NT, ... etc.
          [0x42, 0x41], //BA - OS/2 struct bitmap array
          [0x43, 0x49], //CI - OS/2 struct color icon
          [0x43, 0x50], //CP - OS/2 const color pointer
          [0x49, 0x43], //IC - OS/2 struct icon
          [0x50, 0x54] //PT - OS/2 pointer
        ]
      };

      /**
       * Recognize filetype of Image by magic-bytes
       *
       * https://en.wikipedia.org/wiki/List_of_file_signatures
       *
       * @name getImageFileTypeByImageData
       * @public
       * @function
       * @param {string|arraybuffer} imageData imageData as binary String or arraybuffer
       * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG'
       *
       * @returns {string} filetype of Image
       */
      var getImageFileTypeByImageData = (jsPDFAPI.__addimage__.getImageFileTypeByImageData = function(
        imageData,
        fallbackFormat
      ) {
        fallbackFormat = fallbackFormat || UNKNOWN;
        var i;
        var j;
        var result = UNKNOWN;
        var headerSchemata;
        var compareResult;
        var fileType;

        if (
          fallbackFormat === "RGBA" ||
          (imageData.data !== undefined &&
            imageData.data instanceof Uint8ClampedArray &&
            "height" in imageData &&
            "width" in imageData)
        ) {
          return "RGBA";
        }

        if (isArrayBufferView(imageData)) {
          for (fileType in imageFileTypeHeaders) {
            headerSchemata = imageFileTypeHeaders[fileType];
            for (i = 0; i < headerSchemata.length; i += 1) {
              compareResult = true;
              for (j = 0; j < headerSchemata[i].length; j += 1) {
                if (headerSchemata[i][j] === undefined) {
                  continue;
                }
                if (headerSchemata[i][j] !== imageData[j]) {
                  compareResult = false;
                  break;
                }
              }
              if (compareResult === true) {
                result = fileType;
                break;
              }
            }
          }
        } else {
          for (fileType in imageFileTypeHeaders) {
            headerSchemata = imageFileTypeHeaders[fileType];
            for (i = 0; i < headerSchemata.length; i += 1) {
              compareResult = true;
              for (j = 0; j < headerSchemata[i].length; j += 1) {
                if (headerSchemata[i][j] === undefined) {
                  continue;
                }
                if (headerSchemata[i][j] !== imageData.charCodeAt(j)) {
                  compareResult = false;
                  break;
                }
              }
              if (compareResult === true) {
                result = fileType;
                break;
              }
            }
          }
        }

        if (result === UNKNOWN && fallbackFormat !== UNKNOWN) {
          result = fallbackFormat;
        }
        return result;
      });

      // Image functionality ported from pdf.js
      var putImage = function(image) {
        var out = this.internal.write;
        var putStream = this.internal.putStream;
        var getFilters = this.internal.getFilters;

        var filter = getFilters();
        while (filter.indexOf("FlateEncode") !== -1) {
          filter.splice(filter.indexOf("FlateEncode"), 1);
        }

        image.objectId = this.internal.newObject();

        var additionalKeyValues = [];
        additionalKeyValues.push({ key: "Type", value: "/XObject" });
        additionalKeyValues.push({ key: "Subtype", value: "/Image" });
        additionalKeyValues.push({ key: "Width", value: image.width });
        additionalKeyValues.push({ key: "Height", value: image.height });

        if (image.colorSpace === color_spaces.INDEXED) {
          additionalKeyValues.push({
            key: "ColorSpace",
            value:
              "[/Indexed /DeviceRGB " +
              // if an indexed png defines more than one colour with transparency, we've created a sMask
              (image.palette.length / 3 - 1) +
              " " +
              ("sMask" in image && typeof image.sMask !== "undefined"
                ? image.objectId + 2
                : image.objectId + 1) +
              " 0 R]"
          });
        } else {
          additionalKeyValues.push({
            key: "ColorSpace",
            value: "/" + image.colorSpace
          });
          if (image.colorSpace === color_spaces.DEVICE_CMYK) {
            additionalKeyValues.push({ key: "Decode", value: "[1 0 1 0 1 0 1 0]" });
          }
        }
        additionalKeyValues.push({
          key: "BitsPerComponent",
          value: image.bitsPerComponent
        });
        if (
          "decodeParameters" in image &&
          typeof image.decodeParameters !== "undefined"
        ) {
          additionalKeyValues.push({
            key: "DecodeParms",
            value: "<<" + image.decodeParameters + ">>"
          });
        }
        if ("transparency" in image && Array.isArray(image.transparency)) {
          var transparency = "",
            i = 0,
            len = image.transparency.length;
          for (; i < len; i++)
            transparency +=
              image.transparency[i] + " " + image.transparency[i] + " ";

          additionalKeyValues.push({
            key: "Mask",
            value: "[" + transparency + "]"
          });
        }
        if (typeof image.sMask !== "undefined") {
          additionalKeyValues.push({
            key: "SMask",
            value: image.objectId + 1 + " 0 R"
          });
        }

        var alreadyAppliedFilters =
          typeof image.filter !== "undefined" ? ["/" + image.filter] : undefined;

        putStream({
          data: image.data,
          additionalKeyValues: additionalKeyValues,
          alreadyAppliedFilters: alreadyAppliedFilters,
          objectId: image.objectId
        });

        out("endobj");

        // Soft mask
        if ("sMask" in image && typeof image.sMask !== "undefined") {
          var decodeParameters =
            "/Predictor " +
            image.predictor +
            " /Colors 1 /BitsPerComponent " +
            image.bitsPerComponent +
            " /Columns " +
            image.width;
          var sMask = {
            width: image.width,
            height: image.height,
            colorSpace: "DeviceGray",
            bitsPerComponent: image.bitsPerComponent,
            decodeParameters: decodeParameters,
            data: image.sMask
          };
          if ("filter" in image) {
            sMask.filter = image.filter;
          }
          putImage.call(this, sMask);
        }

        //Palette
        if (image.colorSpace === color_spaces.INDEXED) {
          var objId = this.internal.newObject();
          //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
          //putStream(zlib.compress(img['pal']));
          putStream({
            data: arrayBufferToBinaryString(new Uint8Array(image.palette)),
            objectId: objId
          });
          out("endobj");
        }
      };
      var putResourcesCallback = function() {
        var images = this.internal.collections[namespace + "images"];
        for (var i in images) {
          putImage.call(this, images[i]);
        }
      };
      var putXObjectsDictCallback = function() {
        var images = this.internal.collections[namespace + "images"],
          out = this.internal.write,
          image;
        for (var i in images) {
          image = images[i];
          out("/I" + image.index, image.objectId, "0", "R");
        }
      };

      var checkCompressValue = function(value) {
        if (value && typeof value === "string") value = value.toUpperCase();
        return value in jsPDFAPI.image_compression ? value : image_compression.NONE;
      };

      var initialize = function() {
        if (!this.internal.collections[namespace + "images"]) {
          this.internal.collections[namespace + "images"] = {};
          this.internal.events.subscribe("putResources", putResourcesCallback);
          this.internal.events.subscribe("putXobjectDict", putXObjectsDictCallback);
        }
      };

      var getImages = function() {
        var images = this.internal.collections[namespace + "images"];
        initialize.call(this);
        return images;
      };
      var getImageIndex = function() {
        return Object.keys(this.internal.collections[namespace + "images"]).length;
      };
      var notDefined = function(value) {
        return typeof value === "undefined" || value === null || value.length === 0;
      };
      var generateAliasFromImageData = function(imageData) {
        if (typeof imageData === "string" || isArrayBufferView(imageData)) {
          return sHashCode(imageData);
        } else if (isArrayBufferView(imageData.data)) {
          return sHashCode(imageData.data);
        }

        return null;
      };

      var isImageTypeSupported = function(type) {
        return typeof jsPDFAPI["process" + type.toUpperCase()] === "function";
      };

      var isDOMElement = function(object) {
        return typeof object === "object" && object.nodeType === 1;
      };

      var getImageDataFromElement = function(element, format) {
        //if element is an image which uses data url definition, just return the dataurl
        if (element.nodeName === "IMG" && element.hasAttribute("src")) {
          var src = "" + element.getAttribute("src");

          //is base64 encoded dataUrl, directly process it
          if (src.indexOf("data:image/") === 0) {
            return atob(
              unescape(src)
                .split("base64,")
                .pop()
            );
          }

          //it is probably an url, try to load it
          var tmpImageData = jsPDFAPI.loadFile(src, true);
          if (tmpImageData !== undefined) {
            return tmpImageData;
          }
        }

        if (element.nodeName === "CANVAS") {
          if (element.width === 0 || element.height === 0) {
            throw new Error(
              "Given canvas must have data. Canvas width: " +
                element.width +
                ", height: " +
                element.height
            );
          }
          var mimeType;
          switch (format) {
            case "PNG":
              mimeType = "image/png";
              break;
            case "WEBP":
              mimeType = "image/webp";
              break;
            case "JPEG":
            case "JPG":
            default:
              mimeType = "image/jpeg";
              break;
          }
          return atob(
            element
              .toDataURL(mimeType, 1.0)
              .split("base64,")
              .pop()
          );
        }
      };

      var checkImagesForAlias = function(alias) {
        var images = this.internal.collections[namespace + "images"];
        if (images) {
          for (var e in images) {
            if (alias === images[e].alias) {
              return images[e];
            }
          }
        }
      };

      var determineWidthAndHeight = function(width, height, image) {
        if (!width && !height) {
          width = -96;
          height = -96;
        }
        if (width < 0) {
          width = (-1 * image.width * 72) / width / this.internal.scaleFactor;
        }
        if (height < 0) {
          height = (-1 * image.height * 72) / height / this.internal.scaleFactor;
        }
        if (width === 0) {
          width = (height * image.width) / image.height;
        }
        if (height === 0) {
          height = (width * image.height) / image.width;
        }

        return [width, height];
      };

      var writeImageToPDF = function(x, y, width, height, image, rotation) {
        var dims = determineWidthAndHeight.call(this, width, height, image),
          coord = this.internal.getCoordinateString,
          vcoord = this.internal.getVerticalCoordinateString;

        var images = getImages.call(this);

        width = dims[0];
        height = dims[1];
        images[image.index] = image;

        if (rotation) {
          rotation *= Math.PI / 180;
          var c = Math.cos(rotation);
          var s = Math.sin(rotation);
          //like in pdf Reference do it 4 digits instead of 2
          var f4 = function(number) {
            return number.toFixed(4);
          };
          var rotationTransformationMatrix = [
            f4(c),
            f4(s),
            f4(s * -1),
            f4(c),
            0,
            0,
            "cm"
          ];
        }
        this.internal.write("q"); //Save graphics state
        if (rotation) {
          this.internal.write(
            [1, "0", "0", 1, coord(x), vcoord(y + height), "cm"].join(" ")
          ); //Translate
          this.internal.write(rotationTransformationMatrix.join(" ")); //Rotate
          this.internal.write(
            [coord(width), "0", "0", coord(height), "0", "0", "cm"].join(" ")
          ); //Scale
        } else {
          this.internal.write(
            [
              coord(width),
              "0",
              "0",
              coord(height),
              coord(x),
              vcoord(y + height),
              "cm"
            ].join(" ")
          ); //Translate and Scale
        }

        if (this.isAdvancedAPI()) {
          // draw image bottom up when in "advanced" API mode
          this.internal.write([1, 0, 0, -1, 0, 0, "cm"].join(" "));
        }

        this.internal.write("/I" + image.index + " Do"); //Paint Image
        this.internal.write("Q"); //Restore graphics state
      };

      /**
       * COLOR SPACES
       */
      var color_spaces = (jsPDFAPI.color_spaces = {
        DEVICE_RGB: "DeviceRGB",
        DEVICE_GRAY: "DeviceGray",
        DEVICE_CMYK: "DeviceCMYK",
        CAL_GREY: "CalGray",
        CAL_RGB: "CalRGB",
        LAB: "Lab",
        ICC_BASED: "ICCBased",
        INDEXED: "Indexed",
        PATTERN: "Pattern",
        SEPARATION: "Separation",
        DEVICE_N: "DeviceN"
      });

      /**
       * DECODE METHODS
       */
      jsPDFAPI.decode = {
        DCT_DECODE: "DCTDecode",
        FLATE_DECODE: "FlateDecode",
        LZW_DECODE: "LZWDecode",
        JPX_DECODE: "JPXDecode",
        JBIG2_DECODE: "JBIG2Decode",
        ASCII85_DECODE: "ASCII85Decode",
        ASCII_HEX_DECODE: "ASCIIHexDecode",
        RUN_LENGTH_DECODE: "RunLengthDecode",
        CCITT_FAX_DECODE: "CCITTFaxDecode"
      };

      /**
       * IMAGE COMPRESSION TYPES
       */
      var image_compression = (jsPDFAPI.image_compression = {
        NONE: "NONE",
        FAST: "FAST",
        MEDIUM: "MEDIUM",
        SLOW: "SLOW"
      });

      /**
       * @name sHashCode
       * @function
       * @param {string} data
       * @returns {string}
       */
      var sHashCode = (jsPDFAPI.__addimage__.sHashCode = function(data) {
        var hash = 0,
          i,
          len;

        if (typeof data === "string") {
          len = data.length;
          for (i = 0; i < len; i++) {
            hash = (hash << 5) - hash + data.charCodeAt(i);
            hash |= 0; // Convert to 32bit integer
          }
        } else if (isArrayBufferView(data)) {
          len = data.byteLength / 2;
          for (i = 0; i < len; i++) {
            hash = (hash << 5) - hash + data[i];
            hash |= 0; // Convert to 32bit integer
          }
        }
        return hash;
      });

      /**
       * Validates if given String is a valid Base64-String
       *
       * @name validateStringAsBase64
       * @public
       * @function
       * @param {String} possible Base64-String
       *
       * @returns {boolean}
       */
      var validateStringAsBase64 = (jsPDFAPI.__addimage__.validateStringAsBase64 = function(
        possibleBase64String
      ) {
        possibleBase64String = possibleBase64String || "";
        possibleBase64String.toString().trim();

        var result = true;

        if (possibleBase64String.length === 0) {
          result = false;
        }

        if (possibleBase64String.length % 4 !== 0) {
          result = false;
        }

        if (
          /^[A-Za-z0-9+/]+$/.test(
            possibleBase64String.substr(0, possibleBase64String.length - 2)
          ) === false
        ) {
          result = false;
        }

        if (
          /^[A-Za-z0-9/][A-Za-z0-9+/]|[A-Za-z0-9+/]=|==$/.test(
            possibleBase64String.substr(-2)
          ) === false
        ) {
          result = false;
        }
        return result;
      });

      /**
       * Strips out and returns info from a valid base64 data URI
       *
       * @name extractImageFromDataUrl
       * @function
       * @param {string} dataUrl a valid data URI of format 'data:[<MIME-type>][;base64],<data>'
       * @returns {Array}an Array containing the following
       * [0] the complete data URI
       * [1] <MIME-type>
       * [2] format - the second part of the mime-type i.e 'png' in 'image/png'
       * [4] <data>
       */
      var extractImageFromDataUrl = (jsPDFAPI.__addimage__.extractImageFromDataUrl = function(
        dataUrl
      ) {
        dataUrl = dataUrl || "";
        var dataUrlParts = dataUrl.split("base64,");
        var result = null;

        if (dataUrlParts.length === 2) {
          var extractedInfo = /^data:(\w*\/\w*);*(charset=(?!charset=)[\w=-]*)*;*$/.exec(
            dataUrlParts[0]
          );
          if (Array.isArray(extractedInfo)) {
            result = {
              mimeType: extractedInfo[1],
              charset: extractedInfo[2],
              data: dataUrlParts[1]
            };
          }
        }
        return result;
      });

      /**
       * Check to see if ArrayBuffer is supported
       *
       * @name supportsArrayBuffer
       * @function
       * @returns {boolean}
       */
      var supportsArrayBuffer = (jsPDFAPI.__addimage__.supportsArrayBuffer = function() {
        return (
          typeof ArrayBuffer !== "undefined" && typeof Uint8Array !== "undefined"
        );
      });

      /**
       * Tests supplied object to determine if ArrayBuffer
       *
       * @name isArrayBuffer
       * @function
       * @param {Object} object an Object
       *
       * @returns {boolean}
       */
      jsPDFAPI.__addimage__.isArrayBuffer = function(object) {
        return supportsArrayBuffer() && object instanceof ArrayBuffer;
      };

      /**
       * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
       *
       * @name isArrayBufferView
       * @function
       * @param {Object} object an Object
       * @returns {boolean}
       */
      var isArrayBufferView = (jsPDFAPI.__addimage__.isArrayBufferView = function(
        object
      ) {
        return (
          supportsArrayBuffer() &&
          typeof Uint32Array !== "undefined" &&
          (object instanceof Int8Array ||
            object instanceof Uint8Array ||
            (typeof Uint8ClampedArray !== "undefined" &&
              object instanceof Uint8ClampedArray) ||
            object instanceof Int16Array ||
            object instanceof Uint16Array ||
            object instanceof Int32Array ||
            object instanceof Uint32Array ||
            object instanceof Float32Array ||
            object instanceof Float64Array)
        );
      });

      /**
       * Convert Binary String to ArrayBuffer
       *
       * @name binaryStringToUint8Array
       * @public
       * @function
       * @param {string} BinaryString with ImageData
       * @returns {Uint8Array}
       */
      var binaryStringToUint8Array = (jsPDFAPI.__addimage__.binaryStringToUint8Array = function(
        binary_string
      ) {
        var len = binary_string.length;
        var bytes = new Uint8Array(len);
        for (var i = 0; i < len; i++) {
          bytes[i] = binary_string.charCodeAt(i);
        }
        return bytes;
      });

      /**
       * Convert the Buffer to a Binary String
       *
       * @name arrayBufferToBinaryString
       * @public
       * @function
       * @param {ArrayBuffer|ArrayBufferView} ArrayBuffer buffer or bufferView with ImageData
       *
       * @returns {String}
       */
      var arrayBufferToBinaryString = (jsPDFAPI.__addimage__.arrayBufferToBinaryString = function(
        buffer
      ) {
        var out = "";
        // There are calls with both ArrayBuffer and already converted Uint8Array or other BufferView.
        // Do not copy the array if input is already an array.
        var buf = isArrayBufferView(buffer) ? buffer : new Uint8Array(buffer);
        for (var i = 0; i < buf.length; i += ARRAY_APPLY_BATCH) {
          // Limit the amount of characters being parsed to prevent overflow.
          // Note that while TextDecoder would be faster, it does not have the same
          // functionality as fromCharCode with any provided encodings as of 3/2021.
          out += String.fromCharCode.apply(
            null,
            buf.subarray(i, i + ARRAY_APPLY_BATCH)
          );
        }
        return out;
      });

      /**
       * Possible parameter for addImage, an RGBA buffer with size.
       *
       * @typedef {Object} RGBAData
       * @property {Uint8ClampedArray} data - Single dimensional array of RGBA values. For example from canvas getImageData.
       * @property {number} width - Image width as the data does not carry this information in itself.
       * @property {number} height - Image height as the data does not carry this information in itself.
       */

      /**
       * Adds an Image to the PDF.
       *
       * @name addImage
       * @public
       * @function
       * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array|RGBAData} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement or object containing RGBA array (like output from canvas.getImageData).
       * @param {string} format format of file if filetype-recognition fails or in case of a Canvas-Element needs to be specified (default for Canvas is JPEG), e.g. 'JPEG', 'PNG', 'WEBP'
       * @param {number} x x Coordinate (in units declared at inception of PDF document) against left edge of the page
       * @param {number} y y Coordinate (in units declared at inception of PDF document) against upper edge of the page
       * @param {number} width width of the image (in units declared at inception of PDF document)
       * @param {number} height height of the Image (in units declared at inception of PDF document)
       * @param {string} alias alias of the image (if used multiple times)
       * @param {string} compression compression of the generated JPEG, can have the values 'NONE', 'FAST', 'MEDIUM' and 'SLOW'
       * @param {number} rotation rotation of the image in degrees (0-359)
       *
       * @returns jsPDF
       */
      jsPDFAPI.addImage = function() {
        var imageData, format, x, y, w, h, alias, compression, rotation;

        imageData = arguments[0];
        if (typeof arguments[1] === "number") {
          format = UNKNOWN;
          x = arguments[1];
          y = arguments[2];
          w = arguments[3];
          h = arguments[4];
          alias = arguments[5];
          compression = arguments[6];
          rotation = arguments[7];
        } else {
          format = arguments[1];
          x = arguments[2];
          y = arguments[3];
          w = arguments[4];
          h = arguments[5];
          alias = arguments[6];
          compression = arguments[7];
          rotation = arguments[8];
        }

        if (
          typeof imageData === "object" &&
          !isDOMElement(imageData) &&
          "imageData" in imageData
        ) {
          var options = imageData;

          imageData = options.imageData;
          format = options.format || format || UNKNOWN;
          x = options.x || x || 0;
          y = options.y || y || 0;
          w = options.w || options.width || w;
          h = options.h || options.height || h;
          alias = options.alias || alias;
          compression = options.compression || compression;
          rotation = options.rotation || options.angle || rotation;
        }

        //If compression is not explicitly set, determine if we should use compression
        var filter = this.internal.getFilters();
        if (compression === undefined && filter.indexOf("FlateEncode") !== -1) {
          compression = "SLOW";
        }

        if (isNaN(x) || isNaN(y)) {
          throw new Error("Invalid coordinates passed to jsPDF.addImage");
        }

        initialize.call(this);

        var image = processImageData.call(
          this,
          imageData,
          format,
          alias,
          compression
        );

        writeImageToPDF.call(this, x, y, w, h, image, rotation);

        return this;
      };

      var processImageData = function(imageData, format, alias, compression) {
        var result, dataAsBinaryString;

        if (
          typeof imageData === "string" &&
          getImageFileTypeByImageData(imageData) === UNKNOWN
        ) {
          imageData = unescape(imageData);
          var tmpImageData = convertBase64ToBinaryString(imageData, false);

          if (tmpImageData !== "") {
            imageData = tmpImageData;
          } else {
            tmpImageData = jsPDFAPI.loadFile(imageData, true);
            if (tmpImageData !== undefined) {
              imageData = tmpImageData;
            }
          }
        }

        if (isDOMElement(imageData)) {
          imageData = getImageDataFromElement(imageData, format);
        }

        format = getImageFileTypeByImageData(imageData, format);
        if (!isImageTypeSupported(format)) {
          throw new Error(
            "addImage does not support files of type '" +
              format +
              "', please ensure that a plugin for '" +
              format +
              "' support is added."
          );
        }

        // now do the heavy lifting

        if (notDefined(alias)) {
          alias = generateAliasFromImageData(imageData);
        }
        result = checkImagesForAlias.call(this, alias);

        if (!result) {
          if (supportsArrayBuffer()) {
            // no need to convert if imageData is already uint8array
            if (!(imageData instanceof Uint8Array) && format !== "RGBA") {
              dataAsBinaryString = imageData;
              imageData = binaryStringToUint8Array(imageData);
            }
          }

          result = this["process" + format.toUpperCase()](
            imageData,
            getImageIndex.call(this),
            alias,
            checkCompressValue(compression),
            dataAsBinaryString
          );
        }

        if (!result) {
          throw new Error("An unknown error occurred whilst processing the image.");
        }
        return result;
      };

      /**
       * @name convertBase64ToBinaryString
       * @function
       * @param {string} stringData
       * @returns {string} binary string
       */
      var convertBase64ToBinaryString = (jsPDFAPI.__addimage__.convertBase64ToBinaryString = function(
        stringData,
        throwError
      ) {
        throwError = typeof throwError === "boolean" ? throwError : true;
        var base64Info;
        var imageData = "";
        var rawData;

        if (typeof stringData === "string") {
          base64Info = extractImageFromDataUrl(stringData);
          rawData = base64Info !== null ? base64Info.data : stringData;

          try {
            imageData = atob(rawData);
          } catch (e) {
            if (throwError) {
              if (!validateStringAsBase64(rawData)) {
                throw new Error(
                  "Supplied Data is not a valid base64-String jsPDF.convertBase64ToBinaryString "
                );
              } else {
                throw new Error(
                  "atob-Error in jsPDF.convertBase64ToBinaryString " + e.message
                );
              }
            }
          }
        }
        return imageData;
      });

      /**
       * @name getImageProperties
       * @function
       * @param {Object} imageData
       * @returns {Object}
       */
      jsPDFAPI.getImageProperties = function(imageData) {
        var image;
        var tmpImageData = "";
        var format;

        if (isDOMElement(imageData)) {
          imageData = getImageDataFromElement(imageData);
        }

        if (
          typeof imageData === "string" &&
          getImageFileTypeByImageData(imageData) === UNKNOWN
        ) {
          tmpImageData = convertBase64ToBinaryString(imageData, false);

          if (tmpImageData === "") {
            tmpImageData = jsPDFAPI.loadFile(imageData) || "";
          }
          imageData = tmpImageData;
        }

        format = getImageFileTypeByImageData(imageData);
        if (!isImageTypeSupported(format)) {
          throw new Error(
            "addImage does not support files of type '" +
              format +
              "', please ensure that a plugin for '" +
              format +
              "' support is added."
          );
        }

        if (supportsArrayBuffer() && !(imageData instanceof Uint8Array)) {
          imageData = binaryStringToUint8Array(imageData);
        }

        image = this["process" + format.toUpperCase()](imageData);

        if (!image) {
          throw new Error("An unknown error occurred whilst processing the image");
        }

        image.fileType = format;

        return image;
      };
    })(jsPDF.API);