# plugins/addimage.js

    /** @preserve
     * 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.
     */

    ;(function(jsPDFAPI) {
    	'use strict'

    	var namespace = 'addImage_',
    		supported_image_types = ['jpeg', 'jpg', 'png'];

    	// Image functionality ported from pdf.js
    	var putImage = function(img) {

    		var objectNumber = this.internal.newObject()
    		, out = this.internal.write
    		, putStream = this.internal.putStream

    		img['n'] = objectNumber

    		out('<</Type /XObject')
    		out('/Subtype /Image')
    		out('/Width ' + img['w'])
    		out('/Height ' + img['h'])
    		if (img['cs'] === this.color_spaces.INDEXED) {
    			out('/ColorSpace [/Indexed /DeviceRGB '
    					// if an indexed png defines more than one colour with transparency, we've created a smask
    					+ (img['pal'].length / 3 - 1) + ' ' + ('smask' in img ? objectNumber + 2 : objectNumber + 1)
    					+ ' 0 R]');
    		} else {
    			out('/ColorSpace /' + img['cs']);
    			if (img['cs'] === this.color_spaces.DEVICE_CMYK) {
    				out('/Decode [1 0 1 0 1 0 1 0]');
    			}
    		}
    		out('/BitsPerComponent ' + img['bpc']);
    		if ('f' in img) {
    			out('/Filter /' + img['f']);
    		}
    		if ('dp' in img) {
    			out('/DecodeParms <<' + img['dp'] + '>>');
    		}
    		if ('trns' in img && img['trns'].constructor == Array) {
    			var trns = '',
    				i = 0,
    				len = img['trns'].length;
    			for (; i < len; i++)
    				trns += (img['trns'][i] + ' ' + img['trns'][i] + ' ');
    			out('/Mask [' + trns + ']');
    		}
    		if ('smask' in img) {
    			out('/SMask ' + (objectNumber + 1) + ' 0 R');
    		}
    		out('/Length ' + img['data'].length + '>>');

    		putStream(img['data']);

    		out('endobj');

    		// Soft mask
    		if ('smask' in img) {
    			var dp = '/Predictor '+ img['p'] +' /Colors 1 /BitsPerComponent ' + img['bpc'] + ' /Columns ' + img['w'];
    			var smask = {'w': img['w'], 'h': img['h'], 'cs': 'DeviceGray', 'bpc': img['bpc'], 'dp': dp, 'data': img['smask']};
    			if ('f' in img)
    				smask.f = img['f'];
    			putImage.call(this, smask);
    		}

    	    //Palette
    		if (img['cs'] === this.color_spaces.INDEXED) {

    			this.internal.newObject();
    			//out('<< /Filter / ' + img['f'] +' /Length ' + img['pal'].length + '>>');
    			//putStream(zlib.compress(img['pal']));
    			out('<< /Length ' + img['pal'].length + '>>');
    			putStream(this.arrayBufferToBinaryString(new Uint8Array(img['pal'])));
    			out('endobj');
    		}
    	}
    	, putResourcesCallback = function() {
    		var images = this.internal.collections[namespace + 'images']
    		for ( var i in images ) {
    			putImage.call(this, images[i])
    		}
    	}
    	, 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['i']
    				, image['n']
    				, '0'
    				, 'R'
    			)
    		}
    	}
    	, checkCompressValue = function(value) {
    		if(value && typeof value === 'string')
    			value = value.toUpperCase();
    		return value in jsPDFAPI.image_compression ? value : jsPDFAPI.image_compression.NONE;
    	}
    	, getImages = function() {
    		var images = this.internal.collections[namespace + 'images'];
    		//first run, so initialise stuff
    		if(!images) {
    			this.internal.collections[namespace + 'images'] = images = {};
    			this.internal.events.subscribe('putResources', putResourcesCallback);
    			this.internal.events.subscribe('putXobjectDict', putXObjectsDictCallback);
    		}

    		return images;
    	}
    	, getImageIndex = function(images) {
    		var imageIndex = 0;

    		if (images){
    			// this is NOT the first time this method is ran on this instance of jsPDF object.
    			imageIndex = Object.keys ?
    			Object.keys(images).length :
    			(function(o){
    				var i = 0
    				for (var e in o){if(o.hasOwnProperty(e)){ i++ }}
    				return i
    			})(images)
    		}

    		return imageIndex;
    	}
    	, notDefined = function(value) {
    		return typeof value === 'undefined' || value === null;
    	}
    	, generateAliasFromData = function(data) {
    		return typeof data === 'string' && jsPDFAPI.sHashCode(data);
    	}
    	, doesNotSupportImageType = function(type) {
    		return supported_image_types.indexOf(type) === -1;
    	}
    	, processMethodNotEnabled = function(type) {
    		return typeof jsPDFAPI['process' + type.toUpperCase()] !== 'function';
    	}
    	, isDOMElement = function(object) {
    		return typeof object === 'object' && object.nodeType === 1;
    	}
    	, createDataURIFromElement = function(element, format, angle) {

    		//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');
    			if (!angle && src.indexOf('data:image/') === 0) return src;

    			// only if the user doesn't care about a format
    			if (!format && /\.png(?:[?#].*)?$/i.test(src)) format = 'png';
    		}

    		if(element.nodeName === 'CANVAS') {
    			var canvas = element;
    		} else {
    			var canvas = document.createElement('canvas');
    			canvas.width = element.clientWidth || element.width;
    			canvas.height = element.clientHeight || element.height;

    			var ctx = canvas.getContext('2d');
    			if (!ctx) {
    				throw ('addImage requires canvas to be supported by browser.');
    			}
    			if (angle) {
    				var x, y, b, c, s, w, h, to_radians = Math.PI/180, angleInRadians;

    				if (typeof angle === 'object') {
    					x = angle.x;
    					y = angle.y;
    					b = angle.bg;
    					angle = angle.angle;
    				}
    				angleInRadians = angle*to_radians;
    				c = Math.abs(Math.cos(angleInRadians));
    				s = Math.abs(Math.sin(angleInRadians));
    				w = canvas.width;
    				h = canvas.height;
    				canvas.width = h * s + w * c;
    				canvas.height = h * c + w * s;

    				if (isNaN(x)) x = canvas.width / 2;
    				if (isNaN(y)) y = canvas.height / 2;

    				ctx.clearRect(0,0,canvas.width, canvas.height);
    				ctx.fillStyle = b || 'white';
    				ctx.fillRect(0, 0, canvas.width, canvas.height);
    				ctx.save();
    				ctx.translate(x, y);
    				ctx.rotate(angleInRadians);
    				ctx.drawImage(element, -(w/2), -(h/2));
    				ctx.rotate(-angleInRadians);
    				ctx.translate(-x, -y);
    				ctx.restore();
    			} else {
    				ctx.drawImage(element, 0, 0, canvas.width, canvas.height);
    			}
    		}
    		return canvas.toDataURL((''+format).toLowerCase() == 'png' ? 'image/png' : 'image/jpeg');
    	}
    	,checkImagesForAlias = function(alias, images) {
    		var cached_info;
    		if(images) {
    			for(var e in images) {
    				if(alias === images[e].alias) {
    					cached_info = images[e];
    					break;
    				}
    			}
    		}
    		return cached_info;
    	}
    	,determineWidthAndHeight = function(w, h, info) {
    		if (!w && !h) {
    			w = -96;
    			h = -96;
    		}
    		if (w < 0) {
    			w = (-1) * info['w'] * 72 / w / this.internal.scaleFactor;
    		}
    		if (h < 0) {
    			h = (-1) * info['h'] * 72 / h / this.internal.scaleFactor;
    		}
    		if (w === 0) {
    			w = h * info['w'] / info['h'];
    		}
    		if (h === 0) {
    			h = w * info['h'] / info['w'];
    		}

    		return [w, h];
    	}
    	, writeImageToPDF = function(x, y, w, h, info, index, images) {
    		var dims = determineWidthAndHeight.call(this, w, h, info),
    			coord = this.internal.getCoordinateString,
    			vcoord = this.internal.getVerticalCoordinateString;

    		w = dims[0];
    		h = dims[1];

    		images[index] = info;

    		this.internal.write(
    			'q'
    			, coord(w)
    			, '0 0'
    			, coord(h) // TODO: check if this should be shifted by vcoord
    			, coord(x)
    			, vcoord(y + h)
    			, 'cm /I'+info['i']
    			, 'Do Q'
    		)
    	};

    	/**
    	 * 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
    	 */
    	jsPDFAPI.image_compression = {
    		NONE: 'NONE',
    		FAST: 'FAST',
    		MEDIUM: 'MEDIUM',
    		SLOW: 'SLOW'
    	};

    	jsPDFAPI.sHashCode = function(str) {
    		return Array.prototype.reduce && str.split("").reduce(function(a,b){a=((a<<5)-a)+b.charCodeAt(0);return a&a},0);
    	};

    	jsPDFAPI.isString = function(object) {
    		return typeof object === 'string';
    	};

    	/**
    	 * Strips out and returns info from a valid base64 data URI
    	 * @param {String[dataURI]} a valid data URI of format 'data:[<MIME-type>][;base64],<data>'
    	 * @returns 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>
    	 */
    	jsPDFAPI.extractInfoFromBase64DataURI = function(dataURI) {
    		return /^data:([\w]+?\/([\w]+?));base64,(.+?)$/g.exec(dataURI);
    	};

    	/**
    	 * Check to see if ArrayBuffer is supported
    	 */
    	jsPDFAPI.supportsArrayBuffer = function() {
    		return typeof ArrayBuffer !== 'undefined' && typeof Uint8Array !== 'undefined';
    	};

    	/**
    	 * Tests supplied object to determine if ArrayBuffer
    	 * @param {Object[object]}
    	 */
    	jsPDFAPI.isArrayBuffer = function(object) {
    		if(!this.supportsArrayBuffer())
    	        return false;
    		return object instanceof ArrayBuffer;
    	};

    	/**
    	 * Tests supplied object to determine if it implements the ArrayBufferView (TypedArray) interface
    	 * @param {Object[object]}
    	 */
    	jsPDFAPI.isArrayBufferView = function(object) {
    		if(!this.supportsArrayBuffer())
    	        return false;
    		if(typeof Uint32Array === 'undefined')
    			return false;
    		return (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 );
    	};

    	/**
    	 * Exactly what it says on the tin
    	 */
    	jsPDFAPI.binaryStringToUint8Array = function(binary_string) {
    		/*
    		 * not sure how efficient this will be will bigger files. Is there a native method?
    		 */
    		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;
    	};

    	/**
    	 * @see this discussion
    	 * http://stackoverflow.com/questions/6965107/converting-between-strings-and-arraybuffers
    	 *
    	 * As stated, i imagine the method below is highly inefficent for large files.
    	 *
    	 * Also of note from Mozilla,
    	 *
    	 * "However, this is slow and error-prone, due to the need for multiple conversions (especially if the binary data is not actually byte-format data, but, for example, 32-bit integers or floats)."
    	 *
    	 * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/StringView
    	 *
    	 * Although i'm strugglig to see how StringView solves this issue? Doesn't appear to be a direct method for conversion?
    	 *
    	 * Async method using Blob and FileReader could be best, but i'm not sure how to fit it into the flow?
    	 */
    	jsPDFAPI.arrayBufferToBinaryString = function(buffer) {
    		/*if('TextDecoder' in window){
    			var decoder = new TextDecoder('ascii');
    			return decoder.decode(buffer);
    		}*/

    		if(this.isArrayBuffer(buffer))
    			buffer = new Uint8Array(buffer);

    	    var binary_string = '';
    	    var len = buffer.byteLength;
    	    for (var i = 0; i < len; i++) {
    	        binary_string += String.fromCharCode(buffer[i]);
    	    }
    	    return binary_string;
    	    /*
    	     * Another solution is the method below - convert array buffer straight to base64 and then use atob
    	     */
    		//return atob(this.arrayBufferToBase64(buffer));
    	};

    	/**
    	 * Converts an ArrayBuffer directly to base64
    	 *
    	 * Taken from here
    	 *
    	 * http://jsperf.com/encoding-xhr-image-data/31
    	 *
    	 * Need to test if this is a better solution for larger files
    	 *
    	 */
    	jsPDFAPI.arrayBufferToBase64 = function(arrayBuffer) {
    		var base64    = ''
    		var encodings = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'

    		var bytes         = new Uint8Array(arrayBuffer)
    		var byteLength    = bytes.byteLength
    		var byteRemainder = byteLength % 3
    		var mainLength    = byteLength - byteRemainder

    		var a, b, c, d
    		var chunk

    		// Main loop deals with bytes in chunks of 3
    		for (var i = 0; i < mainLength; i = i + 3) {
    			// Combine the three bytes into a single integer
    			chunk = (bytes[i] << 16) | (bytes[i + 1] << 8) | bytes[i + 2]

    			// Use bitmasks to extract 6-bit segments from the triplet
    			a = (chunk & 16515072) >> 18 // 16515072 = (2^6 - 1) << 18
    			b = (chunk & 258048)   >> 12 // 258048   = (2^6 - 1) << 12
    			c = (chunk & 4032)     >>  6 // 4032     = (2^6 - 1) << 6
    			d = chunk & 63               // 63       = 2^6 - 1

    			// Convert the raw binary segments to the appropriate ASCII encoding
    			base64 += encodings[a] + encodings[b] + encodings[c] + encodings[d]
    		}

    		// Deal with the remaining bytes and padding
    		if (byteRemainder == 1) {
    			chunk = bytes[mainLength]

    			a = (chunk & 252) >> 2 // 252 = (2^6 - 1) << 2

    			// Set the 4 least significant bits to zero
    			b = (chunk & 3)   << 4 // 3   = 2^2 - 1

    			base64 += encodings[a] + encodings[b] + '=='
    		} else if (byteRemainder == 2) {
    			chunk = (bytes[mainLength] << 8) | bytes[mainLength + 1]

    			a = (chunk & 64512) >> 10 // 64512 = (2^6 - 1) << 10
    			b = (chunk & 1008)  >>  4 // 1008  = (2^6 - 1) << 4

    			// Set the 2 least significant bits to zero
    			c = (chunk & 15)    <<  2 // 15    = 2^4 - 1

    			base64 += encodings[a] + encodings[b] + encodings[c] + '='
    		}

    		return base64
    	};

    	jsPDFAPI.createImageInfo = function(data, wd, ht, cs, bpc, f, imageIndex, alias, dp, trns, pal, smask, p) {
    		var info = {
    				alias:alias,
    				w : wd,
    				h : ht,
    				cs : cs,
    				bpc : bpc,
    				i : imageIndex,
    				data : data
    				// n: objectNumber will be added by putImage code
    			};

    		if(f) info.f = f;
    		if(dp) info.dp = dp;
    		if(trns) info.trns = trns;
    		if(pal) info.pal = pal;
    		if(smask) info.smask = smask;
    		if(p) info.p = p;// predictor parameter for PNG compression

    		return info;
    	};

    	jsPDFAPI.addImage = function(imageData, format, x, y, w, h, alias, compression, rotation) {
    		'use strict'

    		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;
    			x = options.x || x || 0;
    			y = options.y || y || 0;
    			w = options.w || w;
    			h = options.h || h;
    			alias = options.alias || alias;
    			compression = options.compression || compression;
    			rotation = options.rotation || options.angle || rotation;
    		}

    		if (isNaN(x) || isNaN(y))
    		{
    			console.error('jsPDF.addImage: Invalid coordinates', arguments);
    			throw new Error('Invalid coordinates passed to jsPDF.addImage');
    		}

    		var images = getImages.call(this), info;

    		if (!(info = checkImagesForAlias(imageData, images))) {
    			var dataAsBinaryString;

    			if(isDOMElement(imageData))
    				imageData = createDataURIFromElement(imageData, format, rotation);

    			if(notDefined(alias))
    				alias = generateAliasFromData(imageData);

    			if (!(info = checkImagesForAlias(alias, images))) {

    				if(this.isString(imageData)) {

    					var base64Info = this.extractInfoFromBase64DataURI(imageData);

    					if(base64Info) {

    						format = base64Info[2];
    						imageData = atob(base64Info[3]);//convert to binary string

    					} else {

    						if (imageData.charCodeAt(0) === 0x89 &&
    							imageData.charCodeAt(1) === 0x50 &&
    							imageData.charCodeAt(2) === 0x4e &&
    							imageData.charCodeAt(3) === 0x47  )  format = 'png';
    					}
    				}
    				format = (format || 'JPEG').toLowerCase();

    				if(doesNotSupportImageType(format))
    					throw new Error('addImage currently only supports formats ' + supported_image_types + ', not \''+format+'\'');

    				if(processMethodNotEnabled(format))
    					throw new Error('please ensure that the plugin for \''+format+'\' support is added');

    				/**
    				 * need to test if it's more efficient to convert all binary strings
    				 * to TypedArray - or should we just leave and process as string?
    				 */
    				if(this.supportsArrayBuffer()) {
    					// no need to convert if imageData is already uint8array
    					if(!(imageData instanceof Uint8Array)){
    						dataAsBinaryString = imageData;
    						imageData = this.binaryStringToUint8Array(imageData);
    					}
    				}

    				info = this['process' + format.toUpperCase()](
    					imageData,
    					getImageIndex(images),
    					alias,
    					checkCompressValue(compression),
    					dataAsBinaryString
    				);

    				if(!info)
    					throw new Error('An unkwown error occurred whilst processing the image');
    			}
    		}

    		writeImageToPDF.call(this, x, y, w, h, info, info.i, images);

    		return this
    	};

    	/**
    	 * JPEG SUPPORT
    	 **/

    	//takes a string imgData containing the raw bytes of
    	//a jpeg image and returns [width, height]
    	//Algorithm from: http://www.64lines.com/jpeg-width-height
    	var getJpegSize = function(imgData) {
    		'use strict'
    		var width, height, numcomponents;
    		// Verify we have a valid jpeg header 0xff,0xd8,0xff,0xe0,?,?,'J','F','I','F',0x00
    		if (!imgData.charCodeAt(0) === 0xff ||
    			!imgData.charCodeAt(1) === 0xd8 ||
    			!imgData.charCodeAt(2) === 0xff ||
    			!imgData.charCodeAt(3) === 0xe0 ||
    			!imgData.charCodeAt(6) === 'J'.charCodeAt(0) ||
    			!imgData.charCodeAt(7) === 'F'.charCodeAt(0) ||
    			!imgData.charCodeAt(8) === 'I'.charCodeAt(0) ||
    			!imgData.charCodeAt(9) === 'F'.charCodeAt(0) ||
    			!imgData.charCodeAt(10) === 0x00) {
    				throw new Error('getJpegSize requires a binary string jpeg file')
    		}
    		var blockLength = imgData.charCodeAt(4)*256 + imgData.charCodeAt(5);
    		var i = 4, len = imgData.length;
    		while ( i < len ) {
    			i += blockLength;
    			if (imgData.charCodeAt(i) !== 0xff) {
    				throw new Error('getJpegSize could not find the size of the image');
    			}
    			if (imgData.charCodeAt(i+1) === 0xc0 || //(SOF) Huffman  - Baseline DCT
    			    imgData.charCodeAt(i+1) === 0xc1 || //(SOF) Huffman  - Extended sequential DCT
    			    imgData.charCodeAt(i+1) === 0xc2 || // Progressive DCT (SOF2)
    			    imgData.charCodeAt(i+1) === 0xc3 || // Spatial (sequential) lossless (SOF3)
    			    imgData.charCodeAt(i+1) === 0xc4 || // Differential sequential DCT (SOF5)
    			    imgData.charCodeAt(i+1) === 0xc5 || // Differential progressive DCT (SOF6)
    			    imgData.charCodeAt(i+1) === 0xc6 || // Differential spatial (SOF7)
    			    imgData.charCodeAt(i+1) === 0xc7) {
    				height = imgData.charCodeAt(i+5)*256 + imgData.charCodeAt(i+6);
    				width = imgData.charCodeAt(i+7)*256 + imgData.charCodeAt(i+8);
                    numcomponents = imgData.charCodeAt(i+9);
    				return [width, height, numcomponents];
    			} else {
    				i += 2;
    				blockLength = imgData.charCodeAt(i)*256 + imgData.charCodeAt(i+1)
    			}
    		}
    	}
    	, getJpegSizeFromBytes = function(data) {

    		var hdr = (data[0] << 8) | data[1];

    		if(hdr !== 0xFFD8)
    			throw new Error('Supplied data is not a JPEG');

    		var len = data.length,
    			block = (data[4] << 8) + data[5],
    			pos = 4,
    			bytes, width, height, numcomponents;

    		while(pos < len) {
    			pos += block;
    			bytes = readBytes(data, pos);
    			block = (bytes[2] << 8) + bytes[3];
    			if((bytes[1] === 0xC0 || bytes[1] === 0xC2) && bytes[0] === 0xFF && block > 7) {
    				bytes = readBytes(data, pos + 5);
    				width = (bytes[2] << 8) + bytes[3];
    				height = (bytes[0] << 8) + bytes[1];
                    numcomponents = bytes[4];
    				return {width:width, height:height, numcomponents: numcomponents};
    			}

    			pos+=2;
    		}

    		throw new Error('getJpegSizeFromBytes could not find the size of the image');
    	}
    	, readBytes = function(data, offset) {
    		return data.subarray(offset, offset+ 5);
    	};

    	jsPDFAPI.processJPEG = function(data, index, alias, compression, dataAsBinaryString) {
    		'use strict'
    		var colorSpace = this.color_spaces.DEVICE_RGB,
    			filter = this.decode.DCT_DECODE,
    			bpc = 8,
    			dims;

    		if(this.isString(data)) {
    			dims = getJpegSize(data);
    			return this.createImageInfo(data, dims[0], dims[1], dims[3] == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
    		}

    		if(this.isArrayBuffer(data))
    			data = new Uint8Array(data);

    		if(this.isArrayBufferView(data)) {

    			dims = getJpegSizeFromBytes(data);

    			// if we already have a stored binary string rep use that
    			data = dataAsBinaryString || this.arrayBufferToBinaryString(data);

    			return this.createImageInfo(data, dims.width, dims.height, dims.numcomponents == 1 ? this.color_spaces.DEVICE_GRAY:colorSpace, bpc, filter, index, alias);
    		}

    		return null;
    	};

    	jsPDFAPI.processJPG = function(/*data, index, alias, compression, dataAsBinaryString*/) {
    		return this.processJPEG.apply(this, arguments);
    	}

    })(jsPDF.API);