modules/addimage.js

/* global jsPDF */
/** @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
*/
(function (jsPDFAPI) {
    'use strict'

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

    var UNKNOWN = 'UNKNOWN';

    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 (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 });

        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) {

            this.internal.newObject();
            //out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
            //putStream(zlib.compress(img['pal']));
            putStream({ data: arrayBufferToBinaryString(new Uint8Array(image.palette)) });
            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);
        }

        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) {
        //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') {
            return atob(element.toDataURL('image/jpeg', 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
        }
        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, chr, len;

        if (typeof data === "string") {
            len = data.length;
            for (i = 0; i < len; i++) {
                chr = data.charCodeAt(i);
                hash = ((hash << 5) - hash) + chr;
                hash |= 0; // Convert to 32bit integer
            }
        } else if (isArrayBufferView(data)) {
            len = data.byteLength / 2;
            for (i = 0; i < len; i++) {
                chr = data[i];
                hash = ((hash << 5) - hash) + chr;
                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=[\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} ArrayBuffer with ImageData
    * 
    * @returns {String}
    */
    var arrayBufferToBinaryString = jsPDFAPI.__addimage__.arrayBufferToBinaryString = function (buffer) {
        try {
            return atob(btoa(String.fromCharCode.apply(null, buffer)));
        } catch (e) {
            if (typeof Uint8Array !== 'undefined' && typeof Uint8Array.prototype.reduce !== 'undefined') {
                return new Uint8Array(buffer).reduce(function (data, byte) {
                    return data.push(String.fromCharCode(byte)), data;
                }, []).join('');
            }
        }
    };

    /**
    * Adds an Image to the PDF.
    *
    * @name addImage
    * @public
    * @function
    * @param {string|HTMLImageElement|HTMLCanvasElement|Uint8Array} imageData imageData as base64 encoded DataUrl or Image-HTMLElement or Canvas-HTMLElement
    * @param {string} format format of file if filetype-recognition fails, e.g. 'JPEG'
    * @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 (imageData, format, x, y, w, h, alias, compression, rotation) {
        // backwards compatibility
        if (typeof format !== 'string') {
            var tmp = h;
            h = w;
            w = y;
            y = x;
            x = format;
            format = tmp;
        }

        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);
        }

        if (typeof imageData === 'string') {
            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 = 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)) {
                    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);