bv7_jpeg2array

jpeg -> array

Tính đến 14-03-2018. Xem phiên bản mới nhất.

Script này sẽ không được không được cài đặt trực tiếp. Nó là một thư viện cho các script khác để bao gồm các chỉ thị meta // @require https://update.greatest.deepsurf.us/scripts/39479/258532/bv7_jpeg2array.js

// ==UserScript==
// @name         bv7_jpeg2array
// @namespace    bv7
// @version      0.7
// @description  jpeg -> array
// @author       bv7
// @grant        GM_xmlhttpRequest
// ==/UserScript==

class BaseImage {
	constructor() {
		this._src   = null;
		this.onload = (() => '');
	}
	load() {
		GM_xmlhttpRequest({
			method          : 'GET',
			url             : this._src,
			overrideMimeType: 'text/plain; charset=x-user-defined',
			onload          : (v) => {
				let data = new Uint8Array(v.responseText.length);
				data.forEach((val, i) => data[i] = v.responseText.charCodeAt(i));
				this.parse(data);
				this.onload();
			}
		});		
	}
	get src() {return this._src;}
	set src(v) {
		if (this._src !== v) {
			this._src = v;
			this.load();
		}
    }
	drawCanvas(canvas, dx, dy, dWidth, dHeight, sx, sy, sWidth, sHeight) {
		let context = canvas.getContext('2d');
		let imageData = context.getImageData(0, 0, canvas.width, canvas.height);
		this.copyToImageData(imageData);
		context.putImageData(imageData, 0, 0);
		
	}
}

