Greasy Fork

bv7_jpeg2array_b

jpeg -> array

目前为 2018-03-01 提交的版本。查看 最新版本

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.greasyfork.cloud/scripts/38665/255145/bv7_jpeg2array_b.js

// ==UserScript==
// @name         bv7_jpeg2array_b
// @namespace    bv7
// @version      0.6
// @description  jpeg -> array
// @author       bv7
// @include      file:///D:/projects/JSProjects/bv7bbc/bv7_bbc_dark/bv_dev_canvas*.html
// @run-at       document-idle
// @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.decode();
				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);
		//for (let x = dx; x < imageData; x++) for (let y = dy; y < dy + dHeight; y++) context.
		//context.putImageData(imageData, dx, dy, sx, sy, sWidth, sHeight);
		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 = () => ((readUint8() << 8) | readUint8());
		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];
			for (let i = 0, k = 0, q; i < length; i++) {
				for (let j = 0; j < codeLengths[i]; 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 t, v, u;
				// dequant
				dataIn.forEach((v, i) => dataIn[i] = zz[i] * component.quantizationTable[i]);
				for (let i = 0; i < 8; ++i) {
					let row = 8 * i;
					v = dataIn.slice(row, 8 + row);
					// check for all-zero AC coefficients
					if (v.every((val, i) => (!i || !val))) dataIn.fill((dctSqrt2 * v[0] + 512) >> 10, row, 8 + row);
					else {
						// stage 4
						u = [
							(dctSqrt2 * v[0] + 128) >> 8,
							(dctSqrt2 * v[4] + 128) >> 8,
							v[2],
							v[6],
							(dctSqrt1d2 * (v[1] - v[7]) + 128) >> 8,
							v[3] << 4,
							v[5] << 4,
							(dctSqrt1d2 * (v[1] + v[7]) + 128) >> 8
						];
						// stage 3
						v = [
							(u[0] + u[1] + 1) >> 1,
							(u[0] - u[1] + 1) >> 1,
							(u[2] * dctCos6 - u[3] * dctSin6 + 128) >> 8,
							(u[2] * dctSin6 + u[3] * dctCos6 + 128) >> 8,
							(u[4] + u[6] + 1) >> 1,
							(u[7] - u[5] + 1) >> 1,
							(u[4] - u[6] + 1) >> 1,
							(u[7] + u[5] + 1) >> 1
						];
						// stage 2
						u = [
							(v[0] + v[3] + 1) >> 1,
							(v[1] + v[2] + 1) >> 1,
							(v[1] - v[2] + 1) >> 1,
							(v[0] - v[3] + 1) >> 1,
							(v[4] * dctCos3 - v[7] * dctSin3 + 2048) >> 12,
							(v[5] * dctCos1 - v[6] * dctSin1 + 2048) >> 12,
							(v[5] * dctSin1 + v[6] * dctCos1 + 2048) >> 12,
							(v[4] * dctSin3 + v[7] * dctCos3 + 2048) >> 12
						];
						// stage 1
						for(let j = 0; j < 4; j++) {
							dataIn[0 + j + row] = u[j] + u[7 - j];
							dataIn[7 - j + row] = u[j] - u[7 - j];
						}
					}
				}
				// inverse DCT on columns
				for (let col = 0; col < 8; ++col) {
					for (let i = 0; i < 8; i++) v[i] = dataIn[i * 8 + col];
					// check for all-zero AC coefficients
					if (v.every((val, i) => (!i || !val))) {
						t = (dctSqrt2 * v[0] + 8192) >> 14;
						v.forEach((val, i) => dataIn[i * 8 + col] = t);
					} else {
						// stage 4
						u = [
							(dctSqrt2 * v[0] + 2048) >> 12,
							(dctSqrt2 * v[4] + 2048) >> 12,
							v[2],
							v[6],
							(dctSqrt1d2 * (v[1] - v[7]) + 2048) >> 12,
							v[3],
							v[5],
							(dctSqrt1d2 * (v[1] + v[7]) + 2048) >> 12
						];
						// stage 3
						v = [
							(u[0] + u[1] + 1) >> 1,
							(u[0] - u[1] + 1) >> 1,
							(u[2] * dctCos6 - u[3] * dctSin6 + 2048) >> 12,
							(u[2] * dctSin6 + u[3] * dctCos6 + 2048) >> 12,
							(u[4] + u[6] + 1) >> 1,
							(u[7] - u[5] + 1) >> 1,
							(u[4] - u[6] + 1) >> 1,
							(u[7] + u[5] + 1) >> 1
						];
						// stage 2
						u = [
							(v[0] + v[3] + 1) >> 1,
							(v[1] + v[2] + 1) >> 1,
							(v[1] - v[2] + 1) >> 1,
							(v[0] - v[3] + 1) >> 1,
							(v[4] * dctCos3 - v[7] * dctSin3 + 2048) >> 12,
							(v[5] * dctCos1 - v[6] * dctSin1 + 2048) >> 12,
							(v[5] * dctSin1 + v[6] * dctCos1 + 2048) >> 12,
							(v[4] * dctSin3 + v[7] * dctCos3 + 2048) >> 12
						];
						// stage 1
						for (let i = 0; i < 4; i++) {
							dataIn[(0 + i) * 8 + col] = u[i] + u[7 - i];
							dataIn[(7 - i) * 8 + col] = u[i] - u[7 - i];
						}
					}
				}
				// convert to 8-bit integers
				dataIn.forEach((val, i) => {
					let sample = 128 + ((val + 8) >> 4);
					dataOut[i] = sample < 0 ? 0 : sample > 0xFF ? 0xFF : sample;
				});
			};
			for (let blockRow = 0; blockRow < component.blocksPerColumn; blockRow++) {
				let scanLine = blockRow << 3;
				for (let i = 0; i < 8; i++) lines.push(new Uint8Array(samplesPerLine));
				for (let blockCol = 0; blockCol < component.blocksPerLine; blockCol++) {
					quantizeAndInverse(component.blocks[blockRow][blockCol], r, R);
					let sample = blockCol << 3;
					for (let j = 0, offset = 0; j < 8; j++) {
						let line = lines[scanLine + j];
						for (let i = 0; i < 8; i++, offset++) line[sample + i] = r[offset];
					}
				}
			}
			return lines;
		}
		let prepareComponents = (frame) => {
			frame.maxH = 0;
			frame.maxV = 0;
			frame.componentsOrder.forEach((v) => {
				let 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) => {
				let 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          = [];
				let blocksPerLineForMcu   = frame.mcusPerLine   * component.h;
				let blocksPerColumnForMcu = frame.mcusPerColumn * component.v;
				for (let i = 0; i < blocksPerColumnForMcu; i++) {
					let row = [];
					for (let 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 = readUint8();
					if (bitsData == 0xFF) {
						let nextByte = readUint8();
						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 n = 0;
				for (; length > 0; length--) {
					let bit = readBit();
					if (bit === null) return;
					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 t = decodeHuffman(component.huffmanTableDC);
				let diff = t === 0 ? 0 : receiveAndExtend(t);
				zz[0] = (component.pred += diff);
				let k = 1;
				while (k < 64) {
					let rs = decodeHuffman(component.huffmanTableAC);
					let s  = rs & 15;
					let r  = rs >> 4;
					if (s === 0) {
						if (r < 15) break;
						else {
							k += 16;
							continue;
						}
					} else {
						k += r;
						zz[dctZigZag[k]] = receiveAndExtend(s);
						k++;
					}
				}
			}
			let decodeDCFirst = (component, zz) => {
				let t    = decodeHuffman(component.huffmanTableDC);
				let diff = t === 0 ? 0 : (receiveAndExtend(t) << successive);
				zz[0]    = (component.pred += diff);
			}
			let decodeDCSuccessive = (component, zz) => zz[0] |= readBit() << successive;
			let eobrun = 0;
			let decodeACFirst = (component, zz) => {
				if (eobrun > 0) eobrun--;
				else {
					let k = spectralStart;
					while (k <= spectralEnd) {
						let rs = decodeHuffman(component.huffmanTableAC);
						let s  = rs & 15;
						let r  = rs >> 4;
						if (s === 0) {
							if (r < 15) {
								eobrun = receive(r) + (1 << r) - 1;
								break;
							} else k += 16;
						} else {
							k += r;
							zz[dctZigZag[k]] = receiveAndExtend(s) * (1 << successive);
							k++;
						}
					}
				}
			};
			let successiveACState     = 0;
			let successiveACNextValue = 0;
			let decodeACSuccessive = (component, zz) => {
				let k = spectralStart;
				let r = 0;
				while(k <= spectralEnd) {
					let z         = dctZigZag[k];
					let direction = zz[z] < 0 ? -1 : 1;
					switch (successiveACState) {
						case 0: // initial state
							let rs = decodeHuffman(component.huffmanTableAC);
							let s  = rs & 15;
							r = rs >> 4;
							if (s === 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 {
								r--;
								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;
			while (mcu < mcuExpected) {
				// reset interval stuff
				components.forEach((v) => v.pred = 0);
				eobrun = 0;
				if (components.length == 1) {
					let component = components[0];
					for (let n = 0; n < resetInterval; n++, mcu++) decodeBlock(component, decodeFn, mcu);
				} else for (let n = 0; n < resetInterval; n++) {
					for (let i = 0; i < components.length; i++) {
						let component = components[i];
						for (let j = 0; j < component.v; j++) for (let k = 0; k < component.h; k++) decodeMcu(component, decodeFn, mcu, j, k);
					}
					mcu++;
					// If we've reached our expected MCU's, stop decoding
					if (mcu === mcuExpected) break;
				}
				// find marker
				bitsCount = 0;
				let 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) // SOI (Start of Image)
			throw new Error('SOI not found');
		while ((fileMarker = readUint16()) != 0xFFD9) { // EOI (End of image)
			let length;
			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 quantizationTablesLength = readUint16();
					let quantizationTablesEnd = quantizationTablesLength + offset - 2;
					while (offset < quantizationTablesEnd) {
						let quantizationTableSpec = readUint8();
						let tableData = new Int32Array(64);
						switch(quantizationTableSpec >> 4){
							case 0: // 8 bit values
									tableData.forEach((v, i) => tableData[dctZigZag[i]] = readUint8());
								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)
					readUint16(); // skip data length
					frame = {
						extended       : fileMarker === 0xFFC1,
						progressive    : fileMarker === 0xFFC2,
						precision      : readUint8(),
						scanLines      : readUint16(),
						samplesPerLine : readUint16(),
						components     : {},
						componentsOrder: new Uint8Array(readUint8())
					};
					frame.componentsOrder.forEach((v, i) => {
						let componentId = readUint8();
						let b           = readUint8();
						frame.components[frame.componentsOrder[i] = componentId] = {
							h              : b >> 4,
							v              : b & 15,
							quantizationIdx: readUint8()
						};
					});
					prepareComponents(frame);
					frames.push(frame);
					break;
				case 0xFFC4: // DHT (Define Huffman Tables)
					let huffmanLength = readUint16() - 2;
					while (huffmanLength > 0) {
						let huffmanTableSpec = readUint8();
						let codeLengths      = new Uint8Array(16);
						let codeLengthSum    = 0;
						codeLengths.forEach((v, i) => codeLengthSum += (codeLengths[i] = readUint8()));
						let huffmanValues = new Uint8Array(codeLengthSum);
						huffmanValues.forEach((v, i) => huffmanValues[i] = readUint8());
						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 = readUint8(); selectorsCount > 0; selectorsCount--) {
						let component = frame.components[readUint8()];
						let tableSpec = readUint8();
						component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4];
						component.huffmanTableAC = huffmanTablesAC[tableSpec & 15];
						components.push(component);
					}
					let spectralStart = readUint8();
					let spectralEnd   = readUint8();
					let successiveApproximation = readUint8();
					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
		frames.forEach((v) => {
			for (var 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;
		if (this.components.length > 0 && this.components.length < 5) {
			let components4 = this.components.length == 4;
			// PDF might compress two component data in custom colorspace
			if (components4 && !this.adobe) throw new Error('Unsupported color mode (4 components)');
			else {
				// The default transform for three components is true
				// The adobe transform marker overrides any previous setting
				let colorTransform = this.components.length > 2 && (
					(this.adobe && this.adobe.transformCode) ||
					(typeof this.colorTransform === 'undefined') ||
					!!this.colorTransform
				);
				let componentLines = [];
				let d              = new Uint8Array(this.components.length);
				for (let y = 0; y < height; y++) {
					this.components.forEach((v, i) => (componentLines[i] = v.lines[0 | (y * v.scaleY * scaleY)]));
					for (let x = 0; x < width; x++) {
						componentLines.forEach((v, i) => (d[i] = v[0 | (x * this.components[i].scaleX * scaleX)])); // Y
						if (colorTransform) {
							data[offset++] = this.clampTo8bit(d[0] + 1.402 * (d[2] - 128));
							data[offset++] = this.clampTo8bit(d[0] - 0.3441363 * (d[1] - 128) - 0.71413636 * (d[2] - 128));
							data[offset++] = this.clampTo8bit(d[0] + 1.772 * (d[1] - 128));
							if (components4) data[offset++] = 255 - d[0];
						} else if (components4) d.forEach((v) => data[offset++] = 255 - v);
						else d.forEach((v) => data[offset++] = v);
					}
				}
			}
		} else throw new Error('Unsupported color mode');
		return data;
    }
	copyToImageData(imageData) {
		let data = this.getData(imageData.width, imageData.height);
		let i    = 0;
		let j    = 0;
		switch (this.components.length) {
			case 1:
				for (let y = 0; y < imageData.height; y++) for (let x = 0; x < imageData.width; x++) {
					imageData.data[j++] = imageData.data[j++] = imageData.data[j++] = data[i++]; // Y
					imageData.data[j++] = 255;
				}
				break;
			case 3:
				for (let y = 0; y < imageData.height; y++) for (let x = 0; x < imageData.width; x++) {
					imageData.data[j++] = data[i++]; // R
					imageData.data[j++] = data[i++]; // G
					imageData.data[j++] = data[i++]; // B
					imageData.data[j++] = 255;
				}
				break;
			case 4:
				for (let y = 0; y < imageData.height; y++) for (let x = 0; x < imageData.width; x++) {
					let C = data[i++];
					let M = data[i++];
					let Y = data[i++];
					let K = data[i++];
					imageData.data[j++] = 255 - this.clampTo8bit(C * (1 - K / 255) + K); // R
					imageData.data[j++] = 255 - this.clampTo8bit(M * (1 - K / 255) + K); // G
					imageData.data[j++] = 255 - this.clampTo8bit(Y * (1 - K / 255) + K); // B
					imageData.data[j++] = 255;
				}
				break;
			default:
				throw new Error('Unsupported color mode');
		}
	}
}

/*
let jpeg = new JpegImage();
jpeg.onload = function() {
	this.drawCanvas(document.getElementById('canvas'), 10, 10, 20, 20, 50, 50);
	console.log('jpeg =', this);
};
jpeg.src = document.getElementById('img1').src;
*/