UNPKG

image-in-browser

Version:

Package for encoding / decoding images, transforming images, applying filters, drawing primitives on images on the client side (no need for server Node.js)

551 lines 20.1 kB
import { InputBuffer } from '../../common/input-buffer.js'; import { LibError } from '../../error/lib-error.js'; import { JpegComponentData } from './jpeg-component-data.js'; import { JpegAdobe } from './jpeg-adobe.js'; import { JpegComponent } from './jpeg-component.js'; import { JpegFrame } from './jpeg-frame.js'; import { JpegHuffman } from './jpeg-huffman.js'; import { JpegInfo } from './jpeg-info.js'; import { JpegJfif } from './jpeg-jfif.js'; import { JpegQuantize } from './jpeg-quantize.js'; import { JpegScan } from './jpeg-scan.js'; import { ExifData } from '../../exif/exif-data.js'; import { ArrayUtils } from '../../common/array-utils.js'; import { JpegMarker } from './jpeg-marker.js'; import { HuffmanValue } from './huffman-value.js'; import { HuffmanParent } from './huffman-parent.js'; import { IccProfile } from '../../image/icc-profile.js'; import { IccProfileCompression } from '../../image/icc-profile-compression.js'; export class JpegData { constructor() { this._exifData = new ExifData(); this._quantizationTables = ArrayUtils.fill(JpegData.numQuantizationTables, undefined); this._frames = new Array(); this._huffmanTablesAC = []; this._huffmanTablesDC = []; this._components = new Array(); } get input() { return this._input; } get jfif() { return this._jfif; } get adobe() { return this._adobe; } get frame() { return this._frame; } get resetInterval() { return this._resetInterval; } get comment() { return this._comment; } get iccProfile() { return this._iccProfile; } get exifData() { return this._exifData; } get quantizationTables() { return this._quantizationTables; } get frames() { return this._frames; } get huffmanTablesAC() { return this._huffmanTablesAC; } get huffmanTablesDC() { return this._huffmanTablesDC; } get components() { return this._components; } get width() { return this._frame.samplesPerLine; } get height() { return this._frame.scanLines; } readMarkers() { let marker = this.nextMarker(); if (marker !== JpegMarker.soi) { throw new LibError('Start Of Image marker not found.'); } marker = this.nextMarker(); while (marker !== JpegMarker.eoi && !this._input.isEOS) { const block = this.readBlock(); switch (marker) { case JpegMarker.app0: case JpegMarker.app1: case JpegMarker.app2: case JpegMarker.app3: case JpegMarker.app4: case JpegMarker.app5: case JpegMarker.app6: case JpegMarker.app7: case JpegMarker.app8: case JpegMarker.app9: case JpegMarker.app10: case JpegMarker.app11: case JpegMarker.app12: case JpegMarker.app13: case JpegMarker.app14: case JpegMarker.app15: case JpegMarker.com: this.readAppData(marker, block); break; case JpegMarker.dqt: this.readDQT(block); break; case JpegMarker.sof0: case JpegMarker.sof1: case JpegMarker.sof2: this.readFrame(marker, block); break; case JpegMarker.sof3: case JpegMarker.sof5: case JpegMarker.sof6: case JpegMarker.sof7: case JpegMarker.jpg: case JpegMarker.sof9: case JpegMarker.sof10: case JpegMarker.sof11: case JpegMarker.sof13: case JpegMarker.sof14: case JpegMarker.sof15: throw new LibError(`Unhandled frame type ${marker.toString(16)}`); case JpegMarker.dht: this.readDHT(block); break; case JpegMarker.dri: this.readDRI(block); break; case JpegMarker.sos: this.readSOS(block); break; case 0xff: if (this._input.get(0) !== 0xff) { this._input.skip(-1); } break; default: if (this._input.get(-3) === 0xff && this._input.get(-2) >= 0xc0 && this._input.get(-2) <= 0xfe) { this._input.skip(-3); break; } if (marker !== 0) { throw new LibError(`Unknown JPEG marker ${marker.toString(16)}`); } break; } marker = this.nextMarker(); } } skipBlock() { const length = this._input.readUint16(); if (length < 2) { throw new LibError('Invalid Block'); } this._input.skip(length - 2); } validate(bytes) { this._input = new InputBuffer({ buffer: bytes, bigEndian: true, }); const soiCheck = this._input.peek(2); if (soiCheck.get(0) !== 0xff || soiCheck.get(1) !== 0xd8) { return false; } let marker = this.nextMarker(); if (marker !== JpegMarker.soi) { return false; } let hasSOF = false; let hasSOS = false; marker = this.nextMarker(); while (marker !== JpegMarker.eoi && !this._input.isEOS) { const sectionByteSize = this._input.readUint16(); if (sectionByteSize < 2) { break; } this._input.skip(sectionByteSize - 2); switch (marker) { case JpegMarker.sof0: case JpegMarker.sof1: case JpegMarker.sof2: hasSOF = true; break; case JpegMarker.sos: hasSOS = true; break; default: } marker = this.nextMarker(); } return hasSOF && hasSOS; } readInfo(bytes) { this._input = new InputBuffer({ buffer: bytes, bigEndian: true, }); let marker = this.nextMarker(); if (marker !== JpegMarker.soi) { return undefined; } const info = new JpegInfo(); let hasSOF = false; let hasSOS = false; marker = this.nextMarker(); while (marker !== JpegMarker.eoi && !this._input.isEOS) { switch (marker) { case JpegMarker.sof0: case JpegMarker.sof1: case JpegMarker.sof2: hasSOF = true; this.readFrame(marker, this.readBlock()); break; case JpegMarker.sos: hasSOS = true; this.skipBlock(); break; default: this.skipBlock(); break; } marker = this.nextMarker(); } if (this._frame !== undefined) { info.setSize(this._frame.samplesPerLine, this._frame.scanLines); info.numComponents = this._frame.components.size; this._frame = undefined; } this.frames.length = 0; return hasSOF && hasSOS ? info : undefined; } read(bytes) { this._input = new InputBuffer({ buffer: bytes, bigEndian: true, }); this.readMarkers(); if (this._frames.length !== 1) { throw new LibError('Only single frame JPEGs supported'); } if (this._frame !== undefined) { for (let i = 0; i < this._frame.componentsOrder.length; ++i) { const component = this._frame.components.get(this._frame.componentsOrder[i]); if (component !== undefined) { this.components.push(new JpegComponentData(component.hSamples, this._frame.maxHSamples, component.vSamples, this._frame.maxVSamples, JpegData.buildComponentData(component))); } } } } getImage() { return JpegQuantize.getImageFromJpeg(this); } static buildHuffmanTable(codeLengths, values) { let k = 0; const code = new Array(); let length = 16; while (length > 0 && codeLengths[length - 1] === 0) { length--; } code.push(new JpegHuffman()); let p = code[0]; for (let i = 0; i < length; i++) { for (let j = 0; j < codeLengths[i]; j++) { p = code.pop(); if (p.children.length <= p.index) { p.children.length = p.index + 1; } p.children[p.index] = new HuffmanValue(values[k]); while (p.index > 0) { p = code.pop(); } p.incrementIndex(); code.push(p); while (code.length <= i) { const q = new JpegHuffman(); code.push(q); if (p.children.length <= p.index) { p.children.length = p.index + 1; } p.children[p.index] = new HuffmanParent(q.children); p = q; } k++; } if (i + 1 < length) { const q = new JpegHuffman(); code.push(q); if (p.children.length <= p.index) { p.children.length = p.index + 1; } p.children[p.index] = new HuffmanParent(q.children); p = q; } } return code[0].children; } static buildComponentData(component) { const blocksPerLine = component.blocksPerLine; const blocksPerColumn = component.blocksPerColumn; const samplesPerLine = blocksPerLine << 3; const R = new Int32Array(64); const r = new Uint8Array(64); const lines = ArrayUtils.fill(blocksPerColumn * 8, undefined); let l = 0; for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { const scanLine = blockRow << 3; for (let i = 0; i < 8; i++) { lines[l++] = new Uint8Array(samplesPerLine); } for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { JpegQuantize.quantizeAndInverse(component.quantizationTable, component.blocks[blockRow][blockCol], r, R); let offset = 0; const sample = blockCol << 3; for (let j = 0; j < 8; j++) { const line = lines[scanLine + j]; for (let i = 0; i < 8; i++) { line[sample + i] = r[offset++]; } } } } return lines; } static toFix(val) { const fixedPoint = 20; const one = 1 << fixedPoint; return Math.trunc(val * one) & 0xffffffff; } readBlock() { const length = this._input.readUint16(); if (length < 2) { throw new LibError('Invalid Block'); } return this._input.readRange(length - 2); } nextMarker() { let c = 0; if (this._input.isEOS) { return c; } do { do { c = this._input.read(); } while (c !== 0xff && !this._input.isEOS); if (this._input.isEOS) { return c; } do { c = this._input.read(); } while (c === 0xff && !this._input.isEOS); } while (c === 0 && !this._input.isEOS); return c; } readIccProfile(block) { const iccProfileSignature = [ 0x49, 0x43, 0x43, 0x5f, 0x50, 0x52, 0x4f, 0x46, 0x49, 0x4c, 0x45, 0x00, ]; for (let i = 0; i < iccProfileSignature.length; i++) { const b = block.read(); if (b !== iccProfileSignature[i]) { return; } } const data = block.toUint8Array(); this._iccProfile = new IccProfile('ICC_PROFILE', IccProfileCompression.none, data); } readExifData(block) { const exifSignature = 0x45786966; const signature = block.readUint32(); if (signature !== exifSignature) { return; } if (block.readUint16() !== 0) { return; } this.exifData.read(block); } readAppData(marker, block) { const appData = block; if (marker === JpegMarker.app0) { if (appData.get(0) === 0x4a && appData.get(1) === 0x46 && appData.get(2) === 0x49 && appData.get(3) === 0x46 && appData.get(4) === 0) { const majorVersion = appData.get(5); const minorVersion = appData.get(6); const densityUnits = appData.get(7); const xDensity = (appData.get(8) << 8) | appData.get(9); const yDensity = (appData.get(10) << 8) | appData.get(11); const thumbWidth = appData.get(12); const thumbHeight = appData.get(13); const thumbSize = 3 * thumbWidth * thumbHeight; const thumbData = appData.subarray(14 + thumbSize, undefined, 14); this._jfif = new JpegJfif(thumbWidth, thumbHeight, majorVersion, minorVersion, densityUnits, xDensity, yDensity, thumbData); } } else if (marker === JpegMarker.app1) { this.readExifData(appData); } else if (marker === JpegMarker.app2) { this.readIccProfile(appData); } else if (marker === JpegMarker.app14) { if (appData.get(0) === 0x41 && appData.get(1) === 0x64 && appData.get(2) === 0x6f && appData.get(3) === 0x62 && appData.get(4) === 0x65 && appData.get(5) === 0) { const version = appData.get(6); const flags0 = (appData.get(7) << 8) | appData.get(8); const flags1 = (appData.get(9) << 8) | appData.get(10); const transformCode = appData.get(11); this._adobe = new JpegAdobe(version, flags0, flags1, transformCode); } } else if (marker === JpegMarker.com) { try { this._comment = appData.readStringUtf8(); } catch (_) { } } } readDQT(block) { while (!block.isEOS) { let n = block.read(); const prec = n >>> 4; n &= 0x0f; if (n >= JpegData.numQuantizationTables) { throw new LibError('Invalid number of quantization tables'); } if (this._quantizationTables[n] === undefined) { this._quantizationTables[n] = new Int16Array(64); } const tableData = this._quantizationTables[n]; if (tableData !== undefined) { for (let i = 0; i < JpegData.dctSize2; i++) { const tmp = prec !== 0 ? block.readUint16() : block.read(); tableData[JpegData.dctZigZag[i]] = tmp; } } } if (!block.isEOS) { throw new LibError('Bad length for DQT block'); } } readFrame(marker, block) { if (this._frame !== undefined) { throw new LibError('Duplicate JPG frame data found.'); } const extended = marker === JpegMarker.sof1; const progressive = marker === JpegMarker.sof2; const precision = block.read(); const scanLines = block.readUint16(); const samplesPerLine = block.readUint16(); const numComponents = block.read(); const components = new Map(); const componentsOrder = new Array(); for (let i = 0; i < numComponents; i++) { const componentId = block.read(); const x = block.read(); const h = (x >>> 4) & 15; const v = x & 15; const qId = block.read(); componentsOrder.push(componentId); const component = new JpegComponent(h, v, this._quantizationTables, qId); components.set(componentId, component); } this._frame = new JpegFrame(components, componentsOrder, extended, progressive, precision, scanLines, samplesPerLine); this._frame.prepare(); this.frames.push(this._frame); } readDHT(block) { while (!block.isEOS) { let index = block.read(); const bits = new Uint8Array(16); let count = 0; for (let j = 0; j < 16; j++) { bits[j] = block.read(); count += bits[j]; } const huffmanValues = block.readRange(count).toUint8Array(); let ht = []; if ((index & 0x10) !== 0) { index -= 0x10; ht = this._huffmanTablesAC; } else { ht = this._huffmanTablesDC; } if (ht.length <= index) { ht.length = index + 1; } ht[index] = JpegData.buildHuffmanTable(bits, huffmanValues); } } readDRI(block) { this._resetInterval = block.readUint16(); } readSOS(block) { const n = block.read(); if (n < 1 || n > JpegData.maxCompsInScan) { throw new LibError('Invalid SOS block'); } const components = new Array(); for (let i = 0; i < n; i++) { const id = block.read(); const c = block.read(); if (!this._frame.components.has(id)) { throw new LibError('Invalid Component in SOS block'); } const component = this._frame.components.get(id); if (component !== undefined) { const dcTableNumber = (c >>> 4) & 15; const acTableNumber = c & 15; if (dcTableNumber < this._huffmanTablesDC.length) { component.huffmanTableDC = this._huffmanTablesDC[dcTableNumber]; } if (acTableNumber < this._huffmanTablesAC.length) { component.huffmanTableAC = this._huffmanTablesAC[acTableNumber]; } components.push(component); } } const spectralStart = block.read(); const spectralEnd = block.read(); const successiveApproximation = block.read(); const ah = (successiveApproximation >>> 4) & 15; const al = successiveApproximation & 15; const scan = new JpegScan(this._input, this._frame, components, spectralStart, spectralEnd, ah, al, this._resetInterval); scan.decode(); } } JpegData.dctZigZag = [ 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, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, ]; JpegData.dctSize = 8; JpegData.dctSize2 = 64; JpegData.numQuantizationTables = 4; JpegData.numHuffmanTables = 4; JpegData.numArithTables = 16; JpegData.maxCompsInScan = 4; JpegData.maxSamplingFactor = 4; //# sourceMappingURL=jpeg-data.js.map