class JpegImage extends BaseImage {
	parse(data) {
		this.jfif  = null;
		this.adobe = null;
		const dctZigZag = new Int32Array([
			 0,
			 1,  8,
			16,  9,  2,
			 3, 10, 17, 24,
			32, 25, 18, 11, 4,
			 5, 12, 19, 26, 33, 40,
			48, 41, 34, 27, 20, 13,  6,
			 7, 14, 21, 28, 35, 42, 49, 56,
			57, 50, 43, 36, 29, 22, 15,
			23, 30, 37, 44, 51, 58,
			59, 52, 45, 38, 31,
			39, 46, 53, 60,
			61, 54, 47,
			55, 62,
			63
		]);
		const dctCos1    = 4017; // cos(  pi/16)
		const dctSin1    =  799; // sin(  pi/16)
		const dctCos3    = 3406; // cos(3*pi/16)
		const dctSin3    = 2276; // sin(3*pi/16)
		const dctCos6    = 1567; // cos(6*pi/16)
		const dctSin6    = 3784; // sin(6*pi/16)
		const dctSqrt2   = 5793; // sqrt(2)
		const dctSqrt1d2 = 2896; // sqrt(2) / 2
		let frame;
		let resetInterval;
		let quantizationTables = [];
		let frames             = [];
		let huffmanTablesAC    = [];
		let huffmanTablesDC    = [];
		let offset             = 0;
		//let readUint8  = () => data[offset++];
		let readUint16 = () => ((data[offset++] << 8) | data[offset++]);
		let readDataBlock = () => {
			let length = readUint16() - 2;
			let value = data.slice(offset, offset + length - 2);
			offset += length;
			return value;
		};
		let buildHuffmanTable = (codeLengths, values) => {
			let length = codeLengths.length;
			while (length > 0 && !codeLengths[length - 1]) length--;
			let p = {children: [], index: 0};
			let code = [p];
			let i, j, k, q, codeLength;
			for (i = 0, k = 0; i < length; i++) {
				codeLength = codeLengths[i];
				for (j = 0; j < codeLength; j++, k++) {
					p = code.pop();
					p.children[p.index] = values[k];
					while (p.index > 0) p = code.pop();
					p.index++;
					code.push(p);
					while (code.length <= i) {
						code.push(q = {children: [], index: 0});
						p.children[p.index] = q.children;
						p = q;
					}
				}
				if (i + 1 < length) { // p here points to last code
					code.push(q = {children: [], index: 0});
					p.children[p.index] = q.children;
					p = q;
				}
			}
			return code[0].children;
		};
		let buildComponentData = (component) => {
			let lines          = [];
			let samplesPerLine = component.blocksPerLine << 3;
			let R              = new Int32Array(64);
			let r              = new Uint8Array(64);
			// A port of poppler's IDCT method which in turn is taken from:
			//   Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
			//   "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
			//   IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989,
			//   988-991.
			let quantizeAndInverse = (zz, dataOut, dataIn) => {
				let v, u, i;
				let v0, v1, v2, v3, v4, v5, v6, v7;
				let u0, u1, u2, u3, u4, u5, u6, u7;
				// dequant
				for (i = 0; i < 64; i++) dataIn[i] = zz[i] * component.quantizationTable[i];
				for (i = 0; i < 64; i += 8) {
					v0 = dataIn[i    ];
					v1 = dataIn[i + 1];
					v2 = dataIn[i + 2];
					v3 = dataIn[i + 3];
					v4 = dataIn[i + 4];
					v5 = dataIn[i + 5];
					v6 = dataIn[i + 6];
					v7 = dataIn[i + 7];
					// check for all-zero AC coefficients
					if (
						v1 == 0 &&
						v2 == 0 &&
						v3 == 0 &&
						v4 == 0 &&
						v5 == 0 &&
						v6 == 0 &&
						v7 == 0
					) dataIn[i    ] =
						dataIn[i + 1] =
						dataIn[i + 2] =
						dataIn[i + 3] =
						dataIn[i + 4] =
						dataIn[i + 5] =
						dataIn[i + 6] =
						dataIn[i + 7] = (dctSqrt2 * v0 + 512) >> 10;
					else {
						// stage 4
						u0 = (dctSqrt2 * v0 + 128) >> 8;
						u1 = (dctSqrt2 * v4 + 128) >> 8;
						u4 = (dctSqrt1d2 * (v1 - v7) + 128) >> 8;
						u5 = v3 << 4;
						u6 = v5 << 4;
						u7 = (dctSqrt1d2 * (v1 + v7) + 128) >> 8;
						// stage 3
						v0 = (u0 + u1 + 1) >> 1;
						v1 = (u0 - u1 + 1) >> 1;
						u2 = (v2 * dctCos6 - v6 * dctSin6 + 128) >> 8;
						v3 = (v2 * dctSin6 + v6 * dctCos6 + 128) >> 8;
						v4 = (u4 + u6 + 1) >> 1;
						v5 = (u7 - u5 + 1) >> 1;
						u3 = (u4 - u6 + 1) >> 1;
						v7 = (u7 + u5 + 1) >> 1;
						// stage 2
						u0 = (v0 + v3 + 1) >> 1;
						u1 = (v1 + u2 + 1) >> 1;
						v2 = (v1 - u2 + 1) >> 1;
						v6 = (v0 - v3 + 1) >> 1;
						u4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
						u5 = (v5 * dctCos1 - u3 * dctSin1 + 2048) >> 12;
						u6 = (v5 * dctSin1 + u3 * dctCos1 + 2048) >> 12;
						u7 = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
						// stage 1
						dataIn[i    ] = u0 + u7;
						dataIn[i + 1] = u1 + u6;
						dataIn[i + 2] = v2 + u5;
						dataIn[i + 3] = v6 + u4;
						dataIn[i + 4] = v6 - u4;
						dataIn[i + 5] = v2 - u5;
						dataIn[i + 6] = u1 - u6;
						dataIn[i + 7] = u0 - u7;
					}
				}
				// inverse DCT on columns
				for (i = 0; i < 8; ++i) {
					v0 = dataIn[i     ];
					v1 = dataIn[i +  8];
					v2 = dataIn[i + 16];
					v3 = dataIn[i + 24];
					v4 = dataIn[i + 32];
					v5 = dataIn[i + 40];
					v6 = dataIn[i + 48];
					v7 = dataIn[i + 56];
					// check for all-zero AC coefficients
					if (
						v1 == 0 &&
						v2 == 0 &&
						v3 == 0 &&
						v4 == 0 &&
						v5 == 0 &&
						v6 == 0 &&
						v7 == 0
					) dataIn[i] =
						dataIn[i +  8] =
						dataIn[i + 16] =
						dataIn[i + 24] =
						dataIn[i + 32] =
						dataIn[i + 40] =
						dataIn[i + 48] =
						dataIn[i + 56] = (dctSqrt2 * v0 + 8192) >> 14;
					else {
						// stage 4
						u0 = (dctSqrt2 * v0 + 2048) >> 12;
						u1 = (dctSqrt2 * v4 + 2048) >> 12;
						u4 = (dctSqrt1d2 * (v1 - v7) + 2048) >> 12;
						u7 = (dctSqrt1d2 * (v1 + v7) + 2048) >> 12;
						// stage 3
						v0 = (u0 + u1 + 1) >> 1;
						v1 = (u0 - u1 + 1) >> 1;
						u2 = (v2 * dctCos6 - v6 * dctSin6 + 2048) >> 12;
						u5 = (v2 * dctSin6 + v6 * dctCos6 + 2048) >> 12;
						v4 = (u4 + v5 + 1) >> 1;
						u6 = (u7 - v3 + 1) >> 1;
						u3 = (u4 - v5 + 1) >> 1;
						v7 = (u7 + v3 + 1) >> 1;
						// stage 2
						u0 = (v0 + u5 + 1) >> 1;
						u1 = (v1 + u2 + 1) >> 1;
						v2 = (v1 - u2 + 1) >> 1;
						v6 = (v0 - u5 + 1) >> 1;
						u4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12;
						v3 = (u6 * dctCos1 - u3 * dctSin1 + 2048) >> 12;
						v5 = (u6 * dctSin1 + u3 * dctCos1 + 2048) >> 12;
						u7 = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12;
						// stage 1
						dataIn[i     ] = u0 + u7;
						dataIn[i +  8] = u1 + v5;
						dataIn[i + 16] = v2 + v3;
						dataIn[i + 24] = v6 + u4;
						dataIn[i + 32] = v6 - u4;
						dataIn[i + 40] = v2 - v3;
						dataIn[i + 48] = u1 - v5;
						dataIn[i + 56] = u0 - u7;
					}
				}
				// convert to 8-bit integers
				let sample;
				for (i = 0; i < 64; i++) {
					sample = 128 + ((dataIn[i] + 8) >> 4);
					dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample;
				}
			};
			let scanLine, j, sample, line, samleI, scanLineJ;
			let blockRow;
			let blockCol;
			for (blockRow = 0; blockRow < component.blocksPerColumn; blockRow++) {
				scanLine = blockRow << 3;
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				lines.push(new Uint8Array(samplesPerLine));
				for (blockCol = 0; blockCol < component.blocksPerLine; blockCol++) {
					quantizeAndInverse(component.blocks[blockRow][blockCol], r, R);
					sample = blockCol << 3;
					for (j = 0, offset = 0, scanLineJ = scanLine; j < 8; j++) {
						line = lines[scanLineJ++];
						samleI = sample;
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
						line[samleI++] = r[offset++];
					}
				}
			}
			return lines;
		}
		let prepareComponents = (frame) => {
			let component, i, j, row;
			let blocksPerLineForMcu;
			let blocksPerColumnForMcu;
			frame.maxH = 0;
			frame.maxV = 0;
			frame.componentsOrder.forEach((v) => {
				component = frame.components[v];
				if (frame.maxH < component.h) frame.maxH = component.h;
				if (frame.maxV < component.v) frame.maxV = component.v;
			});
			frame.mcusPerLine   = Math.ceil(frame.samplesPerLine / 8 / frame.maxH);
			frame.mcusPerColumn = Math.ceil(frame.scanLines      / 8 / frame.maxV);
			frame.componentsOrder.forEach((v) => {
				component = frame.components[v];
	            component.blocksPerLine   = Math.ceil(Math.ceil(frame.samplesPerLine / 8) * component.h / frame.maxH);
	            component.blocksPerColumn = Math.ceil(Math.ceil(frame.scanLines      / 8) * component.v / frame.maxV);
				component.blocks          = [];
				blocksPerLineForMcu       = frame.mcusPerLine   * component.h;
				blocksPerColumnForMcu     = frame.mcusPerColumn * component.v;
				for (i = 0; i < blocksPerColumnForMcu; i++) {
					row = [];
					for (j = 0; j < blocksPerLineForMcu; j++) row.push(new Int32Array(64));
					component.blocks.push(row);
	            }
			});
		};
		let decodeScan = (components, resetInterval, spectralStart, spectralEnd, successivePrev, successive) => {
			let bitsData  = 0;
			let bitsCount = 0;
			let readBit = () => {
				if (bitsCount > 0) bitsCount--;
				else {
					bitsData = data[offset++];
					if (bitsData == 0xFF) {
						let nextByte = data[offset++];
						if (nextByte) throw new Error('Unexpected marker: ' + ((bitsData << 8) | nextByte).toString(16));
						// unstuff 0
					}
					bitsCount = 7;
				}
				return (bitsData >> bitsCount) & 1;
			};
			let decodeHuffman = (tree) => {
				let node = tree;
				let bit;
				while ((bit = readBit()) !== null) if (typeof (node = node[bit]) === 'number') return node;
				else if (typeof node !== 'object') throw new Error('Invalid huffman sequence');
				return null;
			};
			let receive = (length) => {
				let bit;
				let n = 0;
				while ((length--) > 0) if ((bit = readBit()) === null) return;
				else n = (n << 1) | bit;
				return n;
			};
			let receiveAndExtend = (length) => {
				let n = receive(length);
				return (n >= 1 << (length - 1)) ? n : (n + (-1 << length) + 1);
			};
			let decodeBaseline = (component, zz) => {
				let rs, s, r;
				let t = decodeHuffman(component.huffmanTableDC);
				let diff = t === 0 ? 0 : receiveAndExtend(t);
				zz[0] = (component.pred += diff);
				let k = 1;
				while (k < 64) {
					r  = (rs = decodeHuffman(component.huffmanTableAC)) >> 4;
					if ((s  = rs & 15) === 0) {
						if (r < 15) break;
						else {
							k += 16;
							continue;
						}
					} else {
						zz[dctZigZag[k += r]] = receiveAndExtend(s);
						k++;
					}
				}
			}
			let decodeDCFirst = (component, zz) => {
				let t = decodeHuffman(component.huffmanTableDC);
				zz[0] = (component.pred += t === 0 ? 0 : (receiveAndExtend(t) << successive));
			}
			let decodeDCSuccessive = (component, zz) => zz[0] |= readBit() << successive;
			let eobrun = 0;
			let decodeACFirst = (component, zz) => {
				if (eobrun > 0) eobrun--;
				else {
					let rs, s, r;
					let k = spectralStart;
					while (k <= spectralEnd) {
						r  = (rs = decodeHuffman(component.huffmanTableAC)) >> 4;
						if ((s  = rs & 15) === 0) {
							if (r < 15) {
								eobrun = receive(r) + (1 << r) - 1;
								break;
							} else k += 16;
						} else {
							zz[dctZigZag[k += r]] = receiveAndExtend(s) * (1 << successive);
							k++;
						}
					}
				}
			};
			let successiveACState     = 0;
			let successiveACNextValue = 0;
			let decodeACSuccessive = (component, zz) => {
				let z, direction, rs, s;
				let k = spectralStart;
				let r = 0;
				while(k <= spectralEnd) {
					direction = zz[z = dctZigZag[k]] < 0 ? -1 : 1;
					switch (successiveACState) {
						case 0: // initial state
							r = (rs = decodeHuffman(component.huffmanTableAC)) >> 4;
							if ((s  = rs & 15) === 0) {
								if (r < 15) {
									eobrun            = receive(r) + (1 << r);
									successiveACState = 4;
								} else {
									r = 16;
									successiveACState = 1;
								}
							} else if (s !== 1) throw new Error('Invalid ACn encoding');
							else {
								successiveACNextValue = receiveAndExtend(s);
								successiveACState     = r ? 2 : 3;
							}
							continue;
						case 1: // skipping r zero items
						case 2:
							if (zz[z]) zz[z] += (readBit() << successive) * direction;
							else if ((--r) === 0) successiveACState = successiveACState == 2 ? 3 : 0;
							break;
						case 3: // set value for a zero item
							if (zz[z]) zz[z] += (readBit() << successive) * direction;
							else {
								zz[z]             = successiveACNextValue << successive;
								successiveACState = 0;
							}
							break;
						case 4: // eob
							if (zz[z]) zz[z] += (readBit() << successive) * direction;
							break;
					}
					k++;
				}
				if (successiveACState === 4) {
					eobrun--;
					if (eobrun === 0) successiveACState = 0;
				}
			};
			let decodeMcu = (component, decode, mcu, row, col) => decode(
				component,
				component.blocks[((mcu / frame.mcusPerLine) | 0) * component.v + row][(mcu % frame.mcusPerLine) * component.h + col]
			);
			let decodeBlock = (component, decode, mcu) => decode(
				component,
				component.blocks[(mcu / component.blocksPerLine) | 0][mcu % component.blocksPerLine]
			);
			let decodeFn = frame.progressive ? (
				spectralStart === 0 ? (
					successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive
				) : (
					successivePrev === 0 ? decodeACFirst : decodeACSuccessive
				)
			) : decodeBaseline;
			let mcu         = 0;
			let mcuExpected = (components.length == 1) ? (
				components[0].blocksPerLine * components[0].blocksPerColumn
			) : (
				frame.mcusPerLine * frame.mcusPerColumn
			);
			if (!resetInterval) resetInterval = mcuExpected;
			let component, n, i, j, k, marker;
			while (mcu < mcuExpected) {
				// reset interval stuff
				components.forEach((v) => v.pred = 0);
				eobrun = 0;
				if (components.length == 1) {
					component = components[0];
					for (n = 0; n < resetInterval; n++, mcu++) decodeBlock(component, decodeFn, mcu);
				} else for (n = 0; n < resetInterval; n++) {
					for (i = 0; i < components.length; i++) {
						component = components[i];
						for (j = 0; j < component.v; j++) for (k = 0; k < component.h; k++) decodeMcu(component, decodeFn, mcu, j, k);
					}
					if ((++mcu) === mcuExpected) break; // If we've reached our expected MCU's, stop decoding
				}
				// find marker
				bitsCount = 0;
				marker    = readUint16();
				if (marker < 0xFFD0 || marker > 0xFFD7) { // !RSTx
					offset -= 2;
					if (marker < 0xFF00) throw new Error('Marker was not found');
					break;
				}
			}
		};
		let fileMarker = readUint16();
		if (fileMarker != 0xFFD8) throw new Error('SOI not found'); // SOI (Start of Image)
		//let length;
		while ((fileMarker = readUint16()) != 0xFFD9) { // EOI (End of image)
			switch(fileMarker) {
				case 0xFF00: break;
				case 0xFFE0: // APP0 (Application Specific)
				case 0xFFE1: // APP1
				case 0xFFE2: // APP2
				case 0xFFE3: // APP3
				case 0xFFE4: // APP4
				case 0xFFE5: // APP5
				case 0xFFE6: // APP6
				case 0xFFE7: // APP7
				case 0xFFE8: // APP8
				case 0xFFE9: // APP9
				case 0xFFEA: // APP10
				case 0xFFEB: // APP11
				case 0xFFEC: // APP12
				case 0xFFED: // APP13
				case 0xFFEE: // APP14
				case 0xFFEF: // APP15
				case 0xFFFE: // COM (Comment)
					let appData = readDataBlock();
					switch(fileMarker){
						case 0xFFE0:
							if (
								appData[0] === 0x4A &&
								appData[1] === 0x46 &&
								appData[2] === 0x49 &&
								appData[3] === 0x46 &&
								appData[4] === 0
							) this.jfif = { // 'JFIF\x00'
								version     : { major: appData[5], minor: appData[6] },
								densityUnits: appData[7],
								xDensity    : (appData[8 ] << 8) | appData[9 ],
								yDensity    : (appData[10] << 8) | appData[11],
								thumbWidth  : appData[12],
								thumbHeight : appData[13],
								thumbData   : appData.slice(14, 14 + 3 * appData[12] * appData[13] + 1)
							};
							break;
						// TODO APP1 - Exif
						case 0xFFEE:
							if (
								appData[0] === 0x41 &&
								appData[1] === 0x64 &&
								appData[2] === 0x6F &&
								appData[3] === 0x62 &&
								appData[4] === 0x65 &&
								appData[5] === 0
							) this.adobe = { // 'Adobe\x00'
								version      : appData[6],
								flags0       : (appData[7] << 8) | appData[8],
								flags1       : (appData[9] << 8) | appData[10],
								transformCode: appData[11]
							};
							break;
					}
					break;
				case 0xFFDB: // DQT (Define Quantization Tables)
					let quantizationTableSpec, tableData;
					let quantizationTablesLength = readUint16();
					let quantizationTablesEnd = quantizationTablesLength + offset - 2;
					while (offset < quantizationTablesEnd) {
						quantizationTableSpec = data[offset++];
						tableData = new Int32Array(64);
						switch(quantizationTableSpec >> 4){
							case 0: // 8 bit values
								tableData.forEach((v, i) => tableData[dctZigZag[i]] = data[offset++]);
								break;
							case 1: //16 bit
								tableData.forEach((v, i) => tableData[dctZigZag[i]] = readUint16());
								break;
							default:
								throw new Error('DQT: invalid table spec: ' + (quantizationTableSpec >> 4));
						}
						quantizationTables[quantizationTableSpec & 15] = tableData;
					}
					break;
				case 0xFFC0: // SOF0 (Start of Frame, Baseline DCT)
				case 0xFFC1: // SOF1 (Start of Frame, Extended DCT)
				case 0xFFC2: // SOF2 (Start of Frame, Progressive DCT)
					let b;
					readUint16(); // skip data length
					frame = {
						extended       : fileMarker === 0xFFC1,
						progressive    : fileMarker === 0xFFC2,
						precision      : data[offset++],
						scanLines      : readUint16(),
						samplesPerLine : readUint16(),
						components     : {},
						componentsOrder: new Uint8Array(data[offset++])
					};
					frame.componentsOrder.forEach((v, i) => {
						frame.components[frame.componentsOrder[i] = data[offset++]] = {
							h              : (b = data[offset++]) >> 4,
							v              : b & 15,
							quantizationIdx: data[offset++]
						};
					});
					prepareComponents(frame);
					frames.push(frame);
					break;
				case 0xFFC4: // DHT (Define Huffman Tables)
					let huffmanLength = readUint16() - 2;
					while (huffmanLength > 0) {
						let huffmanTableSpec = data[offset++];
						let codeLengths      = new Uint8Array(16);
						let codeLengthSum    = 0;
						codeLengths.forEach((v, i) => codeLengthSum += (codeLengths[i] = data[offset++]));
						let huffmanValues = new Uint8Array(codeLengthSum);
						huffmanValues.forEach((v, i) => huffmanValues[i] = data[offset++]);
						huffmanLength -= 1 + codeLengths.length + huffmanValues.length;
						((huffmanTableSpec >> 4) === 0 ? huffmanTablesDC : huffmanTablesAC)[huffmanTableSpec & 15] = buildHuffmanTable(codeLengths, huffmanValues);
					}
					break;
				case 0xFFDD: // DRI (Define Restart Interval)
					readUint16(); // skip data length
					resetInterval = readUint16();
					break;
				case 0xFFDA: // SOS (Start of Scan)
					readUint16(); // scanLength
					let components = [];
					for (let selectorsCount = data[offset++]; selectorsCount > 0; selectorsCount--) {
						let component = frame.components[data[offset++]];
						let tableSpec = data[offset++];
						component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
						component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
						components.push(component);
					}
					let spectralStart = data[offset++];
					let spectralEnd   = data[offset++];
					let successiveApproximation = data[offset++];
					decodeScan(
						components,
						resetInterval,
						spectralStart,
						spectralEnd,
						successiveApproximation >> 4,
						successiveApproximation & 15
					);
					break;
				case 0xFFFF: // Fill bytes
					if (data[offset] !== 0xFF) offset--; // Avoid skipping a valid marker.
					break;
				default:
					// could be incorrect encoding -- last 0xFF byte of the previous
					// block was eaten by the encoder
					if (data[offset - 3] == data[offset - 2] && data[offset - 2] >= 0xC0 && d1 <= 0xFE) {
						offset -= 3;
						break;
					}
					else throw new Error('Unknown JPEG marker ' + fileMarker.toString(16));
			}
		}
		if (frames.length != 1) throw new Error("Only single frame JPEGs supported");
		// set each frame's components quantization table
		let j;
		frames.forEach((v) => {
			for (j in v.components) {
				v.components[j].quantizationTable = quantizationTables[v.components[j].quantizationIdx];
				delete v.components[j].quantizationIdx;
			}
		});
		this.width      = frame.samplesPerLine;
		this.height     = frame.scanLines;
		this.components = [];
		frame.componentsOrder.forEach((v) => {
			let component = frame.components[v];
			this.components.push({
				lines : buildComponentData(component),
				scaleX: component.h / frame.maxH,
				scaleY: component.v / frame.maxV
			});
		});
	}
	clampTo8bit(a) {
		return a < 0 ? 0 : a > 255 ? 255 : a;
	}
	getData(width, height) {
		let scaleX = this.width  / width ;
		let scaleY = this.height / height;
		let data   = new Uint8Array(width * height * this.components.length);
		let offset = 0;
		// The default transform for three components is true
		// The adobe transform marker overrides any previous setting
		let colorTransform = (this.adobe && this.adobe.transformCode) ||
			(typeof this.colorTransform === 'undefined') ||
			!!this.colorTransform;
		let y, x;
		let component0Line, component1Line, component2Line, component3Line;
		let component0    , component1    , component2    , component3    ;
		let c0            , c1            , c2            , c3            ;
		switch (this.components.length) {
			case 1:
				component0 = this.components[0];
				for (y = 0; y < height; y++) {
					component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
					for (x = 0; x < width; x++) data[offset++] = component0Line[0 | (x * component0.scaleX * scaleX)]; // Y
				}
				break;
			case 2:
				component0 = this.components[0];
				component1 = this.components[1];
				for (y = 0; y < height; y++) {
					component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
					component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
					for (x = 0; x < width; x++) {
						data[offset++] = component0Line[0 | (x * component0.scaleX * scaleX)]; // Y
						data[offset++] = component1Line[0 | (x * component1.scaleX * scaleX)]; // Y
					}
				}
				break;
			case 3:
				component0 = this.components[0];
				component1 = this.components[1];
				component2 = this.components[2];
				for (y = 0; y < height; y++) {
					component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
					component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
					component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
					for (x = 0; x < width; x++) {
						c0 = component0Line[0 | (x * component0.scaleX * scaleX)]; // R / Y
						c1 = component1Line[0 | (x * component1.scaleX * scaleX)]; // G / Cb
						c2 = component2Line[0 | (x * component2.scaleX * scaleX)]; // B / Cr
						if (colorTransform) {
							data[offset++] = this.clampTo8bit(c0 + 1.402 * (c2 - 128));                               // R
							data[offset++] = this.clampTo8bit(c0 - 0.3441363 * (c1 - 128) - 0.71413636 * (c2 - 128)); // G
							data[offset++] = this.clampTo8bit(c0 + 1.772 * (c1 - 128));                               // B
						} else {
							data[offset++] = c0; // R
							data[offset++] = c1; // G
							data[offset++] = c2; // B
						}
					}
				}
				break;
			case 4:
				if (this.adobe) { // PDF might compress two component data in custom colorspace
					component0 = this.components[0];
					component1 = this.components[1];
					component2 = this.components[2];
					component3 = this.components[3];
					for (y = 0; y < height; y++) {
						component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
						component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
						component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
						component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
						for (x = 0; x < width; x++) {
							c0 = component0Line[0 | (x * component0.scaleX * scaleX)]; // C  / Y
							c1 = component1Line[0 | (x * component1.scaleX * scaleX)]; // M  / Cb
							c2 = component2Line[0 | (x * component2.scaleX * scaleX)]; // Ye / Cr
							c3 = component3Line[0 | (x * component3.scaleX * scaleX)]; // K  / K
							if (colorTransform) {
								data[offset++] = this.clampTo8bit(c0 + 1.402 * (c2 - 128));                               // C
								data[offset++] = this.clampTo8bit(c0 - 0.3441363 * (c1 - 128) - 0.71413636 * (c2 - 128)); // M
								data[offset++] = this.clampTo8bit(c0 + 1.772 * (c1 - 128));                               // Ye
							} else {
								data[offset++] = 255 - c0; // C
								data[offset++] = 255 - c1; // M
								data[offset++] = 255 - c2; // Ye
							}
							data[offset++] = 255 - c3;     // K
						}
					}
				}
				else throw new Error('Unsupported color mode (4 components)');
				break;
			default:
				throw new Error('Unsupported color mode');
		}
		return data;
    }
	copyToImageData(imageData) {
		let scaleX = this.width  / imageData.width ;
		let scaleY = this.height / imageData.height;
		let offset = 0;
		let j      = 0;
		// The default transform for three components is true
		// The adobe transform marker overrides any previous setting
		let colorTransform = (this.adobe && this.adobe.transformCode) ||
			(typeof this.colorTransform === 'undefined') ||
			!!this.colorTransform;
		let y, x;
		let component0Line, component1Line, component2Line, component3Line;
		let component0    , component1    , component2    , component3    ;
		let c0            , c1            , c2            , c3            ;
		switch (this.components.length) {
			case 1:
				component0 = this.components[0];
				for (y = 0; y < imageData.height; y++) {
					component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
					for (x = 0; x < imageData.width; x++) {
						imageData.data[j++] =
						imageData.data[j++] =
						imageData.data[j++] = component0Line[0 | (x * component0.scaleX * scaleX)]; // Y
						imageData.data[j++] = 255;
					}
				}
				break;
			case 3:
				component0 = this.components[0];
				component1 = this.components[1];
				component2 = this.components[2];
				for (y = 0; y < imageData.height; y++) {
					component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
					component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
					component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
					for (x = 0; x < imageData.width; x++) {
						c0 = component0Line[0 | (x * component0.scaleX * scaleX)]; // R / Y
						c1 = component1Line[0 | (x * component1.scaleX * scaleX)]; // G / Cb
						c2 = component2Line[0 | (x * component2.scaleX * scaleX)]; // B / Cr
						if (colorTransform) {
							imageData.data[j++] = this.clampTo8bit(c0 + 1.402 * (c2 - 128));                               // R
							imageData.data[j++] = this.clampTo8bit(c0 - 0.3441363 * (c1 - 128) - 0.71413636 * (c2 - 128)); // G
							imageData.data[j++] = this.clampTo8bit(c0 + 1.772 * (c1 - 128));                               // B
						} else {
							imageData.data[j++] = c0; // R
							imageData.data[j++] = c1; // G
							imageData.data[j++] = c2; // B
						}
						imageData.data[j++] = 255;
					}
				}
				break;
			case 4:
				if (this.adobe) { // PDF might compress two component data in custom colorspace
					component0 = this.components[0];
					component1 = this.components[1];
					component2 = this.components[2];
					component3 = this.components[3];
					for (y = 0; y < imageData.height; y++) {
						component0Line = component0.lines[0 | (y * component0.scaleY * scaleY)];
						component1Line = component1.lines[0 | (y * component1.scaleY * scaleY)];
						component2Line = component2.lines[0 | (y * component2.scaleY * scaleY)];
						component3Line = component3.lines[0 | (y * component3.scaleY * scaleY)];
						for (x = 0; x < imageData.width; x++) {
							c0 = component0Line[0 | (x * component0.scaleX * scaleX)]; // C  / Y
							c1 = component1Line[0 | (x * component1.scaleX * scaleX)]; // M  / Cb
							c2 = component2Line[0 | (x * component2.scaleX * scaleX)]; // Ye / Cr
							c3 = component3Line[0 | (x * component3.scaleX * scaleX)]; // K  / K
							if (colorTransform) {
								imageData.data[j++] = 0 | ((255 - this.clampTo8bit(c0 + 1.402     * (c2 - 128)                          )) * c3 / 255); // R
								imageData.data[j++] = 0 | ((255 - this.clampTo8bit(c0 - 0.3441363 * (c1 - 128) - 0.71413636 * (c2 - 128))) * c3 / 255); // G
								imageData.data[j++] = 0 | ((255 - this.clampTo8bit(c0 + 1.772     * (c1 - 128)                          )) * c3 / 255); // B
							} else {
								imageData.data[j++] = 0 | (c0 * c3 / 255); // R
								imageData.data[j++] = 0 | (c1 * c3 / 255); // G
								imageData.data[j++] = 0 | (c2 * c3 / 255); // B
							}
							imageData.data[j++] = 255;
						}
					}
				} else throw new Error('Unsupported color mode (4 components)');
				break;
			default:
				throw new Error('Unsupported color mode');
		}
		return imageData;
    }
}