From 7ca3a34e1f5d01beb0d58925844544beb1720ab9 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:12:35 +0200 Subject: [PATCH 1/9] Enable the `no-var` rule in the `src/core/jbig2.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/jbig2.js | 343 +++++++++++++++++++++++----------------------- 1 file changed, 175 insertions(+), 168 deletions(-) diff --git a/src/core/jbig2.js b/src/core/jbig2.js index db3301960..ef200d0e8 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { BaseException, shadow } from "../shared/util.js"; import { log2, readInt8, readUint16, readUint32 } from "./core_utils.js"; @@ -25,7 +24,7 @@ class Jbig2Error extends BaseException { } } -var Jbig2Image = (function Jbig2ImageClosure() { +const Jbig2Image = (function Jbig2ImageClosure() { // Utility data structures function ContextCache() {} @@ -46,11 +45,11 @@ var Jbig2Image = (function Jbig2ImageClosure() { DecodingContext.prototype = { get decoder() { - var decoder = new ArithmeticDecoder(this.data, this.start, this.end); + const decoder = new ArithmeticDecoder(this.data, this.start, this.end); return shadow(this, "decoder", decoder); }, get contextCache() { - var cache = new ContextCache(); + const cache = new ContextCache(); return shadow(this, "contextCache", cache); }, }; @@ -58,13 +57,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { // Annex A. Arithmetic Integer Decoding Procedure // A.2 Procedure for decoding values function decodeInteger(contextCache, procedure, decoder) { - var contexts = contextCache.getContexts(procedure); - var prev = 1; + const contexts = contextCache.getContexts(procedure); + let prev = 1; function readBits(length) { - var v = 0; - for (var i = 0; i < length; i++) { - var bit = decoder.readBit(contexts, prev); + let v = 0; + for (let i = 0; i < length; i++) { + const bit = decoder.readBit(contexts, prev); prev = prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; v = (v << 1) | bit; @@ -72,10 +71,10 @@ var Jbig2Image = (function Jbig2ImageClosure() { return v >>> 0; } - var sign = readBits(1); + const sign = readBits(1); // prettier-ignore /* eslint-disable no-nested-ternary */ - var value = readBits(1) ? + const value = readBits(1) ? (readBits(1) ? (readBits(1) ? (readBits(1) ? @@ -97,11 +96,11 @@ var Jbig2Image = (function Jbig2ImageClosure() { // A.3 The IAID decoding procedure function decodeIAID(contextCache, decoder, codeLength) { - var contexts = contextCache.getContexts("IAID"); + const contexts = contextCache.getContexts("IAID"); - var prev = 1; - for (var i = 0; i < codeLength; i++) { - var bit = decoder.readBit(contexts, prev); + let prev = 1; + for (let i = 0; i < codeLength; i++) { + const bit = decoder.readBit(contexts, prev); prev = (prev << 1) | bit; } if (codeLength < 31) { @@ -111,7 +110,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { } // 7.3 Segment types - var SegmentTypes = [ + const SegmentTypes = [ "SymbolDictionary", null, null, @@ -177,7 +176,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { "Extension", ]; - var CodingTemplates = [ + const CodingTemplates = [ [ { x: -1, y: -2 }, { x: 0, y: -2 }, @@ -230,7 +229,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { ], ]; - var RefinementTemplates = [ + const RefinementTemplates = [ { coding: [ { x: 0, y: -1 }, @@ -267,22 +266,22 @@ var Jbig2Image = (function Jbig2ImageClosure() { ]; // See 6.2.5.7 Decoding the bitmap. - var ReusedContexts = [ + const ReusedContexts = [ 0x9b25, // 10011 0110010 0101 0x0795, // 0011 110010 101 0x00e5, // 001 11001 01 0x0195, // 011001 0101 ]; - var RefinementReusedContexts = [ + const RefinementReusedContexts = [ 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) 0x0008, // '0000' + '001000' ]; function decodeBitmapTemplate0(width, height, decodingContext) { - var decoder = decodingContext.decoder; - var contexts = decodingContext.contextCache.getContexts("GB"); - var contextLabel, + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + let contextLabel, i, j, pixel, @@ -294,7 +293,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { // ...ooooo.... // ..ooooooo... Context template for current pixel (X) // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) - var OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 + const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 for (i = 0; i < height; i++) { row = bitmap[i] = new Uint8Array(width); @@ -366,8 +365,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { return decodeBitmapTemplate0(width, height, decodingContext); } - var useskip = !!skip; - var template = CodingTemplates[templateIndex].concat(at); + const useskip = !!skip; + const template = CodingTemplates[templateIndex].concat(at); // Sorting is non-standard, and it is not required. But sorting increases // the number of template bits that can be reused from the previous @@ -376,15 +375,15 @@ var Jbig2Image = (function Jbig2ImageClosure() { return a.y - b.y || a.x - b.x; }); - var templateLength = template.length; - var templateX = new Int8Array(templateLength); - var templateY = new Int8Array(templateLength); - var changingTemplateEntries = []; - var reuseMask = 0, + const templateLength = template.length; + const templateX = new Int8Array(templateLength); + const templateY = new Int8Array(templateLength); + const changingTemplateEntries = []; + let reuseMask = 0, minX = 0, maxX = 0, minY = 0; - var c, k; + let c, k; for (k = 0; k < templateLength; k++) { templateX[k] = template[k].x; @@ -405,11 +404,11 @@ var Jbig2Image = (function Jbig2ImageClosure() { changingTemplateEntries.push(k); } } - var changingEntriesLength = changingTemplateEntries.length; + const changingEntriesLength = changingTemplateEntries.length; - var changingTemplateX = new Int8Array(changingEntriesLength); - var changingTemplateY = new Int8Array(changingEntriesLength); - var changingTemplateBit = new Uint16Array(changingEntriesLength); + const changingTemplateX = new Int8Array(changingEntriesLength); + const changingTemplateY = new Int8Array(changingEntriesLength); + const changingTemplateBit = new Uint16Array(changingEntriesLength); for (c = 0; c < changingEntriesLength; c++) { k = changingTemplateEntries[c]; changingTemplateX[c] = template[k].x; @@ -418,27 +417,27 @@ var Jbig2Image = (function Jbig2ImageClosure() { } // Get the safe bounding box edges from the width, height, minX, maxX, minY - var sbb_left = -minX; - var sbb_top = -minY; - var sbb_right = width - maxX; + const sbb_left = -minX; + const sbb_top = -minY; + const sbb_right = width - maxX; - var pseudoPixelContext = ReusedContexts[templateIndex]; - var row = new Uint8Array(width); - var bitmap = []; + const pseudoPixelContext = ReusedContexts[templateIndex]; + let row = new Uint8Array(width); + const bitmap = []; - var decoder = decodingContext.decoder; - var contexts = decodingContext.contextCache.getContexts("GB"); + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); - var ltp = 0, + let ltp = 0, j, i0, j0, contextLabel = 0, bit, shift; - for (var i = 0; i < height; i++) { + for (let i = 0; i < height; i++) { if (prediction) { - var sltp = decoder.readBit(contexts, pseudoPixelContext); + const sltp = decoder.readBit(contexts, pseudoPixelContext); ltp ^= sltp; if (ltp) { bitmap.push(row); // duplicate previous row @@ -484,7 +483,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { } } } - var pixel = decoder.readBit(contexts, contextLabel); + const pixel = decoder.readBit(contexts, contextLabel); row[j] = pixel; } } @@ -503,53 +502,53 @@ var Jbig2Image = (function Jbig2ImageClosure() { at, decodingContext ) { - var codingTemplate = RefinementTemplates[templateIndex].coding; + let codingTemplate = RefinementTemplates[templateIndex].coding; if (templateIndex === 0) { codingTemplate = codingTemplate.concat([at[0]]); } - var codingTemplateLength = codingTemplate.length; - var codingTemplateX = new Int32Array(codingTemplateLength); - var codingTemplateY = new Int32Array(codingTemplateLength); - var k; + const codingTemplateLength = codingTemplate.length; + const codingTemplateX = new Int32Array(codingTemplateLength); + const codingTemplateY = new Int32Array(codingTemplateLength); + let k; for (k = 0; k < codingTemplateLength; k++) { codingTemplateX[k] = codingTemplate[k].x; codingTemplateY[k] = codingTemplate[k].y; } - var referenceTemplate = RefinementTemplates[templateIndex].reference; + let referenceTemplate = RefinementTemplates[templateIndex].reference; if (templateIndex === 0) { referenceTemplate = referenceTemplate.concat([at[1]]); } - var referenceTemplateLength = referenceTemplate.length; - var referenceTemplateX = new Int32Array(referenceTemplateLength); - var referenceTemplateY = new Int32Array(referenceTemplateLength); + const referenceTemplateLength = referenceTemplate.length; + const referenceTemplateX = new Int32Array(referenceTemplateLength); + const referenceTemplateY = new Int32Array(referenceTemplateLength); for (k = 0; k < referenceTemplateLength; k++) { referenceTemplateX[k] = referenceTemplate[k].x; referenceTemplateY[k] = referenceTemplate[k].y; } - var referenceWidth = referenceBitmap[0].length; - var referenceHeight = referenceBitmap.length; + const referenceWidth = referenceBitmap[0].length; + const referenceHeight = referenceBitmap.length; - var pseudoPixelContext = RefinementReusedContexts[templateIndex]; - var bitmap = []; + const pseudoPixelContext = RefinementReusedContexts[templateIndex]; + const bitmap = []; - var decoder = decodingContext.decoder; - var contexts = decodingContext.contextCache.getContexts("GR"); + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GR"); - var ltp = 0; - for (var i = 0; i < height; i++) { + let ltp = 0; + for (let i = 0; i < height; i++) { if (prediction) { - var sltp = decoder.readBit(contexts, pseudoPixelContext); + const sltp = decoder.readBit(contexts, pseudoPixelContext); ltp ^= sltp; if (ltp) { throw new Jbig2Error("prediction is not supported"); } } - var row = new Uint8Array(width); + const row = new Uint8Array(width); bitmap.push(row); - for (var j = 0; j < width; j++) { + for (let j = 0; j < width; j++) { var i0, j0; - var contextLabel = 0; + let contextLabel = 0; for (k = 0; k < codingTemplateLength; k++) { i0 = i + codingTemplateY[k]; j0 = j + codingTemplateX[k]; @@ -573,7 +572,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; } } - var pixel = decoder.readBit(contexts, contextLabel); + const pixel = decoder.readBit(contexts, contextLabel); row[j] = pixel; } } @@ -600,12 +599,12 @@ var Jbig2Image = (function Jbig2ImageClosure() { throw new Jbig2Error("symbol refinement with Huffman is not supported"); } - var newSymbols = []; - var currentHeight = 0; - var symbolCodeLength = log2(symbols.length + numberOfNewSymbols); + const newSymbols = []; + let currentHeight = 0; + let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); - var decoder = decodingContext.decoder; - var contextCache = decodingContext.contextCache; + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; let tableB1, symbolWidths; if (huffman) { tableB1 = getStandardTable(1); // standard table B.1 @@ -614,7 +613,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { } while (newSymbols.length < numberOfNewSymbols) { - var deltaHeight = huffman + const deltaHeight = huffman ? huffmanTables.tableDeltaHeight.decode(huffmanInput) : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 currentHeight += deltaHeight; @@ -622,7 +621,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { totalWidth = 0; const firstSymbol = huffman ? symbolWidths.length : 0; while (true) { - var deltaWidth = huffman + const deltaWidth = huffman ? huffmanTables.tableDeltaWidth.decode(huffmanInput) : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 if (deltaWidth === null) { @@ -633,7 +632,11 @@ var Jbig2Image = (function Jbig2ImageClosure() { var bitmap; if (refinement) { // 6.5.8.2 Refinement/aggregate-coded symbol bitmap - var numberOfInstances = decodeInteger(contextCache, "IAAI", decoder); + const numberOfInstances = decodeInteger( + contextCache, + "IAAI", + decoder + ); if (numberOfInstances > 1) { bitmap = decodeTextRegion( huffman, @@ -657,10 +660,14 @@ var Jbig2Image = (function Jbig2ImageClosure() { huffmanInput ); } else { - var symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); - var rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 - var rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 - var symbol = + const symbolId = decodeIAID( + contextCache, + decoder, + symbolCodeLength + ); + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + const symbol = symbolId < symbols.length ? symbols[symbolId] : newSymbols[symbolId - symbols.length]; @@ -749,12 +756,12 @@ var Jbig2Image = (function Jbig2ImageClosure() { } // 6.5.10 Exported symbols - var exportedSymbols = []; - var flags = [], + const exportedSymbols = []; + let flags = [], currentFlag = false; - var totalSymbolsLength = symbols.length + numberOfNewSymbols; + const totalSymbolsLength = symbols.length + numberOfNewSymbols; while (flags.length < totalSymbolsLength) { - var runLength = huffman + let runLength = huffman ? tableB1.decode(huffmanInput) : decodeInteger(contextCache, "IAEX", decoder); while (runLength--) { @@ -767,7 +774,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { exportedSymbols.push(symbols[i]); } } - for (var j = 0; j < numberOfNewSymbols; i++, j++) { + for (let j = 0; j < numberOfNewSymbols; i++, j++) { if (flags[i]) { exportedSymbols.push(newSymbols[j]); } @@ -801,37 +808,37 @@ var Jbig2Image = (function Jbig2ImageClosure() { } // Prepare bitmap - var bitmap = []; - var i, row; + const bitmap = []; + let i, row; for (i = 0; i < height; i++) { row = new Uint8Array(width); if (defaultPixelValue) { - for (var j = 0; j < width; j++) { + for (let j = 0; j < width; j++) { row[j] = defaultPixelValue; } } bitmap.push(row); } - var decoder = decodingContext.decoder; - var contextCache = decodingContext.contextCache; + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; - var stripT = huffman + let stripT = huffman ? -huffmanTables.tableDeltaT.decode(huffmanInput) : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 - var firstS = 0; + let firstS = 0; i = 0; while (i < numberOfSymbolInstances) { - var deltaT = huffman + const deltaT = huffman ? huffmanTables.tableDeltaT.decode(huffmanInput) : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 stripT += deltaT; - var deltaFirstS = huffman + const deltaFirstS = huffman ? huffmanTables.tableFirstS.decode(huffmanInput) : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 firstS += deltaFirstS; - var currentS = firstS; + let currentS = firstS; do { let currentT = 0; // 6.4.9 if (stripSize > 1) { @@ -839,23 +846,23 @@ var Jbig2Image = (function Jbig2ImageClosure() { ? huffmanInput.readBits(logStripSize) : decodeInteger(contextCache, "IAIT", decoder); } - var t = stripSize * stripT + currentT; - var symbolId = huffman + const t = stripSize * stripT + currentT; + const symbolId = huffman ? huffmanTables.symbolIDTable.decode(huffmanInput) : decodeIAID(contextCache, decoder, symbolCodeLength); - var applyRefinement = + const applyRefinement = refinement && (huffman ? huffmanInput.readBit() : decodeInteger(contextCache, "IARI", decoder)); - var symbolBitmap = inputSymbols[symbolId]; - var symbolWidth = symbolBitmap[0].length; - var symbolHeight = symbolBitmap.length; + let symbolBitmap = inputSymbols[symbolId]; + let symbolWidth = symbolBitmap[0].length; + let symbolHeight = symbolBitmap.length; if (applyRefinement) { - var rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 - var rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 - var rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 - var rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 + const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 symbolWidth += rdw; symbolHeight += rdh; symbolBitmap = decodeRefinement( @@ -870,8 +877,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { decodingContext ); } - var offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); - var offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); + const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); + const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); var s2, t2, symbolRow; if (transposed) { // Place Symbol Bitmap from T1,S1 @@ -883,7 +890,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { symbolRow = symbolBitmap[s2]; // To ignore Parts of Symbol bitmap which goes // outside bitmap region - var maxWidth = Math.min(width - offsetT, symbolWidth); + const maxWidth = Math.min(width - offsetT, symbolWidth); switch (combinationOperator) { case 0: // OR for (t2 = 0; t2 < maxWidth; t2++) { @@ -929,7 +936,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { currentS += symbolWidth - 1; } i++; - var deltaS = huffman + const deltaS = huffman ? huffmanTables.tableDeltaS.decode(huffmanInput) : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 if (deltaS === null) { @@ -1142,10 +1149,10 @@ var Jbig2Image = (function Jbig2ImageClosure() { } function readSegmentHeader(data, start) { - var segmentHeader = {}; + const segmentHeader = {}; segmentHeader.number = readUint32(data, start); - var flags = data[start + 4]; - var segmentType = flags & 0x3f; + const flags = data[start + 4]; + const segmentType = flags & 0x3f; if (!SegmentTypes[segmentType]) { throw new Jbig2Error("invalid segment type: " + segmentType); } @@ -1153,15 +1160,15 @@ var Jbig2Image = (function Jbig2ImageClosure() { segmentHeader.typeName = SegmentTypes[segmentType]; segmentHeader.deferredNonRetain = !!(flags & 0x80); - var pageAssociationFieldSize = !!(flags & 0x40); - var referredFlags = data[start + 5]; - var referredToCount = (referredFlags >> 5) & 7; - var retainBits = [referredFlags & 31]; - var position = start + 6; + const pageAssociationFieldSize = !!(flags & 0x40); + const referredFlags = data[start + 5]; + let referredToCount = (referredFlags >> 5) & 7; + const retainBits = [referredFlags & 31]; + let position = start + 6; if (referredFlags === 7) { referredToCount = readUint32(data, position - 1) & 0x1fffffff; position += 3; - var bytes = (referredToCount + 7) >> 3; + let bytes = (referredToCount + 7) >> 3; retainBits[0] = data[position++]; while (--bytes > 0) { retainBits.push(data[position++]); @@ -1178,8 +1185,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { } else if (segmentHeader.number <= 65536) { referredToSegmentNumberSize = 2; } - var referredTo = []; - var i, ii; + const referredTo = []; + let i, ii; for (i = 0; i < referredToCount; i++) { let number; if (referredToSegmentNumberSize === 1) { @@ -1206,13 +1213,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { // 7.2.7 Segment data length, unknown segment length if (segmentType === 38) { // ImmediateGenericRegion - var genericRegionInfo = readRegionSegmentInformation(data, position); - var genericRegionSegmentFlags = + const genericRegionInfo = readRegionSegmentInformation(data, position); + const genericRegionSegmentFlags = data[position + RegionSegmentInformationFieldLength]; - var genericRegionMmr = !!(genericRegionSegmentFlags & 1); + const genericRegionMmr = !!(genericRegionSegmentFlags & 1); // searching for the segment end - var searchPatternLength = 6; - var searchPattern = new Uint8Array(searchPatternLength); + const searchPatternLength = 6; + const searchPattern = new Uint8Array(searchPatternLength); if (!genericRegionMmr) { searchPattern[0] = 0xff; searchPattern[1] = 0xac; @@ -1222,7 +1229,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; searchPattern[5] = genericRegionInfo.height & 0xff; for (i = position, ii = data.length; i < ii; i++) { - var j = 0; + let j = 0; while (j < searchPatternLength && searchPattern[j] === data[i + j]) { j++; } @@ -1243,12 +1250,12 @@ var Jbig2Image = (function Jbig2ImageClosure() { } function readSegments(header, data, start, end) { - var segments = []; - var position = start; + const segments = []; + let position = start; while (position < end) { - var segmentHeader = readSegmentHeader(data, position); + const segmentHeader = readSegmentHeader(data, position); position = segmentHeader.headerEnd; - var segment = { + const segment = { header: segmentHeader, data, }; @@ -1263,7 +1270,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { } } if (header.randomAccess) { - for (var i = 0, ii = segments.length; i < ii; i++) { + for (let i = 0, ii = segments.length; i < ii; i++) { segments[i].start = position; position += segments[i].header.length; segments[i].end = position; @@ -1285,12 +1292,12 @@ var Jbig2Image = (function Jbig2ImageClosure() { var RegionSegmentInformationFieldLength = 17; function processSegment(segment, visitor) { - var header = segment.header; + const header = segment.header; - var data = segment.data, + let data = segment.data, position = segment.start, end = segment.end; - var args, at, i, atLength; + let args, at, i, atLength; switch (header.type) { case 0: // SymbolDictionary // 7.4.2 Symbol dictionary segment syntax @@ -1361,7 +1368,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; if (textRegion.huffman) { - var textRegionHuffmanFlags = readUint16(data, position); + const textRegionHuffmanFlags = readUint16(data, position); position += 2; textRegion.huffmanFS = textRegionHuffmanFlags & 3; textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; @@ -1488,23 +1495,23 @@ var Jbig2Image = (function Jbig2ImageClosure() { " is not implemented" ); } - var callbackName = "on" + header.typeName; + const callbackName = "on" + header.typeName; if (callbackName in visitor) { visitor[callbackName].apply(visitor, args); } } function processSegments(segments, visitor) { - for (var i = 0, ii = segments.length; i < ii; i++) { + for (let i = 0, ii = segments.length; i < ii; i++) { processSegment(segments[i], visitor); } } function parseJbig2Chunks(chunks) { - var visitor = new SimpleSegmentVisitor(); - for (var i = 0, ii = chunks.length; i < ii; i++) { - var chunk = chunks[i]; - var segments = readSegments({}, chunk.data, chunk.start, chunk.end); + const visitor = new SimpleSegmentVisitor(); + for (let i = 0, ii = chunks.length; i < ii; i++) { + const chunk = chunks[i]; + const segments = readSegments({}, chunk.data, chunk.start, chunk.end); processSegments(segments, visitor); } return visitor.buffer; @@ -1566,29 +1573,29 @@ var Jbig2Image = (function Jbig2ImageClosure() { SimpleSegmentVisitor.prototype = { onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) { this.currentPageInfo = info; - var rowSize = (info.width + 7) >> 3; - var buffer = new Uint8ClampedArray(rowSize * info.height); + const rowSize = (info.width + 7) >> 3; + const buffer = new Uint8ClampedArray(rowSize * info.height); // The contents of ArrayBuffers are initialized to 0. // Fill the buffer with 0xFF only if info.defaultPixelValue is set if (info.defaultPixelValue) { - for (var i = 0, ii = buffer.length; i < ii; i++) { + for (let i = 0, ii = buffer.length; i < ii; i++) { buffer[i] = 0xff; } } this.buffer = buffer; }, drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) { - var pageInfo = this.currentPageInfo; - var width = regionInfo.width, + const pageInfo = this.currentPageInfo; + const width = regionInfo.width, height = regionInfo.height; - var rowSize = (pageInfo.width + 7) >> 3; - var combinationOperator = pageInfo.combinationOperatorOverride + const rowSize = (pageInfo.width + 7) >> 3; + const combinationOperator = pageInfo.combinationOperatorOverride ? regionInfo.combinationOperator : pageInfo.combinationOperator; - var buffer = this.buffer; - var mask0 = 128 >> (regionInfo.x & 7); - var offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); - var i, j, mask, offset; + const buffer = this.buffer; + const mask0 = 128 >> (regionInfo.x & 7); + let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); + let i, j, mask, offset; switch (combinationOperator) { case 0: // OR for (i = 0; i < height; i++) { @@ -1636,9 +1643,9 @@ var Jbig2Image = (function Jbig2ImageClosure() { start, end ) { - var regionInfo = region.info; - var decodingContext = new DecodingContext(data, start, end); - var bitmap = decodeBitmap( + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeBitmap( region.mmr, regionInfo.width, regionInfo.height, @@ -1672,13 +1679,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { } // Combines exported symbols from all referred segments - var symbols = this.symbols; + let symbols = this.symbols; if (!symbols) { this.symbols = symbols = {}; } - var inputSymbols = []; - for (var i = 0, ii = referredSegments.length; i < ii; i++) { + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { const referredSymbols = symbols[referredSegments[i]]; // referredSymbols is undefined when we have a reference to a Tables // segment instead of a SymbolDictionary. @@ -1687,7 +1694,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { } } - var decodingContext = new DecodingContext(data, start, end); + const decodingContext = new DecodingContext(data, start, end); symbols[currentSegment] = decodeSymbolDictionary( dictionary.huffman, dictionary.refinement, @@ -1710,13 +1717,13 @@ var Jbig2Image = (function Jbig2ImageClosure() { start, end ) { - var regionInfo = region.info; + const regionInfo = region.info; let huffmanTables, huffmanInput; // Combines exported symbols from all referred segments - var symbols = this.symbols; - var inputSymbols = []; - for (var i = 0, ii = referredSegments.length; i < ii; i++) { + const symbols = this.symbols; + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { const referredSymbols = symbols[referredSegments[i]]; // referredSymbols is undefined when we have a reference to a Tables // segment instead of a SymbolDictionary. @@ -1724,7 +1731,7 @@ var Jbig2Image = (function Jbig2ImageClosure() { inputSymbols = inputSymbols.concat(referredSymbols); } } - var symbolCodeLength = log2(inputSymbols.length); + const symbolCodeLength = log2(inputSymbols.length); if (region.huffman) { huffmanInput = new Reader(data, start, end); huffmanTables = getTextRegionHuffmanTables( @@ -1736,8 +1743,8 @@ var Jbig2Image = (function Jbig2ImageClosure() { ); } - var decodingContext = new DecodingContext(data, start, end); - var bitmap = decodeTextRegion( + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeTextRegion( region.huffman, region.refinement, regionInfo.width, From d59c9ab3abedc4c032a3702a8756698a7cbbeeff Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:21:43 +0200 Subject: [PATCH 2/9] Fix the remaining `no-var` failures, which couldn't be handled automatically, in the `src/core/jbig2.js` file --- src/core/jbig2.js | 48 ++++++++++++++++++++++------------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/core/jbig2.js b/src/core/jbig2.js index ef200d0e8..50375df4a 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -281,14 +281,8 @@ const Jbig2Image = (function Jbig2ImageClosure() { function decodeBitmapTemplate0(width, height, decodingContext) { const decoder = decodingContext.decoder; const contexts = decodingContext.contextCache.getContexts("GB"); - let contextLabel, - i, - j, - pixel, - row, - row1, - row2, - bitmap = []; + const bitmap = []; + let contextLabel, i, j, pixel, row, row1, row2; // ...ooooo.... // ..ooooooo... Context template for current pixel (X) @@ -547,7 +541,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { const row = new Uint8Array(width); bitmap.push(row); for (let j = 0; j < width; j++) { - var i0, j0; + let i0, j0; let contextLabel = 0; for (k = 0; k < codingTemplateLength; k++) { i0 = i + codingTemplateY[k]; @@ -629,7 +623,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { } currentWidth += deltaWidth; totalWidth += currentWidth; - var bitmap; + let bitmap; if (refinement) { // 6.5.8.2 Refinement/aggregate-coded symbol bitmap const numberOfInstances = decodeInteger( @@ -756,9 +750,11 @@ const Jbig2Image = (function Jbig2ImageClosure() { } // 6.5.10 Exported symbols - const exportedSymbols = []; - let flags = [], - currentFlag = false; + const exportedSymbols = [], + flags = []; + let currentFlag = false, + i, + ii; const totalSymbolsLength = symbols.length + numberOfNewSymbols; while (flags.length < totalSymbolsLength) { let runLength = huffman @@ -769,7 +765,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { } currentFlag = !currentFlag; } - for (var i = 0, ii = symbols.length; i < ii; i++) { + for (i = 0, ii = symbols.length; i < ii; i++) { if (flags[i]) { exportedSymbols.push(symbols[i]); } @@ -879,7 +875,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { } const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); - var s2, t2, symbolRow; + let s2, t2, symbolRow; if (transposed) { // Place Symbol Bitmap from T1,S1 for (s2 = 0; s2 < symbolHeight; s2++) { @@ -1289,20 +1285,20 @@ const Jbig2Image = (function Jbig2ImageClosure() { combinationOperator: data[start + 16] & 7, }; } - var RegionSegmentInformationFieldLength = 17; + const RegionSegmentInformationFieldLength = 17; function processSegment(segment, visitor) { const header = segment.header; - let data = segment.data, - position = segment.start, + const data = segment.data, end = segment.end; + let position = segment.start; let args, at, i, atLength; switch (header.type) { case 0: // SymbolDictionary // 7.4.2 Symbol dictionary segment syntax - var dictionary = {}; - var dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 + const dictionary = {}; + const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 dictionary.huffman = !!(dictionaryFlags & 1); dictionary.refinement = !!(dictionaryFlags & 2); dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; @@ -1352,10 +1348,10 @@ const Jbig2Image = (function Jbig2ImageClosure() { break; case 6: // ImmediateTextRegion case 7: // ImmediateLosslessTextRegion - var textRegion = {}; + const textRegion = {}; textRegion.info = readRegionSegmentInformation(data, position); position += RegionSegmentInformationFieldLength; - var textRegionSegmentFlags = readUint16(data, position); + const textRegionSegmentFlags = readUint16(data, position); position += 2; textRegion.huffman = !!(textRegionSegmentFlags & 1); textRegion.refinement = !!(textRegionSegmentFlags & 2); @@ -1436,10 +1432,10 @@ const Jbig2Image = (function Jbig2ImageClosure() { break; case 38: // ImmediateGenericRegion case 39: // ImmediateLosslessGenericRegion - var genericRegion = {}; + const genericRegion = {}; genericRegion.info = readRegionSegmentInformation(data, position); position += RegionSegmentInformationFieldLength; - var genericRegionSegmentFlags = data[position++]; + const genericRegionSegmentFlags = data[position++]; genericRegion.mmr = !!(genericRegionSegmentFlags & 1); genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; genericRegion.prediction = !!(genericRegionSegmentFlags & 8); @@ -1458,7 +1454,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { args = [genericRegion, data, position, end]; break; case 48: // PageInformation - var pageInfo = { + const pageInfo = { width: readUint32(data, position), height: readUint32(data, position + 4), resolutionX: readUint32(data, position + 8), @@ -1467,7 +1463,7 @@ const Jbig2Image = (function Jbig2ImageClosure() { if (pageInfo.height === 0xffffffff) { delete pageInfo.height; } - var pageSegmentFlags = data[position + 16]; + const pageSegmentFlags = data[position + 16]; readUint16(data, position + 17); // pageStripingInformation pageInfo.lossless = !!(pageSegmentFlags & 1); pageInfo.refinement = !!(pageSegmentFlags & 2); From 0addf3a0d4bd77f0db5c865c2ad5c515f193a04d Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:27:53 +0200 Subject: [PATCH 3/9] Convert `src/core/jbig2.js` to use standard classes *Please note:* Ignoring whitespace-only changes is probably necessary in order to review this. --- src/core/jbig2.js | 4621 ++++++++++++++++++++++----------------------- 1 file changed, 2298 insertions(+), 2323 deletions(-) diff --git a/src/core/jbig2.js b/src/core/jbig2.js index 50375df4a..4bfeee70f 100644 --- a/src/core/jbig2.js +++ b/src/core/jbig2.js @@ -24,57 +24,54 @@ class Jbig2Error extends BaseException { } } -const Jbig2Image = (function Jbig2ImageClosure() { - // Utility data structures - function ContextCache() {} +// Utility data structures +class ContextCache { + getContexts(id) { + if (id in this) { + return this[id]; + } + return (this[id] = new Int8Array(1 << 16)); + } +} - ContextCache.prototype = { - getContexts(id) { - if (id in this) { - return this[id]; - } - return (this[id] = new Int8Array(1 << 16)); - }, - }; - - function DecodingContext(data, start, end) { +class DecodingContext { + constructor(data, start, end) { this.data = data; this.start = start; this.end = end; } - DecodingContext.prototype = { - get decoder() { - const decoder = new ArithmeticDecoder(this.data, this.start, this.end); - return shadow(this, "decoder", decoder); - }, - get contextCache() { - const cache = new ContextCache(); - return shadow(this, "contextCache", cache); - }, - }; + get decoder() { + const decoder = new ArithmeticDecoder(this.data, this.start, this.end); + return shadow(this, "decoder", decoder); + } - // Annex A. Arithmetic Integer Decoding Procedure - // A.2 Procedure for decoding values - function decodeInteger(contextCache, procedure, decoder) { - const contexts = contextCache.getContexts(procedure); - let prev = 1; + get contextCache() { + const cache = new ContextCache(); + return shadow(this, "contextCache", cache); + } +} - function readBits(length) { - let v = 0; - for (let i = 0; i < length; i++) { - const bit = decoder.readBit(contexts, prev); - prev = - prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; - v = (v << 1) | bit; - } - return v >>> 0; +// Annex A. Arithmetic Integer Decoding Procedure +// A.2 Procedure for decoding values +function decodeInteger(contextCache, procedure, decoder) { + const contexts = contextCache.getContexts(procedure); + let prev = 1; + + function readBits(length) { + let v = 0; + for (let i = 0; i < length; i++) { + const bit = decoder.readBit(contexts, prev); + prev = prev < 256 ? (prev << 1) | bit : (((prev << 1) | bit) & 511) | 256; + v = (v << 1) | bit; } + return v >>> 0; + } - const sign = readBits(1); - // prettier-ignore - /* eslint-disable no-nested-ternary */ - const value = readBits(1) ? + const sign = readBits(1); + // prettier-ignore + /* eslint-disable no-nested-ternary */ + const value = readBits(1) ? (readBits(1) ? (readBits(1) ? (readBits(1) ? @@ -85,1739 +82,1722 @@ const Jbig2Image = (function Jbig2ImageClosure() { readBits(6) + 20) : readBits(4) + 4) : readBits(2); - /* eslint-enable no-nested-ternary */ - if (sign === 0) { - return value; - } else if (value > 0) { - return -value; - } - return null; + /* eslint-enable no-nested-ternary */ + if (sign === 0) { + return value; + } else if (value > 0) { + return -value; } + return null; +} - // A.3 The IAID decoding procedure - function decodeIAID(contextCache, decoder, codeLength) { - const contexts = contextCache.getContexts("IAID"); +// A.3 The IAID decoding procedure +function decodeIAID(contextCache, decoder, codeLength) { + const contexts = contextCache.getContexts("IAID"); - let prev = 1; - for (let i = 0; i < codeLength; i++) { - const bit = decoder.readBit(contexts, prev); - prev = (prev << 1) | bit; - } - if (codeLength < 31) { - return prev & ((1 << codeLength) - 1); - } - return prev & 0x7fffffff; + let prev = 1; + for (let i = 0; i < codeLength; i++) { + const bit = decoder.readBit(contexts, prev); + prev = (prev << 1) | bit; } + if (codeLength < 31) { + return prev & ((1 << codeLength) - 1); + } + return prev & 0x7fffffff; +} - // 7.3 Segment types - const SegmentTypes = [ - "SymbolDictionary", - null, - null, - null, - "IntermediateTextRegion", - null, - "ImmediateTextRegion", - "ImmediateLosslessTextRegion", - null, - null, - null, - null, - null, - null, - null, - null, - "PatternDictionary", - null, - null, - null, - "IntermediateHalftoneRegion", - null, - "ImmediateHalftoneRegion", - "ImmediateLosslessHalftoneRegion", - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - null, - "IntermediateGenericRegion", - null, - "ImmediateGenericRegion", - "ImmediateLosslessGenericRegion", - "IntermediateGenericRefinementRegion", - null, - "ImmediateGenericRefinementRegion", - "ImmediateLosslessGenericRefinementRegion", - null, - null, - null, - null, - "PageInformation", - "EndOfPage", - "EndOfStripe", - "EndOfFile", - "Profiles", - "Tables", - null, - null, - null, - null, - null, - null, - null, - null, - "Extension", - ]; +// 7.3 Segment types +const SegmentTypes = [ + "SymbolDictionary", + null, + null, + null, + "IntermediateTextRegion", + null, + "ImmediateTextRegion", + "ImmediateLosslessTextRegion", + null, + null, + null, + null, + null, + null, + null, + null, + "PatternDictionary", + null, + null, + null, + "IntermediateHalftoneRegion", + null, + "ImmediateHalftoneRegion", + "ImmediateLosslessHalftoneRegion", + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + null, + "IntermediateGenericRegion", + null, + "ImmediateGenericRegion", + "ImmediateLosslessGenericRegion", + "IntermediateGenericRefinementRegion", + null, + "ImmediateGenericRefinementRegion", + "ImmediateLosslessGenericRefinementRegion", + null, + null, + null, + null, + "PageInformation", + "EndOfPage", + "EndOfStripe", + "EndOfFile", + "Profiles", + "Tables", + null, + null, + null, + null, + null, + null, + null, + null, + "Extension", +]; - const CodingTemplates = [ - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: -2, y: -1 }, +const CodingTemplates = [ + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: 2, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: 2, y: -1 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -1, y: -2 }, + { x: 0, y: -2 }, + { x: 1, y: -2 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], + [ + { x: -3, y: -1 }, + { x: -2, y: -1 }, + { x: -1, y: -1 }, + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -4, y: 0 }, + { x: -3, y: 0 }, + { x: -2, y: 0 }, + { x: -1, y: 0 }, + ], +]; + +const RefinementTemplates = [ + { + coding: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + ], + reference: [ + { x: 0, y: -1 }, + { x: 1, y: -1 }, + { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: -1, y: 1 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }, + { + coding: [ { x: -1, y: -1 }, { x: 0, y: -1 }, { x: 1, y: -1 }, - { x: 2, y: -1 }, - { x: -4, y: 0 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, { x: -1, y: 0 }, ], - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: 2, y: -2 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, + reference: [ { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: 2, y: -1 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, { x: -1, y: 0 }, + { x: 0, y: 0 }, + { x: 1, y: 0 }, + { x: 0, y: 1 }, + { x: 1, y: 1 }, ], - [ - { x: -1, y: -2 }, - { x: 0, y: -2 }, - { x: 1, y: -2 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -2, y: 0 }, - { x: -1, y: 0 }, - ], - [ - { x: -3, y: -1 }, - { x: -2, y: -1 }, - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -4, y: 0 }, - { x: -3, y: 0 }, - { x: -2, y: 0 }, - { x: -1, y: 0 }, - ], - ]; + }, +]; - const RefinementTemplates = [ - { - coding: [ - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - ], - reference: [ - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - { x: 0, y: 0 }, - { x: 1, y: 0 }, - { x: -1, y: 1 }, - { x: 0, y: 1 }, - { x: 1, y: 1 }, - ], - }, - { - coding: [ - { x: -1, y: -1 }, - { x: 0, y: -1 }, - { x: 1, y: -1 }, - { x: -1, y: 0 }, - ], - reference: [ - { x: 0, y: -1 }, - { x: -1, y: 0 }, - { x: 0, y: 0 }, - { x: 1, y: 0 }, - { x: 0, y: 1 }, - { x: 1, y: 1 }, - ], - }, - ]; +// See 6.2.5.7 Decoding the bitmap. +const ReusedContexts = [ + 0x9b25, // 10011 0110010 0101 + 0x0795, // 0011 110010 101 + 0x00e5, // 001 11001 01 + 0x0195, // 011001 0101 +]; - // See 6.2.5.7 Decoding the bitmap. - const ReusedContexts = [ - 0x9b25, // 10011 0110010 0101 - 0x0795, // 0011 110010 101 - 0x00e5, // 001 11001 01 - 0x0195, // 011001 0101 - ]; +const RefinementReusedContexts = [ + 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) + 0x0008, // '0000' + '001000' +]; - const RefinementReusedContexts = [ - 0x0020, // '000' + '0' (coding) + '00010000' + '0' (reference) - 0x0008, // '0000' + '001000' - ]; +function decodeBitmapTemplate0(width, height, decodingContext) { + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + const bitmap = []; + let contextLabel, i, j, pixel, row, row1, row2; - function decodeBitmapTemplate0(width, height, decodingContext) { - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GB"); - const bitmap = []; - let contextLabel, i, j, pixel, row, row1, row2; + // ...ooooo.... + // ..ooooooo... Context template for current pixel (X) + // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) + const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 - // ...ooooo.... - // ..ooooooo... Context template for current pixel (X) - // .ooooX...... (concatenate values of 'o'-pixels to get contextLabel) - const OLD_PIXEL_MASK = 0x7bf7; // 01111 0111111 0111 + for (i = 0; i < height; i++) { + row = bitmap[i] = new Uint8Array(width); + row1 = i < 1 ? row : bitmap[i - 1]; + row2 = i < 2 ? row : bitmap[i - 2]; - for (i = 0; i < height; i++) { - row = bitmap[i] = new Uint8Array(width); - row1 = i < 1 ? row : bitmap[i - 1]; - row2 = i < 2 ? row : bitmap[i - 2]; + // At the beginning of each row: + // Fill contextLabel with pixels that are above/right of (X) + contextLabel = + (row2[0] << 13) | + (row2[1] << 12) | + (row2[2] << 11) | + (row1[0] << 7) | + (row1[1] << 6) | + (row1[2] << 5) | + (row1[3] << 4); - // At the beginning of each row: - // Fill contextLabel with pixels that are above/right of (X) + for (j = 0; j < width; j++) { + row[j] = pixel = decoder.readBit(contexts, contextLabel); + + // At each pixel: Clear contextLabel pixels that are shifted + // out of the context, then add new ones. contextLabel = - (row2[0] << 13) | - (row2[1] << 12) | - (row2[2] << 11) | - (row1[0] << 7) | - (row1[1] << 6) | - (row1[2] << 5) | - (row1[3] << 4); - - for (j = 0; j < width; j++) { - row[j] = pixel = decoder.readBit(contexts, contextLabel); - - // At each pixel: Clear contextLabel pixels that are shifted - // out of the context, then add new ones. - contextLabel = - ((contextLabel & OLD_PIXEL_MASK) << 1) | - (j + 3 < width ? row2[j + 3] << 11 : 0) | - (j + 4 < width ? row1[j + 4] << 4 : 0) | - pixel; - } + ((contextLabel & OLD_PIXEL_MASK) << 1) | + (j + 3 < width ? row2[j + 3] << 11 : 0) | + (j + 4 < width ? row1[j + 4] << 4 : 0) | + pixel; } - - return bitmap; } - // 6.2 Generic Region Decoding Procedure - function decodeBitmap( - mmr, - width, - height, - templateIndex, - prediction, - skip, - at, - decodingContext + return bitmap; +} + +// 6.2 Generic Region Decoding Procedure +function decodeBitmap( + mmr, + width, + height, + templateIndex, + prediction, + skip, + at, + decodingContext +) { + if (mmr) { + const input = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + return decodeMMRBitmap(input, width, height, false); + } + + // Use optimized version for the most common case + if ( + templateIndex === 0 && + !skip && + !prediction && + at.length === 4 && + at[0].x === 3 && + at[0].y === -1 && + at[1].x === -3 && + at[1].y === -1 && + at[2].x === 2 && + at[2].y === -2 && + at[3].x === -2 && + at[3].y === -2 ) { - if (mmr) { - const input = new Reader( - decodingContext.data, - decodingContext.start, - decodingContext.end - ); - return decodeMMRBitmap(input, width, height, false); - } + return decodeBitmapTemplate0(width, height, decodingContext); + } - // Use optimized version for the most common case + const useskip = !!skip; + const template = CodingTemplates[templateIndex].concat(at); + + // Sorting is non-standard, and it is not required. But sorting increases + // the number of template bits that can be reused from the previous + // contextLabel in the main loop. + template.sort(function (a, b) { + return a.y - b.y || a.x - b.x; + }); + + const templateLength = template.length; + const templateX = new Int8Array(templateLength); + const templateY = new Int8Array(templateLength); + const changingTemplateEntries = []; + let reuseMask = 0, + minX = 0, + maxX = 0, + minY = 0; + let c, k; + + for (k = 0; k < templateLength; k++) { + templateX[k] = template[k].x; + templateY[k] = template[k].y; + minX = Math.min(minX, template[k].x); + maxX = Math.max(maxX, template[k].x); + minY = Math.min(minY, template[k].y); + // Check if the template pixel appears in two consecutive context labels, + // so it can be reused. Otherwise, we add it to the list of changing + // template entries. if ( - templateIndex === 0 && - !skip && - !prediction && - at.length === 4 && - at[0].x === 3 && - at[0].y === -1 && - at[1].x === -3 && - at[1].y === -1 && - at[2].x === 2 && - at[2].y === -2 && - at[3].x === -2 && - at[3].y === -2 + k < templateLength - 1 && + template[k].y === template[k + 1].y && + template[k].x === template[k + 1].x - 1 ) { - return decodeBitmapTemplate0(width, height, decodingContext); + reuseMask |= 1 << (templateLength - 1 - k); + } else { + changingTemplateEntries.push(k); } + } + const changingEntriesLength = changingTemplateEntries.length; - const useskip = !!skip; - const template = CodingTemplates[templateIndex].concat(at); + const changingTemplateX = new Int8Array(changingEntriesLength); + const changingTemplateY = new Int8Array(changingEntriesLength); + const changingTemplateBit = new Uint16Array(changingEntriesLength); + for (c = 0; c < changingEntriesLength; c++) { + k = changingTemplateEntries[c]; + changingTemplateX[c] = template[k].x; + changingTemplateY[c] = template[k].y; + changingTemplateBit[c] = 1 << (templateLength - 1 - k); + } - // Sorting is non-standard, and it is not required. But sorting increases - // the number of template bits that can be reused from the previous - // contextLabel in the main loop. - template.sort(function (a, b) { - return a.y - b.y || a.x - b.x; - }); + // Get the safe bounding box edges from the width, height, minX, maxX, minY + const sbb_left = -minX; + const sbb_top = -minY; + const sbb_right = width - maxX; - const templateLength = template.length; - const templateX = new Int8Array(templateLength); - const templateY = new Int8Array(templateLength); - const changingTemplateEntries = []; - let reuseMask = 0, - minX = 0, - maxX = 0, - minY = 0; - let c, k; + const pseudoPixelContext = ReusedContexts[templateIndex]; + let row = new Uint8Array(width); + const bitmap = []; - for (k = 0; k < templateLength; k++) { - templateX[k] = template[k].x; - templateY[k] = template[k].y; - minX = Math.min(minX, template[k].x); - maxX = Math.max(maxX, template[k].x); - minY = Math.min(minY, template[k].y); - // Check if the template pixel appears in two consecutive context labels, - // so it can be reused. Otherwise, we add it to the list of changing - // template entries. - if ( - k < templateLength - 1 && - template[k].y === template[k + 1].y && - template[k].x === template[k + 1].x - 1 - ) { - reuseMask |= 1 << (templateLength - 1 - k); - } else { - changingTemplateEntries.push(k); + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GB"); + + let ltp = 0, + j, + i0, + j0, + contextLabel = 0, + bit, + shift; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + bitmap.push(row); // duplicate previous row + continue; } } - const changingEntriesLength = changingTemplateEntries.length; - - const changingTemplateX = new Int8Array(changingEntriesLength); - const changingTemplateY = new Int8Array(changingEntriesLength); - const changingTemplateBit = new Uint16Array(changingEntriesLength); - for (c = 0; c < changingEntriesLength; c++) { - k = changingTemplateEntries[c]; - changingTemplateX[c] = template[k].x; - changingTemplateY[c] = template[k].y; - changingTemplateBit[c] = 1 << (templateLength - 1 - k); - } - - // Get the safe bounding box edges from the width, height, minX, maxX, minY - const sbb_left = -minX; - const sbb_top = -minY; - const sbb_right = width - maxX; - - const pseudoPixelContext = ReusedContexts[templateIndex]; - let row = new Uint8Array(width); - const bitmap = []; - - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GB"); - - let ltp = 0, - j, - i0, - j0, - contextLabel = 0, - bit, - shift; - for (let i = 0; i < height; i++) { - if (prediction) { - const sltp = decoder.readBit(contexts, pseudoPixelContext); - ltp ^= sltp; - if (ltp) { - bitmap.push(row); // duplicate previous row - continue; - } + row = new Uint8Array(row); + bitmap.push(row); + for (j = 0; j < width; j++) { + if (useskip && skip[i][j]) { + row[j] = 0; + continue; } - row = new Uint8Array(row); - bitmap.push(row); - for (j = 0; j < width; j++) { - if (useskip && skip[i][j]) { - row[j] = 0; - continue; - } - // Are we in the middle of a scanline, so we can reuse contextLabel - // bits? - if (j >= sbb_left && j < sbb_right && i >= sbb_top) { - // If yes, we can just shift the bits that are reusable and only - // fetch the remaining ones. - contextLabel = (contextLabel << 1) & reuseMask; - for (k = 0; k < changingEntriesLength; k++) { - i0 = i + changingTemplateY[k]; - j0 = j + changingTemplateX[k]; - bit = bitmap[i0][j0]; - if (bit) { - bit = changingTemplateBit[k]; - contextLabel |= bit; - } + // Are we in the middle of a scanline, so we can reuse contextLabel + // bits? + if (j >= sbb_left && j < sbb_right && i >= sbb_top) { + // If yes, we can just shift the bits that are reusable and only + // fetch the remaining ones. + contextLabel = (contextLabel << 1) & reuseMask; + for (k = 0; k < changingEntriesLength; k++) { + i0 = i + changingTemplateY[k]; + j0 = j + changingTemplateX[k]; + bit = bitmap[i0][j0]; + if (bit) { + bit = changingTemplateBit[k]; + contextLabel |= bit; } - } else { - // compute the contextLabel from scratch - contextLabel = 0; - shift = templateLength - 1; - for (k = 0; k < templateLength; k++, shift--) { - j0 = j + templateX[k]; - if (j0 >= 0 && j0 < width) { - i0 = i + templateY[k]; - if (i0 >= 0) { - bit = bitmap[i0][j0]; - if (bit) { - contextLabel |= bit << shift; - } + } + } else { + // compute the contextLabel from scratch + contextLabel = 0; + shift = templateLength - 1; + for (k = 0; k < templateLength; k++, shift--) { + j0 = j + templateX[k]; + if (j0 >= 0 && j0 < width) { + i0 = i + templateY[k]; + if (i0 >= 0) { + bit = bitmap[i0][j0]; + if (bit) { + contextLabel |= bit << shift; } } } } - const pixel = decoder.readBit(contexts, contextLabel); - row[j] = pixel; } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; } - return bitmap; + } + return bitmap; +} + +// 6.3.2 Generic Refinement Region Decoding Procedure +function decodeRefinement( + width, + height, + templateIndex, + referenceBitmap, + offsetX, + offsetY, + prediction, + at, + decodingContext +) { + let codingTemplate = RefinementTemplates[templateIndex].coding; + if (templateIndex === 0) { + codingTemplate = codingTemplate.concat([at[0]]); + } + const codingTemplateLength = codingTemplate.length; + const codingTemplateX = new Int32Array(codingTemplateLength); + const codingTemplateY = new Int32Array(codingTemplateLength); + let k; + for (k = 0; k < codingTemplateLength; k++) { + codingTemplateX[k] = codingTemplate[k].x; + codingTemplateY[k] = codingTemplate[k].y; } - // 6.3.2 Generic Refinement Region Decoding Procedure - function decodeRefinement( - width, - height, - templateIndex, - referenceBitmap, - offsetX, - offsetY, - prediction, - at, - decodingContext - ) { - let codingTemplate = RefinementTemplates[templateIndex].coding; - if (templateIndex === 0) { - codingTemplate = codingTemplate.concat([at[0]]); - } - const codingTemplateLength = codingTemplate.length; - const codingTemplateX = new Int32Array(codingTemplateLength); - const codingTemplateY = new Int32Array(codingTemplateLength); - let k; - for (k = 0; k < codingTemplateLength; k++) { - codingTemplateX[k] = codingTemplate[k].x; - codingTemplateY[k] = codingTemplate[k].y; - } - - let referenceTemplate = RefinementTemplates[templateIndex].reference; - if (templateIndex === 0) { - referenceTemplate = referenceTemplate.concat([at[1]]); - } - const referenceTemplateLength = referenceTemplate.length; - const referenceTemplateX = new Int32Array(referenceTemplateLength); - const referenceTemplateY = new Int32Array(referenceTemplateLength); - for (k = 0; k < referenceTemplateLength; k++) { - referenceTemplateX[k] = referenceTemplate[k].x; - referenceTemplateY[k] = referenceTemplate[k].y; - } - const referenceWidth = referenceBitmap[0].length; - const referenceHeight = referenceBitmap.length; - - const pseudoPixelContext = RefinementReusedContexts[templateIndex]; - const bitmap = []; - - const decoder = decodingContext.decoder; - const contexts = decodingContext.contextCache.getContexts("GR"); - - let ltp = 0; - for (let i = 0; i < height; i++) { - if (prediction) { - const sltp = decoder.readBit(contexts, pseudoPixelContext); - ltp ^= sltp; - if (ltp) { - throw new Jbig2Error("prediction is not supported"); - } - } - const row = new Uint8Array(width); - bitmap.push(row); - for (let j = 0; j < width; j++) { - let i0, j0; - let contextLabel = 0; - for (k = 0; k < codingTemplateLength; k++) { - i0 = i + codingTemplateY[k]; - j0 = j + codingTemplateX[k]; - if (i0 < 0 || j0 < 0 || j0 >= width) { - contextLabel <<= 1; // out of bound pixel - } else { - contextLabel = (contextLabel << 1) | bitmap[i0][j0]; - } - } - for (k = 0; k < referenceTemplateLength; k++) { - i0 = i + referenceTemplateY[k] - offsetY; - j0 = j + referenceTemplateX[k] - offsetX; - if ( - i0 < 0 || - i0 >= referenceHeight || - j0 < 0 || - j0 >= referenceWidth - ) { - contextLabel <<= 1; // out of bound pixel - } else { - contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; - } - } - const pixel = decoder.readBit(contexts, contextLabel); - row[j] = pixel; - } - } - - return bitmap; + let referenceTemplate = RefinementTemplates[templateIndex].reference; + if (templateIndex === 0) { + referenceTemplate = referenceTemplate.concat([at[1]]); } + const referenceTemplateLength = referenceTemplate.length; + const referenceTemplateX = new Int32Array(referenceTemplateLength); + const referenceTemplateY = new Int32Array(referenceTemplateLength); + for (k = 0; k < referenceTemplateLength; k++) { + referenceTemplateX[k] = referenceTemplate[k].x; + referenceTemplateY[k] = referenceTemplate[k].y; + } + const referenceWidth = referenceBitmap[0].length; + const referenceHeight = referenceBitmap.length; - // 6.5.5 Decoding the symbol dictionary - function decodeSymbolDictionary( - huffman, - refinement, - symbols, - numberOfNewSymbols, - numberOfExportedSymbols, - huffmanTables, - templateIndex, - at, - refinementTemplateIndex, - refinementAt, - decodingContext, - huffmanInput - ) { - if (huffman && refinement) { - throw new Jbig2Error("symbol refinement with Huffman is not supported"); + const pseudoPixelContext = RefinementReusedContexts[templateIndex]; + const bitmap = []; + + const decoder = decodingContext.decoder; + const contexts = decodingContext.contextCache.getContexts("GR"); + + let ltp = 0; + for (let i = 0; i < height; i++) { + if (prediction) { + const sltp = decoder.readBit(contexts, pseudoPixelContext); + ltp ^= sltp; + if (ltp) { + throw new Jbig2Error("prediction is not supported"); + } } - - const newSymbols = []; - let currentHeight = 0; - let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); - - const decoder = decodingContext.decoder; - const contextCache = decodingContext.contextCache; - let tableB1, symbolWidths; - if (huffman) { - tableB1 = getStandardTable(1); // standard table B.1 - symbolWidths = []; - symbolCodeLength = Math.max(symbolCodeLength, 1); // 6.5.8.2.3 - } - - while (newSymbols.length < numberOfNewSymbols) { - const deltaHeight = huffman - ? huffmanTables.tableDeltaHeight.decode(huffmanInput) - : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 - currentHeight += deltaHeight; - let currentWidth = 0, - totalWidth = 0; - const firstSymbol = huffman ? symbolWidths.length : 0; - while (true) { - const deltaWidth = huffman - ? huffmanTables.tableDeltaWidth.decode(huffmanInput) - : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 - if (deltaWidth === null) { - break; // OOB - } - currentWidth += deltaWidth; - totalWidth += currentWidth; - let bitmap; - if (refinement) { - // 6.5.8.2 Refinement/aggregate-coded symbol bitmap - const numberOfInstances = decodeInteger( - contextCache, - "IAAI", - decoder - ); - if (numberOfInstances > 1) { - bitmap = decodeTextRegion( - huffman, - refinement, - currentWidth, - currentHeight, - 0, - numberOfInstances, - 1, // strip size - symbols.concat(newSymbols), - symbolCodeLength, - 0, // transposed - 0, // ds offset - 1, // top left 7.4.3.1.1 - 0, // OR operator - huffmanTables, - refinementTemplateIndex, - refinementAt, - decodingContext, - 0, - huffmanInput - ); - } else { - const symbolId = decodeIAID( - contextCache, - decoder, - symbolCodeLength - ); - const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 - const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 - const symbol = - symbolId < symbols.length - ? symbols[symbolId] - : newSymbols[symbolId - symbols.length]; - bitmap = decodeRefinement( - currentWidth, - currentHeight, - refinementTemplateIndex, - symbol, - rdx, - rdy, - false, - refinementAt, - decodingContext - ); - } - newSymbols.push(bitmap); - } else if (huffman) { - // Store only symbol width and decode a collective bitmap when the - // height class is done. - symbolWidths.push(currentWidth); + const row = new Uint8Array(width); + bitmap.push(row); + for (let j = 0; j < width; j++) { + let i0, j0; + let contextLabel = 0; + for (k = 0; k < codingTemplateLength; k++) { + i0 = i + codingTemplateY[k]; + j0 = j + codingTemplateX[k]; + if (i0 < 0 || j0 < 0 || j0 >= width) { + contextLabel <<= 1; // out of bound pixel } else { - // 6.5.8.1 Direct-coded symbol bitmap - bitmap = decodeBitmap( - false, + contextLabel = (contextLabel << 1) | bitmap[i0][j0]; + } + } + for (k = 0; k < referenceTemplateLength; k++) { + i0 = i + referenceTemplateY[k] - offsetY; + j0 = j + referenceTemplateX[k] - offsetX; + if (i0 < 0 || i0 >= referenceHeight || j0 < 0 || j0 >= referenceWidth) { + contextLabel <<= 1; // out of bound pixel + } else { + contextLabel = (contextLabel << 1) | referenceBitmap[i0][j0]; + } + } + const pixel = decoder.readBit(contexts, contextLabel); + row[j] = pixel; + } + } + + return bitmap; +} + +// 6.5.5 Decoding the symbol dictionary +function decodeSymbolDictionary( + huffman, + refinement, + symbols, + numberOfNewSymbols, + numberOfExportedSymbols, + huffmanTables, + templateIndex, + at, + refinementTemplateIndex, + refinementAt, + decodingContext, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("symbol refinement with Huffman is not supported"); + } + + const newSymbols = []; + let currentHeight = 0; + let symbolCodeLength = log2(symbols.length + numberOfNewSymbols); + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + let tableB1, symbolWidths; + if (huffman) { + tableB1 = getStandardTable(1); // standard table B.1 + symbolWidths = []; + symbolCodeLength = Math.max(symbolCodeLength, 1); // 6.5.8.2.3 + } + + while (newSymbols.length < numberOfNewSymbols) { + const deltaHeight = huffman + ? huffmanTables.tableDeltaHeight.decode(huffmanInput) + : decodeInteger(contextCache, "IADH", decoder); // 6.5.6 + currentHeight += deltaHeight; + let currentWidth = 0, + totalWidth = 0; + const firstSymbol = huffman ? symbolWidths.length : 0; + while (true) { + const deltaWidth = huffman + ? huffmanTables.tableDeltaWidth.decode(huffmanInput) + : decodeInteger(contextCache, "IADW", decoder); // 6.5.7 + if (deltaWidth === null) { + break; // OOB + } + currentWidth += deltaWidth; + totalWidth += currentWidth; + let bitmap; + if (refinement) { + // 6.5.8.2 Refinement/aggregate-coded symbol bitmap + const numberOfInstances = decodeInteger(contextCache, "IAAI", decoder); + if (numberOfInstances > 1) { + bitmap = decodeTextRegion( + huffman, + refinement, currentWidth, currentHeight, - templateIndex, - false, - null, - at, - decodingContext - ); - newSymbols.push(bitmap); - } - } - if (huffman && !refinement) { - // 6.5.9 Height class collective bitmap - const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); - huffmanInput.byteAlign(); - let collectiveBitmap; - if (bitmapSize === 0) { - // Uncompressed collective bitmap - collectiveBitmap = readUncompressedBitmap( - huffmanInput, - totalWidth, - currentHeight + 0, + numberOfInstances, + 1, // strip size + symbols.concat(newSymbols), + symbolCodeLength, + 0, // transposed + 0, // ds offset + 1, // top left 7.4.3.1.1 + 0, // OR operator + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + 0, + huffmanInput ); } else { - // MMR collective bitmap - const originalEnd = huffmanInput.end; - const bitmapEnd = huffmanInput.position + bitmapSize; - huffmanInput.end = bitmapEnd; - collectiveBitmap = decodeMMRBitmap( - huffmanInput, - totalWidth, - currentHeight, - false - ); - huffmanInput.end = originalEnd; - huffmanInput.position = bitmapEnd; - } - const numberOfSymbolsDecoded = symbolWidths.length; - if (firstSymbol === numberOfSymbolsDecoded - 1) { - // collectiveBitmap is a single symbol. - newSymbols.push(collectiveBitmap); - } else { - // Divide collectiveBitmap into symbols. - let i, - y, - xMin = 0, - xMax, - bitmapWidth, - symbolBitmap; - for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { - bitmapWidth = symbolWidths[i]; - xMax = xMin + bitmapWidth; - symbolBitmap = []; - for (y = 0; y < currentHeight; y++) { - symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); - } - newSymbols.push(symbolBitmap); - xMin = xMax; - } - } - } - } - - // 6.5.10 Exported symbols - const exportedSymbols = [], - flags = []; - let currentFlag = false, - i, - ii; - const totalSymbolsLength = symbols.length + numberOfNewSymbols; - while (flags.length < totalSymbolsLength) { - let runLength = huffman - ? tableB1.decode(huffmanInput) - : decodeInteger(contextCache, "IAEX", decoder); - while (runLength--) { - flags.push(currentFlag); - } - currentFlag = !currentFlag; - } - for (i = 0, ii = symbols.length; i < ii; i++) { - if (flags[i]) { - exportedSymbols.push(symbols[i]); - } - } - for (let j = 0; j < numberOfNewSymbols; i++, j++) { - if (flags[i]) { - exportedSymbols.push(newSymbols[j]); - } - } - return exportedSymbols; - } - - function decodeTextRegion( - huffman, - refinement, - width, - height, - defaultPixelValue, - numberOfSymbolInstances, - stripSize, - inputSymbols, - symbolCodeLength, - transposed, - dsOffset, - referenceCorner, - combinationOperator, - huffmanTables, - refinementTemplateIndex, - refinementAt, - decodingContext, - logStripSize, - huffmanInput - ) { - if (huffman && refinement) { - throw new Jbig2Error("refinement with Huffman is not supported"); - } - - // Prepare bitmap - const bitmap = []; - let i, row; - for (i = 0; i < height; i++) { - row = new Uint8Array(width); - if (defaultPixelValue) { - for (let j = 0; j < width; j++) { - row[j] = defaultPixelValue; - } - } - bitmap.push(row); - } - - const decoder = decodingContext.decoder; - const contextCache = decodingContext.contextCache; - - let stripT = huffman - ? -huffmanTables.tableDeltaT.decode(huffmanInput) - : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 - let firstS = 0; - i = 0; - while (i < numberOfSymbolInstances) { - const deltaT = huffman - ? huffmanTables.tableDeltaT.decode(huffmanInput) - : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 - stripT += deltaT; - - const deltaFirstS = huffman - ? huffmanTables.tableFirstS.decode(huffmanInput) - : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 - firstS += deltaFirstS; - let currentS = firstS; - do { - let currentT = 0; // 6.4.9 - if (stripSize > 1) { - currentT = huffman - ? huffmanInput.readBits(logStripSize) - : decodeInteger(contextCache, "IAIT", decoder); - } - const t = stripSize * stripT + currentT; - const symbolId = huffman - ? huffmanTables.symbolIDTable.decode(huffmanInput) - : decodeIAID(contextCache, decoder, symbolCodeLength); - const applyRefinement = - refinement && - (huffman - ? huffmanInput.readBit() - : decodeInteger(contextCache, "IARI", decoder)); - let symbolBitmap = inputSymbols[symbolId]; - let symbolWidth = symbolBitmap[0].length; - let symbolHeight = symbolBitmap.length; - if (applyRefinement) { - const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 - const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const symbolId = decodeIAID(contextCache, decoder, symbolCodeLength); const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 - symbolWidth += rdw; - symbolHeight += rdh; - symbolBitmap = decodeRefinement( - symbolWidth, - symbolHeight, + const symbol = + symbolId < symbols.length + ? symbols[symbolId] + : newSymbols[symbolId - symbols.length]; + bitmap = decodeRefinement( + currentWidth, + currentHeight, refinementTemplateIndex, - symbolBitmap, - (rdw >> 1) + rdx, - (rdh >> 1) + rdy, + symbol, + rdx, + rdy, false, refinementAt, decodingContext ); } - const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); - const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); - let s2, t2, symbolRow; - if (transposed) { - // Place Symbol Bitmap from T1,S1 - for (s2 = 0; s2 < symbolHeight; s2++) { - row = bitmap[offsetS + s2]; - if (!row) { - continue; - } - symbolRow = symbolBitmap[s2]; - // To ignore Parts of Symbol bitmap which goes - // outside bitmap region - const maxWidth = Math.min(width - offsetT, symbolWidth); - switch (combinationOperator) { - case 0: // OR - for (t2 = 0; t2 < maxWidth; t2++) { - row[offsetT + t2] |= symbolRow[t2]; - } - break; - case 2: // XOR - for (t2 = 0; t2 < maxWidth; t2++) { - row[offsetT + t2] ^= symbolRow[t2]; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - } - currentS += symbolHeight - 1; - } else { - for (t2 = 0; t2 < symbolHeight; t2++) { - row = bitmap[offsetT + t2]; - if (!row) { - continue; - } - symbolRow = symbolBitmap[t2]; - switch (combinationOperator) { - case 0: // OR - for (s2 = 0; s2 < symbolWidth; s2++) { - row[offsetS + s2] |= symbolRow[s2]; - } - break; - case 2: // XOR - for (s2 = 0; s2 < symbolWidth; s2++) { - row[offsetS + s2] ^= symbolRow[s2]; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - } - currentS += symbolWidth - 1; - } - i++; - const deltaS = huffman - ? huffmanTables.tableDeltaS.decode(huffmanInput) - : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 - if (deltaS === null) { - break; // OOB - } - currentS += deltaS + dsOffset; - } while (true); - } - return bitmap; - } - - function decodePatternDictionary( - mmr, - patternWidth, - patternHeight, - maxPatternIndex, - template, - decodingContext - ) { - const at = []; - if (!mmr) { - at.push({ - x: -patternWidth, - y: 0, - }); - if (template === 0) { - at.push({ - x: -3, - y: -1, - }); - at.push({ - x: 2, - y: -2, - }); - at.push({ - x: -2, - y: -2, - }); - } - } - const collectiveWidth = (maxPatternIndex + 1) * patternWidth; - const collectiveBitmap = decodeBitmap( - mmr, - collectiveWidth, - patternHeight, - template, - false, - null, - at, - decodingContext - ); - // Divide collective bitmap into patterns. - const patterns = []; - for (let i = 0; i <= maxPatternIndex; i++) { - const patternBitmap = []; - const xMin = patternWidth * i; - const xMax = xMin + patternWidth; - for (let y = 0; y < patternHeight; y++) { - patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); - } - patterns.push(patternBitmap); - } - return patterns; - } - - function decodeHalftoneRegion( - mmr, - patterns, - template, - regionWidth, - regionHeight, - defaultPixelValue, - enableSkip, - combinationOperator, - gridWidth, - gridHeight, - gridOffsetX, - gridOffsetY, - gridVectorX, - gridVectorY, - decodingContext - ) { - const skip = null; - if (enableSkip) { - throw new Jbig2Error("skip is not supported"); - } - if (combinationOperator !== 0) { - throw new Jbig2Error( - "operator " + - combinationOperator + - " is not supported in halftone region" - ); - } - - // Prepare bitmap. - const regionBitmap = []; - let i, j, row; - for (i = 0; i < regionHeight; i++) { - row = new Uint8Array(regionWidth); - if (defaultPixelValue) { - for (j = 0; j < regionWidth; j++) { - row[j] = defaultPixelValue; - } - } - regionBitmap.push(row); - } - - const numberOfPatterns = patterns.length; - const pattern0 = patterns[0]; - const patternWidth = pattern0[0].length, - patternHeight = pattern0.length; - const bitsPerValue = log2(numberOfPatterns); - const at = []; - if (!mmr) { - at.push({ - x: template <= 1 ? 3 : 2, - y: -1, - }); - if (template === 0) { - at.push({ - x: -3, - y: -1, - }); - at.push({ - x: 2, - y: -2, - }); - at.push({ - x: -2, - y: -2, - }); - } - } - // Annex C. Gray-scale Image Decoding Procedure. - const grayScaleBitPlanes = []; - let mmrInput, bitmap; - if (mmr) { - // MMR bit planes are in one continuous stream. Only EOFB codes indicate - // the end of each bitmap, so EOFBs must be decoded. - mmrInput = new Reader( - decodingContext.data, - decodingContext.start, - decodingContext.end - ); - } - for (i = bitsPerValue - 1; i >= 0; i--) { - if (mmr) { - bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); + newSymbols.push(bitmap); + } else if (huffman) { + // Store only symbol width and decode a collective bitmap when the + // height class is done. + symbolWidths.push(currentWidth); } else { + // 6.5.8.1 Direct-coded symbol bitmap bitmap = decodeBitmap( false, - gridWidth, - gridHeight, - template, + currentWidth, + currentHeight, + templateIndex, false, - skip, + null, at, decodingContext ); + newSymbols.push(bitmap); } - grayScaleBitPlanes[i] = bitmap; } - // 6.6.5.2 Rendering the patterns. - let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; - for (mg = 0; mg < gridHeight; mg++) { - for (ng = 0; ng < gridWidth; ng++) { - bit = 0; - patternIndex = 0; - for (j = bitsPerValue - 1; j >= 0; j--) { - bit = grayScaleBitPlanes[j][mg][ng] ^ bit; // Gray decoding - patternIndex |= bit << j; - } - patternBitmap = patterns[patternIndex]; - x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8; - y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8; - // Draw patternBitmap at (x, y). - if ( - x >= 0 && - x + patternWidth <= regionWidth && - y >= 0 && - y + patternHeight <= regionHeight - ) { - for (i = 0; i < patternHeight; i++) { - regionRow = regionBitmap[y + i]; - patternRow = patternBitmap[i]; - for (j = 0; j < patternWidth; j++) { - regionRow[x + j] |= patternRow[j]; - } + if (huffman && !refinement) { + // 6.5.9 Height class collective bitmap + const bitmapSize = huffmanTables.tableBitmapSize.decode(huffmanInput); + huffmanInput.byteAlign(); + let collectiveBitmap; + if (bitmapSize === 0) { + // Uncompressed collective bitmap + collectiveBitmap = readUncompressedBitmap( + huffmanInput, + totalWidth, + currentHeight + ); + } else { + // MMR collective bitmap + const originalEnd = huffmanInput.end; + const bitmapEnd = huffmanInput.position + bitmapSize; + huffmanInput.end = bitmapEnd; + collectiveBitmap = decodeMMRBitmap( + huffmanInput, + totalWidth, + currentHeight, + false + ); + huffmanInput.end = originalEnd; + huffmanInput.position = bitmapEnd; + } + const numberOfSymbolsDecoded = symbolWidths.length; + if (firstSymbol === numberOfSymbolsDecoded - 1) { + // collectiveBitmap is a single symbol. + newSymbols.push(collectiveBitmap); + } else { + // Divide collectiveBitmap into symbols. + let i, + y, + xMin = 0, + xMax, + bitmapWidth, + symbolBitmap; + for (i = firstSymbol; i < numberOfSymbolsDecoded; i++) { + bitmapWidth = symbolWidths[i]; + xMax = xMin + bitmapWidth; + symbolBitmap = []; + for (y = 0; y < currentHeight; y++) { + symbolBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); } - } else { - let regionX, regionY; - for (i = 0; i < patternHeight; i++) { - regionY = y + i; - if (regionY < 0 || regionY >= regionHeight) { - continue; - } - regionRow = regionBitmap[regionY]; - patternRow = patternBitmap[i]; - for (j = 0; j < patternWidth; j++) { - regionX = x + j; - if (regionX >= 0 && regionX < regionWidth) { - regionRow[regionX] |= patternRow[j]; + newSymbols.push(symbolBitmap); + xMin = xMax; + } + } + } + } + + // 6.5.10 Exported symbols + const exportedSymbols = [], + flags = []; + let currentFlag = false, + i, + ii; + const totalSymbolsLength = symbols.length + numberOfNewSymbols; + while (flags.length < totalSymbolsLength) { + let runLength = huffman + ? tableB1.decode(huffmanInput) + : decodeInteger(contextCache, "IAEX", decoder); + while (runLength--) { + flags.push(currentFlag); + } + currentFlag = !currentFlag; + } + for (i = 0, ii = symbols.length; i < ii; i++) { + if (flags[i]) { + exportedSymbols.push(symbols[i]); + } + } + for (let j = 0; j < numberOfNewSymbols; i++, j++) { + if (flags[i]) { + exportedSymbols.push(newSymbols[j]); + } + } + return exportedSymbols; +} + +function decodeTextRegion( + huffman, + refinement, + width, + height, + defaultPixelValue, + numberOfSymbolInstances, + stripSize, + inputSymbols, + symbolCodeLength, + transposed, + dsOffset, + referenceCorner, + combinationOperator, + huffmanTables, + refinementTemplateIndex, + refinementAt, + decodingContext, + logStripSize, + huffmanInput +) { + if (huffman && refinement) { + throw new Jbig2Error("refinement with Huffman is not supported"); + } + + // Prepare bitmap + const bitmap = []; + let i, row; + for (i = 0; i < height; i++) { + row = new Uint8Array(width); + if (defaultPixelValue) { + for (let j = 0; j < width; j++) { + row[j] = defaultPixelValue; + } + } + bitmap.push(row); + } + + const decoder = decodingContext.decoder; + const contextCache = decodingContext.contextCache; + + let stripT = huffman + ? -huffmanTables.tableDeltaT.decode(huffmanInput) + : -decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + let firstS = 0; + i = 0; + while (i < numberOfSymbolInstances) { + const deltaT = huffman + ? huffmanTables.tableDeltaT.decode(huffmanInput) + : decodeInteger(contextCache, "IADT", decoder); // 6.4.6 + stripT += deltaT; + + const deltaFirstS = huffman + ? huffmanTables.tableFirstS.decode(huffmanInput) + : decodeInteger(contextCache, "IAFS", decoder); // 6.4.7 + firstS += deltaFirstS; + let currentS = firstS; + do { + let currentT = 0; // 6.4.9 + if (stripSize > 1) { + currentT = huffman + ? huffmanInput.readBits(logStripSize) + : decodeInteger(contextCache, "IAIT", decoder); + } + const t = stripSize * stripT + currentT; + const symbolId = huffman + ? huffmanTables.symbolIDTable.decode(huffmanInput) + : decodeIAID(contextCache, decoder, symbolCodeLength); + const applyRefinement = + refinement && + (huffman + ? huffmanInput.readBit() + : decodeInteger(contextCache, "IARI", decoder)); + let symbolBitmap = inputSymbols[symbolId]; + let symbolWidth = symbolBitmap[0].length; + let symbolHeight = symbolBitmap.length; + if (applyRefinement) { + const rdw = decodeInteger(contextCache, "IARDW", decoder); // 6.4.11.1 + const rdh = decodeInteger(contextCache, "IARDH", decoder); // 6.4.11.2 + const rdx = decodeInteger(contextCache, "IARDX", decoder); // 6.4.11.3 + const rdy = decodeInteger(contextCache, "IARDY", decoder); // 6.4.11.4 + symbolWidth += rdw; + symbolHeight += rdh; + symbolBitmap = decodeRefinement( + symbolWidth, + symbolHeight, + refinementTemplateIndex, + symbolBitmap, + (rdw >> 1) + rdx, + (rdh >> 1) + rdy, + false, + refinementAt, + decodingContext + ); + } + const offsetT = t - (referenceCorner & 1 ? 0 : symbolHeight - 1); + const offsetS = currentS - (referenceCorner & 2 ? symbolWidth - 1 : 0); + let s2, t2, symbolRow; + if (transposed) { + // Place Symbol Bitmap from T1,S1 + for (s2 = 0; s2 < symbolHeight; s2++) { + row = bitmap[offsetS + s2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[s2]; + // To ignore Parts of Symbol bitmap which goes + // outside bitmap region + const maxWidth = Math.min(width - offsetT, symbolWidth); + switch (combinationOperator) { + case 0: // OR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] |= symbolRow[t2]; } + break; + case 2: // XOR + for (t2 = 0; t2 < maxWidth; t2++) { + row[offsetT + t2] ^= symbolRow[t2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + currentS += symbolHeight - 1; + } else { + for (t2 = 0; t2 < symbolHeight; t2++) { + row = bitmap[offsetT + t2]; + if (!row) { + continue; + } + symbolRow = symbolBitmap[t2]; + switch (combinationOperator) { + case 0: // OR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] |= symbolRow[s2]; + } + break; + case 2: // XOR + for (s2 = 0; s2 < symbolWidth; s2++) { + row[offsetS + s2] ^= symbolRow[s2]; + } + break; + default: + throw new Jbig2Error( + `operator ${combinationOperator} is not supported` + ); + } + } + currentS += symbolWidth - 1; + } + i++; + const deltaS = huffman + ? huffmanTables.tableDeltaS.decode(huffmanInput) + : decodeInteger(contextCache, "IADS", decoder); // 6.4.8 + if (deltaS === null) { + break; // OOB + } + currentS += deltaS + dsOffset; + } while (true); + } + return bitmap; +} + +function decodePatternDictionary( + mmr, + patternWidth, + patternHeight, + maxPatternIndex, + template, + decodingContext +) { + const at = []; + if (!mmr) { + at.push({ + x: -patternWidth, + y: 0, + }); + if (template === 0) { + at.push({ + x: -3, + y: -1, + }); + at.push({ + x: 2, + y: -2, + }); + at.push({ + x: -2, + y: -2, + }); + } + } + const collectiveWidth = (maxPatternIndex + 1) * patternWidth; + const collectiveBitmap = decodeBitmap( + mmr, + collectiveWidth, + patternHeight, + template, + false, + null, + at, + decodingContext + ); + // Divide collective bitmap into patterns. + const patterns = []; + for (let i = 0; i <= maxPatternIndex; i++) { + const patternBitmap = []; + const xMin = patternWidth * i; + const xMax = xMin + patternWidth; + for (let y = 0; y < patternHeight; y++) { + patternBitmap.push(collectiveBitmap[y].subarray(xMin, xMax)); + } + patterns.push(patternBitmap); + } + return patterns; +} + +function decodeHalftoneRegion( + mmr, + patterns, + template, + regionWidth, + regionHeight, + defaultPixelValue, + enableSkip, + combinationOperator, + gridWidth, + gridHeight, + gridOffsetX, + gridOffsetY, + gridVectorX, + gridVectorY, + decodingContext +) { + const skip = null; + if (enableSkip) { + throw new Jbig2Error("skip is not supported"); + } + if (combinationOperator !== 0) { + throw new Jbig2Error( + `operator "${combinationOperator}" is not supported in halftone region` + ); + } + + // Prepare bitmap. + const regionBitmap = []; + let i, j, row; + for (i = 0; i < regionHeight; i++) { + row = new Uint8Array(regionWidth); + if (defaultPixelValue) { + for (j = 0; j < regionWidth; j++) { + row[j] = defaultPixelValue; + } + } + regionBitmap.push(row); + } + + const numberOfPatterns = patterns.length; + const pattern0 = patterns[0]; + const patternWidth = pattern0[0].length, + patternHeight = pattern0.length; + const bitsPerValue = log2(numberOfPatterns); + const at = []; + if (!mmr) { + at.push({ + x: template <= 1 ? 3 : 2, + y: -1, + }); + if (template === 0) { + at.push({ + x: -3, + y: -1, + }); + at.push({ + x: 2, + y: -2, + }); + at.push({ + x: -2, + y: -2, + }); + } + } + // Annex C. Gray-scale Image Decoding Procedure. + const grayScaleBitPlanes = []; + let mmrInput, bitmap; + if (mmr) { + // MMR bit planes are in one continuous stream. Only EOFB codes indicate + // the end of each bitmap, so EOFBs must be decoded. + mmrInput = new Reader( + decodingContext.data, + decodingContext.start, + decodingContext.end + ); + } + for (i = bitsPerValue - 1; i >= 0; i--) { + if (mmr) { + bitmap = decodeMMRBitmap(mmrInput, gridWidth, gridHeight, true); + } else { + bitmap = decodeBitmap( + false, + gridWidth, + gridHeight, + template, + false, + skip, + at, + decodingContext + ); + } + grayScaleBitPlanes[i] = bitmap; + } + // 6.6.5.2 Rendering the patterns. + let mg, ng, bit, patternIndex, patternBitmap, x, y, patternRow, regionRow; + for (mg = 0; mg < gridHeight; mg++) { + for (ng = 0; ng < gridWidth; ng++) { + bit = 0; + patternIndex = 0; + for (j = bitsPerValue - 1; j >= 0; j--) { + bit = grayScaleBitPlanes[j][mg][ng] ^ bit; // Gray decoding + patternIndex |= bit << j; + } + patternBitmap = patterns[patternIndex]; + x = (gridOffsetX + mg * gridVectorY + ng * gridVectorX) >> 8; + y = (gridOffsetY + mg * gridVectorX - ng * gridVectorY) >> 8; + // Draw patternBitmap at (x, y). + if ( + x >= 0 && + x + patternWidth <= regionWidth && + y >= 0 && + y + patternHeight <= regionHeight + ) { + for (i = 0; i < patternHeight; i++) { + regionRow = regionBitmap[y + i]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionRow[x + j] |= patternRow[j]; + } + } + } else { + let regionX, regionY; + for (i = 0; i < patternHeight; i++) { + regionY = y + i; + if (regionY < 0 || regionY >= regionHeight) { + continue; + } + regionRow = regionBitmap[regionY]; + patternRow = patternBitmap[i]; + for (j = 0; j < patternWidth; j++) { + regionX = x + j; + if (regionX >= 0 && regionX < regionWidth) { + regionRow[regionX] |= patternRow[j]; } } } } } - return regionBitmap; + } + return regionBitmap; +} + +function readSegmentHeader(data, start) { + const segmentHeader = {}; + segmentHeader.number = readUint32(data, start); + const flags = data[start + 4]; + const segmentType = flags & 0x3f; + if (!SegmentTypes[segmentType]) { + throw new Jbig2Error("invalid segment type: " + segmentType); + } + segmentHeader.type = segmentType; + segmentHeader.typeName = SegmentTypes[segmentType]; + segmentHeader.deferredNonRetain = !!(flags & 0x80); + + const pageAssociationFieldSize = !!(flags & 0x40); + const referredFlags = data[start + 5]; + let referredToCount = (referredFlags >> 5) & 7; + const retainBits = [referredFlags & 31]; + let position = start + 6; + if (referredFlags === 7) { + referredToCount = readUint32(data, position - 1) & 0x1fffffff; + position += 3; + let bytes = (referredToCount + 7) >> 3; + retainBits[0] = data[position++]; + while (--bytes > 0) { + retainBits.push(data[position++]); + } + } else if (referredFlags === 5 || referredFlags === 6) { + throw new Jbig2Error("invalid referred-to flags"); } - function readSegmentHeader(data, start) { - const segmentHeader = {}; - segmentHeader.number = readUint32(data, start); - const flags = data[start + 4]; - const segmentType = flags & 0x3f; - if (!SegmentTypes[segmentType]) { - throw new Jbig2Error("invalid segment type: " + segmentType); - } - segmentHeader.type = segmentType; - segmentHeader.typeName = SegmentTypes[segmentType]; - segmentHeader.deferredNonRetain = !!(flags & 0x80); + segmentHeader.retainBits = retainBits; - const pageAssociationFieldSize = !!(flags & 0x40); - const referredFlags = data[start + 5]; - let referredToCount = (referredFlags >> 5) & 7; - const retainBits = [referredFlags & 31]; - let position = start + 6; - if (referredFlags === 7) { - referredToCount = readUint32(data, position - 1) & 0x1fffffff; - position += 3; - let bytes = (referredToCount + 7) >> 3; - retainBits[0] = data[position++]; - while (--bytes > 0) { - retainBits.push(data[position++]); - } - } else if (referredFlags === 5 || referredFlags === 6) { - throw new Jbig2Error("invalid referred-to flags"); - } - - segmentHeader.retainBits = retainBits; - - let referredToSegmentNumberSize = 4; - if (segmentHeader.number <= 256) { - referredToSegmentNumberSize = 1; - } else if (segmentHeader.number <= 65536) { - referredToSegmentNumberSize = 2; - } - const referredTo = []; - let i, ii; - for (i = 0; i < referredToCount; i++) { - let number; - if (referredToSegmentNumberSize === 1) { - number = data[position]; - } else if (referredToSegmentNumberSize === 2) { - number = readUint16(data, position); - } else { - number = readUint32(data, position); - } - referredTo.push(number); - position += referredToSegmentNumberSize; - } - segmentHeader.referredTo = referredTo; - if (!pageAssociationFieldSize) { - segmentHeader.pageAssociation = data[position++]; + let referredToSegmentNumberSize = 4; + if (segmentHeader.number <= 256) { + referredToSegmentNumberSize = 1; + } else if (segmentHeader.number <= 65536) { + referredToSegmentNumberSize = 2; + } + const referredTo = []; + let i, ii; + for (i = 0; i < referredToCount; i++) { + let number; + if (referredToSegmentNumberSize === 1) { + number = data[position]; + } else if (referredToSegmentNumberSize === 2) { + number = readUint16(data, position); } else { - segmentHeader.pageAssociation = readUint32(data, position); - position += 4; + number = readUint32(data, position); } - segmentHeader.length = readUint32(data, position); + referredTo.push(number); + position += referredToSegmentNumberSize; + } + segmentHeader.referredTo = referredTo; + if (!pageAssociationFieldSize) { + segmentHeader.pageAssociation = data[position++]; + } else { + segmentHeader.pageAssociation = readUint32(data, position); position += 4; - - if (segmentHeader.length === 0xffffffff) { - // 7.2.7 Segment data length, unknown segment length - if (segmentType === 38) { - // ImmediateGenericRegion - const genericRegionInfo = readRegionSegmentInformation(data, position); - const genericRegionSegmentFlags = - data[position + RegionSegmentInformationFieldLength]; - const genericRegionMmr = !!(genericRegionSegmentFlags & 1); - // searching for the segment end - const searchPatternLength = 6; - const searchPattern = new Uint8Array(searchPatternLength); - if (!genericRegionMmr) { - searchPattern[0] = 0xff; - searchPattern[1] = 0xac; - } - searchPattern[2] = (genericRegionInfo.height >>> 24) & 0xff; - searchPattern[3] = (genericRegionInfo.height >> 16) & 0xff; - searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; - searchPattern[5] = genericRegionInfo.height & 0xff; - for (i = position, ii = data.length; i < ii; i++) { - let j = 0; - while (j < searchPatternLength && searchPattern[j] === data[i + j]) { - j++; - } - if (j === searchPatternLength) { - segmentHeader.length = i + searchPatternLength; - break; - } - } - if (segmentHeader.length === 0xffffffff) { - throw new Jbig2Error("segment end was not found"); - } - } else { - throw new Jbig2Error("invalid unknown segment length"); - } - } - segmentHeader.headerEnd = position; - return segmentHeader; } + segmentHeader.length = readUint32(data, position); + position += 4; - function readSegments(header, data, start, end) { - const segments = []; - let position = start; - while (position < end) { - const segmentHeader = readSegmentHeader(data, position); - position = segmentHeader.headerEnd; - const segment = { - header: segmentHeader, - data, - }; - if (!header.randomAccess) { - segment.start = position; - position += segmentHeader.length; - segment.end = position; + if (segmentHeader.length === 0xffffffff) { + // 7.2.7 Segment data length, unknown segment length + if (segmentType === 38) { + // ImmediateGenericRegion + const genericRegionInfo = readRegionSegmentInformation(data, position); + const genericRegionSegmentFlags = + data[position + RegionSegmentInformationFieldLength]; + const genericRegionMmr = !!(genericRegionSegmentFlags & 1); + // searching for the segment end + const searchPatternLength = 6; + const searchPattern = new Uint8Array(searchPatternLength); + if (!genericRegionMmr) { + searchPattern[0] = 0xff; + searchPattern[1] = 0xac; } - segments.push(segment); - if (segmentHeader.type === 51) { - break; // end of file is found + searchPattern[2] = (genericRegionInfo.height >>> 24) & 0xff; + searchPattern[3] = (genericRegionInfo.height >> 16) & 0xff; + searchPattern[4] = (genericRegionInfo.height >> 8) & 0xff; + searchPattern[5] = genericRegionInfo.height & 0xff; + for (i = position, ii = data.length; i < ii; i++) { + let j = 0; + while (j < searchPatternLength && searchPattern[j] === data[i + j]) { + j++; + } + if (j === searchPatternLength) { + segmentHeader.length = i + searchPatternLength; + break; + } } + if (segmentHeader.length === 0xffffffff) { + throw new Jbig2Error("segment end was not found"); + } + } else { + throw new Jbig2Error("invalid unknown segment length"); } - if (header.randomAccess) { - for (let i = 0, ii = segments.length; i < ii; i++) { - segments[i].start = position; - position += segments[i].header.length; - segments[i].end = position; - } - } - return segments; } + segmentHeader.headerEnd = position; + return segmentHeader; +} - // 7.4.1 Region segment information field - function readRegionSegmentInformation(data, start) { - return { - width: readUint32(data, start), - height: readUint32(data, start + 4), - x: readUint32(data, start + 8), - y: readUint32(data, start + 12), - combinationOperator: data[start + 16] & 7, +function readSegments(header, data, start, end) { + const segments = []; + let position = start; + while (position < end) { + const segmentHeader = readSegmentHeader(data, position); + position = segmentHeader.headerEnd; + const segment = { + header: segmentHeader, + data, }; + if (!header.randomAccess) { + segment.start = position; + position += segmentHeader.length; + segment.end = position; + } + segments.push(segment); + if (segmentHeader.type === 51) { + break; // end of file is found + } } - const RegionSegmentInformationFieldLength = 17; + if (header.randomAccess) { + for (let i = 0, ii = segments.length; i < ii; i++) { + segments[i].start = position; + position += segments[i].header.length; + segments[i].end = position; + } + } + return segments; +} - function processSegment(segment, visitor) { - const header = segment.header; +// 7.4.1 Region segment information field +function readRegionSegmentInformation(data, start) { + return { + width: readUint32(data, start), + height: readUint32(data, start + 4), + x: readUint32(data, start + 8), + y: readUint32(data, start + 12), + combinationOperator: data[start + 16] & 7, + }; +} +const RegionSegmentInformationFieldLength = 17; - const data = segment.data, - end = segment.end; - let position = segment.start; - let args, at, i, atLength; - switch (header.type) { - case 0: // SymbolDictionary - // 7.4.2 Symbol dictionary segment syntax - const dictionary = {}; - const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 - dictionary.huffman = !!(dictionaryFlags & 1); - dictionary.refinement = !!(dictionaryFlags & 2); - dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; - dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3; - dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1; - dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1; - dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); - dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); - dictionary.template = (dictionaryFlags >> 10) & 3; - dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; - position += 2; - if (!dictionary.huffman) { - atLength = dictionary.template === 0 ? 4 : 1; - at = []; - for (i = 0; i < atLength; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - dictionary.at = at; - } - if (dictionary.refinement && !dictionary.refinementTemplate) { - at = []; - for (i = 0; i < 2; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - dictionary.refinementAt = at; - } - dictionary.numberOfExportedSymbols = readUint32(data, position); - position += 4; - dictionary.numberOfNewSymbols = readUint32(data, position); - position += 4; - args = [ - dictionary, - header.number, - header.referredTo, - data, - position, - end, - ]; - break; - case 6: // ImmediateTextRegion - case 7: // ImmediateLosslessTextRegion - const textRegion = {}; - textRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const textRegionSegmentFlags = readUint16(data, position); - position += 2; - textRegion.huffman = !!(textRegionSegmentFlags & 1); - textRegion.refinement = !!(textRegionSegmentFlags & 2); - textRegion.logStripSize = (textRegionSegmentFlags >> 2) & 3; - textRegion.stripSize = 1 << textRegion.logStripSize; - textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3; - textRegion.transposed = !!(textRegionSegmentFlags & 64); - textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3; - textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1; - textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; - textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; - if (textRegion.huffman) { - const textRegionHuffmanFlags = readUint16(data, position); +function processSegment(segment, visitor) { + const header = segment.header; + + const data = segment.data, + end = segment.end; + let position = segment.start; + let args, at, i, atLength; + switch (header.type) { + case 0: // SymbolDictionary + // 7.4.2 Symbol dictionary segment syntax + const dictionary = {}; + const dictionaryFlags = readUint16(data, position); // 7.4.2.1.1 + dictionary.huffman = !!(dictionaryFlags & 1); + dictionary.refinement = !!(dictionaryFlags & 2); + dictionary.huffmanDHSelector = (dictionaryFlags >> 2) & 3; + dictionary.huffmanDWSelector = (dictionaryFlags >> 4) & 3; + dictionary.bitmapSizeSelector = (dictionaryFlags >> 6) & 1; + dictionary.aggregationInstancesSelector = (dictionaryFlags >> 7) & 1; + dictionary.bitmapCodingContextUsed = !!(dictionaryFlags & 256); + dictionary.bitmapCodingContextRetained = !!(dictionaryFlags & 512); + dictionary.template = (dictionaryFlags >> 10) & 3; + dictionary.refinementTemplate = (dictionaryFlags >> 12) & 1; + position += 2; + if (!dictionary.huffman) { + atLength = dictionary.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); position += 2; - textRegion.huffmanFS = textRegionHuffmanFlags & 3; - textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; - textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3; - textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3; - textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3; - textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3; - textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3; - textRegion.huffmanRefinementSizeSelector = !!( - textRegionHuffmanFlags & 0x4000 - ); } - if (textRegion.refinement && !textRegion.refinementTemplate) { - at = []; - for (i = 0; i < 2; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; - } - textRegion.refinementAt = at; + dictionary.at = at; + } + if (dictionary.refinement && !dictionary.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; } - textRegion.numberOfSymbolInstances = readUint32(data, position); - position += 4; - args = [textRegion, header.referredTo, data, position, end]; - break; - case 16: // PatternDictionary - // 7.4.4. Pattern dictionary segment syntax - const patternDictionary = {}; - const patternDictionaryFlags = data[position++]; - patternDictionary.mmr = !!(patternDictionaryFlags & 1); - patternDictionary.template = (patternDictionaryFlags >> 1) & 3; - patternDictionary.patternWidth = data[position++]; - patternDictionary.patternHeight = data[position++]; - patternDictionary.maxPatternIndex = readUint32(data, position); - position += 4; - args = [patternDictionary, header.number, data, position, end]; - break; - case 22: // ImmediateHalftoneRegion - case 23: // ImmediateLosslessHalftoneRegion - // 7.4.5 Halftone region segment syntax - const halftoneRegion = {}; - halftoneRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const halftoneRegionFlags = data[position++]; - halftoneRegion.mmr = !!(halftoneRegionFlags & 1); - halftoneRegion.template = (halftoneRegionFlags >> 1) & 3; - halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); - halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; - halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; - halftoneRegion.gridWidth = readUint32(data, position); - position += 4; - halftoneRegion.gridHeight = readUint32(data, position); - position += 4; - halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; - position += 4; - halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; - position += 4; - halftoneRegion.gridVectorX = readUint16(data, position); + dictionary.refinementAt = at; + } + dictionary.numberOfExportedSymbols = readUint32(data, position); + position += 4; + dictionary.numberOfNewSymbols = readUint32(data, position); + position += 4; + args = [ + dictionary, + header.number, + header.referredTo, + data, + position, + end, + ]; + break; + case 6: // ImmediateTextRegion + case 7: // ImmediateLosslessTextRegion + const textRegion = {}; + textRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const textRegionSegmentFlags = readUint16(data, position); + position += 2; + textRegion.huffman = !!(textRegionSegmentFlags & 1); + textRegion.refinement = !!(textRegionSegmentFlags & 2); + textRegion.logStripSize = (textRegionSegmentFlags >> 2) & 3; + textRegion.stripSize = 1 << textRegion.logStripSize; + textRegion.referenceCorner = (textRegionSegmentFlags >> 4) & 3; + textRegion.transposed = !!(textRegionSegmentFlags & 64); + textRegion.combinationOperator = (textRegionSegmentFlags >> 7) & 3; + textRegion.defaultPixelValue = (textRegionSegmentFlags >> 9) & 1; + textRegion.dsOffset = (textRegionSegmentFlags << 17) >> 27; + textRegion.refinementTemplate = (textRegionSegmentFlags >> 15) & 1; + if (textRegion.huffman) { + const textRegionHuffmanFlags = readUint16(data, position); position += 2; - halftoneRegion.gridVectorY = readUint16(data, position); - position += 2; - args = [halftoneRegion, header.referredTo, data, position, end]; - break; - case 38: // ImmediateGenericRegion - case 39: // ImmediateLosslessGenericRegion - const genericRegion = {}; - genericRegion.info = readRegionSegmentInformation(data, position); - position += RegionSegmentInformationFieldLength; - const genericRegionSegmentFlags = data[position++]; - genericRegion.mmr = !!(genericRegionSegmentFlags & 1); - genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; - genericRegion.prediction = !!(genericRegionSegmentFlags & 8); - if (!genericRegion.mmr) { - atLength = genericRegion.template === 0 ? 4 : 1; - at = []; - for (i = 0; i < atLength; i++) { - at.push({ - x: readInt8(data, position), - y: readInt8(data, position + 1), - }); - position += 2; + textRegion.huffmanFS = textRegionHuffmanFlags & 3; + textRegion.huffmanDS = (textRegionHuffmanFlags >> 2) & 3; + textRegion.huffmanDT = (textRegionHuffmanFlags >> 4) & 3; + textRegion.huffmanRefinementDW = (textRegionHuffmanFlags >> 6) & 3; + textRegion.huffmanRefinementDH = (textRegionHuffmanFlags >> 8) & 3; + textRegion.huffmanRefinementDX = (textRegionHuffmanFlags >> 10) & 3; + textRegion.huffmanRefinementDY = (textRegionHuffmanFlags >> 12) & 3; + textRegion.huffmanRefinementSizeSelector = !!( + textRegionHuffmanFlags & 0x4000 + ); + } + if (textRegion.refinement && !textRegion.refinementTemplate) { + at = []; + for (i = 0; i < 2; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + textRegion.refinementAt = at; + } + textRegion.numberOfSymbolInstances = readUint32(data, position); + position += 4; + args = [textRegion, header.referredTo, data, position, end]; + break; + case 16: // PatternDictionary + // 7.4.4. Pattern dictionary segment syntax + const patternDictionary = {}; + const patternDictionaryFlags = data[position++]; + patternDictionary.mmr = !!(patternDictionaryFlags & 1); + patternDictionary.template = (patternDictionaryFlags >> 1) & 3; + patternDictionary.patternWidth = data[position++]; + patternDictionary.patternHeight = data[position++]; + patternDictionary.maxPatternIndex = readUint32(data, position); + position += 4; + args = [patternDictionary, header.number, data, position, end]; + break; + case 22: // ImmediateHalftoneRegion + case 23: // ImmediateLosslessHalftoneRegion + // 7.4.5 Halftone region segment syntax + const halftoneRegion = {}; + halftoneRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const halftoneRegionFlags = data[position++]; + halftoneRegion.mmr = !!(halftoneRegionFlags & 1); + halftoneRegion.template = (halftoneRegionFlags >> 1) & 3; + halftoneRegion.enableSkip = !!(halftoneRegionFlags & 8); + halftoneRegion.combinationOperator = (halftoneRegionFlags >> 4) & 7; + halftoneRegion.defaultPixelValue = (halftoneRegionFlags >> 7) & 1; + halftoneRegion.gridWidth = readUint32(data, position); + position += 4; + halftoneRegion.gridHeight = readUint32(data, position); + position += 4; + halftoneRegion.gridOffsetX = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridOffsetY = readUint32(data, position) & 0xffffffff; + position += 4; + halftoneRegion.gridVectorX = readUint16(data, position); + position += 2; + halftoneRegion.gridVectorY = readUint16(data, position); + position += 2; + args = [halftoneRegion, header.referredTo, data, position, end]; + break; + case 38: // ImmediateGenericRegion + case 39: // ImmediateLosslessGenericRegion + const genericRegion = {}; + genericRegion.info = readRegionSegmentInformation(data, position); + position += RegionSegmentInformationFieldLength; + const genericRegionSegmentFlags = data[position++]; + genericRegion.mmr = !!(genericRegionSegmentFlags & 1); + genericRegion.template = (genericRegionSegmentFlags >> 1) & 3; + genericRegion.prediction = !!(genericRegionSegmentFlags & 8); + if (!genericRegion.mmr) { + atLength = genericRegion.template === 0 ? 4 : 1; + at = []; + for (i = 0; i < atLength; i++) { + at.push({ + x: readInt8(data, position), + y: readInt8(data, position + 1), + }); + position += 2; + } + genericRegion.at = at; + } + args = [genericRegion, data, position, end]; + break; + case 48: // PageInformation + const pageInfo = { + width: readUint32(data, position), + height: readUint32(data, position + 4), + resolutionX: readUint32(data, position + 8), + resolutionY: readUint32(data, position + 12), + }; + if (pageInfo.height === 0xffffffff) { + delete pageInfo.height; + } + const pageSegmentFlags = data[position + 16]; + readUint16(data, position + 17); // pageStripingInformation + pageInfo.lossless = !!(pageSegmentFlags & 1); + pageInfo.refinement = !!(pageSegmentFlags & 2); + pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; + pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3; + pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); + pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); + args = [pageInfo]; + break; + case 49: // EndOfPage + break; + case 50: // EndOfStripe + break; + case 51: // EndOfFile + break; + case 53: // Tables + args = [header.number, data, position, end]; + break; + case 62: // 7.4.15 defines 2 extension types which + // are comments and can be ignored. + break; + default: + throw new Jbig2Error( + `segment type ${header.typeName}(${header.type})` + + " is not implemented" + ); + } + const callbackName = "on" + header.typeName; + if (callbackName in visitor) { + visitor[callbackName].apply(visitor, args); + } +} + +function processSegments(segments, visitor) { + for (let i = 0, ii = segments.length; i < ii; i++) { + processSegment(segments[i], visitor); + } +} + +function parseJbig2Chunks(chunks) { + const visitor = new SimpleSegmentVisitor(); + for (let i = 0, ii = chunks.length; i < ii; i++) { + const chunk = chunks[i]; + const segments = readSegments({}, chunk.data, chunk.start, chunk.end); + processSegments(segments, visitor); + } + return visitor.buffer; +} + +function parseJbig2(data) { + const end = data.length; + let position = 0; + + if ( + data[position] !== 0x97 || + data[position + 1] !== 0x4a || + data[position + 2] !== 0x42 || + data[position + 3] !== 0x32 || + data[position + 4] !== 0x0d || + data[position + 5] !== 0x0a || + data[position + 6] !== 0x1a || + data[position + 7] !== 0x0a + ) { + throw new Jbig2Error("parseJbig2 - invalid header."); + } + + const header = Object.create(null); + position += 8; + const flags = data[position++]; + header.randomAccess = !(flags & 1); + if (!(flags & 2)) { + header.numberOfPages = readUint32(data, position); + position += 4; + } + + const segments = readSegments(header, data, position, end); + const visitor = new SimpleSegmentVisitor(); + processSegments(segments, visitor); + + const { width, height } = visitor.currentPageInfo; + const bitPacked = visitor.buffer; + const imgData = new Uint8ClampedArray(width * height); + let q = 0, + k = 0; + for (let i = 0; i < height; i++) { + let mask = 0, + buffer; + for (let j = 0; j < width; j++) { + if (!mask) { + mask = 128; + buffer = bitPacked[k++]; + } + imgData[q++] = buffer & mask ? 0 : 255; + mask >>= 1; + } + } + + return { imgData, width, height }; +} + +class SimpleSegmentVisitor { + onPageInformation(info) { + this.currentPageInfo = info; + const rowSize = (info.width + 7) >> 3; + const buffer = new Uint8ClampedArray(rowSize * info.height); + // The contents of ArrayBuffers are initialized to 0. + // Fill the buffer with 0xFF only if info.defaultPixelValue is set + if (info.defaultPixelValue) { + for (let i = 0, ii = buffer.length; i < ii; i++) { + buffer[i] = 0xff; + } + } + this.buffer = buffer; + } + + drawBitmap(regionInfo, bitmap) { + const pageInfo = this.currentPageInfo; + const width = regionInfo.width, + height = regionInfo.height; + const rowSize = (pageInfo.width + 7) >> 3; + const combinationOperator = pageInfo.combinationOperatorOverride + ? regionInfo.combinationOperator + : pageInfo.combinationOperator; + const buffer = this.buffer; + const mask0 = 128 >> (regionInfo.x & 7); + let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); + let i, j, mask, offset; + switch (combinationOperator) { + case 0: // OR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] |= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } } - genericRegion.at = at; + offset0 += rowSize; } - args = [genericRegion, data, position, end]; break; - case 48: // PageInformation - const pageInfo = { - width: readUint32(data, position), - height: readUint32(data, position + 4), - resolutionX: readUint32(data, position + 8), - resolutionY: readUint32(data, position + 12), - }; - if (pageInfo.height === 0xffffffff) { - delete pageInfo.height; + case 2: // XOR + for (i = 0; i < height; i++) { + mask = mask0; + offset = offset0; + for (j = 0; j < width; j++) { + if (bitmap[i][j]) { + buffer[offset] ^= mask; + } + mask >>= 1; + if (!mask) { + mask = 128; + offset++; + } + } + offset0 += rowSize; } - const pageSegmentFlags = data[position + 16]; - readUint16(data, position + 17); // pageStripingInformation - pageInfo.lossless = !!(pageSegmentFlags & 1); - pageInfo.refinement = !!(pageSegmentFlags & 2); - pageInfo.defaultPixelValue = (pageSegmentFlags >> 2) & 1; - pageInfo.combinationOperator = (pageSegmentFlags >> 3) & 3; - pageInfo.requiresBuffer = !!(pageSegmentFlags & 32); - pageInfo.combinationOperatorOverride = !!(pageSegmentFlags & 64); - args = [pageInfo]; - break; - case 49: // EndOfPage - break; - case 50: // EndOfStripe - break; - case 51: // EndOfFile - break; - case 53: // Tables - args = [header.number, data, position, end]; - break; - case 62: // 7.4.15 defines 2 extension types which - // are comments and can be ignored. break; default: throw new Jbig2Error( - `segment type ${header.typeName}(${header.type})` + - " is not implemented" + `operator ${combinationOperator} is not supported` ); } - const callbackName = "on" + header.typeName; - if (callbackName in visitor) { - visitor[callbackName].apply(visitor, args); - } } - function processSegments(segments, visitor) { - for (let i = 0, ii = segments.length; i < ii; i++) { - processSegment(segments[i], visitor); - } + onImmediateGenericRegion(region, data, start, end) { + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeBitmap( + region.mmr, + regionInfo.width, + regionInfo.height, + region.template, + region.prediction, + null, + region.at, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); } - function parseJbig2Chunks(chunks) { - const visitor = new SimpleSegmentVisitor(); - for (let i = 0, ii = chunks.length; i < ii; i++) { - const chunk = chunks[i]; - const segments = readSegments({}, chunk.data, chunk.start, chunk.end); - processSegments(segments, visitor); - } - return visitor.buffer; + onImmediateLosslessGenericRegion() { + this.onImmediateGenericRegion.apply(this, arguments); } - function parseJbig2(data) { - const end = data.length; - let position = 0; - - if ( - data[position] !== 0x97 || - data[position + 1] !== 0x4a || - data[position + 2] !== 0x42 || - data[position + 3] !== 0x32 || - data[position + 4] !== 0x0d || - data[position + 5] !== 0x0a || - data[position + 6] !== 0x1a || - data[position + 7] !== 0x0a - ) { - throw new Jbig2Error("parseJbig2 - invalid header."); - } - - const header = Object.create(null); - position += 8; - const flags = data[position++]; - header.randomAccess = !(flags & 1); - if (!(flags & 2)) { - header.numberOfPages = readUint32(data, position); - position += 4; - } - - const segments = readSegments(header, data, position, end); - const visitor = new SimpleSegmentVisitor(); - processSegments(segments, visitor); - - const { width, height } = visitor.currentPageInfo; - const bitPacked = visitor.buffer; - const imgData = new Uint8ClampedArray(width * height); - let q = 0, - k = 0; - for (let i = 0; i < height; i++) { - let mask = 0, - buffer; - for (let j = 0; j < width; j++) { - if (!mask) { - mask = 128; - buffer = bitPacked[k++]; - } - imgData[q++] = buffer & mask ? 0 : 255; - mask >>= 1; - } - } - - return { imgData, width, height }; - } - - function SimpleSegmentVisitor() {} - - SimpleSegmentVisitor.prototype = { - onPageInformation: function SimpleSegmentVisitor_onPageInformation(info) { - this.currentPageInfo = info; - const rowSize = (info.width + 7) >> 3; - const buffer = new Uint8ClampedArray(rowSize * info.height); - // The contents of ArrayBuffers are initialized to 0. - // Fill the buffer with 0xFF only if info.defaultPixelValue is set - if (info.defaultPixelValue) { - for (let i = 0, ii = buffer.length; i < ii; i++) { - buffer[i] = 0xff; - } - } - this.buffer = buffer; - }, - drawBitmap: function SimpleSegmentVisitor_drawBitmap(regionInfo, bitmap) { - const pageInfo = this.currentPageInfo; - const width = regionInfo.width, - height = regionInfo.height; - const rowSize = (pageInfo.width + 7) >> 3; - const combinationOperator = pageInfo.combinationOperatorOverride - ? regionInfo.combinationOperator - : pageInfo.combinationOperator; - const buffer = this.buffer; - const mask0 = 128 >> (regionInfo.x & 7); - let offset0 = regionInfo.y * rowSize + (regionInfo.x >> 3); - let i, j, mask, offset; - switch (combinationOperator) { - case 0: // OR - for (i = 0; i < height; i++) { - mask = mask0; - offset = offset0; - for (j = 0; j < width; j++) { - if (bitmap[i][j]) { - buffer[offset] |= mask; - } - mask >>= 1; - if (!mask) { - mask = 128; - offset++; - } - } - offset0 += rowSize; - } - break; - case 2: // XOR - for (i = 0; i < height; i++) { - mask = mask0; - offset = offset0; - for (j = 0; j < width; j++) { - if (bitmap[i][j]) { - buffer[offset] ^= mask; - } - mask >>= 1; - if (!mask) { - mask = 128; - offset++; - } - } - offset0 += rowSize; - } - break; - default: - throw new Jbig2Error( - `operator ${combinationOperator} is not supported` - ); - } - }, - onImmediateGenericRegion: function SimpleSegmentVisitor_onImmediateGenericRegion( - region, - data, - start, - end - ) { - const regionInfo = region.info; - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeBitmap( - region.mmr, - regionInfo.width, - regionInfo.height, - region.template, - region.prediction, - null, - region.at, - decodingContext + onSymbolDictionary( + dictionary, + currentSegment, + referredSegments, + data, + start, + end + ) { + let huffmanTables, huffmanInput; + if (dictionary.huffman) { + huffmanTables = getSymbolDictionaryHuffmanTables( + dictionary, + referredSegments, + this.customTables ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessGenericRegion: function SimpleSegmentVisitor_onImmediateLosslessGenericRegion() { - this.onImmediateGenericRegion.apply(this, arguments); - }, - onSymbolDictionary: function SimpleSegmentVisitor_onSymbolDictionary( - dictionary, - currentSegment, - referredSegments, - data, - start, - end - ) { - let huffmanTables, huffmanInput; - if (dictionary.huffman) { - huffmanTables = getSymbolDictionaryHuffmanTables( - dictionary, - referredSegments, - this.customTables - ); - huffmanInput = new Reader(data, start, end); - } + huffmanInput = new Reader(data, start, end); + } - // Combines exported symbols from all referred segments - let symbols = this.symbols; - if (!symbols) { - this.symbols = symbols = {}; - } + // Combines exported symbols from all referred segments + let symbols = this.symbols; + if (!symbols) { + this.symbols = symbols = {}; + } - let inputSymbols = []; - for (let i = 0, ii = referredSegments.length; i < ii; i++) { - const referredSymbols = symbols[referredSegments[i]]; - // referredSymbols is undefined when we have a reference to a Tables - // segment instead of a SymbolDictionary. - if (referredSymbols) { - inputSymbols = inputSymbols.concat(referredSymbols); - } + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { + const referredSymbols = symbols[referredSegments[i]]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols = inputSymbols.concat(referredSymbols); } + } - const decodingContext = new DecodingContext(data, start, end); - symbols[currentSegment] = decodeSymbolDictionary( - dictionary.huffman, - dictionary.refinement, - inputSymbols, - dictionary.numberOfNewSymbols, - dictionary.numberOfExportedSymbols, - huffmanTables, - dictionary.template, - dictionary.at, - dictionary.refinementTemplate, - dictionary.refinementAt, - decodingContext, + const decodingContext = new DecodingContext(data, start, end); + symbols[currentSegment] = decodeSymbolDictionary( + dictionary.huffman, + dictionary.refinement, + inputSymbols, + dictionary.numberOfNewSymbols, + dictionary.numberOfExportedSymbols, + huffmanTables, + dictionary.template, + dictionary.at, + dictionary.refinementTemplate, + dictionary.refinementAt, + decodingContext, + huffmanInput + ); + } + + onImmediateTextRegion(region, referredSegments, data, start, end) { + const regionInfo = region.info; + let huffmanTables, huffmanInput; + + // Combines exported symbols from all referred segments + const symbols = this.symbols; + let inputSymbols = []; + for (let i = 0, ii = referredSegments.length; i < ii; i++) { + const referredSymbols = symbols[referredSegments[i]]; + // referredSymbols is undefined when we have a reference to a Tables + // segment instead of a SymbolDictionary. + if (referredSymbols) { + inputSymbols = inputSymbols.concat(referredSymbols); + } + } + const symbolCodeLength = log2(inputSymbols.length); + if (region.huffman) { + huffmanInput = new Reader(data, start, end); + huffmanTables = getTextRegionHuffmanTables( + region, + referredSegments, + this.customTables, + inputSymbols.length, huffmanInput ); - }, - onImmediateTextRegion: function SimpleSegmentVisitor_onImmediateTextRegion( - region, - referredSegments, - data, - start, - end - ) { - const regionInfo = region.info; - let huffmanTables, huffmanInput; + } - // Combines exported symbols from all referred segments - const symbols = this.symbols; - let inputSymbols = []; - for (let i = 0, ii = referredSegments.length; i < ii; i++) { - const referredSymbols = symbols[referredSegments[i]]; - // referredSymbols is undefined when we have a reference to a Tables - // segment instead of a SymbolDictionary. - if (referredSymbols) { - inputSymbols = inputSymbols.concat(referredSymbols); - } - } - const symbolCodeLength = log2(inputSymbols.length); - if (region.huffman) { - huffmanInput = new Reader(data, start, end); - huffmanTables = getTextRegionHuffmanTables( - region, - referredSegments, - this.customTables, - inputSymbols.length, - huffmanInput - ); - } + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeTextRegion( + region.huffman, + region.refinement, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.numberOfSymbolInstances, + region.stripSize, + inputSymbols, + symbolCodeLength, + region.transposed, + region.dsOffset, + region.referenceCorner, + region.combinationOperator, + huffmanTables, + region.refinementTemplate, + region.refinementAt, + decodingContext, + region.logStripSize, + huffmanInput + ); + this.drawBitmap(regionInfo, bitmap); + } - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeTextRegion( - region.huffman, - region.refinement, - regionInfo.width, - regionInfo.height, - region.defaultPixelValue, - region.numberOfSymbolInstances, - region.stripSize, - inputSymbols, - symbolCodeLength, - region.transposed, - region.dsOffset, - region.referenceCorner, - region.combinationOperator, - huffmanTables, - region.refinementTemplate, - region.refinementAt, - decodingContext, - region.logStripSize, - huffmanInput - ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessTextRegion: function SimpleSegmentVisitor_onImmediateLosslessTextRegion() { - this.onImmediateTextRegion.apply(this, arguments); - }, - onPatternDictionary(dictionary, currentSegment, data, start, end) { - let patterns = this.patterns; - if (!patterns) { - this.patterns = patterns = {}; - } - const decodingContext = new DecodingContext(data, start, end); - patterns[currentSegment] = decodePatternDictionary( - dictionary.mmr, - dictionary.patternWidth, - dictionary.patternHeight, - dictionary.maxPatternIndex, - dictionary.template, - decodingContext - ); - }, - onImmediateHalftoneRegion(region, referredSegments, data, start, end) { - // HalftoneRegion refers to exactly one PatternDictionary. - const patterns = this.patterns[referredSegments[0]]; - const regionInfo = region.info; - const decodingContext = new DecodingContext(data, start, end); - const bitmap = decodeHalftoneRegion( - region.mmr, - patterns, - region.template, - regionInfo.width, - regionInfo.height, - region.defaultPixelValue, - region.enableSkip, - region.combinationOperator, - region.gridWidth, - region.gridHeight, - region.gridOffsetX, - region.gridOffsetY, - region.gridVectorX, - region.gridVectorY, - decodingContext - ); - this.drawBitmap(regionInfo, bitmap); - }, - onImmediateLosslessHalftoneRegion() { - this.onImmediateHalftoneRegion.apply(this, arguments); - }, - onTables(currentSegment, data, start, end) { - let customTables = this.customTables; - if (!customTables) { - this.customTables = customTables = {}; - } - customTables[currentSegment] = decodeTablesSegment(data, start, end); - }, - }; + onImmediateLosslessTextRegion() { + this.onImmediateTextRegion.apply(this, arguments); + } - function HuffmanLine(lineData) { + onPatternDictionary(dictionary, currentSegment, data, start, end) { + let patterns = this.patterns; + if (!patterns) { + this.patterns = patterns = {}; + } + const decodingContext = new DecodingContext(data, start, end); + patterns[currentSegment] = decodePatternDictionary( + dictionary.mmr, + dictionary.patternWidth, + dictionary.patternHeight, + dictionary.maxPatternIndex, + dictionary.template, + decodingContext + ); + } + + onImmediateHalftoneRegion(region, referredSegments, data, start, end) { + // HalftoneRegion refers to exactly one PatternDictionary. + const patterns = this.patterns[referredSegments[0]]; + const regionInfo = region.info; + const decodingContext = new DecodingContext(data, start, end); + const bitmap = decodeHalftoneRegion( + region.mmr, + patterns, + region.template, + regionInfo.width, + regionInfo.height, + region.defaultPixelValue, + region.enableSkip, + region.combinationOperator, + region.gridWidth, + region.gridHeight, + region.gridOffsetX, + region.gridOffsetY, + region.gridVectorX, + region.gridVectorY, + decodingContext + ); + this.drawBitmap(regionInfo, bitmap); + } + + onImmediateLosslessHalftoneRegion() { + this.onImmediateHalftoneRegion.apply(this, arguments); + } + + onTables(currentSegment, data, start, end) { + let customTables = this.customTables; + if (!customTables) { + this.customTables = customTables = {}; + } + customTables[currentSegment] = decodeTablesSegment(data, start, end); + } +} + +class HuffmanLine { + constructor(lineData) { if (lineData.length === 2) { // OOB line. this.isOOB = true; @@ -1837,8 +1817,10 @@ const Jbig2Image = (function Jbig2ImageClosure() { this.isLowerRange = lineData[4] === "lower"; } } +} - function HuffmanTreeNode(line) { +class HuffmanTreeNode { + constructor(line) { this.children = []; if (line) { // Leaf node @@ -1853,38 +1835,39 @@ const Jbig2Image = (function Jbig2ImageClosure() { } } - HuffmanTreeNode.prototype = { - buildTree(line, shift) { - const bit = (line.prefixCode >> shift) & 1; - if (shift <= 0) { - // Create a leaf node. - this.children[bit] = new HuffmanTreeNode(line); - } else { - // Create an intermediate node and continue recursively. - let node = this.children[bit]; - if (!node) { - this.children[bit] = node = new HuffmanTreeNode(null); - } - node.buildTree(line, shift - 1); - } - }, - decodeNode(reader) { - if (this.isLeaf) { - if (this.isOOB) { - return null; - } - const htOffset = reader.readBits(this.rangeLength); - return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); - } - const node = this.children[reader.readBit()]; + buildTree(line, shift) { + const bit = (line.prefixCode >> shift) & 1; + if (shift <= 0) { + // Create a leaf node. + this.children[bit] = new HuffmanTreeNode(line); + } else { + // Create an intermediate node and continue recursively. + let node = this.children[bit]; if (!node) { - throw new Jbig2Error("invalid Huffman data"); + this.children[bit] = node = new HuffmanTreeNode(null); } - return node.decodeNode(reader); - }, - }; + node.buildTree(line, shift - 1); + } + } - function HuffmanTable(lines, prefixCodesDone) { + decodeNode(reader) { + if (this.isLeaf) { + if (this.isOOB) { + return null; + } + const htOffset = reader.readBits(this.rangeLength); + return this.rangeLow + (this.isLowerRange ? -htOffset : htOffset); + } + const node = this.children[reader.readBit()]; + if (!node) { + throw new Jbig2Error("invalid Huffman data"); + } + return node.decodeNode(reader); + } +} + +class HuffmanTable { + constructor(lines, prefixCodesDone) { if (!prefixCodesDone) { this.assignPrefixCodes(lines); } @@ -1898,357 +1881,356 @@ const Jbig2Image = (function Jbig2ImageClosure() { } } - HuffmanTable.prototype = { - decode(reader) { - return this.rootNode.decodeNode(reader); - }, - assignPrefixCodes(lines) { - // Annex B.3 Assigning the prefix codes. - const linesLength = lines.length; - let prefixLengthMax = 0; - for (let i = 0; i < linesLength; i++) { - prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); - } - - const histogram = new Uint32Array(prefixLengthMax + 1); - for (let i = 0; i < linesLength; i++) { - histogram[lines[i].prefixLength]++; - } - let currentLength = 1, - firstCode = 0, - currentCode, - currentTemp, - line; - histogram[0] = 0; - - while (currentLength <= prefixLengthMax) { - firstCode = (firstCode + histogram[currentLength - 1]) << 1; - currentCode = firstCode; - currentTemp = 0; - while (currentTemp < linesLength) { - line = lines[currentTemp]; - if (line.prefixLength === currentLength) { - line.prefixCode = currentCode; - currentCode++; - } - currentTemp++; - } - currentLength++; - } - }, - }; - - function decodeTablesSegment(data, start, end) { - // Decodes a Tables segment, i.e., a custom Huffman table. - // Annex B.2 Code table structure. - const flags = data[start]; - const lowestValue = readUint32(data, start + 1) & 0xffffffff; - const highestValue = readUint32(data, start + 5) & 0xffffffff; - const reader = new Reader(data, start + 9, end); - - const prefixSizeBits = ((flags >> 1) & 7) + 1; - const rangeSizeBits = ((flags >> 4) & 7) + 1; - const lines = []; - let prefixLength, - rangeLength, - currentRangeLow = lowestValue; - - // Normal table lines - do { - prefixLength = reader.readBits(prefixSizeBits); - rangeLength = reader.readBits(rangeSizeBits); - lines.push( - new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]) - ); - currentRangeLow += 1 << rangeLength; - } while (currentRangeLow < highestValue); - - // Lower range table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push( - new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"]) - ); - - // Upper range table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); - - if (flags & 1) { - // Out-of-band table line - prefixLength = reader.readBits(prefixSizeBits); - lines.push(new HuffmanLine([prefixLength, 0])); - } - - return new HuffmanTable(lines, false); + decode(reader) { + return this.rootNode.decodeNode(reader); } - const standardTablesCache = {}; - - function getStandardTable(number) { - // Annex B.5 Standard Huffman tables. - let table = standardTablesCache[number]; - if (table) { - return table; - } - let lines; - switch (number) { - case 1: - lines = [ - [0, 1, 4, 0x0], - [16, 2, 8, 0x2], - [272, 3, 16, 0x6], - [65808, 3, 32, 0x7], // upper - ]; - break; - case 2: - lines = [ - [0, 1, 0, 0x0], - [1, 2, 0, 0x2], - [2, 3, 0, 0x6], - [3, 4, 3, 0xe], - [11, 5, 6, 0x1e], - [75, 6, 32, 0x3e], // upper - [6, 0x3f], // OOB - ]; - break; - case 3: - lines = [ - [-256, 8, 8, 0xfe], - [0, 1, 0, 0x0], - [1, 2, 0, 0x2], - [2, 3, 0, 0x6], - [3, 4, 3, 0xe], - [11, 5, 6, 0x1e], - [-257, 8, 32, 0xff, "lower"], - [75, 7, 32, 0x7e], // upper - [6, 0x3e], // OOB - ]; - break; - case 4: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 0, 0x6], - [4, 4, 3, 0xe], - [12, 5, 6, 0x1e], - [76, 5, 32, 0x1f], // upper - ]; - break; - case 5: - lines = [ - [-255, 7, 8, 0x7e], - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 0, 0x6], - [4, 4, 3, 0xe], - [12, 5, 6, 0x1e], - [-256, 7, 32, 0x7f, "lower"], - [76, 6, 32, 0x3e], // upper - ]; - break; - case 6: - lines = [ - [-2048, 5, 10, 0x1c], - [-1024, 4, 9, 0x8], - [-512, 4, 8, 0x9], - [-256, 4, 7, 0xa], - [-128, 5, 6, 0x1d], - [-64, 5, 5, 0x1e], - [-32, 4, 5, 0xb], - [0, 2, 7, 0x0], - [128, 3, 7, 0x2], - [256, 3, 8, 0x3], - [512, 4, 9, 0xc], - [1024, 4, 10, 0xd], - [-2049, 6, 32, 0x3e, "lower"], - [2048, 6, 32, 0x3f], // upper - ]; - break; - case 7: - lines = [ - [-1024, 4, 9, 0x8], - [-512, 3, 8, 0x0], - [-256, 4, 7, 0x9], - [-128, 5, 6, 0x1a], - [-64, 5, 5, 0x1b], - [-32, 4, 5, 0xa], - [0, 4, 5, 0xb], - [32, 5, 5, 0x1c], - [64, 5, 6, 0x1d], - [128, 4, 7, 0xc], - [256, 3, 8, 0x1], - [512, 3, 9, 0x2], - [1024, 3, 10, 0x3], - [-1025, 5, 32, 0x1e, "lower"], - [2048, 5, 32, 0x1f], // upper - ]; - break; - case 8: - lines = [ - [-15, 8, 3, 0xfc], - [-7, 9, 1, 0x1fc], - [-5, 8, 1, 0xfd], - [-3, 9, 0, 0x1fd], - [-2, 7, 0, 0x7c], - [-1, 4, 0, 0xa], - [0, 2, 1, 0x0], - [2, 5, 0, 0x1a], - [3, 6, 0, 0x3a], - [4, 3, 4, 0x4], - [20, 6, 1, 0x3b], - [22, 4, 4, 0xb], - [38, 4, 5, 0xc], - [70, 5, 6, 0x1b], - [134, 5, 7, 0x1c], - [262, 6, 7, 0x3c], - [390, 7, 8, 0x7d], - [646, 6, 10, 0x3d], - [-16, 9, 32, 0x1fe, "lower"], - [1670, 9, 32, 0x1ff], // upper - [2, 0x1], // OOB - ]; - break; - case 9: - lines = [ - [-31, 8, 4, 0xfc], - [-15, 9, 2, 0x1fc], - [-11, 8, 2, 0xfd], - [-7, 9, 1, 0x1fd], - [-5, 7, 1, 0x7c], - [-3, 4, 1, 0xa], - [-1, 3, 1, 0x2], - [1, 3, 1, 0x3], - [3, 5, 1, 0x1a], - [5, 6, 1, 0x3a], - [7, 3, 5, 0x4], - [39, 6, 2, 0x3b], - [43, 4, 5, 0xb], - [75, 4, 6, 0xc], - [139, 5, 7, 0x1b], - [267, 5, 8, 0x1c], - [523, 6, 8, 0x3c], - [779, 7, 9, 0x7d], - [1291, 6, 11, 0x3d], - [-32, 9, 32, 0x1fe, "lower"], - [3339, 9, 32, 0x1ff], // upper - [2, 0x0], // OOB - ]; - break; - case 10: - lines = [ - [-21, 7, 4, 0x7a], - [-5, 8, 0, 0xfc], - [-4, 7, 0, 0x7b], - [-3, 5, 0, 0x18], - [-2, 2, 2, 0x0], - [2, 5, 0, 0x19], - [3, 6, 0, 0x36], - [4, 7, 0, 0x7c], - [5, 8, 0, 0xfd], - [6, 2, 6, 0x1], - [70, 5, 5, 0x1a], - [102, 6, 5, 0x37], - [134, 6, 6, 0x38], - [198, 6, 7, 0x39], - [326, 6, 8, 0x3a], - [582, 6, 9, 0x3b], - [1094, 6, 10, 0x3c], - [2118, 7, 11, 0x7d], - [-22, 8, 32, 0xfe, "lower"], - [4166, 8, 32, 0xff], // upper - [2, 0x2], // OOB - ]; - break; - case 11: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 1, 0x2], - [4, 4, 0, 0xc], - [5, 4, 1, 0xd], - [7, 5, 1, 0x1c], - [9, 5, 2, 0x1d], - [13, 6, 2, 0x3c], - [17, 7, 2, 0x7a], - [21, 7, 3, 0x7b], - [29, 7, 4, 0x7c], - [45, 7, 5, 0x7d], - [77, 7, 6, 0x7e], - [141, 7, 32, 0x7f], // upper - ]; - break; - case 12: - lines = [ - [1, 1, 0, 0x0], - [2, 2, 0, 0x2], - [3, 3, 1, 0x6], - [5, 5, 0, 0x1c], - [6, 5, 1, 0x1d], - [8, 6, 1, 0x3c], - [10, 7, 0, 0x7a], - [11, 7, 1, 0x7b], - [13, 7, 2, 0x7c], - [17, 7, 3, 0x7d], - [25, 7, 4, 0x7e], - [41, 8, 5, 0xfe], - [73, 8, 32, 0xff], // upper - ]; - break; - case 13: - lines = [ - [1, 1, 0, 0x0], - [2, 3, 0, 0x4], - [3, 4, 0, 0xc], - [4, 5, 0, 0x1c], - [5, 4, 1, 0xd], - [7, 3, 3, 0x5], - [15, 6, 1, 0x3a], - [17, 6, 2, 0x3b], - [21, 6, 3, 0x3c], - [29, 6, 4, 0x3d], - [45, 6, 5, 0x3e], - [77, 7, 6, 0x7e], - [141, 7, 32, 0x7f], // upper - ]; - break; - case 14: - lines = [ - [-2, 3, 0, 0x4], - [-1, 3, 0, 0x5], - [0, 1, 0, 0x0], - [1, 3, 0, 0x6], - [2, 3, 0, 0x7], - ]; - break; - case 15: - lines = [ - [-24, 7, 4, 0x7c], - [-8, 6, 2, 0x3c], - [-4, 5, 1, 0x1c], - [-2, 4, 0, 0xc], - [-1, 3, 0, 0x4], - [0, 1, 0, 0x0], - [1, 3, 0, 0x5], - [2, 4, 0, 0xd], - [3, 5, 1, 0x1d], - [5, 6, 2, 0x3d], - [9, 7, 4, 0x7d], - [-25, 7, 32, 0x7e, "lower"], - [25, 7, 32, 0x7f], // upper - ]; - break; - default: - throw new Jbig2Error(`standard table B.${number} does not exist`); + assignPrefixCodes(lines) { + // Annex B.3 Assigning the prefix codes. + const linesLength = lines.length; + let prefixLengthMax = 0; + for (let i = 0; i < linesLength; i++) { + prefixLengthMax = Math.max(prefixLengthMax, lines[i].prefixLength); } - for (let i = 0, ii = lines.length; i < ii; i++) { - lines[i] = new HuffmanLine(lines[i]); + const histogram = new Uint32Array(prefixLengthMax + 1); + for (let i = 0; i < linesLength; i++) { + histogram[lines[i].prefixLength]++; } - table = new HuffmanTable(lines, true); - standardTablesCache[number] = table; + let currentLength = 1, + firstCode = 0, + currentCode, + currentTemp, + line; + histogram[0] = 0; + + while (currentLength <= prefixLengthMax) { + firstCode = (firstCode + histogram[currentLength - 1]) << 1; + currentCode = firstCode; + currentTemp = 0; + while (currentTemp < linesLength) { + line = lines[currentTemp]; + if (line.prefixLength === currentLength) { + line.prefixCode = currentCode; + currentCode++; + } + currentTemp++; + } + currentLength++; + } + } +} + +function decodeTablesSegment(data, start, end) { + // Decodes a Tables segment, i.e., a custom Huffman table. + // Annex B.2 Code table structure. + const flags = data[start]; + const lowestValue = readUint32(data, start + 1) & 0xffffffff; + const highestValue = readUint32(data, start + 5) & 0xffffffff; + const reader = new Reader(data, start + 9, end); + + const prefixSizeBits = ((flags >> 1) & 7) + 1; + const rangeSizeBits = ((flags >> 4) & 7) + 1; + const lines = []; + let prefixLength, + rangeLength, + currentRangeLow = lowestValue; + + // Normal table lines + do { + prefixLength = reader.readBits(prefixSizeBits); + rangeLength = reader.readBits(rangeSizeBits); + lines.push( + new HuffmanLine([currentRangeLow, prefixLength, rangeLength, 0]) + ); + currentRangeLow += 1 << rangeLength; + } while (currentRangeLow < highestValue); + + // Lower range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([lowestValue - 1, prefixLength, 32, 0, "lower"])); + + // Upper range table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([highestValue, prefixLength, 32, 0])); + + if (flags & 1) { + // Out-of-band table line + prefixLength = reader.readBits(prefixSizeBits); + lines.push(new HuffmanLine([prefixLength, 0])); + } + + return new HuffmanTable(lines, false); +} + +const standardTablesCache = {}; + +function getStandardTable(number) { + // Annex B.5 Standard Huffman tables. + let table = standardTablesCache[number]; + if (table) { return table; } + let lines; + switch (number) { + case 1: + lines = [ + [0, 1, 4, 0x0], + [16, 2, 8, 0x2], + [272, 3, 16, 0x6], + [65808, 3, 32, 0x7], // upper + ]; + break; + case 2: + lines = [ + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [75, 6, 32, 0x3e], // upper + [6, 0x3f], // OOB + ]; + break; + case 3: + lines = [ + [-256, 8, 8, 0xfe], + [0, 1, 0, 0x0], + [1, 2, 0, 0x2], + [2, 3, 0, 0x6], + [3, 4, 3, 0xe], + [11, 5, 6, 0x1e], + [-257, 8, 32, 0xff, "lower"], + [75, 7, 32, 0x7e], // upper + [6, 0x3e], // OOB + ]; + break; + case 4: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [76, 5, 32, 0x1f], // upper + ]; + break; + case 5: + lines = [ + [-255, 7, 8, 0x7e], + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 0, 0x6], + [4, 4, 3, 0xe], + [12, 5, 6, 0x1e], + [-256, 7, 32, 0x7f, "lower"], + [76, 6, 32, 0x3e], // upper + ]; + break; + case 6: + lines = [ + [-2048, 5, 10, 0x1c], + [-1024, 4, 9, 0x8], + [-512, 4, 8, 0x9], + [-256, 4, 7, 0xa], + [-128, 5, 6, 0x1d], + [-64, 5, 5, 0x1e], + [-32, 4, 5, 0xb], + [0, 2, 7, 0x0], + [128, 3, 7, 0x2], + [256, 3, 8, 0x3], + [512, 4, 9, 0xc], + [1024, 4, 10, 0xd], + [-2049, 6, 32, 0x3e, "lower"], + [2048, 6, 32, 0x3f], // upper + ]; + break; + case 7: + lines = [ + [-1024, 4, 9, 0x8], + [-512, 3, 8, 0x0], + [-256, 4, 7, 0x9], + [-128, 5, 6, 0x1a], + [-64, 5, 5, 0x1b], + [-32, 4, 5, 0xa], + [0, 4, 5, 0xb], + [32, 5, 5, 0x1c], + [64, 5, 6, 0x1d], + [128, 4, 7, 0xc], + [256, 3, 8, 0x1], + [512, 3, 9, 0x2], + [1024, 3, 10, 0x3], + [-1025, 5, 32, 0x1e, "lower"], + [2048, 5, 32, 0x1f], // upper + ]; + break; + case 8: + lines = [ + [-15, 8, 3, 0xfc], + [-7, 9, 1, 0x1fc], + [-5, 8, 1, 0xfd], + [-3, 9, 0, 0x1fd], + [-2, 7, 0, 0x7c], + [-1, 4, 0, 0xa], + [0, 2, 1, 0x0], + [2, 5, 0, 0x1a], + [3, 6, 0, 0x3a], + [4, 3, 4, 0x4], + [20, 6, 1, 0x3b], + [22, 4, 4, 0xb], + [38, 4, 5, 0xc], + [70, 5, 6, 0x1b], + [134, 5, 7, 0x1c], + [262, 6, 7, 0x3c], + [390, 7, 8, 0x7d], + [646, 6, 10, 0x3d], + [-16, 9, 32, 0x1fe, "lower"], + [1670, 9, 32, 0x1ff], // upper + [2, 0x1], // OOB + ]; + break; + case 9: + lines = [ + [-31, 8, 4, 0xfc], + [-15, 9, 2, 0x1fc], + [-11, 8, 2, 0xfd], + [-7, 9, 1, 0x1fd], + [-5, 7, 1, 0x7c], + [-3, 4, 1, 0xa], + [-1, 3, 1, 0x2], + [1, 3, 1, 0x3], + [3, 5, 1, 0x1a], + [5, 6, 1, 0x3a], + [7, 3, 5, 0x4], + [39, 6, 2, 0x3b], + [43, 4, 5, 0xb], + [75, 4, 6, 0xc], + [139, 5, 7, 0x1b], + [267, 5, 8, 0x1c], + [523, 6, 8, 0x3c], + [779, 7, 9, 0x7d], + [1291, 6, 11, 0x3d], + [-32, 9, 32, 0x1fe, "lower"], + [3339, 9, 32, 0x1ff], // upper + [2, 0x0], // OOB + ]; + break; + case 10: + lines = [ + [-21, 7, 4, 0x7a], + [-5, 8, 0, 0xfc], + [-4, 7, 0, 0x7b], + [-3, 5, 0, 0x18], + [-2, 2, 2, 0x0], + [2, 5, 0, 0x19], + [3, 6, 0, 0x36], + [4, 7, 0, 0x7c], + [5, 8, 0, 0xfd], + [6, 2, 6, 0x1], + [70, 5, 5, 0x1a], + [102, 6, 5, 0x37], + [134, 6, 6, 0x38], + [198, 6, 7, 0x39], + [326, 6, 8, 0x3a], + [582, 6, 9, 0x3b], + [1094, 6, 10, 0x3c], + [2118, 7, 11, 0x7d], + [-22, 8, 32, 0xfe, "lower"], + [4166, 8, 32, 0xff], // upper + [2, 0x2], // OOB + ]; + break; + case 11: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 1, 0x2], + [4, 4, 0, 0xc], + [5, 4, 1, 0xd], + [7, 5, 1, 0x1c], + [9, 5, 2, 0x1d], + [13, 6, 2, 0x3c], + [17, 7, 2, 0x7a], + [21, 7, 3, 0x7b], + [29, 7, 4, 0x7c], + [45, 7, 5, 0x7d], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 12: + lines = [ + [1, 1, 0, 0x0], + [2, 2, 0, 0x2], + [3, 3, 1, 0x6], + [5, 5, 0, 0x1c], + [6, 5, 1, 0x1d], + [8, 6, 1, 0x3c], + [10, 7, 0, 0x7a], + [11, 7, 1, 0x7b], + [13, 7, 2, 0x7c], + [17, 7, 3, 0x7d], + [25, 7, 4, 0x7e], + [41, 8, 5, 0xfe], + [73, 8, 32, 0xff], // upper + ]; + break; + case 13: + lines = [ + [1, 1, 0, 0x0], + [2, 3, 0, 0x4], + [3, 4, 0, 0xc], + [4, 5, 0, 0x1c], + [5, 4, 1, 0xd], + [7, 3, 3, 0x5], + [15, 6, 1, 0x3a], + [17, 6, 2, 0x3b], + [21, 6, 3, 0x3c], + [29, 6, 4, 0x3d], + [45, 6, 5, 0x3e], + [77, 7, 6, 0x7e], + [141, 7, 32, 0x7f], // upper + ]; + break; + case 14: + lines = [ + [-2, 3, 0, 0x4], + [-1, 3, 0, 0x5], + [0, 1, 0, 0x0], + [1, 3, 0, 0x6], + [2, 3, 0, 0x7], + ]; + break; + case 15: + lines = [ + [-24, 7, 4, 0x7c], + [-8, 6, 2, 0x3c], + [-4, 5, 1, 0x1c], + [-2, 4, 0, 0xc], + [-1, 3, 0, 0x4], + [0, 1, 0, 0x0], + [1, 3, 0, 0x5], + [2, 4, 0, 0xd], + [3, 5, 1, 0x1d], + [5, 6, 2, 0x3d], + [9, 7, 4, 0x7d], + [-25, 7, 32, 0x7e, "lower"], + [25, 7, 32, 0x7f], // upper + ]; + break; + default: + throw new Jbig2Error(`standard table B.${number} does not exist`); + } - function Reader(data, start, end) { + for (let i = 0, ii = lines.length; i < ii; i++) { + lines[i] = new HuffmanLine(lines[i]); + } + table = new HuffmanTable(lines, true); + standardTablesCache[number] = table; + return table; +} + +class Reader { + constructor(data, start, end) { this.data = data; this.start = start; this.end = end; @@ -2257,337 +2239,330 @@ const Jbig2Image = (function Jbig2ImageClosure() { this.currentByte = 0; } - Reader.prototype = { - readBit() { - if (this.shift < 0) { - if (this.position >= this.end) { - throw new Jbig2Error("end of data while reading bit"); - } - this.currentByte = this.data[this.position++]; - this.shift = 7; - } - const bit = (this.currentByte >> this.shift) & 1; - this.shift--; - return bit; - }, - - readBits(numBits) { - let result = 0, - i; - for (i = numBits - 1; i >= 0; i--) { - result |= this.readBit() << i; - } - return result; - }, - - byteAlign() { - this.shift = -1; - }, - - next() { + readBit() { + if (this.shift < 0) { if (this.position >= this.end) { - return -1; - } - return this.data[this.position++]; - }, - }; - - function getCustomHuffmanTable(index, referredTo, customTables) { - // Returns a Tables segment that has been earlier decoded. - // See 7.4.2.1.6 (symbol dictionary) or 7.4.3.1.6 (text region). - let currentIndex = 0; - for (let i = 0, ii = referredTo.length; i < ii; i++) { - const table = customTables[referredTo[i]]; - if (table) { - if (index === currentIndex) { - return table; - } - currentIndex++; + throw new Jbig2Error("end of data while reading bit"); } + this.currentByte = this.data[this.position++]; + this.shift = 7; } - throw new Jbig2Error("can't find custom Huffman table"); + const bit = (this.currentByte >> this.shift) & 1; + this.shift--; + return bit; } - function getTextRegionHuffmanTables( - textRegion, - referredTo, - customTables, - numberOfSymbols, - reader - ) { - // 7.4.3.1.7 Symbol ID Huffman table decoding - - // Read code lengths for RUNCODEs 0...34. - const codes = []; - for (let i = 0; i <= 34; i++) { - const codeLength = reader.readBits(4); - codes.push(new HuffmanLine([i, codeLength, 0, 0])); + readBits(numBits) { + let result = 0, + i; + for (i = numBits - 1; i >= 0; i--) { + result |= this.readBit() << i; } - // Assign Huffman codes for RUNCODEs. - const runCodesTable = new HuffmanTable(codes, false); + return result; + } - // Read a Huffman code using the assignment above. - // Interpret the RUNCODE codes and the additional bits (if any). - codes.length = 0; - for (let i = 0; i < numberOfSymbols; ) { - const codeLength = runCodesTable.decode(reader); - if (codeLength >= 32) { - let repeatedLength, numberOfRepeats, j; - switch (codeLength) { - case 32: - if (i === 0) { - throw new Jbig2Error("no previous value in symbol ID table"); - } - numberOfRepeats = reader.readBits(2) + 3; - repeatedLength = codes[i - 1].prefixLength; - break; - case 33: - numberOfRepeats = reader.readBits(3) + 3; - repeatedLength = 0; - break; - case 34: - numberOfRepeats = reader.readBits(7) + 11; - repeatedLength = 0; - break; - default: - throw new Jbig2Error("invalid code length in symbol ID table"); - } - for (j = 0; j < numberOfRepeats; j++) { - codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); - i++; - } - } else { - codes.push(new HuffmanLine([i, codeLength, 0, 0])); + byteAlign() { + this.shift = -1; + } + + next() { + if (this.position >= this.end) { + return -1; + } + return this.data[this.position++]; + } +} + +function getCustomHuffmanTable(index, referredTo, customTables) { + // Returns a Tables segment that has been earlier decoded. + // See 7.4.2.1.6 (symbol dictionary) or 7.4.3.1.6 (text region). + let currentIndex = 0; + for (let i = 0, ii = referredTo.length; i < ii; i++) { + const table = customTables[referredTo[i]]; + if (table) { + if (index === currentIndex) { + return table; + } + currentIndex++; + } + } + throw new Jbig2Error("can't find custom Huffman table"); +} + +function getTextRegionHuffmanTables( + textRegion, + referredTo, + customTables, + numberOfSymbols, + reader +) { + // 7.4.3.1.7 Symbol ID Huffman table decoding + + // Read code lengths for RUNCODEs 0...34. + const codes = []; + for (let i = 0; i <= 34; i++) { + const codeLength = reader.readBits(4); + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + } + // Assign Huffman codes for RUNCODEs. + const runCodesTable = new HuffmanTable(codes, false); + + // Read a Huffman code using the assignment above. + // Interpret the RUNCODE codes and the additional bits (if any). + codes.length = 0; + for (let i = 0; i < numberOfSymbols; ) { + const codeLength = runCodesTable.decode(reader); + if (codeLength >= 32) { + let repeatedLength, numberOfRepeats, j; + switch (codeLength) { + case 32: + if (i === 0) { + throw new Jbig2Error("no previous value in symbol ID table"); + } + numberOfRepeats = reader.readBits(2) + 3; + repeatedLength = codes[i - 1].prefixLength; + break; + case 33: + numberOfRepeats = reader.readBits(3) + 3; + repeatedLength = 0; + break; + case 34: + numberOfRepeats = reader.readBits(7) + 11; + repeatedLength = 0; + break; + default: + throw new Jbig2Error("invalid code length in symbol ID table"); + } + for (j = 0; j < numberOfRepeats; j++) { + codes.push(new HuffmanLine([i, repeatedLength, 0, 0])); i++; } + } else { + codes.push(new HuffmanLine([i, codeLength, 0, 0])); + i++; } - reader.byteAlign(); - const symbolIDTable = new HuffmanTable(codes, false); - - // 7.4.3.1.6 Text region segment Huffman table selection - - let customIndex = 0, - tableFirstS, - tableDeltaS, - tableDeltaT; - - switch (textRegion.huffmanFS) { - case 0: - case 1: - tableFirstS = getStandardTable(textRegion.huffmanFS + 6); - break; - case 3: - tableFirstS = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman FS selector"); - } - - switch (textRegion.huffmanDS) { - case 0: - case 1: - case 2: - tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); - break; - case 3: - tableDeltaS = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DS selector"); - } - - switch (textRegion.huffmanDT) { - case 0: - case 1: - case 2: - tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); - break; - case 3: - tableDeltaT = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DT selector"); - } - - if (textRegion.refinement) { - // Load tables RDW, RDH, RDX and RDY. - throw new Jbig2Error("refinement with Huffman is not supported"); - } - - return { - symbolIDTable, - tableFirstS, - tableDeltaS, - tableDeltaT, - }; } + reader.byteAlign(); + const symbolIDTable = new HuffmanTable(codes, false); - function getSymbolDictionaryHuffmanTables( - dictionary, - referredTo, - customTables - ) { - // 7.4.2.1.6 Symbol dictionary segment Huffman table selection + // 7.4.3.1.6 Text region segment Huffman table selection - let customIndex = 0, - tableDeltaHeight, - tableDeltaWidth; - switch (dictionary.huffmanDHSelector) { - case 0: - case 1: - tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); - break; - case 3: - tableDeltaHeight = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DH selector"); - } + let customIndex = 0, + tableFirstS, + tableDeltaS, + tableDeltaT; - switch (dictionary.huffmanDWSelector) { - case 0: - case 1: - tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); - break; - case 3: - tableDeltaWidth = getCustomHuffmanTable( - customIndex, - referredTo, - customTables - ); - customIndex++; - break; - default: - throw new Jbig2Error("invalid Huffman DW selector"); - } - - let tableBitmapSize, tableAggregateInstances; - if (dictionary.bitmapSizeSelector) { - tableBitmapSize = getCustomHuffmanTable( + switch (textRegion.huffmanFS) { + case 0: + case 1: + tableFirstS = getStandardTable(textRegion.huffmanFS + 6); + break; + case 3: + tableFirstS = getCustomHuffmanTable( customIndex, referredTo, customTables ); customIndex++; - } else { - tableBitmapSize = getStandardTable(1); - } + break; + default: + throw new Jbig2Error("invalid Huffman FS selector"); + } - if (dictionary.aggregationInstancesSelector) { - tableAggregateInstances = getCustomHuffmanTable( + switch (textRegion.huffmanDS) { + case 0: + case 1: + case 2: + tableDeltaS = getStandardTable(textRegion.huffmanDS + 8); + break; + case 3: + tableDeltaS = getCustomHuffmanTable( customIndex, referredTo, customTables ); - } else { - tableAggregateInstances = getStandardTable(1); - } - - return { - tableDeltaHeight, - tableDeltaWidth, - tableBitmapSize, - tableAggregateInstances, - }; + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DS selector"); } - function readUncompressedBitmap(reader, width, height) { - const bitmap = []; - for (let y = 0; y < height; y++) { - const row = new Uint8Array(width); - bitmap.push(row); - for (let x = 0; x < width; x++) { - row[x] = reader.readBit(); - } - reader.byteAlign(); - } - return bitmap; + switch (textRegion.huffmanDT) { + case 0: + case 1: + case 2: + tableDeltaT = getStandardTable(textRegion.huffmanDT + 11); + break; + case 3: + tableDeltaT = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DT selector"); } - function decodeMMRBitmap(input, width, height, endOfBlock) { - // MMR is the same compression algorithm as the PDF filter - // CCITTFaxDecode with /K -1. - const params = { - K: -1, - Columns: width, - Rows: height, - BlackIs1: true, - EndOfBlock: endOfBlock, - }; - const decoder = new CCITTFaxDecoder(input, params); - const bitmap = []; - let currentByte, - eof = false; - - for (let y = 0; y < height; y++) { - const row = new Uint8Array(width); - bitmap.push(row); - let shift = -1; - for (let x = 0; x < width; x++) { - if (shift < 0) { - currentByte = decoder.readNextChar(); - if (currentByte === -1) { - // Set the rest of the bits to zero. - currentByte = 0; - eof = true; - } - shift = 7; - } - row[x] = (currentByte >> shift) & 1; - shift--; - } - } - - if (endOfBlock && !eof) { - // Read until EOFB has been consumed. - const lookForEOFLimit = 5; - for (let i = 0; i < lookForEOFLimit; i++) { - if (decoder.readNextChar() === -1) { - break; - } - } - } - - return bitmap; + if (textRegion.refinement) { + // Load tables RDW, RDH, RDX and RDY. + throw new Jbig2Error("refinement with Huffman is not supported"); } - // eslint-disable-next-line no-shadow - function Jbig2Image() {} - - Jbig2Image.prototype = { - parseChunks(chunks) { - return parseJbig2Chunks(chunks); - }, - - parse(data) { - const { imgData, width, height } = parseJbig2(data); - this.width = width; - this.height = height; - return imgData; - }, + return { + symbolIDTable, + tableFirstS, + tableDeltaS, + tableDeltaT, }; +} - return Jbig2Image; -})(); +function getSymbolDictionaryHuffmanTables( + dictionary, + referredTo, + customTables +) { + // 7.4.2.1.6 Symbol dictionary segment Huffman table selection + + let customIndex = 0, + tableDeltaHeight, + tableDeltaWidth; + switch (dictionary.huffmanDHSelector) { + case 0: + case 1: + tableDeltaHeight = getStandardTable(dictionary.huffmanDHSelector + 4); + break; + case 3: + tableDeltaHeight = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DH selector"); + } + + switch (dictionary.huffmanDWSelector) { + case 0: + case 1: + tableDeltaWidth = getStandardTable(dictionary.huffmanDWSelector + 2); + break; + case 3: + tableDeltaWidth = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + break; + default: + throw new Jbig2Error("invalid Huffman DW selector"); + } + + let tableBitmapSize, tableAggregateInstances; + if (dictionary.bitmapSizeSelector) { + tableBitmapSize = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + customIndex++; + } else { + tableBitmapSize = getStandardTable(1); + } + + if (dictionary.aggregationInstancesSelector) { + tableAggregateInstances = getCustomHuffmanTable( + customIndex, + referredTo, + customTables + ); + } else { + tableAggregateInstances = getStandardTable(1); + } + + return { + tableDeltaHeight, + tableDeltaWidth, + tableBitmapSize, + tableAggregateInstances, + }; +} + +function readUncompressedBitmap(reader, width, height) { + const bitmap = []; + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + for (let x = 0; x < width; x++) { + row[x] = reader.readBit(); + } + reader.byteAlign(); + } + return bitmap; +} + +function decodeMMRBitmap(input, width, height, endOfBlock) { + // MMR is the same compression algorithm as the PDF filter + // CCITTFaxDecode with /K -1. + const params = { + K: -1, + Columns: width, + Rows: height, + BlackIs1: true, + EndOfBlock: endOfBlock, + }; + const decoder = new CCITTFaxDecoder(input, params); + const bitmap = []; + let currentByte, + eof = false; + + for (let y = 0; y < height; y++) { + const row = new Uint8Array(width); + bitmap.push(row); + let shift = -1; + for (let x = 0; x < width; x++) { + if (shift < 0) { + currentByte = decoder.readNextChar(); + if (currentByte === -1) { + // Set the rest of the bits to zero. + currentByte = 0; + eof = true; + } + shift = 7; + } + row[x] = (currentByte >> shift) & 1; + shift--; + } + } + + if (endOfBlock && !eof) { + // Read until EOFB has been consumed. + const lookForEOFLimit = 5; + for (let i = 0; i < lookForEOFLimit; i++) { + if (decoder.readNextChar() === -1) { + break; + } + } + } + + return bitmap; +} + +class Jbig2Image { + parseChunks(chunks) { + return parseJbig2Chunks(chunks); + } + + parse(data) { + const { imgData, width, height } = parseJbig2(data); + this.width = width; + this.height = height; + return imgData; + } +} export { Jbig2Image }; From 1e5a17960011750b5caf32dd0147194a7d444442 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:35:33 +0200 Subject: [PATCH 4/9] Enable the `no-var` rule in the `src/core/jpg.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/jpg.js | 237 ++++++++++++++++++++++++------------------------ 1 file changed, 118 insertions(+), 119 deletions(-) diff --git a/src/core/jpg.js b/src/core/jpg.js index ab048e959..98cd3f434 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { assert, BaseException, warn } from "../shared/util.js"; import { readUint16 } from "./core_utils.js"; @@ -45,9 +44,9 @@ class EOIMarkerError extends BaseException {} * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) */ -var JpegImage = (function JpegImageClosure() { +const JpegImage = (function JpegImageClosure() { // prettier-ignore - var dctZigZag = new Uint8Array([ + const dctZigZag = new Uint8Array([ 0, 1, 8, 16, 9, 2, @@ -65,14 +64,14 @@ var JpegImage = (function JpegImageClosure() { 63 ]); - var dctCos1 = 4017; // cos(pi/16) - var dctSin1 = 799; // sin(pi/16) - var dctCos3 = 3406; // cos(3*pi/16) - var dctSin3 = 2276; // sin(3*pi/16) - var dctCos6 = 1567; // cos(6*pi/16) - var dctSin6 = 3784; // sin(6*pi/16) - var dctSqrt2 = 5793; // sqrt(2) - var dctSqrt1d2 = 2896; // sqrt(2) / 2 + 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 // eslint-disable-next-line no-shadow function JpegImage({ decodeTransform = null, colorTransform = -1 } = {}) { @@ -81,7 +80,7 @@ var JpegImage = (function JpegImageClosure() { } function buildHuffmanTable(codeLengths, values) { - var k = 0, + let k = 0, code = [], i, j, @@ -90,7 +89,7 @@ var JpegImage = (function JpegImageClosure() { length--; } code.push({ children: [], index: 0 }); - var p = code[0], + let p = code[0], q; for (i = 0; i < length; i++) { for (j = 0; j < codeLengths[i]; j++) { @@ -134,8 +133,8 @@ var JpegImage = (function JpegImageClosure() { successive, parseDNLMarker = false ) { - var mcusPerLine = frame.mcusPerLine; - var progressive = frame.progressive; + const mcusPerLine = frame.mcusPerLine; + const progressive = frame.progressive; const startOffset = offset; let bitsData = 0, @@ -148,7 +147,7 @@ var JpegImage = (function JpegImageClosure() { } bitsData = data[offset++]; if (bitsData === 0xff) { - var nextByte = data[offset++]; + const nextByte = data[offset++]; if (nextByte) { if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) { offset += 2; // Skip marker length. @@ -196,7 +195,7 @@ var JpegImage = (function JpegImageClosure() { } function decodeHuffman(tree) { - var node = tree; + let node = tree; while (true) { node = node[readBit()]; switch (typeof node) { @@ -210,7 +209,7 @@ var JpegImage = (function JpegImageClosure() { } function receive(length) { - var n = 0; + let n = 0; while (length > 0) { n = (n << 1) | readBit(); length--; @@ -222,7 +221,7 @@ var JpegImage = (function JpegImageClosure() { if (length === 1) { return readBit() === 1 ? 1 : -1; } - var n = receive(length); + const n = receive(length); if (n >= 1 << (length - 1)) { return n; } @@ -230,13 +229,13 @@ var JpegImage = (function JpegImageClosure() { } function decodeBaseline(component, blockOffset) { - var t = decodeHuffman(component.huffmanTableDC); - var diff = t === 0 ? 0 : receiveAndExtend(t); + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t); component.blockData[blockOffset] = component.pred += diff; - var k = 1; + let k = 1; while (k < 64) { - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, r = rs >> 4; if (s === 0) { if (r < 15) { @@ -246,15 +245,15 @@ var JpegImage = (function JpegImageClosure() { continue; } k += r; - var z = dctZigZag[k]; + const z = dctZigZag[k]; component.blockData[blockOffset + z] = receiveAndExtend(s); k++; } } function decodeDCFirst(component, blockOffset) { - var t = decodeHuffman(component.huffmanTableDC); - var diff = t === 0 ? 0 : receiveAndExtend(t) << successive; + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t) << successive; component.blockData[blockOffset] = component.pred += diff; } @@ -262,17 +261,17 @@ var JpegImage = (function JpegImageClosure() { component.blockData[blockOffset] |= readBit() << successive; } - var eobrun = 0; + let eobrun = 0; function decodeACFirst(component, blockOffset) { if (eobrun > 0) { eobrun--; return; } - var k = spectralStart, + let k = spectralStart, e = spectralEnd; while (k <= e) { - var rs = decodeHuffman(component.huffmanTableAC); - var s = rs & 15, + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, r = rs >> 4; if (s === 0) { if (r < 15) { @@ -283,21 +282,21 @@ var JpegImage = (function JpegImageClosure() { continue; } k += r; - var z = dctZigZag[k]; + const z = dctZigZag[k]; component.blockData[blockOffset + z] = receiveAndExtend(s) * (1 << successive); k++; } } - var successiveACState = 0, + let successiveACState = 0, successiveACNextValue; function decodeACSuccessive(component, blockOffset) { - var k = spectralStart; - var e = spectralEnd; - var r = 0; - var s; - var rs; + let k = spectralStart; + const e = spectralEnd; + let r = 0; + let s; + let rs; while (k <= e) { const offsetZ = blockOffset + dctZigZag[k]; const sign = component.blockData[offsetZ] < 0 ? -1 : 1; @@ -360,24 +359,24 @@ var JpegImage = (function JpegImageClosure() { let blockRow = 0; function decodeMcu(component, decode, mcu, row, col) { - var mcuRow = (mcu / mcusPerLine) | 0; - var mcuCol = mcu % mcusPerLine; + const mcuRow = (mcu / mcusPerLine) | 0; + const mcuCol = mcu % mcusPerLine; blockRow = mcuRow * component.v + row; - var blockCol = mcuCol * component.h + col; + const blockCol = mcuCol * component.h + col; const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); decode(component, blockOffset); } function decodeBlock(component, decode, mcu) { blockRow = (mcu / component.blocksPerLine) | 0; - var blockCol = mcu % component.blocksPerLine; + const blockCol = mcu % component.blocksPerLine; const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); decode(component, blockOffset); } - var componentsLength = components.length; - var component, i, j, k, n; - var decodeFn; + const componentsLength = components.length; + let component, i, j, k, n; + let decodeFn; if (progressive) { if (spectralStart === 0) { decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; @@ -388,19 +387,19 @@ var JpegImage = (function JpegImageClosure() { decodeFn = decodeBaseline; } - var mcu = 0, + let mcu = 0, fileMarker; - var mcuExpected; + let mcuExpected; if (componentsLength === 1) { mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; } else { mcuExpected = mcusPerLine * frame.mcusPerColumn; } - var h, v; + let h, v; while (mcu <= mcuExpected) { // reset interval stuff - var mcuToRead = resetInterval + const mcuToRead = resetInterval ? Math.min(mcuExpected - mcu, resetInterval) : mcuExpected; @@ -469,18 +468,18 @@ var JpegImage = (function JpegImageClosure() { // IEEE Intl. Conf. on Acoustics, Speech & Signal Processing, 1989, // 988-991. function quantizeAndInverse(component, blockBufferOffset, p) { - var qt = component.quantizationTable, + const qt = component.quantizationTable, blockData = component.blockData; - var v0, v1, v2, v3, v4, v5, v6, v7; - var p0, p1, p2, p3, p4, p5, p6, p7; - var t; + let v0, v1, v2, v3, v4, v5, v6, v7; + let p0, p1, p2, p3, p4, p5, p6, p7; + let t; if (!qt) { throw new JpegError("missing required Quantization Table."); } // inverse DCT on rows - for (var row = 0; row < 64; row += 8) { + for (let row = 0; row < 64; row += 8) { // gather block data p0 = blockData[blockBufferOffset + row]; p1 = blockData[blockBufferOffset + row + 1]; @@ -561,7 +560,7 @@ var JpegImage = (function JpegImageClosure() { } // inverse DCT on columns - for (var col = 0; col < 8; ++col) { + for (let col = 0; col < 8; ++col) { p0 = p[col]; p1 = p[col + 8]; p2 = p[col + 16]; @@ -709,13 +708,13 @@ var JpegImage = (function JpegImageClosure() { } function buildComponentData(frame, component) { - var blocksPerLine = component.blocksPerLine; - var blocksPerColumn = component.blocksPerColumn; - var computationBuffer = new Int16Array(64); + const blocksPerLine = component.blocksPerLine; + const blocksPerColumn = component.blocksPerColumn; + const computationBuffer = new Int16Array(64); - for (var blockRow = 0; blockRow < blocksPerColumn; blockRow++) { - for (var blockCol = 0; blockCol < blocksPerLine; blockCol++) { - var offset = getBlockBufferOffset(component, blockRow, blockCol); + for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { + const offset = getBlockBufferOffset(component, blockRow, blockCol); quantizeAndInverse(component, offset, computationBuffer); } } @@ -724,12 +723,12 @@ var JpegImage = (function JpegImageClosure() { function findNextFileMarker(data, currentPos, startPos = currentPos) { const maxPos = data.length - 1; - var newPos = startPos < currentPos ? startPos : currentPos; + let newPos = startPos < currentPos ? startPos : currentPos; if (currentPos >= maxPos) { return null; // Don't attempt to read non-existent data and just return. } - var currentMarker = readUint16(data, currentPos); + const currentMarker = readUint16(data, currentPos); if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { return { invalid: null, @@ -737,7 +736,7 @@ var JpegImage = (function JpegImageClosure() { offset: currentPos, }; } - var newMarker = readUint16(data, newPos); + let newMarker = readUint16(data, newPos); while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { if (++newPos >= maxPos) { return null; // Don't attempt to read non-existent data and just return. @@ -758,7 +757,7 @@ var JpegImage = (function JpegImageClosure() { offset += 2; let endOffset = offset + length - 2; - var fileMarker = findNextFileMarker(data, endOffset, offset); + const fileMarker = findNextFileMarker(data, endOffset, offset); if (fileMarker && fileMarker.invalid) { warn( "readDataBlock - incorrect length, current marker is: " + @@ -767,26 +766,26 @@ var JpegImage = (function JpegImageClosure() { endOffset = fileMarker.offset; } - var array = data.subarray(offset, endOffset); + const array = data.subarray(offset, endOffset); offset += array.length; return array; } function prepareComponents(frame) { - var mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); - var mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); - for (var i = 0; i < frame.components.length; i++) { + const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (let i = 0; i < frame.components.length; i++) { component = frame.components[i]; - var blocksPerLine = Math.ceil( + const blocksPerLine = Math.ceil( (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH ); - var blocksPerColumn = Math.ceil( + const blocksPerColumn = Math.ceil( (Math.ceil(frame.scanLines / 8) * component.v) / frame.maxV ); - var blocksPerLineForMcu = mcusPerLine * component.h; - var blocksPerColumnForMcu = mcusPerColumn * component.v; + const blocksPerLineForMcu = mcusPerLine * component.h; + const blocksPerColumnForMcu = mcusPerColumn * component.v; - var blocksBufferSize = + const blocksBufferSize = 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); component.blockData = new Int16Array(blocksBufferSize); component.blocksPerLine = blocksPerLine; @@ -797,12 +796,12 @@ var JpegImage = (function JpegImageClosure() { } var offset = 0; - var jfif = null; - var adobe = null; - var frame, resetInterval; + let jfif = null; + let adobe = null; + let frame, resetInterval; let numSOSMarkers = 0; - var quantizationTables = []; - var huffmanTablesAC = [], + const quantizationTables = []; + const huffmanTablesAC = [], huffmanTablesDC = []; let fileMarker = readUint16(data, offset); @@ -884,8 +883,8 @@ var JpegImage = (function JpegImageClosure() { var quantizationTablesEnd = quantizationTablesLength + offset - 2; var z; while (offset < quantizationTablesEnd) { - var quantizationTableSpec = data[offset++]; - var tableData = new Uint16Array(64); + const quantizationTableSpec = data[offset++]; + const tableData = new Uint16Array(64); if (quantizationTableSpec >> 4 === 0) { // 8 bit values for (j = 0; j < 64; j++) { @@ -931,15 +930,15 @@ var JpegImage = (function JpegImageClosure() { maxV = 0; for (i = 0; i < componentsCount; i++) { componentId = data[offset]; - var h = data[offset + 1] >> 4; - var v = data[offset + 1] & 15; + const h = data[offset + 1] >> 4; + const v = data[offset + 1] & 15; if (maxH < h) { maxH = h; } if (maxV < v) { maxV = v; } - var qId = data[offset + 2]; + const qId = data[offset + 2]; l = frame.components.push({ h, v, @@ -958,13 +957,13 @@ var JpegImage = (function JpegImageClosure() { const huffmanLength = readUint16(data, offset); offset += 2; for (i = 2; i < huffmanLength; ) { - var huffmanTableSpec = data[offset++]; - var codeLengths = new Uint8Array(16); - var codeLengthSum = 0; + const huffmanTableSpec = data[offset++]; + const codeLengths = new Uint8Array(16); + let codeLengthSum = 0; for (j = 0; j < 16; j++, offset++) { codeLengthSum += codeLengths[j] = data[offset]; } - var huffmanValues = new Uint8Array(codeLengthSum); + const huffmanValues = new Uint8Array(codeLengthSum); for (j = 0; j < codeLengthSum; j++, offset++) { huffmanValues[j] = data[offset]; } @@ -997,10 +996,10 @@ var JpegImage = (function JpegImageClosure() { component; for (i = 0; i < selectorsCount; i++) { const index = data[offset++]; - var componentIndex = frame.componentIds[index]; + const componentIndex = frame.componentIds[index]; component = frame.components[componentIndex]; component.index = index; - var tableSpec = data[offset++]; + const tableSpec = data[offset++]; component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; components.push(component); @@ -1009,7 +1008,7 @@ var JpegImage = (function JpegImageClosure() { var spectralEnd = data[offset++]; var successiveApproximation = data[offset++]; try { - var processed = decodeScan( + const processed = decodeScan( data, offset, frame, @@ -1089,7 +1088,7 @@ var JpegImage = (function JpegImageClosure() { // Prevent errors when DQT markers are placed after SOF{n} markers, // by assigning the `quantizationTable` entry after the entire image // has been parsed (fixes issue7406.pdf). - var quantizationTable = quantizationTables[component.quantizationId]; + const quantizationTable = quantizationTables[component.quantizationId]; if (quantizationTable) { component.quantizationTable = quantizationTable; } @@ -1108,19 +1107,19 @@ var JpegImage = (function JpegImageClosure() { }, _getLinearizedBlockData(width, height, isSourcePDF = false) { - var scaleX = this.width / width, + const scaleX = this.width / width, scaleY = this.height / height; - var component, componentScaleX, componentScaleY, blocksPerScanline; - var x, y, i, j, k; - var index; - var offset = 0; - var output; - var numComponents = this.components.length; - var dataLength = width * height * numComponents; - var data = new Uint8ClampedArray(dataLength); - var xScaleBlockOffset = new Uint32Array(width); - var mask3LSB = 0xfffffff8; // used to clear the 3 LSBs + let component, componentScaleX, componentScaleY, blocksPerScanline; + let x, y, i, j, k; + let index; + let offset = 0; + let output; + const numComponents = this.components.length; + const dataLength = width * height * numComponents; + const data = new Uint8ClampedArray(dataLength); + const xScaleBlockOffset = new Uint32Array(width); + const mask3LSB = 0xfffffff8; // used to clear the 3 LSBs let lastComponentScaleX; for (i = 0; i < numComponents; i++) { @@ -1213,8 +1212,8 @@ var JpegImage = (function JpegImageClosure() { }, _convertYccToRgb: function convertYccToRgb(data) { - var Y, Cb, Cr; - for (var i = 0, length = data.length; i < length; i += 3) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 3) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; @@ -1226,9 +1225,9 @@ var JpegImage = (function JpegImageClosure() { }, _convertYcckToRgb: function convertYcckToRgb(data) { - var Y, Cb, Cr, k; - var offset = 0; - for (var i = 0, length = data.length; i < length; i += 4) { + let Y, Cb, Cr, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; @@ -1296,8 +1295,8 @@ var JpegImage = (function JpegImageClosure() { }, _convertYcckToCmyk: function convertYcckToCmyk(data) { - var Y, Cb, Cr; - for (var i = 0, length = data.length; i < length; i += 4) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 4) { Y = data[i]; Cb = data[i + 1]; Cr = data[i + 2]; @@ -1310,9 +1309,9 @@ var JpegImage = (function JpegImageClosure() { }, _convertCmykToRgb: function convertCmykToRgb(data) { - var c, m, y, k; - var offset = 0; - for (var i = 0, length = data.length; i < length; i += 4) { + let c, m, y, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { c = data[i]; m = data[i + 1]; y = data[i + 2]; @@ -1393,14 +1392,14 @@ var JpegImage = (function JpegImageClosure() { throw new JpegError("Unsupported color mode"); } // Type of data: Uint8ClampedArray(width * height * numComponents) - var data = this._getLinearizedBlockData(width, height, isSourcePDF); + const data = this._getLinearizedBlockData(width, height, isSourcePDF); if (this.numComponents === 1 && forceRGB) { - var dataLength = data.length; - var rgbData = new Uint8ClampedArray(dataLength * 3); - var offset = 0; - for (var i = 0; i < dataLength; i++) { - var grayColor = data[i]; + const dataLength = data.length; + const rgbData = new Uint8ClampedArray(dataLength * 3); + let offset = 0; + for (let i = 0; i < dataLength; i++) { + const grayColor = data[i]; rgbData[offset++] = grayColor; rgbData[offset++] = grayColor; rgbData[offset++] = grayColor; From d0a299713cb2523f365b40723571b77d3ea96029 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:36:52 +0200 Subject: [PATCH 5/9] Fix the remaining `no-var` failures, which couldn't be handled automatically, in the `src/core/jpg.js` file --- src/core/jpg.js | 45 +++++++++++++++++++++------------------------ 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/src/core/jpg.js b/src/core/jpg.js index 98cd3f434..7a2d7a25a 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -81,14 +81,13 @@ const JpegImage = (function JpegImageClosure() { function buildHuffmanTable(codeLengths, values) { let k = 0, - code = [], i, j, length = 16; while (length > 0 && !codeLengths[length - 1]) { length--; } - code.push({ children: [], index: 0 }); + const code = [{ children: [], index: 0 }]; let p = code[0], q; for (i = 0; i < length; i++) { @@ -267,8 +266,8 @@ const JpegImage = (function JpegImageClosure() { eobrun--; return; } - let k = spectralStart, - e = spectralEnd; + let k = spectralStart; + const e = spectralEnd; while (k <= e) { const rs = decodeHuffman(component.huffmanTableAC); const s = rs & 15, @@ -774,8 +773,8 @@ const JpegImage = (function JpegImageClosure() { function prepareComponents(frame) { const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); - for (let i = 0; i < frame.components.length; i++) { - component = frame.components[i]; + for (let i = 0, ii = frame.components.length; i < ii; i++) { + const component = frame.components[i]; const blocksPerLine = Math.ceil( (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH ); @@ -795,7 +794,7 @@ const JpegImage = (function JpegImageClosure() { frame.mcusPerColumn = mcusPerColumn; } - var offset = 0; + let offset = 0; let jfif = null; let adobe = null; let frame, resetInterval; @@ -813,7 +812,7 @@ const JpegImage = (function JpegImageClosure() { offset += 2; markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { - var i, j, l; + let i, j, l; switch (fileMarker) { case 0xffe0: // APP0 (Application Specific) case 0xffe1: // APP1 @@ -832,7 +831,7 @@ const JpegImage = (function JpegImageClosure() { case 0xffee: // APP14 case 0xffef: // APP15 case 0xfffe: // COM (Comment) - var appData = readDataBlock(); + const appData = readDataBlock(); if (fileMarker === 0xffe0) { // 'JFIF\x00' @@ -880,8 +879,8 @@ const JpegImage = (function JpegImageClosure() { case 0xffdb: // DQT (Define Quantization Tables) const quantizationTablesLength = readUint16(data, offset); offset += 2; - var quantizationTablesEnd = quantizationTablesLength + offset - 2; - var z; + const quantizationTablesEnd = quantizationTablesLength + offset - 2; + let z; while (offset < quantizationTablesEnd) { const quantizationTableSpec = data[offset++]; const tableData = new Uint16Array(64); @@ -924,12 +923,11 @@ const JpegImage = (function JpegImageClosure() { offset += 2; frame.components = []; frame.componentIds = {}; - var componentsCount = data[offset++], - componentId; - var maxH = 0, + const componentsCount = data[offset++]; + let maxH = 0, maxV = 0; for (i = 0; i < componentsCount; i++) { - componentId = data[offset]; + const componentId = data[offset]; const h = data[offset + 1] >> 4; const v = data[offset + 1] & 15; if (maxH < h) { @@ -991,22 +989,21 @@ const JpegImage = (function JpegImageClosure() { offset += 2; // Skip marker length. - var selectorsCount = data[offset++]; - var components = [], - component; + const selectorsCount = data[offset++], + components = []; for (i = 0; i < selectorsCount; i++) { const index = data[offset++]; const componentIndex = frame.componentIds[index]; - component = frame.components[componentIndex]; + const component = frame.components[componentIndex]; component.index = index; const tableSpec = data[offset++]; component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; components.push(component); } - var spectralStart = data[offset++]; - var spectralEnd = data[offset++]; - var successiveApproximation = data[offset++]; + const spectralStart = data[offset++], + spectralEnd = data[offset++], + successiveApproximation = data[offset++]; try { const processed = decodeScan( data, @@ -1082,8 +1079,8 @@ const JpegImage = (function JpegImageClosure() { this.jfif = jfif; this.adobe = adobe; this.components = []; - for (i = 0; i < frame.components.length; i++) { - component = frame.components[i]; + for (let i = 0, ii = frame.components.length; i < ii; i++) { + const component = frame.components[i]; // Prevent errors when DQT markers are placed after SOF{n} markers, // by assigning the `quantizationTable` entry after the entire image From 69dea39a42463d4fac4235853ce781f82f2fe23b Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:43:28 +0200 Subject: [PATCH 6/9] Convert `src/core/jpg.js` to use standard classes *Please note:* Ignoring whitespace-only changes is probably necessary in order to review this. --- src/core/jpg.js | 2556 +++++++++++++++++++++++------------------------ 1 file changed, 1275 insertions(+), 1281 deletions(-) diff --git a/src/core/jpg.js b/src/core/jpg.js index 7a2d7a25a..70ef923a6 100644 --- a/src/core/jpg.js +++ b/src/core/jpg.js @@ -44,9 +44,8 @@ class EOIMarkerError extends BaseException {} * (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf) */ -const JpegImage = (function JpegImageClosure() { - // prettier-ignore - const dctZigZag = new Uint8Array([ +// prettier-ignore +const dctZigZag = new Uint8Array([ 0, 1, 8, 16, 9, 2, @@ -64,1361 +63,1356 @@ const JpegImage = (function JpegImageClosure() { 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 +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 - // eslint-disable-next-line no-shadow - function JpegImage({ decodeTransform = null, colorTransform = -1 } = {}) { - this._decodeTransform = decodeTransform; - this._colorTransform = colorTransform; +function buildHuffmanTable(codeLengths, values) { + let k = 0, + i, + j, + length = 16; + while (length > 0 && !codeLengths[length - 1]) { + length--; } - - function buildHuffmanTable(codeLengths, values) { - let k = 0, - i, - j, - length = 16; - while (length > 0 && !codeLengths[length - 1]) { - length--; - } - const code = [{ children: [], index: 0 }]; - let p = code[0], - q; - for (i = 0; i < length; i++) { - for (j = 0; j < codeLengths[i]; j++) { + const code = [{ children: [], index: 0 }]; + let p = code[0], + q; + for (i = 0; i < length; i++) { + for (j = 0; j < codeLengths[i]; j++) { + p = code.pop(); + p.children[p.index] = values[k]; + while (p.index > 0) { 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; - } - k++; } - if (i + 1 < length) { - // p here points to last code + p.index++; + code.push(p); + while (code.length <= i) { code.push((q = { children: [], index: 0 })); p.children[p.index] = q.children; p = q; } + k++; + } + 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; } + return code[0].children; +} - function getBlockBufferOffset(component, row, col) { - return 64 * ((component.blocksPerLine + 1) * row + col); - } +function getBlockBufferOffset(component, row, col) { + return 64 * ((component.blocksPerLine + 1) * row + col); +} - function decodeScan( - data, - offset, - frame, - components, - resetInterval, - spectralStart, - spectralEnd, - successivePrev, - successive, - parseDNLMarker = false - ) { - const mcusPerLine = frame.mcusPerLine; - const progressive = frame.progressive; +function decodeScan( + data, + offset, + frame, + components, + resetInterval, + spectralStart, + spectralEnd, + successivePrev, + successive, + parseDNLMarker = false +) { + const mcusPerLine = frame.mcusPerLine; + const progressive = frame.progressive; - const startOffset = offset; - let bitsData = 0, - bitsCount = 0; + const startOffset = offset; + let bitsData = 0, + bitsCount = 0; - function readBit() { - if (bitsCount > 0) { - bitsCount--; - return (bitsData >> bitsCount) & 1; - } - bitsData = data[offset++]; - if (bitsData === 0xff) { - const nextByte = data[offset++]; - if (nextByte) { - if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) { - offset += 2; // Skip marker length. + function readBit() { + if (bitsCount > 0) { + bitsCount--; + return (bitsData >> bitsCount) & 1; + } + bitsData = data[offset++]; + if (bitsData === 0xff) { + const nextByte = data[offset++]; + if (nextByte) { + if (nextByte === /* DNL = */ 0xdc && parseDNLMarker) { + offset += 2; // Skip marker length. - const scanLines = readUint16(data, offset); - offset += 2; - if (scanLines > 0 && scanLines !== frame.scanLines) { - throw new DNLMarkerError( - "Found DNL marker (0xFFDC) while parsing scan data", - scanLines - ); - } - } else if (nextByte === /* EOI = */ 0xd9) { - if (parseDNLMarker) { - // NOTE: only 8-bit JPEG images are supported in this decoder. - const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0); - // Heuristic to attempt to handle corrupt JPEG images with too - // large `scanLines` parameter, by falling back to the currently - // parsed number of scanLines when it's at least (approximately) - // one order of magnitude smaller than expected (fixes - // issue10880.pdf and issue10989.pdf). - if ( - maybeScanLines > 0 && - Math.round(frame.scanLines / maybeScanLines) >= 10 - ) { - throw new DNLMarkerError( - "Found EOI marker (0xFFD9) while parsing scan data, " + - "possibly caused by incorrect `scanLines` parameter", - maybeScanLines - ); - } - } - throw new EOIMarkerError( - "Found EOI marker (0xFFD9) while parsing scan data" + const scanLines = readUint16(data, offset); + offset += 2; + if (scanLines > 0 && scanLines !== frame.scanLines) { + throw new DNLMarkerError( + "Found DNL marker (0xFFDC) while parsing scan data", + scanLines ); } - throw new JpegError( - `unexpected marker ${((bitsData << 8) | nextByte).toString(16)}` + } else if (nextByte === /* EOI = */ 0xd9) { + if (parseDNLMarker) { + // NOTE: only 8-bit JPEG images are supported in this decoder. + const maybeScanLines = blockRow * (frame.precision === 8 ? 8 : 0); + // Heuristic to attempt to handle corrupt JPEG images with too + // large `scanLines` parameter, by falling back to the currently + // parsed number of scanLines when it's at least (approximately) + // one order of magnitude smaller than expected (fixes + // issue10880.pdf and issue10989.pdf). + if ( + maybeScanLines > 0 && + Math.round(frame.scanLines / maybeScanLines) >= 10 + ) { + throw new DNLMarkerError( + "Found EOI marker (0xFFD9) while parsing scan data, " + + "possibly caused by incorrect `scanLines` parameter", + maybeScanLines + ); + } + } + throw new EOIMarkerError( + "Found EOI marker (0xFFD9) while parsing scan data" ); } - // unstuff 0 + throw new JpegError( + `unexpected marker ${((bitsData << 8) | nextByte).toString(16)}` + ); } - bitsCount = 7; - return bitsData >>> 7; + // unstuff 0 } + bitsCount = 7; + return bitsData >>> 7; + } - function decodeHuffman(tree) { - let node = tree; - while (true) { - node = node[readBit()]; - switch (typeof node) { - case "number": - return node; - case "object": - continue; - } - throw new JpegError("invalid huffman sequence"); + function decodeHuffman(tree) { + let node = tree; + while (true) { + node = node[readBit()]; + switch (typeof node) { + case "number": + return node; + case "object": + continue; } + throw new JpegError("invalid huffman sequence"); } + } - function receive(length) { - let n = 0; - while (length > 0) { - n = (n << 1) | readBit(); - length--; - } + function receive(length) { + let n = 0; + while (length > 0) { + n = (n << 1) | readBit(); + length--; + } + return n; + } + + function receiveAndExtend(length) { + if (length === 1) { + return readBit() === 1 ? 1 : -1; + } + const n = receive(length); + if (n >= 1 << (length - 1)) { return n; } - - function receiveAndExtend(length) { - if (length === 1) { - return readBit() === 1 ? 1 : -1; - } - const n = receive(length); - if (n >= 1 << (length - 1)) { - return n; - } - return n + (-1 << length) + 1; - } - - function decodeBaseline(component, blockOffset) { - const t = decodeHuffman(component.huffmanTableDC); - const diff = t === 0 ? 0 : receiveAndExtend(t); - component.blockData[blockOffset] = component.pred += diff; - let k = 1; - while (k < 64) { - const rs = decodeHuffman(component.huffmanTableAC); - const s = rs & 15, - r = rs >> 4; - if (s === 0) { - if (r < 15) { - break; - } - k += 16; - continue; - } - k += r; - const z = dctZigZag[k]; - component.blockData[blockOffset + z] = receiveAndExtend(s); - k++; - } - } - - function decodeDCFirst(component, blockOffset) { - const t = decodeHuffman(component.huffmanTableDC); - const diff = t === 0 ? 0 : receiveAndExtend(t) << successive; - component.blockData[blockOffset] = component.pred += diff; - } - - function decodeDCSuccessive(component, blockOffset) { - component.blockData[blockOffset] |= readBit() << successive; - } - - let eobrun = 0; - function decodeACFirst(component, blockOffset) { - if (eobrun > 0) { - eobrun--; - return; - } - let k = spectralStart; - const e = spectralEnd; - while (k <= e) { - const rs = decodeHuffman(component.huffmanTableAC); - const s = rs & 15, - r = rs >> 4; - if (s === 0) { - if (r < 15) { - eobrun = receive(r) + (1 << r) - 1; - break; - } - k += 16; - continue; - } - k += r; - const z = dctZigZag[k]; - component.blockData[blockOffset + z] = - receiveAndExtend(s) * (1 << successive); - k++; - } - } - - let successiveACState = 0, - successiveACNextValue; - function decodeACSuccessive(component, blockOffset) { - let k = spectralStart; - const e = spectralEnd; - let r = 0; - let s; - let rs; - while (k <= e) { - const offsetZ = blockOffset + dctZigZag[k]; - const sign = component.blockData[offsetZ] < 0 ? -1 : 1; - switch (successiveACState) { - case 0: // initial state - rs = decodeHuffman(component.huffmanTableAC); - 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 JpegError("invalid ACn encoding"); - } - successiveACNextValue = receiveAndExtend(s); - successiveACState = r ? 2 : 3; - } - continue; - case 1: // skipping r zero items - case 2: - if (component.blockData[offsetZ]) { - component.blockData[offsetZ] += sign * (readBit() << successive); - } else { - r--; - if (r === 0) { - successiveACState = successiveACState === 2 ? 3 : 0; - } - } - break; - case 3: // set value for a zero item - if (component.blockData[offsetZ]) { - component.blockData[offsetZ] += sign * (readBit() << successive); - } else { - component.blockData[offsetZ] = - successiveACNextValue << successive; - successiveACState = 0; - } - break; - case 4: // eob - if (component.blockData[offsetZ]) { - component.blockData[offsetZ] += sign * (readBit() << successive); - } - break; - } - k++; - } - if (successiveACState === 4) { - eobrun--; - if (eobrun === 0) { - successiveACState = 0; - } - } - } - - let blockRow = 0; - function decodeMcu(component, decode, mcu, row, col) { - const mcuRow = (mcu / mcusPerLine) | 0; - const mcuCol = mcu % mcusPerLine; - blockRow = mcuRow * component.v + row; - const blockCol = mcuCol * component.h + col; - const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); - decode(component, blockOffset); - } - - function decodeBlock(component, decode, mcu) { - blockRow = (mcu / component.blocksPerLine) | 0; - const blockCol = mcu % component.blocksPerLine; - const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); - decode(component, blockOffset); - } - - const componentsLength = components.length; - let component, i, j, k, n; - let decodeFn; - if (progressive) { - if (spectralStart === 0) { - decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; - } else { - decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; - } - } else { - decodeFn = decodeBaseline; - } - - let mcu = 0, - fileMarker; - let mcuExpected; - if (componentsLength === 1) { - mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; - } else { - mcuExpected = mcusPerLine * frame.mcusPerColumn; - } - - let h, v; - while (mcu <= mcuExpected) { - // reset interval stuff - const mcuToRead = resetInterval - ? Math.min(mcuExpected - mcu, resetInterval) - : mcuExpected; - - // The `mcuToRead === 0` case should only occur when all of the expected - // MCU data has been already parsed, i.e. when `mcu === mcuExpected`, but - // some corrupt JPEG images contain more data than intended and we thus - // want to skip over any extra RSTx markers below (fixes issue11794.pdf). - if (mcuToRead > 0) { - for (i = 0; i < componentsLength; i++) { - components[i].pred = 0; - } - eobrun = 0; - - if (componentsLength === 1) { - component = components[0]; - for (n = 0; n < mcuToRead; n++) { - decodeBlock(component, decodeFn, mcu); - mcu++; - } - } else { - for (n = 0; n < mcuToRead; n++) { - for (i = 0; i < componentsLength; i++) { - component = components[i]; - h = component.h; - v = component.v; - for (j = 0; j < v; j++) { - for (k = 0; k < h; k++) { - decodeMcu(component, decodeFn, mcu, j, k); - } - } - } - mcu++; - } - } - } - - // find marker - bitsCount = 0; - fileMarker = findNextFileMarker(data, offset); - if (!fileMarker) { - break; // Reached the end of the image data without finding any marker. - } - if (fileMarker.invalid) { - // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip - // past those to attempt to find a valid marker (fixes issue4090.pdf). - const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive"; - warn( - `decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}` - ); - offset = fileMarker.offset; - } - if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) { - // RSTx - offset += 2; - } else { - break; - } - } - - return offset - startOffset; + return n + (-1 << length) + 1; } - // 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. - function quantizeAndInverse(component, blockBufferOffset, p) { - const qt = component.quantizationTable, - blockData = component.blockData; - let v0, v1, v2, v3, v4, v5, v6, v7; - let p0, p1, p2, p3, p4, p5, p6, p7; - let t; - - if (!qt) { - throw new JpegError("missing required Quantization Table."); - } - - // inverse DCT on rows - for (let row = 0; row < 64; row += 8) { - // gather block data - p0 = blockData[blockBufferOffset + row]; - p1 = blockData[blockBufferOffset + row + 1]; - p2 = blockData[blockBufferOffset + row + 2]; - p3 = blockData[blockBufferOffset + row + 3]; - p4 = blockData[blockBufferOffset + row + 4]; - p5 = blockData[blockBufferOffset + row + 5]; - p6 = blockData[blockBufferOffset + row + 6]; - p7 = blockData[blockBufferOffset + row + 7]; - - // dequant p0 - p0 *= qt[row]; - - // check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { - t = (dctSqrt2 * p0 + 512) >> 10; - p[row] = t; - p[row + 1] = t; - p[row + 2] = t; - p[row + 3] = t; - p[row + 4] = t; - p[row + 5] = t; - p[row + 6] = t; - p[row + 7] = t; + function decodeBaseline(component, blockOffset) { + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t); + component.blockData[blockOffset] = component.pred += diff; + let k = 1; + while (k < 64) { + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, + r = rs >> 4; + if (s === 0) { + if (r < 15) { + break; + } + k += 16; continue; } - // dequant p1 ... p7 - p1 *= qt[row + 1]; - p2 *= qt[row + 2]; - p3 *= qt[row + 3]; - p4 *= qt[row + 4]; - p5 *= qt[row + 5]; - p6 *= qt[row + 6]; - p7 *= qt[row + 7]; - - // stage 4 - v0 = (dctSqrt2 * p0 + 128) >> 8; - v1 = (dctSqrt2 * p4 + 128) >> 8; - v2 = p2; - v3 = p6; - v4 = (dctSqrt1d2 * (p1 - p7) + 128) >> 8; - v7 = (dctSqrt1d2 * (p1 + p7) + 128) >> 8; - v5 = p3 << 4; - v6 = p5 << 4; - - // stage 3 - v0 = (v0 + v1 + 1) >> 1; - v1 = v0 - v1; - t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; - v3 = t; - v4 = (v4 + v6 + 1) >> 1; - v6 = v4 - v6; - v7 = (v7 + v5 + 1) >> 1; - v5 = v7 - v5; - - // stage 2 - v0 = (v0 + v3 + 1) >> 1; - v3 = v0 - v3; - v1 = (v1 + v2 + 1) >> 1; - v2 = v1 - v2; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; - - // stage 1 - p[row] = v0 + v7; - p[row + 7] = v0 - v7; - p[row + 1] = v1 + v6; - p[row + 6] = v1 - v6; - p[row + 2] = v2 + v5; - p[row + 5] = v2 - v5; - p[row + 3] = v3 + v4; - p[row + 4] = v3 - v4; + k += r; + const z = dctZigZag[k]; + component.blockData[blockOffset + z] = receiveAndExtend(s); + k++; } + } - // inverse DCT on columns - for (let col = 0; col < 8; ++col) { - p0 = p[col]; - p1 = p[col + 8]; - p2 = p[col + 16]; - p3 = p[col + 24]; - p4 = p[col + 32]; - p5 = p[col + 40]; - p6 = p[col + 48]; - p7 = p[col + 56]; + function decodeDCFirst(component, blockOffset) { + const t = decodeHuffman(component.huffmanTableDC); + const diff = t === 0 ? 0 : receiveAndExtend(t) << successive; + component.blockData[blockOffset] = component.pred += diff; + } - // check for all-zero AC coefficients - if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { - t = (dctSqrt2 * p0 + 8192) >> 14; - // Convert to 8-bit. - if (t < -2040) { - t = 0; - } else if (t >= 2024) { - t = 255; - } else { - t = (t + 2056) >> 4; + function decodeDCSuccessive(component, blockOffset) { + component.blockData[blockOffset] |= readBit() << successive; + } + + let eobrun = 0; + function decodeACFirst(component, blockOffset) { + if (eobrun > 0) { + eobrun--; + return; + } + let k = spectralStart; + const e = spectralEnd; + while (k <= e) { + const rs = decodeHuffman(component.huffmanTableAC); + const s = rs & 15, + r = rs >> 4; + if (s === 0) { + if (r < 15) { + eobrun = receive(r) + (1 << r) - 1; + break; } - blockData[blockBufferOffset + col] = t; - blockData[blockBufferOffset + col + 8] = t; - blockData[blockBufferOffset + col + 16] = t; - blockData[blockBufferOffset + col + 24] = t; - blockData[blockBufferOffset + col + 32] = t; - blockData[blockBufferOffset + col + 40] = t; - blockData[blockBufferOffset + col + 48] = t; - blockData[blockBufferOffset + col + 56] = t; + k += 16; continue; } - - // stage 4 - v0 = (dctSqrt2 * p0 + 2048) >> 12; - v1 = (dctSqrt2 * p4 + 2048) >> 12; - v2 = p2; - v3 = p6; - v4 = (dctSqrt1d2 * (p1 - p7) + 2048) >> 12; - v7 = (dctSqrt1d2 * (p1 + p7) + 2048) >> 12; - v5 = p3; - v6 = p5; - - // stage 3 - // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when - // converting to UInt8 range later. - v0 = ((v0 + v1 + 1) >> 1) + 4112; - v1 = v0 - v1; - t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; - v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; - v3 = t; - v4 = (v4 + v6 + 1) >> 1; - v6 = v4 - v6; - v7 = (v7 + v5 + 1) >> 1; - v5 = v7 - v5; - - // stage 2 - v0 = (v0 + v3 + 1) >> 1; - v3 = v0 - v3; - v1 = (v1 + v2 + 1) >> 1; - v2 = v1 - v2; - t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; - v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; - v7 = t; - t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; - v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; - v6 = t; - - // stage 1 - p0 = v0 + v7; - p7 = v0 - v7; - p1 = v1 + v6; - p6 = v1 - v6; - p2 = v2 + v5; - p5 = v2 - v5; - p3 = v3 + v4; - p4 = v3 - v4; - - // Convert to 8-bit integers. - if (p0 < 16) { - p0 = 0; - } else if (p0 >= 4080) { - p0 = 255; - } else { - p0 >>= 4; - } - if (p1 < 16) { - p1 = 0; - } else if (p1 >= 4080) { - p1 = 255; - } else { - p1 >>= 4; - } - if (p2 < 16) { - p2 = 0; - } else if (p2 >= 4080) { - p2 = 255; - } else { - p2 >>= 4; - } - if (p3 < 16) { - p3 = 0; - } else if (p3 >= 4080) { - p3 = 255; - } else { - p3 >>= 4; - } - if (p4 < 16) { - p4 = 0; - } else if (p4 >= 4080) { - p4 = 255; - } else { - p4 >>= 4; - } - if (p5 < 16) { - p5 = 0; - } else if (p5 >= 4080) { - p5 = 255; - } else { - p5 >>= 4; - } - if (p6 < 16) { - p6 = 0; - } else if (p6 >= 4080) { - p6 = 255; - } else { - p6 >>= 4; - } - if (p7 < 16) { - p7 = 0; - } else if (p7 >= 4080) { - p7 = 255; - } else { - p7 >>= 4; - } - - // store block data - blockData[blockBufferOffset + col] = p0; - blockData[blockBufferOffset + col + 8] = p1; - blockData[blockBufferOffset + col + 16] = p2; - blockData[blockBufferOffset + col + 24] = p3; - blockData[blockBufferOffset + col + 32] = p4; - blockData[blockBufferOffset + col + 40] = p5; - blockData[blockBufferOffset + col + 48] = p6; - blockData[blockBufferOffset + col + 56] = p7; + k += r; + const z = dctZigZag[k]; + component.blockData[blockOffset + z] = + receiveAndExtend(s) * (1 << successive); + k++; } } - function buildComponentData(frame, component) { - const blocksPerLine = component.blocksPerLine; - const blocksPerColumn = component.blocksPerColumn; - const computationBuffer = new Int16Array(64); - - for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { - for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { - const offset = getBlockBufferOffset(component, blockRow, blockCol); - quantizeAndInverse(component, offset, computationBuffer); + let successiveACState = 0, + successiveACNextValue; + function decodeACSuccessive(component, blockOffset) { + let k = spectralStart; + const e = spectralEnd; + let r = 0; + let s; + let rs; + while (k <= e) { + const offsetZ = blockOffset + dctZigZag[k]; + const sign = component.blockData[offsetZ] < 0 ? -1 : 1; + switch (successiveACState) { + case 0: // initial state + rs = decodeHuffman(component.huffmanTableAC); + 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 JpegError("invalid ACn encoding"); + } + successiveACNextValue = receiveAndExtend(s); + successiveACState = r ? 2 : 3; + } + continue; + case 1: // skipping r zero items + case 2: + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } else { + r--; + if (r === 0) { + successiveACState = successiveACState === 2 ? 3 : 0; + } + } + break; + case 3: // set value for a zero item + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } else { + component.blockData[offsetZ] = successiveACNextValue << successive; + successiveACState = 0; + } + break; + case 4: // eob + if (component.blockData[offsetZ]) { + component.blockData[offsetZ] += sign * (readBit() << successive); + } + break; + } + k++; + } + if (successiveACState === 4) { + eobrun--; + if (eobrun === 0) { + successiveACState = 0; } } - return component.blockData; } - function findNextFileMarker(data, currentPos, startPos = currentPos) { - const maxPos = data.length - 1; - let newPos = startPos < currentPos ? startPos : currentPos; + let blockRow = 0; + function decodeMcu(component, decode, mcu, row, col) { + const mcuRow = (mcu / mcusPerLine) | 0; + const mcuCol = mcu % mcusPerLine; + blockRow = mcuRow * component.v + row; + const blockCol = mcuCol * component.h + col; + const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, blockOffset); + } - if (currentPos >= maxPos) { - return null; // Don't attempt to read non-existent data and just return. + function decodeBlock(component, decode, mcu) { + blockRow = (mcu / component.blocksPerLine) | 0; + const blockCol = mcu % component.blocksPerLine; + const blockOffset = getBlockBufferOffset(component, blockRow, blockCol); + decode(component, blockOffset); + } + + const componentsLength = components.length; + let component, i, j, k, n; + let decodeFn; + if (progressive) { + if (spectralStart === 0) { + decodeFn = successivePrev === 0 ? decodeDCFirst : decodeDCSuccessive; + } else { + decodeFn = successivePrev === 0 ? decodeACFirst : decodeACSuccessive; } - const currentMarker = readUint16(data, currentPos); - if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { - return { - invalid: null, - marker: currentMarker, - offset: currentPos, - }; - } - let newMarker = readUint16(data, newPos); - while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { - if (++newPos >= maxPos) { - return null; // Don't attempt to read non-existent data and just return. + } else { + decodeFn = decodeBaseline; + } + + let mcu = 0, + fileMarker; + let mcuExpected; + if (componentsLength === 1) { + mcuExpected = components[0].blocksPerLine * components[0].blocksPerColumn; + } else { + mcuExpected = mcusPerLine * frame.mcusPerColumn; + } + + let h, v; + while (mcu <= mcuExpected) { + // reset interval stuff + const mcuToRead = resetInterval + ? Math.min(mcuExpected - mcu, resetInterval) + : mcuExpected; + + // The `mcuToRead === 0` case should only occur when all of the expected + // MCU data has been already parsed, i.e. when `mcu === mcuExpected`, but + // some corrupt JPEG images contain more data than intended and we thus + // want to skip over any extra RSTx markers below (fixes issue11794.pdf). + if (mcuToRead > 0) { + for (i = 0; i < componentsLength; i++) { + components[i].pred = 0; + } + eobrun = 0; + + if (componentsLength === 1) { + component = components[0]; + for (n = 0; n < mcuToRead; n++) { + decodeBlock(component, decodeFn, mcu); + mcu++; + } + } else { + for (n = 0; n < mcuToRead; n++) { + for (i = 0; i < componentsLength; i++) { + component = components[i]; + h = component.h; + v = component.v; + for (j = 0; j < v; j++) { + for (k = 0; k < h; k++) { + decodeMcu(component, decodeFn, mcu, j, k); + } + } + } + mcu++; + } } - newMarker = readUint16(data, newPos); } + + // find marker + bitsCount = 0; + fileMarker = findNextFileMarker(data, offset); + if (!fileMarker) { + break; // Reached the end of the image data without finding any marker. + } + if (fileMarker.invalid) { + // Some bad images seem to pad Scan blocks with e.g. zero bytes, skip + // past those to attempt to find a valid marker (fixes issue4090.pdf). + const partialMsg = mcuToRead > 0 ? "unexpected" : "excessive"; + warn( + `decodeScan - ${partialMsg} MCU data, current marker is: ${fileMarker.invalid}` + ); + offset = fileMarker.offset; + } + if (fileMarker.marker >= 0xffd0 && fileMarker.marker <= 0xffd7) { + // RSTx + offset += 2; + } else { + break; + } + } + + return offset - startOffset; +} + +// 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. +function quantizeAndInverse(component, blockBufferOffset, p) { + const qt = component.quantizationTable, + blockData = component.blockData; + let v0, v1, v2, v3, v4, v5, v6, v7; + let p0, p1, p2, p3, p4, p5, p6, p7; + let t; + + if (!qt) { + throw new JpegError("missing required Quantization Table."); + } + + // inverse DCT on rows + for (let row = 0; row < 64; row += 8) { + // gather block data + p0 = blockData[blockBufferOffset + row]; + p1 = blockData[blockBufferOffset + row + 1]; + p2 = blockData[blockBufferOffset + row + 2]; + p3 = blockData[blockBufferOffset + row + 3]; + p4 = blockData[blockBufferOffset + row + 4]; + p5 = blockData[blockBufferOffset + row + 5]; + p6 = blockData[blockBufferOffset + row + 6]; + p7 = blockData[blockBufferOffset + row + 7]; + + // dequant p0 + p0 *= qt[row]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 512) >> 10; + p[row] = t; + p[row + 1] = t; + p[row + 2] = t; + p[row + 3] = t; + p[row + 4] = t; + p[row + 5] = t; + p[row + 6] = t; + p[row + 7] = t; + continue; + } + // dequant p1 ... p7 + p1 *= qt[row + 1]; + p2 *= qt[row + 2]; + p3 *= qt[row + 3]; + p4 *= qt[row + 4]; + p5 *= qt[row + 5]; + p6 *= qt[row + 6]; + p7 *= qt[row + 7]; + + // stage 4 + v0 = (dctSqrt2 * p0 + 128) >> 8; + v1 = (dctSqrt2 * p4 + 128) >> 8; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 128) >> 8; + v7 = (dctSqrt1d2 * (p1 + p7) + 128) >> 8; + v5 = p3 << 4; + v6 = p5 << 4; + + // stage 3 + v0 = (v0 + v1 + 1) >> 1; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 128) >> 8; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) >> 8; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p[row] = v0 + v7; + p[row + 7] = v0 - v7; + p[row + 1] = v1 + v6; + p[row + 6] = v1 - v6; + p[row + 2] = v2 + v5; + p[row + 5] = v2 - v5; + p[row + 3] = v3 + v4; + p[row + 4] = v3 - v4; + } + + // inverse DCT on columns + for (let col = 0; col < 8; ++col) { + p0 = p[col]; + p1 = p[col + 8]; + p2 = p[col + 16]; + p3 = p[col + 24]; + p4 = p[col + 32]; + p5 = p[col + 40]; + p6 = p[col + 48]; + p7 = p[col + 56]; + + // check for all-zero AC coefficients + if ((p1 | p2 | p3 | p4 | p5 | p6 | p7) === 0) { + t = (dctSqrt2 * p0 + 8192) >> 14; + // Convert to 8-bit. + if (t < -2040) { + t = 0; + } else if (t >= 2024) { + t = 255; + } else { + t = (t + 2056) >> 4; + } + blockData[blockBufferOffset + col] = t; + blockData[blockBufferOffset + col + 8] = t; + blockData[blockBufferOffset + col + 16] = t; + blockData[blockBufferOffset + col + 24] = t; + blockData[blockBufferOffset + col + 32] = t; + blockData[blockBufferOffset + col + 40] = t; + blockData[blockBufferOffset + col + 48] = t; + blockData[blockBufferOffset + col + 56] = t; + continue; + } + + // stage 4 + v0 = (dctSqrt2 * p0 + 2048) >> 12; + v1 = (dctSqrt2 * p4 + 2048) >> 12; + v2 = p2; + v3 = p6; + v4 = (dctSqrt1d2 * (p1 - p7) + 2048) >> 12; + v7 = (dctSqrt1d2 * (p1 + p7) + 2048) >> 12; + v5 = p3; + v6 = p5; + + // stage 3 + // Shift v0 by 128.5 << 5 here, so we don't need to shift p0...p7 when + // converting to UInt8 range later. + v0 = ((v0 + v1 + 1) >> 1) + 4112; + v1 = v0 - v1; + t = (v2 * dctSin6 + v3 * dctCos6 + 2048) >> 12; + v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) >> 12; + v3 = t; + v4 = (v4 + v6 + 1) >> 1; + v6 = v4 - v6; + v7 = (v7 + v5 + 1) >> 1; + v5 = v7 - v5; + + // stage 2 + v0 = (v0 + v3 + 1) >> 1; + v3 = v0 - v3; + v1 = (v1 + v2 + 1) >> 1; + v2 = v1 - v2; + t = (v4 * dctSin3 + v7 * dctCos3 + 2048) >> 12; + v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) >> 12; + v7 = t; + t = (v5 * dctSin1 + v6 * dctCos1 + 2048) >> 12; + v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) >> 12; + v6 = t; + + // stage 1 + p0 = v0 + v7; + p7 = v0 - v7; + p1 = v1 + v6; + p6 = v1 - v6; + p2 = v2 + v5; + p5 = v2 - v5; + p3 = v3 + v4; + p4 = v3 - v4; + + // Convert to 8-bit integers. + if (p0 < 16) { + p0 = 0; + } else if (p0 >= 4080) { + p0 = 255; + } else { + p0 >>= 4; + } + if (p1 < 16) { + p1 = 0; + } else if (p1 >= 4080) { + p1 = 255; + } else { + p1 >>= 4; + } + if (p2 < 16) { + p2 = 0; + } else if (p2 >= 4080) { + p2 = 255; + } else { + p2 >>= 4; + } + if (p3 < 16) { + p3 = 0; + } else if (p3 >= 4080) { + p3 = 255; + } else { + p3 >>= 4; + } + if (p4 < 16) { + p4 = 0; + } else if (p4 >= 4080) { + p4 = 255; + } else { + p4 >>= 4; + } + if (p5 < 16) { + p5 = 0; + } else if (p5 >= 4080) { + p5 = 255; + } else { + p5 >>= 4; + } + if (p6 < 16) { + p6 = 0; + } else if (p6 >= 4080) { + p6 = 255; + } else { + p6 >>= 4; + } + if (p7 < 16) { + p7 = 0; + } else if (p7 >= 4080) { + p7 = 255; + } else { + p7 >>= 4; + } + + // store block data + blockData[blockBufferOffset + col] = p0; + blockData[blockBufferOffset + col + 8] = p1; + blockData[blockBufferOffset + col + 16] = p2; + blockData[blockBufferOffset + col + 24] = p3; + blockData[blockBufferOffset + col + 32] = p4; + blockData[blockBufferOffset + col + 40] = p5; + blockData[blockBufferOffset + col + 48] = p6; + blockData[blockBufferOffset + col + 56] = p7; + } +} + +function buildComponentData(frame, component) { + const blocksPerLine = component.blocksPerLine; + const blocksPerColumn = component.blocksPerColumn; + const computationBuffer = new Int16Array(64); + + for (let blockRow = 0; blockRow < blocksPerColumn; blockRow++) { + for (let blockCol = 0; blockCol < blocksPerLine; blockCol++) { + const offset = getBlockBufferOffset(component, blockRow, blockCol); + quantizeAndInverse(component, offset, computationBuffer); + } + } + return component.blockData; +} + +function findNextFileMarker(data, currentPos, startPos = currentPos) { + const maxPos = data.length - 1; + let newPos = startPos < currentPos ? startPos : currentPos; + + if (currentPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + const currentMarker = readUint16(data, currentPos); + if (currentMarker >= 0xffc0 && currentMarker <= 0xfffe) { return { - invalid: currentMarker.toString(16), - marker: newMarker, - offset: newPos, + invalid: null, + marker: currentMarker, + offset: currentPos, }; } + let newMarker = readUint16(data, newPos); + while (!(newMarker >= 0xffc0 && newMarker <= 0xfffe)) { + if (++newPos >= maxPos) { + return null; // Don't attempt to read non-existent data and just return. + } + newMarker = readUint16(data, newPos); + } + return { + invalid: currentMarker.toString(16), + marker: newMarker, + offset: newPos, + }; +} - JpegImage.prototype = { - parse(data, { dnlScanLines = null } = {}) { - function readDataBlock() { - const length = readUint16(data, offset); - offset += 2; - let endOffset = offset + length - 2; +class JpegImage { + constructor({ decodeTransform = null, colorTransform = -1 } = {}) { + this._decodeTransform = decodeTransform; + this._colorTransform = colorTransform; + } - const fileMarker = findNextFileMarker(data, endOffset, offset); - if (fileMarker && fileMarker.invalid) { - warn( - "readDataBlock - incorrect length, current marker is: " + - fileMarker.invalid - ); - endOffset = fileMarker.offset; - } - - const array = data.subarray(offset, endOffset); - offset += array.length; - return array; - } - - function prepareComponents(frame) { - const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); - const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); - for (let i = 0, ii = frame.components.length; i < ii; i++) { - const component = frame.components[i]; - const blocksPerLine = Math.ceil( - (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH - ); - const blocksPerColumn = Math.ceil( - (Math.ceil(frame.scanLines / 8) * component.v) / frame.maxV - ); - const blocksPerLineForMcu = mcusPerLine * component.h; - const blocksPerColumnForMcu = mcusPerColumn * component.v; - - const blocksBufferSize = - 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); - component.blockData = new Int16Array(blocksBufferSize); - component.blocksPerLine = blocksPerLine; - component.blocksPerColumn = blocksPerColumn; - } - frame.mcusPerLine = mcusPerLine; - frame.mcusPerColumn = mcusPerColumn; - } - - let offset = 0; - let jfif = null; - let adobe = null; - let frame, resetInterval; - let numSOSMarkers = 0; - const quantizationTables = []; - const huffmanTablesAC = [], - huffmanTablesDC = []; - - let fileMarker = readUint16(data, offset); + parse(data, { dnlScanLines = null } = {}) { + function readDataBlock() { + const length = readUint16(data, offset); offset += 2; - if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) { - throw new JpegError("SOI not found"); + let endOffset = offset + length - 2; + + const fileMarker = findNextFileMarker(data, endOffset, offset); + if (fileMarker && fileMarker.invalid) { + warn( + "readDataBlock - incorrect length, current marker is: " + + fileMarker.invalid + ); + endOffset = fileMarker.offset; + } + + const array = data.subarray(offset, endOffset); + offset += array.length; + return array; + } + + function prepareComponents(frame) { + const mcusPerLine = Math.ceil(frame.samplesPerLine / 8 / frame.maxH); + const mcusPerColumn = Math.ceil(frame.scanLines / 8 / frame.maxV); + for (let i = 0, ii = frame.components.length; i < ii; i++) { + const component = frame.components[i]; + const blocksPerLine = Math.ceil( + (Math.ceil(frame.samplesPerLine / 8) * component.h) / frame.maxH + ); + const blocksPerColumn = Math.ceil( + (Math.ceil(frame.scanLines / 8) * component.v) / frame.maxV + ); + const blocksPerLineForMcu = mcusPerLine * component.h; + const blocksPerColumnForMcu = mcusPerColumn * component.v; + + const blocksBufferSize = + 64 * blocksPerColumnForMcu * (blocksPerLineForMcu + 1); + component.blockData = new Int16Array(blocksBufferSize); + component.blocksPerLine = blocksPerLine; + component.blocksPerColumn = blocksPerColumn; + } + frame.mcusPerLine = mcusPerLine; + frame.mcusPerColumn = mcusPerColumn; + } + + let offset = 0; + let jfif = null; + let adobe = null; + let frame, resetInterval; + let numSOSMarkers = 0; + const quantizationTables = []; + const huffmanTablesAC = [], + huffmanTablesDC = []; + + let fileMarker = readUint16(data, offset); + offset += 2; + if (fileMarker !== /* SOI (Start of Image) = */ 0xffd8) { + throw new JpegError("SOI not found"); + } + fileMarker = readUint16(data, offset); + offset += 2; + + markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { + let i, j, l; + switch (fileMarker) { + 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) + const appData = readDataBlock(); + + if (fileMarker === 0xffe0) { + // 'JFIF\x00' + if ( + appData[0] === 0x4a && + appData[1] === 0x46 && + appData[2] === 0x49 && + appData[3] === 0x46 && + appData[4] === 0 + ) { + jfif = { + 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.subarray( + 14, + 14 + 3 * appData[12] * appData[13] + ), + }; + } + } + // TODO APP1 - Exif + if (fileMarker === 0xffee) { + // 'Adobe' + if ( + appData[0] === 0x41 && + appData[1] === 0x64 && + appData[2] === 0x6f && + appData[3] === 0x62 && + appData[4] === 0x65 + ) { + adobe = { + version: (appData[5] << 8) | appData[6], + flags0: (appData[7] << 8) | appData[8], + flags1: (appData[9] << 8) | appData[10], + transformCode: appData[11], + }; + } + } + break; + + case 0xffdb: // DQT (Define Quantization Tables) + const quantizationTablesLength = readUint16(data, offset); + offset += 2; + const quantizationTablesEnd = quantizationTablesLength + offset - 2; + let z; + while (offset < quantizationTablesEnd) { + const quantizationTableSpec = data[offset++]; + const tableData = new Uint16Array(64); + if (quantizationTableSpec >> 4 === 0) { + // 8 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = data[offset++]; + } + } else if (quantizationTableSpec >> 4 === 1) { + // 16 bit values + for (j = 0; j < 64; j++) { + z = dctZigZag[j]; + tableData[z] = readUint16(data, offset); + offset += 2; + } + } else { + throw new JpegError("DQT - invalid table spec"); + } + 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) + if (frame) { + throw new JpegError("Only single frame JPEGs supported"); + } + offset += 2; // Skip marker length. + + frame = {}; + frame.extended = fileMarker === 0xffc1; + frame.progressive = fileMarker === 0xffc2; + frame.precision = data[offset++]; + const sofScanLines = readUint16(data, offset); + offset += 2; + frame.scanLines = dnlScanLines || sofScanLines; + frame.samplesPerLine = readUint16(data, offset); + offset += 2; + frame.components = []; + frame.componentIds = {}; + const componentsCount = data[offset++]; + let maxH = 0, + maxV = 0; + for (i = 0; i < componentsCount; i++) { + const componentId = data[offset]; + const h = data[offset + 1] >> 4; + const v = data[offset + 1] & 15; + if (maxH < h) { + maxH = h; + } + if (maxV < v) { + maxV = v; + } + const qId = data[offset + 2]; + l = frame.components.push({ + h, + v, + quantizationId: qId, + quantizationTable: null, // See comment below. + }); + frame.componentIds[componentId] = l - 1; + offset += 3; + } + frame.maxH = maxH; + frame.maxV = maxV; + prepareComponents(frame); + break; + + case 0xffc4: // DHT (Define Huffman Tables) + const huffmanLength = readUint16(data, offset); + offset += 2; + for (i = 2; i < huffmanLength; ) { + const huffmanTableSpec = data[offset++]; + const codeLengths = new Uint8Array(16); + let codeLengthSum = 0; + for (j = 0; j < 16; j++, offset++) { + codeLengthSum += codeLengths[j] = data[offset]; + } + const huffmanValues = new Uint8Array(codeLengthSum); + for (j = 0; j < codeLengthSum; j++, offset++) { + huffmanValues[j] = data[offset]; + } + i += 17 + codeLengthSum; + + (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[ + huffmanTableSpec & 15 + ] = buildHuffmanTable(codeLengths, huffmanValues); + } + break; + + case 0xffdd: // DRI (Define Restart Interval) + offset += 2; // Skip marker length. + + resetInterval = readUint16(data, offset); + offset += 2; + break; + + case 0xffda: // SOS (Start of Scan) + // A DNL marker (0xFFDC), if it exists, is only allowed at the end + // of the first scan segment and may only occur once in an image. + // Furthermore, to prevent an infinite loop, do *not* attempt to + // parse DNL markers during re-parsing of the JPEG scan data. + const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines; + + offset += 2; // Skip marker length. + + const selectorsCount = data[offset++], + components = []; + for (i = 0; i < selectorsCount; i++) { + const index = data[offset++]; + const componentIndex = frame.componentIds[index]; + const component = frame.components[componentIndex]; + component.index = index; + const tableSpec = data[offset++]; + component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; + component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; + components.push(component); + } + const spectralStart = data[offset++], + spectralEnd = data[offset++], + successiveApproximation = data[offset++]; + try { + const processed = decodeScan( + data, + offset, + frame, + components, + resetInterval, + spectralStart, + spectralEnd, + successiveApproximation >> 4, + successiveApproximation & 15, + parseDNLMarker + ); + offset += processed; + } catch (ex) { + if (ex instanceof DNLMarkerError) { + warn(`${ex.message} -- attempting to re-parse the JPEG image.`); + return this.parse(data, { dnlScanLines: ex.scanLines }); + } else if (ex instanceof EOIMarkerError) { + warn(`${ex.message} -- ignoring the rest of the image data.`); + break markerLoop; + } + throw ex; + } + break; + + case 0xffdc: // DNL (Define Number of Lines) + // Ignore the marker, since it's being handled in `decodeScan`. + offset += 4; + break; + + case 0xffff: // Fill bytes + if (data[offset] !== 0xff) { + // Avoid skipping a valid marker. + offset--; + } + break; + + default: + // Could be incorrect encoding -- the last 0xFF byte of the previous + // block could have been eaten by the encoder, hence we fallback to + // `startPos = offset - 3` when looking for the next valid marker. + const nextFileMarker = findNextFileMarker( + data, + /* currentPos = */ offset - 2, + /* startPos = */ offset - 3 + ); + if (nextFileMarker && nextFileMarker.invalid) { + warn( + "JpegImage.parse - unexpected data, current marker is: " + + nextFileMarker.invalid + ); + offset = nextFileMarker.offset; + break; + } + if (!nextFileMarker || offset >= data.length - 1) { + warn( + "JpegImage.parse - reached the end of the image data " + + "without finding an EOI marker (0xFFD9)." + ); + break markerLoop; + } + throw new JpegError( + "JpegImage.parse - unknown marker: " + fileMarker.toString(16) + ); } fileMarker = readUint16(data, offset); offset += 2; + } - markerLoop: while (fileMarker !== /* EOI (End of Image) = */ 0xffd9) { - let i, j, l; - switch (fileMarker) { - 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) - const appData = readDataBlock(); + this.width = frame.samplesPerLine; + this.height = frame.scanLines; + this.jfif = jfif; + this.adobe = adobe; + this.components = []; + for (let i = 0, ii = frame.components.length; i < ii; i++) { + const component = frame.components[i]; - if (fileMarker === 0xffe0) { - // 'JFIF\x00' - if ( - appData[0] === 0x4a && - appData[1] === 0x46 && - appData[2] === 0x49 && - appData[3] === 0x46 && - appData[4] === 0 - ) { - jfif = { - 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.subarray( - 14, - 14 + 3 * appData[12] * appData[13] - ), - }; - } - } - // TODO APP1 - Exif - if (fileMarker === 0xffee) { - // 'Adobe' - if ( - appData[0] === 0x41 && - appData[1] === 0x64 && - appData[2] === 0x6f && - appData[3] === 0x62 && - appData[4] === 0x65 - ) { - adobe = { - version: (appData[5] << 8) | appData[6], - flags0: (appData[7] << 8) | appData[8], - flags1: (appData[9] << 8) | appData[10], - transformCode: appData[11], - }; - } - } - break; - - case 0xffdb: // DQT (Define Quantization Tables) - const quantizationTablesLength = readUint16(data, offset); - offset += 2; - const quantizationTablesEnd = quantizationTablesLength + offset - 2; - let z; - while (offset < quantizationTablesEnd) { - const quantizationTableSpec = data[offset++]; - const tableData = new Uint16Array(64); - if (quantizationTableSpec >> 4 === 0) { - // 8 bit values - for (j = 0; j < 64; j++) { - z = dctZigZag[j]; - tableData[z] = data[offset++]; - } - } else if (quantizationTableSpec >> 4 === 1) { - // 16 bit values - for (j = 0; j < 64; j++) { - z = dctZigZag[j]; - tableData[z] = readUint16(data, offset); - offset += 2; - } - } else { - throw new JpegError("DQT - invalid table spec"); - } - 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) - if (frame) { - throw new JpegError("Only single frame JPEGs supported"); - } - offset += 2; // Skip marker length. - - frame = {}; - frame.extended = fileMarker === 0xffc1; - frame.progressive = fileMarker === 0xffc2; - frame.precision = data[offset++]; - const sofScanLines = readUint16(data, offset); - offset += 2; - frame.scanLines = dnlScanLines || sofScanLines; - frame.samplesPerLine = readUint16(data, offset); - offset += 2; - frame.components = []; - frame.componentIds = {}; - const componentsCount = data[offset++]; - let maxH = 0, - maxV = 0; - for (i = 0; i < componentsCount; i++) { - const componentId = data[offset]; - const h = data[offset + 1] >> 4; - const v = data[offset + 1] & 15; - if (maxH < h) { - maxH = h; - } - if (maxV < v) { - maxV = v; - } - const qId = data[offset + 2]; - l = frame.components.push({ - h, - v, - quantizationId: qId, - quantizationTable: null, // See comment below. - }); - frame.componentIds[componentId] = l - 1; - offset += 3; - } - frame.maxH = maxH; - frame.maxV = maxV; - prepareComponents(frame); - break; - - case 0xffc4: // DHT (Define Huffman Tables) - const huffmanLength = readUint16(data, offset); - offset += 2; - for (i = 2; i < huffmanLength; ) { - const huffmanTableSpec = data[offset++]; - const codeLengths = new Uint8Array(16); - let codeLengthSum = 0; - for (j = 0; j < 16; j++, offset++) { - codeLengthSum += codeLengths[j] = data[offset]; - } - const huffmanValues = new Uint8Array(codeLengthSum); - for (j = 0; j < codeLengthSum; j++, offset++) { - huffmanValues[j] = data[offset]; - } - i += 17 + codeLengthSum; - - (huffmanTableSpec >> 4 === 0 ? huffmanTablesDC : huffmanTablesAC)[ - huffmanTableSpec & 15 - ] = buildHuffmanTable(codeLengths, huffmanValues); - } - break; - - case 0xffdd: // DRI (Define Restart Interval) - offset += 2; // Skip marker length. - - resetInterval = readUint16(data, offset); - offset += 2; - break; - - case 0xffda: // SOS (Start of Scan) - // A DNL marker (0xFFDC), if it exists, is only allowed at the end - // of the first scan segment and may only occur once in an image. - // Furthermore, to prevent an infinite loop, do *not* attempt to - // parse DNL markers during re-parsing of the JPEG scan data. - const parseDNLMarker = ++numSOSMarkers === 1 && !dnlScanLines; - - offset += 2; // Skip marker length. - - const selectorsCount = data[offset++], - components = []; - for (i = 0; i < selectorsCount; i++) { - const index = data[offset++]; - const componentIndex = frame.componentIds[index]; - const component = frame.components[componentIndex]; - component.index = index; - const tableSpec = data[offset++]; - component.huffmanTableDC = huffmanTablesDC[tableSpec >> 4]; - component.huffmanTableAC = huffmanTablesAC[tableSpec & 15]; - components.push(component); - } - const spectralStart = data[offset++], - spectralEnd = data[offset++], - successiveApproximation = data[offset++]; - try { - const processed = decodeScan( - data, - offset, - frame, - components, - resetInterval, - spectralStart, - spectralEnd, - successiveApproximation >> 4, - successiveApproximation & 15, - parseDNLMarker - ); - offset += processed; - } catch (ex) { - if (ex instanceof DNLMarkerError) { - warn(`${ex.message} -- attempting to re-parse the JPEG image.`); - return this.parse(data, { dnlScanLines: ex.scanLines }); - } else if (ex instanceof EOIMarkerError) { - warn(`${ex.message} -- ignoring the rest of the image data.`); - break markerLoop; - } - throw ex; - } - break; - - case 0xffdc: // DNL (Define Number of Lines) - // Ignore the marker, since it's being handled in `decodeScan`. - offset += 4; - break; - - case 0xffff: // Fill bytes - if (data[offset] !== 0xff) { - // Avoid skipping a valid marker. - offset--; - } - break; - - default: - // Could be incorrect encoding -- the last 0xFF byte of the previous - // block could have been eaten by the encoder, hence we fallback to - // `startPos = offset - 3` when looking for the next valid marker. - const nextFileMarker = findNextFileMarker( - data, - /* currentPos = */ offset - 2, - /* startPos = */ offset - 3 - ); - if (nextFileMarker && nextFileMarker.invalid) { - warn( - "JpegImage.parse - unexpected data, current marker is: " + - nextFileMarker.invalid - ); - offset = nextFileMarker.offset; - break; - } - if (!nextFileMarker || offset >= data.length - 1) { - warn( - "JpegImage.parse - reached the end of the image data " + - "without finding an EOI marker (0xFFD9)." - ); - break markerLoop; - } - throw new JpegError( - "JpegImage.parse - unknown marker: " + fileMarker.toString(16) - ); - } - fileMarker = readUint16(data, offset); - offset += 2; + // Prevent errors when DQT markers are placed after SOF{n} markers, + // by assigning the `quantizationTable` entry after the entire image + // has been parsed (fixes issue7406.pdf). + const quantizationTable = quantizationTables[component.quantizationId]; + if (quantizationTable) { + component.quantizationTable = quantizationTable; } - this.width = frame.samplesPerLine; - this.height = frame.scanLines; - this.jfif = jfif; - this.adobe = adobe; - this.components = []; - for (let i = 0, ii = frame.components.length; i < ii; i++) { - const component = frame.components[i]; + this.components.push({ + index: component.index, + output: buildComponentData(frame, component), + scaleX: component.h / frame.maxH, + scaleY: component.v / frame.maxV, + blocksPerLine: component.blocksPerLine, + blocksPerColumn: component.blocksPerColumn, + }); + } + this.numComponents = this.components.length; + return undefined; + } - // Prevent errors when DQT markers are placed after SOF{n} markers, - // by assigning the `quantizationTable` entry after the entire image - // has been parsed (fixes issue7406.pdf). - const quantizationTable = quantizationTables[component.quantizationId]; - if (quantizationTable) { - component.quantizationTable = quantizationTable; + _getLinearizedBlockData(width, height, isSourcePDF = false) { + const scaleX = this.width / width, + scaleY = this.height / height; + + let component, componentScaleX, componentScaleY, blocksPerScanline; + let x, y, i, j, k; + let index; + let offset = 0; + let output; + const numComponents = this.components.length; + const dataLength = width * height * numComponents; + const data = new Uint8ClampedArray(dataLength); + const xScaleBlockOffset = new Uint32Array(width); + const mask3LSB = 0xfffffff8; // used to clear the 3 LSBs + let lastComponentScaleX; + + for (i = 0; i < numComponents; i++) { + component = this.components[i]; + componentScaleX = component.scaleX * scaleX; + componentScaleY = component.scaleY * scaleY; + offset = i; + output = component.output; + blocksPerScanline = (component.blocksPerLine + 1) << 3; + // Precalculate the `xScaleBlockOffset`. Since it doesn't depend on the + // component data, that's only necessary when `componentScaleX` changes. + if (componentScaleX !== lastComponentScaleX) { + for (x = 0; x < width; x++) { + j = 0 | (x * componentScaleX); + xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); } - - this.components.push({ - index: component.index, - output: buildComponentData(frame, component), - scaleX: component.h / frame.maxH, - scaleY: component.v / frame.maxV, - blocksPerLine: component.blocksPerLine, - blocksPerColumn: component.blocksPerColumn, - }); + lastComponentScaleX = componentScaleX; } - this.numComponents = this.components.length; - return undefined; - }, - - _getLinearizedBlockData(width, height, isSourcePDF = false) { - const scaleX = this.width / width, - scaleY = this.height / height; - - let component, componentScaleX, componentScaleY, blocksPerScanline; - let x, y, i, j, k; - let index; - let offset = 0; - let output; - const numComponents = this.components.length; - const dataLength = width * height * numComponents; - const data = new Uint8ClampedArray(dataLength); - const xScaleBlockOffset = new Uint32Array(width); - const mask3LSB = 0xfffffff8; // used to clear the 3 LSBs - let lastComponentScaleX; - - for (i = 0; i < numComponents; i++) { - component = this.components[i]; - componentScaleX = component.scaleX * scaleX; - componentScaleY = component.scaleY * scaleY; - offset = i; - output = component.output; - blocksPerScanline = (component.blocksPerLine + 1) << 3; - // Precalculate the `xScaleBlockOffset`. Since it doesn't depend on the - // component data, that's only necessary when `componentScaleX` changes. - if (componentScaleX !== lastComponentScaleX) { - for (x = 0; x < width; x++) { - j = 0 | (x * componentScaleX); - xScaleBlockOffset[x] = ((j & mask3LSB) << 3) | (j & 7); - } - lastComponentScaleX = componentScaleX; - } - // linearize the blocks of the component - for (y = 0; y < height; y++) { - j = 0 | (y * componentScaleY); - index = (blocksPerScanline * (j & mask3LSB)) | ((j & 7) << 3); - for (x = 0; x < width; x++) { - data[offset] = output[index + xScaleBlockOffset[x]]; - offset += numComponents; - } + // linearize the blocks of the component + for (y = 0; y < height; y++) { + j = 0 | (y * componentScaleY); + index = (blocksPerScanline * (j & mask3LSB)) | ((j & 7) << 3); + for (x = 0; x < width; x++) { + data[offset] = output[index + xScaleBlockOffset[x]]; + offset += numComponents; } } + } - // decodeTransform contains pairs of multiplier (-256..256) and additive - let transform = this._decodeTransform; + // decodeTransform contains pairs of multiplier (-256..256) and additive + let transform = this._decodeTransform; - // In PDF files, JPEG images with CMYK colour spaces are usually inverted - // (this can be observed by extracting the raw image data). - // Since the conversion algorithms (see below) were written primarily for - // the PDF use-cases, attempting to use `JpegImage` to parse standalone - // JPEG (CMYK) images may thus result in inverted images (see issue 9513). - // - // Unfortunately it's not (always) possible to tell, from the image data - // alone, if it needs to be inverted. Thus in an attempt to provide better - // out-of-box behaviour when `JpegImage` is used standalone, default to - // inverting JPEG (CMYK) images if and only if the image data does *not* - // come from a PDF file and no `decodeTransform` was passed by the user. - if (!isSourcePDF && numComponents === 4 && !transform) { - // prettier-ignore - transform = new Int32Array([ + // In PDF files, JPEG images with CMYK colour spaces are usually inverted + // (this can be observed by extracting the raw image data). + // Since the conversion algorithms (see below) were written primarily for + // the PDF use-cases, attempting to use `JpegImage` to parse standalone + // JPEG (CMYK) images may thus result in inverted images (see issue 9513). + // + // Unfortunately it's not (always) possible to tell, from the image data + // alone, if it needs to be inverted. Thus in an attempt to provide better + // out-of-box behaviour when `JpegImage` is used standalone, default to + // inverting JPEG (CMYK) images if and only if the image data does *not* + // come from a PDF file and no `decodeTransform` was passed by the user. + if (!isSourcePDF && numComponents === 4 && !transform) { + // prettier-ignore + transform = new Int32Array([ -256, 255, -256, 255, -256, 255, -256, 255]); - } + } - if (transform) { - for (i = 0; i < dataLength; ) { - for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { - data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; - } + if (transform) { + for (i = 0; i < dataLength; ) { + for (j = 0, k = 0; j < numComponents; j++, i++, k += 2) { + data[i] = ((data[i] * transform[k]) >> 8) + transform[k + 1]; } } - return data; - }, + } + return data; + } - get _isColorConversionNeeded() { - if (this.adobe) { - // The adobe transform marker overrides any previous setting. - return !!this.adobe.transformCode; - } - if (this.numComponents === 3) { - if (this._colorTransform === 0) { - // If the Adobe transform marker is not present and the image - // dictionary has a 'ColorTransform' entry, explicitly set to `0`, - // then the colours should *not* be transformed. - return false; - } else if ( - this.components[0].index === /* "R" = */ 0x52 && - this.components[1].index === /* "G" = */ 0x47 && - this.components[2].index === /* "B" = */ 0x42 - ) { - // If the three components are indexed as RGB in ASCII - // then the colours should *not* be transformed. - return false; - } - return true; - } - // `this.numComponents !== 3` - if (this._colorTransform === 1) { + get _isColorConversionNeeded() { + if (this.adobe) { + // The adobe transform marker overrides any previous setting. + return !!this.adobe.transformCode; + } + if (this.numComponents === 3) { + if (this._colorTransform === 0) { // If the Adobe transform marker is not present and the image - // dictionary has a 'ColorTransform' entry, explicitly set to `1`, - // then the colours should be transformed. - return true; - } - return false; - }, - - _convertYccToRgb: function convertYccToRgb(data) { - let Y, Cb, Cr; - for (let i = 0, length = data.length; i < length; i += 3) { - Y = data[i]; - Cb = data[i + 1]; - Cr = data[i + 2]; - data[i] = Y - 179.456 + 1.402 * Cr; - data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; - data[i + 2] = Y - 226.816 + 1.772 * Cb; - } - return data; - }, - - _convertYcckToRgb: function convertYcckToRgb(data) { - let Y, Cb, Cr, k; - let offset = 0; - for (let i = 0, length = data.length; i < length; i += 4) { - Y = data[i]; - Cb = data[i + 1]; - Cr = data[i + 2]; - k = data[i + 3]; - - data[offset++] = - -122.67195406894 + - Cb * - (-6.60635669420364e-5 * Cb + - 0.000437130475926232 * Cr - - 5.4080610064599e-5 * Y + - 0.00048449797120281 * k - - 0.154362151871126) + - Cr * - (-0.000957964378445773 * Cr + - 0.000817076911346625 * Y - - 0.00477271405408747 * k + - 1.53380253221734) + - Y * - (0.000961250184130688 * Y - - 0.00266257332283933 * k + - 0.48357088451265) + - k * (-0.000336197177618394 * k + 0.484791561490776); - - data[offset++] = - 107.268039397724 + - Cb * - (2.19927104525741e-5 * Cb - - 0.000640992018297945 * Cr + - 0.000659397001245577 * Y + - 0.000426105652938837 * k - - 0.176491792462875) + - Cr * - (-0.000778269941513683 * Cr + - 0.00130872261408275 * Y + - 0.000770482631801132 * k - - 0.151051492775562) + - Y * - (0.00126935368114843 * Y - - 0.00265090189010898 * k + - 0.25802910206845) + - k * (-0.000318913117588328 * k - 0.213742400323665); - - data[offset++] = - -20.810012546947 + - Cb * - (-0.000570115196973677 * Cb - - 2.63409051004589e-5 * Cr + - 0.0020741088115012 * Y - - 0.00288260236853442 * k + - 0.814272968359295) + - Cr * - (-1.53496057440975e-5 * Cr - - 0.000132689043961446 * Y + - 0.000560833691242812 * k - - 0.195152027534049) + - Y * - (0.00174418132927582 * Y - - 0.00255243321439347 * k + - 0.116935020465145) + - k * (-0.000343531996510555 * k + 0.24165260232407); - } - // Ensure that only the converted RGB data is returned. - return data.subarray(0, offset); - }, - - _convertYcckToCmyk: function convertYcckToCmyk(data) { - let Y, Cb, Cr; - for (let i = 0, length = data.length; i < length; i += 4) { - Y = data[i]; - Cb = data[i + 1]; - Cr = data[i + 2]; - data[i] = 434.456 - Y - 1.402 * Cr; - data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr; - data[i + 2] = 481.816 - Y - 1.772 * Cb; - // K in data[i + 3] is unchanged - } - return data; - }, - - _convertCmykToRgb: function convertCmykToRgb(data) { - let c, m, y, k; - let offset = 0; - for (let i = 0, length = data.length; i < length; i += 4) { - c = data[i]; - m = data[i + 1]; - y = data[i + 2]; - k = data[i + 3]; - - data[offset++] = - 255 + - c * - (-0.00006747147073602441 * c + - 0.0008379262121013727 * m + - 0.0002894718188643294 * y + - 0.003264231057537806 * k - - 1.1185611867203937) + - m * - (0.000026374107616089405 * m - - 0.00008626949158638572 * y - - 0.0002748769067499491 * k - - 0.02155688794978967) + - y * - (-0.00003878099212869363 * y - - 0.0003267808279485286 * k + - 0.0686742238595345) - - k * (0.0003361971776183937 * k + 0.7430659151342254); - - data[offset++] = - 255 + - c * - (0.00013596372813588848 * c + - 0.000924537132573585 * m + - 0.00010567359618683593 * y + - 0.0004791864687436512 * k - - 0.3109689587515875) + - m * - (-0.00023545346108370344 * m + - 0.0002702845253534714 * y + - 0.0020200308977307156 * k - - 0.7488052167015494) + - y * - (0.00006834815998235662 * y + - 0.00015168452363460973 * k - - 0.09751927774728933) - - k * (0.00031891311758832814 * k + 0.7364883807733168); - - data[offset++] = - 255 + - c * - (0.000013598650411385307 * c + - 0.00012423956175490851 * m + - 0.0004751985097583589 * y - - 0.0000036729317476630422 * k - - 0.05562186980264034) + - m * - (0.00016141380598724676 * m + - 0.0009692239130725186 * y + - 0.0007782692450036253 * k - - 0.44015232367526463) + - y * - (5.068882914068769e-7 * y + - 0.0017778369011375071 * k - - 0.7591454649749609) - - k * (0.0003435319965105553 * k + 0.7063770186160144); - } - // Ensure that only the converted RGB data is returned. - return data.subarray(0, offset); - }, - - getData({ width, height, forceRGB = false, isSourcePDF = false }) { - if ( - typeof PDFJSDev === "undefined" || - PDFJSDev.test("!PRODUCTION || TESTING") + // dictionary has a 'ColorTransform' entry, explicitly set to `0`, + // then the colours should *not* be transformed. + return false; + } else if ( + this.components[0].index === /* "R" = */ 0x52 && + this.components[1].index === /* "G" = */ 0x47 && + this.components[2].index === /* "B" = */ 0x42 ) { - assert( - isSourcePDF === true, - 'JpegImage.getData: Unexpected "isSourcePDF" value for PDF files.' - ); + // If the three components are indexed as RGB in ASCII + // then the colours should *not* be transformed. + return false; } - if (this.numComponents > 4) { - throw new JpegError("Unsupported color mode"); - } - // Type of data: Uint8ClampedArray(width * height * numComponents) - const data = this._getLinearizedBlockData(width, height, isSourcePDF); + return true; + } + // `this.numComponents !== 3` + if (this._colorTransform === 1) { + // If the Adobe transform marker is not present and the image + // dictionary has a 'ColorTransform' entry, explicitly set to `1`, + // then the colours should be transformed. + return true; + } + return false; + } - if (this.numComponents === 1 && forceRGB) { - const dataLength = data.length; - const rgbData = new Uint8ClampedArray(dataLength * 3); - let offset = 0; - for (let i = 0; i < dataLength; i++) { - const grayColor = data[i]; - rgbData[offset++] = grayColor; - rgbData[offset++] = grayColor; - rgbData[offset++] = grayColor; - } - return rgbData; - } else if (this.numComponents === 3 && this._isColorConversionNeeded) { - return this._convertYccToRgb(data); - } else if (this.numComponents === 4) { - if (this._isColorConversionNeeded) { - if (forceRGB) { - return this._convertYcckToRgb(data); - } - return this._convertYcckToCmyk(data); - } else if (forceRGB) { - return this._convertCmykToRgb(data); - } - } - return data; - }, - }; + _convertYccToRgb(data) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 3) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i] = Y - 179.456 + 1.402 * Cr; + data[i + 1] = Y + 135.459 - 0.344 * Cb - 0.714 * Cr; + data[i + 2] = Y - 226.816 + 1.772 * Cb; + } + return data; + } - return JpegImage; -})(); + _convertYcckToRgb(data) { + let Y, Cb, Cr, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + k = data[i + 3]; + + data[offset++] = + -122.67195406894 + + Cb * + (-6.60635669420364e-5 * Cb + + 0.000437130475926232 * Cr - + 5.4080610064599e-5 * Y + + 0.00048449797120281 * k - + 0.154362151871126) + + Cr * + (-0.000957964378445773 * Cr + + 0.000817076911346625 * Y - + 0.00477271405408747 * k + + 1.53380253221734) + + Y * + (0.000961250184130688 * Y - + 0.00266257332283933 * k + + 0.48357088451265) + + k * (-0.000336197177618394 * k + 0.484791561490776); + + data[offset++] = + 107.268039397724 + + Cb * + (2.19927104525741e-5 * Cb - + 0.000640992018297945 * Cr + + 0.000659397001245577 * Y + + 0.000426105652938837 * k - + 0.176491792462875) + + Cr * + (-0.000778269941513683 * Cr + + 0.00130872261408275 * Y + + 0.000770482631801132 * k - + 0.151051492775562) + + Y * + (0.00126935368114843 * Y - + 0.00265090189010898 * k + + 0.25802910206845) + + k * (-0.000318913117588328 * k - 0.213742400323665); + + data[offset++] = + -20.810012546947 + + Cb * + (-0.000570115196973677 * Cb - + 2.63409051004589e-5 * Cr + + 0.0020741088115012 * Y - + 0.00288260236853442 * k + + 0.814272968359295) + + Cr * + (-1.53496057440975e-5 * Cr - + 0.000132689043961446 * Y + + 0.000560833691242812 * k - + 0.195152027534049) + + Y * + (0.00174418132927582 * Y - + 0.00255243321439347 * k + + 0.116935020465145) + + k * (-0.000343531996510555 * k + 0.24165260232407); + } + // Ensure that only the converted RGB data is returned. + return data.subarray(0, offset); + } + + _convertYcckToCmyk(data) { + let Y, Cb, Cr; + for (let i = 0, length = data.length; i < length; i += 4) { + Y = data[i]; + Cb = data[i + 1]; + Cr = data[i + 2]; + data[i] = 434.456 - Y - 1.402 * Cr; + data[i + 1] = 119.541 - Y + 0.344 * Cb + 0.714 * Cr; + data[i + 2] = 481.816 - Y - 1.772 * Cb; + // K in data[i + 3] is unchanged + } + return data; + } + + _convertCmykToRgb(data) { + let c, m, y, k; + let offset = 0; + for (let i = 0, length = data.length; i < length; i += 4) { + c = data[i]; + m = data[i + 1]; + y = data[i + 2]; + k = data[i + 3]; + + data[offset++] = + 255 + + c * + (-0.00006747147073602441 * c + + 0.0008379262121013727 * m + + 0.0002894718188643294 * y + + 0.003264231057537806 * k - + 1.1185611867203937) + + m * + (0.000026374107616089405 * m - + 0.00008626949158638572 * y - + 0.0002748769067499491 * k - + 0.02155688794978967) + + y * + (-0.00003878099212869363 * y - + 0.0003267808279485286 * k + + 0.0686742238595345) - + k * (0.0003361971776183937 * k + 0.7430659151342254); + + data[offset++] = + 255 + + c * + (0.00013596372813588848 * c + + 0.000924537132573585 * m + + 0.00010567359618683593 * y + + 0.0004791864687436512 * k - + 0.3109689587515875) + + m * + (-0.00023545346108370344 * m + + 0.0002702845253534714 * y + + 0.0020200308977307156 * k - + 0.7488052167015494) + + y * + (0.00006834815998235662 * y + + 0.00015168452363460973 * k - + 0.09751927774728933) - + k * (0.00031891311758832814 * k + 0.7364883807733168); + + data[offset++] = + 255 + + c * + (0.000013598650411385307 * c + + 0.00012423956175490851 * m + + 0.0004751985097583589 * y - + 0.0000036729317476630422 * k - + 0.05562186980264034) + + m * + (0.00016141380598724676 * m + + 0.0009692239130725186 * y + + 0.0007782692450036253 * k - + 0.44015232367526463) + + y * + (5.068882914068769e-7 * y + + 0.0017778369011375071 * k - + 0.7591454649749609) - + k * (0.0003435319965105553 * k + 0.7063770186160144); + } + // Ensure that only the converted RGB data is returned. + return data.subarray(0, offset); + } + + getData({ width, height, forceRGB = false, isSourcePDF = false }) { + if ( + typeof PDFJSDev === "undefined" || + PDFJSDev.test("!PRODUCTION || TESTING") + ) { + assert( + isSourcePDF === true, + 'JpegImage.getData: Unexpected "isSourcePDF" value for PDF files.' + ); + } + if (this.numComponents > 4) { + throw new JpegError("Unsupported color mode"); + } + // Type of data: Uint8ClampedArray(width * height * numComponents) + const data = this._getLinearizedBlockData(width, height, isSourcePDF); + + if (this.numComponents === 1 && forceRGB) { + const dataLength = data.length; + const rgbData = new Uint8ClampedArray(dataLength * 3); + let offset = 0; + for (let i = 0; i < dataLength; i++) { + const grayColor = data[i]; + rgbData[offset++] = grayColor; + rgbData[offset++] = grayColor; + rgbData[offset++] = grayColor; + } + return rgbData; + } else if (this.numComponents === 3 && this._isColorConversionNeeded) { + return this._convertYccToRgb(data); + } else if (this.numComponents === 4) { + if (this._isColorConversionNeeded) { + if (forceRGB) { + return this._convertYcckToRgb(data); + } + return this._convertYcckToCmyk(data); + } else if (forceRGB) { + return this._convertCmykToRgb(data); + } + } + return data; + } +} export { JpegImage }; From a273599a126752948f7f2b1942b9a8f50cc95c68 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:46:08 +0200 Subject: [PATCH 7/9] Enable the `no-var` rule in the `src/core/jpx.js` file These changes were made automatically, using `gulp lint --fix`. --- src/core/jpx.js | 810 ++++++++++++++++++++++++------------------------ 1 file changed, 405 insertions(+), 405 deletions(-) diff --git a/src/core/jpx.js b/src/core/jpx.js index cdd96c41b..ade3d8bfb 100644 --- a/src/core/jpx.js +++ b/src/core/jpx.js @@ -12,7 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -/* eslint-disable no-var */ import { BaseException, info, warn } from "../shared/util.js"; import { log2, readUint16, readUint32 } from "./core_utils.js"; @@ -24,9 +23,9 @@ class JpxError extends BaseException { } } -var JpxImage = (function JpxImageClosure() { +const JpxImage = (function JpxImageClosure() { // Table E.1 - var SubbandsGainLog2 = { + const SubbandsGainLog2 = { LL: 0, LH: 1, HL: 1, @@ -39,19 +38,19 @@ var JpxImage = (function JpxImageClosure() { } JpxImage.prototype = { parse: function JpxImage_parse(data) { - var head = readUint16(data, 0); + const head = readUint16(data, 0); // No box header, immediate start of codestream (SOC) if (head === 0xff4f) { this.parseCodestream(data, 0, data.length); return; } - var position = 0, + let position = 0, length = data.length; while (position < length) { - var headerSize = 8; - var lbox = readUint32(data, position); - var tbox = readUint32(data, position + 4); + let headerSize = 8; + let lbox = readUint32(data, position); + const tbox = readUint32(data, position + 4); position += headerSize; if (lbox === 1) { // XLBox: read UInt64 according to spec. @@ -68,8 +67,8 @@ var JpxImage = (function JpxImageClosure() { if (lbox < headerSize) { throw new JpxError("Invalid box field size"); } - var dataLength = lbox - headerSize; - var jumpDataLength = true; + const dataLength = lbox - headerSize; + let jumpDataLength = true; switch (tbox) { case 0x6a703268: // 'jp2h' jumpDataLength = false; // parsing child boxes @@ -79,7 +78,7 @@ var JpxImage = (function JpxImageClosure() { var method = data[position]; if (method === 1) { // enumerated colorspace - var colorspace = readUint32(data, position + 3); + const colorspace = readUint32(data, position + 3); switch (colorspace) { case 16: // this indicates a sRGB colorspace case 17: // this indicates a grayscale colorspace @@ -124,20 +123,20 @@ var JpxImage = (function JpxImageClosure() { } }, parseImageProperties: function JpxImage_parseImageProperties(stream) { - var newByte = stream.getByte(); + let newByte = stream.getByte(); while (newByte >= 0) { - var oldByte = newByte; + const oldByte = newByte; newByte = stream.getByte(); - var code = (oldByte << 8) | newByte; + const code = (oldByte << 8) | newByte; // Image and tile size (SIZ) if (code === 0xff51) { stream.skip(4); - var Xsiz = stream.getInt32() >>> 0; // Byte 4 - var Ysiz = stream.getInt32() >>> 0; // Byte 8 - var XOsiz = stream.getInt32() >>> 0; // Byte 12 - var YOsiz = stream.getInt32() >>> 0; // Byte 16 + const Xsiz = stream.getInt32() >>> 0; // Byte 4 + const Ysiz = stream.getInt32() >>> 0; // Byte 8 + const XOsiz = stream.getInt32() >>> 0; // Byte 12 + const YOsiz = stream.getInt32() >>> 0; // Byte 16 stream.skip(16); - var Csiz = stream.getUint16(); // Byte 36 + const Csiz = stream.getUint16(); // Byte 36 this.width = Xsiz - XOsiz; this.height = Ysiz - YOsiz; this.componentsCount = Csiz; @@ -149,12 +148,12 @@ var JpxImage = (function JpxImageClosure() { throw new JpxError("No size marker found in JPX stream"); }, parseCodestream: function JpxImage_parseCodestream(data, start, end) { - var context = {}; - var doNotRecover = false; + const context = {}; + let doNotRecover = false; try { - var position = start; + let position = start; while (position + 1 < end) { - var code = readUint16(data, position); + const code = readUint16(data, position); position += 2; var length = 0, @@ -185,8 +184,8 @@ var JpxImage = (function JpxImageClosure() { siz.Csiz = componentsCount; var components = []; j = position + 38; - for (var i = 0; i < componentsCount; i++) { - var component = { + for (let i = 0; i < componentsCount; i++) { + const component = { precision: (data[j] & 0x7f) + 1, isSigned: !!(data[j] & 0x80), XRsiz: data[j + 1], @@ -323,9 +322,9 @@ var JpxImage = (function JpxImageClosure() { cod.segmentationSymbolUsed = !!(blockStyle & 32); cod.reversibleTransformation = data[j++]; if (cod.entropyCoderWithCustomPrecincts) { - var precinctsSizes = []; + const precinctsSizes = []; while (j < length + position) { - var precinctsSize = data[j++]; + const precinctsSize = data[j++]; precinctsSizes.push({ PPx: precinctsSize & 0xf, PPy: precinctsSize >> 4, @@ -430,14 +429,14 @@ var JpxImage = (function JpxImageClosure() { component.height = component.y1 - component.y0; } function calculateTileGrids(context, components) { - var siz = context.SIZ; + const siz = context.SIZ; // Section B.3 Division into tile and tile-components - var tile, + let tile, tiles = []; - var numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); - var numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); - for (var q = 0; q < numYtiles; q++) { - for (var p = 0; p < numXtiles; p++) { + const numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); + const numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); + for (let q = 0; q < numYtiles; q++) { + for (let p = 0; p < numXtiles; p++) { tile = {}; tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); @@ -451,11 +450,11 @@ var JpxImage = (function JpxImageClosure() { } context.tiles = tiles; - var componentsCount = siz.Csiz; - for (var i = 0, ii = componentsCount; i < ii; i++) { - var component = components[i]; - for (var j = 0, jj = tiles.length; j < jj; j++) { - var tileComponent = {}; + const componentsCount = siz.Csiz; + for (let i = 0, ii = componentsCount; i < ii; i++) { + const component = components[i]; + for (let j = 0, jj = tiles.length; j < jj; j++) { + const tileComponent = {}; tile = tiles[j]; tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz); tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz); @@ -468,8 +467,8 @@ var JpxImage = (function JpxImageClosure() { } } function getBlocksDimensions(context, component, r) { - var codOrCoc = component.codingStyleParameters; - var result = {}; + const codOrCoc = component.codingStyleParameters; + const result = {}; if (!codOrCoc.entropyCoderWithCustomPrecincts) { result.PPx = 15; result.PPy = 15; @@ -490,8 +489,8 @@ var JpxImage = (function JpxImageClosure() { } function buildPrecincts(context, resolution, dimensions) { // Section B.6 Division resolution to precincts - var precinctWidth = 1 << dimensions.PPx; - var precinctHeight = 1 << dimensions.PPy; + const precinctWidth = 1 << dimensions.PPx; + const precinctHeight = 1 << dimensions.PPy; // Jasper introduces codeblock groups for mapping each subband codeblocks // to precincts. Precinct partition divides a resolution according to width // and height parameters. The subband that belongs to the resolution level @@ -506,20 +505,21 @@ var JpxImage = (function JpxImageClosure() { // level. This is accomplished by using the coordinate transformation // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the // coordinates of a point in the LL band and child subband, respectively. - var isZeroRes = resolution.resLevel === 0; - var precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1)); - var precinctHeightInSubband = 1 << (dimensions.PPy + (isZeroRes ? 0 : -1)); - var numprecinctswide = + const isZeroRes = resolution.resLevel === 0; + const precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1)); + const precinctHeightInSubband = + 1 << (dimensions.PPy + (isZeroRes ? 0 : -1)); + const numprecinctswide = resolution.trx1 > resolution.trx0 ? Math.ceil(resolution.trx1 / precinctWidth) - Math.floor(resolution.trx0 / precinctWidth) : 0; - var numprecinctshigh = + const numprecinctshigh = resolution.try1 > resolution.try0 ? Math.ceil(resolution.try1 / precinctHeight) - Math.floor(resolution.try0 / precinctHeight) : 0; - var numprecincts = numprecinctswide * numprecinctshigh; + const numprecincts = numprecinctswide * numprecinctshigh; resolution.precinctParameters = { precinctWidth, @@ -533,18 +533,18 @@ var JpxImage = (function JpxImageClosure() { } function buildCodeblocks(context, subband, dimensions) { // Section B.7 Division sub-band into code-blocks - var xcb_ = dimensions.xcb_; - var ycb_ = dimensions.ycb_; - var codeblockWidth = 1 << xcb_; - var codeblockHeight = 1 << ycb_; - var cbx0 = subband.tbx0 >> xcb_; - var cby0 = subband.tby0 >> ycb_; - var cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_; - var cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_; - var precinctParameters = subband.resolution.precinctParameters; - var codeblocks = []; - var precincts = []; - var i, j, codeblock, precinctNumber; + const xcb_ = dimensions.xcb_; + const ycb_ = dimensions.ycb_; + const codeblockWidth = 1 << xcb_; + const codeblockHeight = 1 << ycb_; + const cbx0 = subband.tbx0 >> xcb_; + const cby0 = subband.tby0 >> ycb_; + const cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_; + const cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_; + const precinctParameters = subband.resolution.precinctParameters; + const codeblocks = []; + const precincts = []; + let i, j, codeblock, precinctNumber; for (j = cby0; j < cby1; j++) { for (i = cbx0; i < cbx1; i++) { codeblock = { @@ -564,11 +564,11 @@ var JpxImage = (function JpxImageClosure() { // Calculate precinct number for this codeblock, codeblock position // should be relative to its subband, use actual dimension and position // See comment about codeblock group width and height - var pi = Math.floor( + const pi = Math.floor( (codeblock.tbx0_ - subband.tbx0) / precinctParameters.precinctWidthInSubband ); - var pj = Math.floor( + const pj = Math.floor( (codeblock.tby0_ - subband.tby0) / precinctParameters.precinctHeightInSubband ); @@ -586,7 +586,7 @@ var JpxImage = (function JpxImageClosure() { } codeblocks.push(codeblock); // building precinct for the sub-band - var precinct = precincts[precinctNumber]; + let precinct = precincts[precinctNumber]; if (precinct !== undefined) { if (i < precinct.cbxMin) { precinct.cbxMin = i; @@ -619,15 +619,15 @@ var JpxImage = (function JpxImageClosure() { subband.precincts = precincts; } function createPacket(resolution, precinctNumber, layerNumber) { - var precinctCodeblocks = []; + const precinctCodeblocks = []; // Section B.10.8 Order of info in packet - var subbands = resolution.subbands; + const subbands = resolution.subbands; // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence - for (var i = 0, ii = subbands.length; i < ii; i++) { - var subband = subbands[i]; - var codeblocks = subband.codeblocks; - for (var j = 0, jj = codeblocks.length; j < jj; j++) { - var codeblock = codeblocks[j]; + for (let i = 0, ii = subbands.length; i < ii; i++) { + const subband = subbands[i]; + const codeblocks = subband.codeblocks; + for (let j = 0, jj = codeblocks.length; j < jj; j++) { + const codeblock = codeblocks[j]; if (codeblock.precinctNumber !== precinctNumber) { continue; } @@ -640,20 +640,20 @@ var JpxImage = (function JpxImageClosure() { }; } function LayerResolutionComponentPositionIterator(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var layersCount = tile.codingStyleDefaultParameters.layersCount; - var componentsCount = siz.Csiz; - var maxDecompositionLevelsCount = 0; - for (var q = 0; q < componentsCount; q++) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let maxDecompositionLevelsCount = 0; + for (let q = 0; q < componentsCount; q++) { maxDecompositionLevelsCount = Math.max( maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount ); } - var l = 0, + let l = 0, r = 0, i = 0, k = 0; @@ -663,15 +663,15 @@ var JpxImage = (function JpxImageClosure() { for (; l < layersCount; l++) { for (; r <= maxDecompositionLevelsCount; r++) { for (; i < componentsCount; i++) { - var component = tile.components[i]; + const component = tile.components[i]; if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } - var resolution = component.resolutions[r]; - var numprecincts = resolution.precinctParameters.numprecincts; + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; for (; k < numprecincts; ) { - var packet = createPacket(resolution, k, l); + const packet = createPacket(resolution, k, l); k++; return packet; } @@ -685,20 +685,20 @@ var JpxImage = (function JpxImageClosure() { }; } function ResolutionLayerComponentPositionIterator(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var layersCount = tile.codingStyleDefaultParameters.layersCount; - var componentsCount = siz.Csiz; - var maxDecompositionLevelsCount = 0; - for (var q = 0; q < componentsCount; q++) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let maxDecompositionLevelsCount = 0; + for (let q = 0; q < componentsCount; q++) { maxDecompositionLevelsCount = Math.max( maxDecompositionLevelsCount, tile.components[q].codingStyleParameters.decompositionLevelsCount ); } - var r = 0, + let r = 0, l = 0, i = 0, k = 0; @@ -708,15 +708,15 @@ var JpxImage = (function JpxImageClosure() { for (; r <= maxDecompositionLevelsCount; r++) { for (; l < layersCount; l++) { for (; i < componentsCount; i++) { - var component = tile.components[i]; + const component = tile.components[i]; if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } - var resolution = component.resolutions[r]; - var numprecincts = resolution.precinctParameters.numprecincts; + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; for (; k < numprecincts; ) { - var packet = createPacket(resolution, k, l); + const packet = createPacket(resolution, k, l); k++; return packet; } @@ -730,13 +730,13 @@ var JpxImage = (function JpxImageClosure() { }; } function ResolutionPositionComponentLayerIterator(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var layersCount = tile.codingStyleDefaultParameters.layersCount; - var componentsCount = siz.Csiz; - var l, r, c, p; - var maxDecompositionLevelsCount = 0; + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let l, r, c, p; + let maxDecompositionLevelsCount = 0; for (c = 0; c < componentsCount; c++) { const component = tile.components[c]; maxDecompositionLevelsCount = Math.max( @@ -744,13 +744,13 @@ var JpxImage = (function JpxImageClosure() { component.codingStyleParameters.decompositionLevelsCount ); } - var maxNumPrecinctsInLevel = new Int32Array( + const maxNumPrecinctsInLevel = new Int32Array( maxDecompositionLevelsCount + 1 ); for (r = 0; r <= maxDecompositionLevelsCount; ++r) { - var maxNumPrecincts = 0; + let maxNumPrecincts = 0; for (c = 0; c < componentsCount; ++c) { - var resolutions = tile.components[c].resolutions; + const resolutions = tile.components[c].resolutions; if (r < resolutions.length) { maxNumPrecincts = Math.max( maxNumPrecincts, @@ -774,13 +774,13 @@ var JpxImage = (function JpxImageClosure() { if (r > component.codingStyleParameters.decompositionLevelsCount) { continue; } - var resolution = component.resolutions[r]; - var numprecincts = resolution.precinctParameters.numprecincts; + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; if (p >= numprecincts) { continue; } for (; l < layersCount; ) { - var packet = createPacket(resolution, p, l); + const packet = createPacket(resolution, p, l); l++; return packet; } @@ -794,14 +794,14 @@ var JpxImage = (function JpxImageClosure() { }; } function PositionComponentResolutionLayerIterator(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var layersCount = tile.codingStyleDefaultParameters.layersCount; - var componentsCount = siz.Csiz; - var precinctsSizes = getPrecinctSizesInImageScale(tile); - var precinctsIterationSizes = precinctsSizes; - var l = 0, + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + const precinctsSizes = getPrecinctSizesInImageScale(tile); + const precinctsIterationSizes = precinctsSizes; + let l = 0, r = 0, c = 0, px = 0, @@ -812,14 +812,14 @@ var JpxImage = (function JpxImageClosure() { for (; py < precinctsIterationSizes.maxNumHigh; py++) { for (; px < precinctsIterationSizes.maxNumWide; px++) { for (; c < componentsCount; c++) { - var component = tile.components[c]; - var decompositionLevelsCount = + const component = tile.components[c]; + const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; r <= decompositionLevelsCount; r++) { - var resolution = component.resolutions[r]; - var sizeInImageScale = + const resolution = component.resolutions[r]; + const sizeInImageScale = precinctsSizes.components[c].resolutions[r]; - var k = getPrecinctIndexIfExist( + const k = getPrecinctIndexIfExist( px, py, sizeInImageScale, @@ -830,7 +830,7 @@ var JpxImage = (function JpxImageClosure() { continue; } for (; l < layersCount; ) { - var packet = createPacket(resolution, k, l); + const packet = createPacket(resolution, k, l); l++; return packet; } @@ -846,13 +846,13 @@ var JpxImage = (function JpxImageClosure() { }; } function ComponentPositionResolutionLayerIterator(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var layersCount = tile.codingStyleDefaultParameters.layersCount; - var componentsCount = siz.Csiz; - var precinctsSizes = getPrecinctSizesInImageScale(tile); - var l = 0, + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + const precinctsSizes = getPrecinctSizesInImageScale(tile); + let l = 0, r = 0, c = 0, px = 0, @@ -861,16 +861,16 @@ var JpxImage = (function JpxImageClosure() { this.nextPacket = function JpxImage_nextPacket() { // Section B.12.1.5 Component-position-resolution-layer for (; c < componentsCount; ++c) { - var component = tile.components[c]; - var precinctsIterationSizes = precinctsSizes.components[c]; - var decompositionLevelsCount = + const component = tile.components[c]; + const precinctsIterationSizes = precinctsSizes.components[c]; + const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; py < precinctsIterationSizes.maxNumHigh; py++) { for (; px < precinctsIterationSizes.maxNumWide; px++) { for (; r <= decompositionLevelsCount; r++) { - var resolution = component.resolutions[r]; - var sizeInImageScale = precinctsIterationSizes.resolutions[r]; - var k = getPrecinctIndexIfExist( + const resolution = component.resolutions[r]; + const sizeInImageScale = precinctsIterationSizes.resolutions[r]; + const k = getPrecinctIndexIfExist( px, py, sizeInImageScale, @@ -881,7 +881,7 @@ var JpxImage = (function JpxImageClosure() { continue; } for (; l < layersCount; ) { - var packet = createPacket(resolution, k, l); + const packet = createPacket(resolution, k, l); l++; return packet; } @@ -903,41 +903,41 @@ var JpxImage = (function JpxImageClosure() { precinctIterationSizes, resolution ) { - var posX = pxIndex * precinctIterationSizes.minWidth; - var posY = pyIndex * precinctIterationSizes.minHeight; + const posX = pxIndex * precinctIterationSizes.minWidth; + const posY = pyIndex * precinctIterationSizes.minHeight; if ( posX % sizeInImageScale.width !== 0 || posY % sizeInImageScale.height !== 0 ) { return null; } - var startPrecinctRowIndex = + const startPrecinctRowIndex = (posY / sizeInImageScale.width) * resolution.precinctParameters.numprecinctswide; return posX / sizeInImageScale.height + startPrecinctRowIndex; } function getPrecinctSizesInImageScale(tile) { - var componentsCount = tile.components.length; - var minWidth = Number.MAX_VALUE; - var minHeight = Number.MAX_VALUE; - var maxNumWide = 0; - var maxNumHigh = 0; - var sizePerComponent = new Array(componentsCount); - for (var c = 0; c < componentsCount; c++) { - var component = tile.components[c]; - var decompositionLevelsCount = + const componentsCount = tile.components.length; + let minWidth = Number.MAX_VALUE; + let minHeight = Number.MAX_VALUE; + let maxNumWide = 0; + let maxNumHigh = 0; + const sizePerComponent = new Array(componentsCount); + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; - var sizePerResolution = new Array(decompositionLevelsCount + 1); - var minWidthCurrentComponent = Number.MAX_VALUE; - var minHeightCurrentComponent = Number.MAX_VALUE; - var maxNumWideCurrentComponent = 0; - var maxNumHighCurrentComponent = 0; - var scale = 1; - for (var r = decompositionLevelsCount; r >= 0; --r) { - var resolution = component.resolutions[r]; - var widthCurrentResolution = + const sizePerResolution = new Array(decompositionLevelsCount + 1); + let minWidthCurrentComponent = Number.MAX_VALUE; + let minHeightCurrentComponent = Number.MAX_VALUE; + let maxNumWideCurrentComponent = 0; + let maxNumHighCurrentComponent = 0; + let scale = 1; + for (let r = decompositionLevelsCount; r >= 0; --r) { + const resolution = component.resolutions[r]; + const widthCurrentResolution = scale * resolution.precinctParameters.precinctWidth; - var heightCurrentResolution = + const heightCurrentResolution = scale * resolution.precinctParameters.precinctHeight; minWidthCurrentComponent = Math.min( minWidthCurrentComponent, @@ -982,22 +982,22 @@ var JpxImage = (function JpxImageClosure() { }; } function buildPackets(context) { - var siz = context.SIZ; - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var componentsCount = siz.Csiz; + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const componentsCount = siz.Csiz; // Creating resolutions and sub-bands for each component - for (var c = 0; c < componentsCount; c++) { - var component = tile.components[c]; - var decompositionLevelsCount = + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; // Section B.5 Resolution levels and sub-bands - var resolutions = []; - var subbands = []; - for (var r = 0; r <= decompositionLevelsCount; r++) { - var blocksDimensions = getBlocksDimensions(context, component, r); - var resolution = {}; - var scale = 1 << (decompositionLevelsCount - r); + const resolutions = []; + const subbands = []; + for (let r = 0; r <= decompositionLevelsCount; r++) { + const blocksDimensions = getBlocksDimensions(context, component, r); + const resolution = {}; + const scale = 1 << (decompositionLevelsCount - r); resolution.trx0 = Math.ceil(component.tcx0 / scale); resolution.try0 = Math.ceil(component.tcy0 / scale); resolution.trx1 = Math.ceil(component.tcx1 / scale); @@ -1020,8 +1020,8 @@ var JpxImage = (function JpxImageClosure() { subbands.push(subband); resolution.subbands = [subband]; } else { - var bscale = 1 << (decompositionLevelsCount - r + 1); - var resolutionSubbands = []; + const bscale = 1 << (decompositionLevelsCount - r + 1); + const resolutionSubbands = []; // three sub-bands (HL, LH and HH) with rest of decompositions subband = {}; subband.type = "HL"; @@ -1063,7 +1063,7 @@ var JpxImage = (function JpxImageClosure() { component.subbands = subbands; } // Generate the packets sequence - var progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; + const progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; switch (progressionOrder) { case 0: tile.packetsIterator = new LayerResolutionComponentPositionIterator( @@ -1095,13 +1095,13 @@ var JpxImage = (function JpxImageClosure() { } } function parseTilePackets(context, data, offset, dataLength) { - var position = 0; - var buffer, + let position = 0; + let buffer, bufferSize = 0, skipNextBit = false; function readBits(count) { while (bufferSize < count) { - var b = data[offset + position]; + const b = data[offset + position]; position++; if (skipNextBit) { buffer = (buffer << 7) | b; @@ -1151,7 +1151,7 @@ var JpxImage = (function JpxImageClosure() { if (readBits(1) === 0) { return 2; } - var value = readBits(2); + let value = readBits(2); if (value < 3) { return value + 3; } @@ -1162,31 +1162,31 @@ var JpxImage = (function JpxImageClosure() { value = readBits(7); return value + 37; } - var tileIndex = context.currentTile.index; - var tile = context.tiles[tileIndex]; - var sopMarkerUsed = context.COD.sopMarkerUsed; - var ephMarkerUsed = context.COD.ephMarkerUsed; - var packetsIterator = tile.packetsIterator; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const sopMarkerUsed = context.COD.sopMarkerUsed; + const ephMarkerUsed = context.COD.ephMarkerUsed; + const packetsIterator = tile.packetsIterator; while (position < dataLength) { alignToByte(); if (sopMarkerUsed && skipMarkerIfEqual(0x91)) { // Skip also marker segment length and packet sequence ID skipBytes(4); } - var packet = packetsIterator.nextPacket(); + const packet = packetsIterator.nextPacket(); if (!readBits(1)) { continue; } - var layerNumber = packet.layerNumber; + const layerNumber = packet.layerNumber; var queue = [], codeblock; - for (var i = 0, ii = packet.codeblocks.length; i < ii; i++) { + for (let i = 0, ii = packet.codeblocks.length; i < ii; i++) { codeblock = packet.codeblocks[i]; - var precinct = codeblock.precinct; - var codeblockColumn = codeblock.cbx - precinct.cbxMin; - var codeblockRow = codeblock.cby - precinct.cbyMin; - var codeblockIncluded = false; - var firstTimeInclusion = false; + let precinct = codeblock.precinct; + const codeblockColumn = codeblock.cbx - precinct.cbxMin; + const codeblockRow = codeblock.cby - precinct.cbyMin; + let codeblockIncluded = false; + let firstTimeInclusion = false; var valueReady; if (codeblock.included !== undefined) { codeblockIncluded = !!readBits(1); @@ -1198,8 +1198,8 @@ var JpxImage = (function JpxImageClosure() { inclusionTree = precinct.inclusionTree; } else { // building inclusion and zero bit-planes trees - var width = precinct.cbxMax - precinct.cbxMin + 1; - var height = precinct.cbyMax - precinct.cbyMin + 1; + const width = precinct.cbxMax - precinct.cbxMin + 1; + const height = precinct.cbyMax - precinct.cbyMin + 1; inclusionTree = new InclusionTree(width, height, layerNumber); zeroBitPlanesTree = new TagTree(width, height); precinct.inclusionTree = inclusionTree; @@ -1240,17 +1240,17 @@ var JpxImage = (function JpxImageClosure() { } codeblock.zeroBitPlanes = zeroBitPlanesTree.value; } - var codingpasses = readCodingpasses(); + const codingpasses = readCodingpasses(); while (readBits(1)) { codeblock.Lblock++; } - var codingpassesLog2 = log2(codingpasses); + const codingpassesLog2 = log2(codingpasses); // rounding down log2 - var bits = + const bits = (codingpasses < 1 << codingpassesLog2 ? codingpassesLog2 - 1 : codingpassesLog2) + codeblock.Lblock; - var codedDataLength = readBits(bits); + const codedDataLength = readBits(bits); queue.push({ codeblock, codingpasses, @@ -1262,7 +1262,7 @@ var JpxImage = (function JpxImageClosure() { skipMarkerIfEqual(0x92); } while (queue.length > 0) { - var packetItem = queue.shift(); + const packetItem = queue.shift(); codeblock = packetItem.codeblock; if (codeblock.data === undefined) { codeblock.data = []; @@ -1288,17 +1288,17 @@ var JpxImage = (function JpxImageClosure() { reversible, segmentationSymbolUsed ) { - var x0 = subband.tbx0; - var y0 = subband.tby0; - var width = subband.tbx1 - subband.tbx0; - var codeblocks = subband.codeblocks; - var right = subband.type.charAt(0) === "H" ? 1 : 0; - var bottom = subband.type.charAt(1) === "H" ? levelWidth : 0; + const x0 = subband.tbx0; + const y0 = subband.tby0; + const width = subband.tbx1 - subband.tbx0; + const codeblocks = subband.codeblocks; + const right = subband.type.charAt(0) === "H" ? 1 : 0; + const bottom = subband.type.charAt(1) === "H" ? levelWidth : 0; - for (var i = 0, ii = codeblocks.length; i < ii; ++i) { - var codeblock = codeblocks[i]; - var blockWidth = codeblock.tbx1_ - codeblock.tbx0_; - var blockHeight = codeblock.tby1_ - codeblock.tby0_; + for (let i = 0, ii = codeblocks.length; i < ii; ++i) { + const codeblock = codeblocks[i]; + const blockWidth = codeblock.tbx1_ - codeblock.tbx0_; + const blockHeight = codeblock.tby1_ - codeblock.tby0_; if (blockWidth === 0 || blockHeight === 0) { continue; } @@ -1317,7 +1317,7 @@ var JpxImage = (function JpxImageClosure() { currentCodingpassType = 2; // first bit plane starts from cleanup // collect data - var data = codeblock.data, + let data = codeblock.data, totalLength = 0, codingpasses = 0; var j, jj, dataItem; @@ -1326,16 +1326,16 @@ var JpxImage = (function JpxImageClosure() { totalLength += dataItem.end - dataItem.start; codingpasses += dataItem.codingpasses; } - var encodedData = new Uint8Array(totalLength); - var position = 0; + const encodedData = new Uint8Array(totalLength); + let position = 0; for (j = 0, jj = data.length; j < jj; j++) { dataItem = data[j]; - var chunk = dataItem.data.subarray(dataItem.start, dataItem.end); + const chunk = dataItem.data.subarray(dataItem.start, dataItem.end); encodedData.set(chunk, position); position += chunk.length; } // decoding the item - var decoder = new ArithmeticDecoder(encodedData, 0, totalLength); + const decoder = new ArithmeticDecoder(encodedData, 0, totalLength); bitModel.setDecoder(decoder); for (j = 0; j < codingpasses; j++) { @@ -1356,19 +1356,19 @@ var JpxImage = (function JpxImageClosure() { currentCodingpassType = (currentCodingpassType + 1) % 3; } - var offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width; - var sign = bitModel.coefficentsSign; - var magnitude = bitModel.coefficentsMagnitude; - var bitsDecoded = bitModel.bitsDecoded; - var magnitudeCorrection = reversible ? 0 : 0.5; + let offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width; + const sign = bitModel.coefficentsSign; + const magnitude = bitModel.coefficentsMagnitude; + const bitsDecoded = bitModel.bitsDecoded; + const magnitudeCorrection = reversible ? 0 : 0.5; var k, n, nb; position = 0; // Do the interleaving of Section F.3.3 here, so we do not need // to copy later. LL level is not interleaved, just copied. - var interleave = subband.type !== "LL"; + const interleave = subband.type !== "LL"; for (j = 0; j < blockHeight; j++) { - var row = (offset / width) | 0; // row in the non-interleaved subband - var levelOffset = 2 * row * (levelWidth - width) + right + bottom; + const row = (offset / width) | 0; // row in the non-interleaved subband + const levelOffset = 2 * row * (levelWidth - width) + right + bottom; for (k = 0; k < blockWidth; k++) { n = magnitude[position]; if (n !== 0) { @@ -1377,7 +1377,7 @@ var JpxImage = (function JpxImageClosure() { n = -n; } nb = bitsDecoded[position]; - var pos = interleave ? levelOffset + (offset << 1) : offset; + const pos = interleave ? levelOffset + (offset << 1) : offset; if (reversible && nb >= mb) { coefficients[pos] = n; } else { @@ -1392,33 +1392,33 @@ var JpxImage = (function JpxImageClosure() { } } function transformTile(context, tile, c) { - var component = tile.components[c]; - var codingStyleParameters = component.codingStyleParameters; - var quantizationParameters = component.quantizationParameters; - var decompositionLevelsCount = + const component = tile.components[c]; + const codingStyleParameters = component.codingStyleParameters; + const quantizationParameters = component.quantizationParameters; + const decompositionLevelsCount = codingStyleParameters.decompositionLevelsCount; - var spqcds = quantizationParameters.SPqcds; - var scalarExpounded = quantizationParameters.scalarExpounded; - var guardBits = quantizationParameters.guardBits; - var segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; - var precision = context.components[c].precision; + const spqcds = quantizationParameters.SPqcds; + const scalarExpounded = quantizationParameters.scalarExpounded; + const guardBits = quantizationParameters.guardBits; + const segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; + const precision = context.components[c].precision; - var reversible = codingStyleParameters.reversibleTransformation; - var transform = reversible + const reversible = codingStyleParameters.reversibleTransformation; + const transform = reversible ? new ReversibleTransform() : new IrreversibleTransform(); - var subbandCoefficients = []; - var b = 0; - for (var i = 0; i <= decompositionLevelsCount; i++) { - var resolution = component.resolutions[i]; + const subbandCoefficients = []; + let b = 0; + for (let i = 0; i <= decompositionLevelsCount; i++) { + const resolution = component.resolutions[i]; - var width = resolution.trx1 - resolution.trx0; - var height = resolution.try1 - resolution.try0; + const width = resolution.trx1 - resolution.trx0; + const height = resolution.try1 - resolution.try0; // Allocate space for the whole sublevel. - var coefficients = new Float32Array(width * height); + const coefficients = new Float32Array(width * height); - for (var j = 0, jj = resolution.subbands.length; j < jj; j++) { + for (let j = 0, jj = resolution.subbands.length; j < jj; j++) { var mu, epsilon; if (!scalarExpounded) { // formula E-5 @@ -1430,14 +1430,14 @@ var JpxImage = (function JpxImageClosure() { b++; } - var subband = resolution.subbands[j]; - var gainLog2 = SubbandsGainLog2[subband.type]; + const subband = resolution.subbands[j]; + const gainLog2 = SubbandsGainLog2[subband.type]; // calculate quantization coefficient (Section E.1.1.1) - var delta = reversible + const delta = reversible ? 1 : 2 ** (precision + gainLog2 - epsilon) * (1 + mu / 2048); - var mb = guardBits + epsilon - 1; + const mb = guardBits + epsilon - 1; // In the first resolution level, copyCoefficients will fill the // whole array with coefficients. In the succeeding passes, @@ -1462,7 +1462,7 @@ var JpxImage = (function JpxImageClosure() { }); } - var result = transform.calculate( + const result = transform.calculate( subbandCoefficients, component.tcx0, component.tcy0 @@ -1476,20 +1476,20 @@ var JpxImage = (function JpxImageClosure() { }; } function transformComponents(context) { - var siz = context.SIZ; - var components = context.components; - var componentsCount = siz.Csiz; - var resultImages = []; - for (var i = 0, ii = context.tiles.length; i < ii; i++) { - var tile = context.tiles[i]; - var transformedTiles = []; + const siz = context.SIZ; + const components = context.components; + const componentsCount = siz.Csiz; + const resultImages = []; + for (let i = 0, ii = context.tiles.length; i < ii; i++) { + const tile = context.tiles[i]; + const transformedTiles = []; var c; for (c = 0; c < componentsCount; c++) { transformedTiles[c] = transformTile(context, tile, c); } - var tile0 = transformedTiles[0]; - var out = new Uint8ClampedArray(tile0.items.length * componentsCount); - var result = { + const tile0 = transformedTiles[0]; + const out = new Uint8ClampedArray(tile0.items.length * componentsCount); + const result = { left: tile0.left, top: tile0.top, width: tile0.width, @@ -1506,11 +1506,11 @@ var JpxImage = (function JpxImageClosure() { y1, y2; if (tile.codingStyleDefaultParameters.multipleComponentTransform) { - var fourComponents = componentsCount === 4; - var y0items = transformedTiles[0].items; - var y1items = transformedTiles[1].items; - var y2items = transformedTiles[2].items; - var y3items = fourComponents ? transformedTiles[3].items : null; + const fourComponents = componentsCount === 4; + const y0items = transformedTiles[0].items; + const y1items = transformedTiles[1].items; + const y2items = transformedTiles[2].items; + const y3items = fourComponents ? transformedTiles[3].items : null; // HACK: The multiple component transform formulas below assume that // all components have the same precision. With this in mind, we @@ -1518,8 +1518,8 @@ var JpxImage = (function JpxImageClosure() { shift = components[0].precision - 8; offset = (128 << shift) + 0.5; - var component0 = tile.components[0]; - var alpha01 = componentsCount - 3; + const component0 = tile.components[0]; + const alpha01 = componentsCount - 3; jj = y0items.length; if (!component0.codingStyleParameters.reversibleTransformation) { // inverse irreversible multiple component transform @@ -1552,7 +1552,7 @@ var JpxImage = (function JpxImageClosure() { } else { // no multi-component transform for (c = 0; c < componentsCount; c++) { - var items = transformedTiles[c].items; + const items = transformedTiles[c].items; shift = components[c].precision - 8; offset = (128 << shift) + 0.5; for (pos = c, j = 0, jj = items.length; j < jj; j++) { @@ -1566,17 +1566,17 @@ var JpxImage = (function JpxImageClosure() { return resultImages; } function initializeTile(context, tileIndex) { - var siz = context.SIZ; - var componentsCount = siz.Csiz; - var tile = context.tiles[tileIndex]; - for (var c = 0; c < componentsCount; c++) { - var component = tile.components[c]; - var qcdOrQcc = + const siz = context.SIZ; + const componentsCount = siz.Csiz; + const tile = context.tiles[tileIndex]; + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const qcdOrQcc = context.currentTile.QCC[c] !== undefined ? context.currentTile.QCC[c] : context.currentTile.QCD; component.quantizationParameters = qcdOrQcc; - var codOrCoc = + const codOrCoc = context.currentTile.COC[c] !== undefined ? context.currentTile.COC[c] : context.currentTile.COD; @@ -1589,10 +1589,10 @@ var JpxImage = (function JpxImageClosure() { var TagTree = (function TagTreeClosure() { // eslint-disable-next-line no-shadow function TagTree(width, height) { - var levelsLength = log2(Math.max(width, height)) + 1; + const levelsLength = log2(Math.max(width, height)) + 1; this.levels = []; - for (var i = 0; i < levelsLength; i++) { - var level = { + for (let i = 0; i < levelsLength; i++) { + const level = { width, height, items: [], @@ -1604,12 +1604,12 @@ var JpxImage = (function JpxImageClosure() { } TagTree.prototype = { reset: function TagTree_reset(i, j) { - var currentLevel = 0, + let currentLevel = 0, value = 0, level; while (currentLevel < this.levels.length) { level = this.levels[currentLevel]; - var index = i + j * level.width; + const index = i + j * level.width; if (level.items[index] !== undefined) { value = level.items[index]; break; @@ -1626,13 +1626,13 @@ var JpxImage = (function JpxImageClosure() { delete this.value; }, incrementValue: function TagTree_incrementValue() { - var level = this.levels[this.currentLevel]; + const level = this.levels[this.currentLevel]; level.items[level.index]++; }, nextLevel: function TagTree_nextLevel() { - var currentLevel = this.currentLevel; - var level = this.levels[currentLevel]; - var value = level.items[level.index]; + let currentLevel = this.currentLevel; + let level = this.levels[currentLevel]; + const value = level.items[level.index]; currentLevel--; if (currentLevel < 0) { this.value = value; @@ -1651,15 +1651,15 @@ var JpxImage = (function JpxImageClosure() { var InclusionTree = (function InclusionTreeClosure() { // eslint-disable-next-line no-shadow function InclusionTree(width, height, defaultValue) { - var levelsLength = log2(Math.max(width, height)) + 1; + const levelsLength = log2(Math.max(width, height)) + 1; this.levels = []; - for (var i = 0; i < levelsLength; i++) { - var items = new Uint8Array(width * height); - for (var j = 0, jj = items.length; j < jj; j++) { + for (let i = 0; i < levelsLength; i++) { + const items = new Uint8Array(width * height); + for (let j = 0, jj = items.length; j < jj; j++) { items[j] = defaultValue; } - var level = { + const level = { width, height, items, @@ -1672,12 +1672,12 @@ var JpxImage = (function JpxImageClosure() { } InclusionTree.prototype = { reset: function InclusionTree_reset(i, j, stopValue) { - var currentLevel = 0; + let currentLevel = 0; while (currentLevel < this.levels.length) { - var level = this.levels[currentLevel]; - var index = i + j * level.width; + const level = this.levels[currentLevel]; + const index = i + j * level.width; level.index = index; - var value = level.items[index]; + const value = level.items[index]; if (value === 0xff) { break; @@ -1698,23 +1698,23 @@ var JpxImage = (function JpxImageClosure() { return true; }, incrementValue: function InclusionTree_incrementValue(stopValue) { - var level = this.levels[this.currentLevel]; + const level = this.levels[this.currentLevel]; level.items[level.index] = stopValue + 1; this.propagateValues(); }, propagateValues: function InclusionTree_propagateValues() { - var levelIndex = this.currentLevel; - var level = this.levels[levelIndex]; - var currentValue = level.items[level.index]; + let levelIndex = this.currentLevel; + let level = this.levels[levelIndex]; + const currentValue = level.items[level.index]; while (--levelIndex >= 0) { level = this.levels[levelIndex]; level.items[level.index] = currentValue; } }, nextLevel: function InclusionTree_nextLevel() { - var currentLevel = this.currentLevel; - var level = this.levels[currentLevel]; - var value = level.items[level.index]; + let currentLevel = this.currentLevel; + let level = this.levels[currentLevel]; + const value = level.items[level.index]; level.items[level.index] = 0xff; currentLevel--; if (currentLevel < 0) { @@ -1732,25 +1732,25 @@ var JpxImage = (function JpxImageClosure() { // Section D. Coefficient bit modeling var BitModel = (function BitModelClosure() { - var UNIFORM_CONTEXT = 17; - var RUNLENGTH_CONTEXT = 18; + const UNIFORM_CONTEXT = 17; + const RUNLENGTH_CONTEXT = 18; // Table D-1 // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) // prettier-ignore - var LLAndLHContextsLabel = new Uint8Array([ + const LLAndLHContextsLabel = new Uint8Array([ 0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8 ]); // prettier-ignore - var HLContextLabel = new Uint8Array([ + const HLContextLabel = new Uint8Array([ 0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8 ]); // prettier-ignore - var HHContextLabel = new Uint8Array([ + const HHContextLabel = new Uint8Array([ 0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8 @@ -1771,7 +1771,7 @@ var JpxImage = (function JpxImageClosure() { } this.contextLabelTable = contextLabelTable; - var coefficientCount = width * height; + const coefficientCount = width * height; // coefficients outside the encoding region treated as insignificant // add border state cells for significanceState @@ -1788,9 +1788,9 @@ var JpxImage = (function JpxImageClosure() { this.coefficentsMagnitude = coefficentsMagnitude; this.processingFlags = new Uint8Array(coefficientCount); - var bitsDecoded = new Uint8Array(coefficientCount); + const bitsDecoded = new Uint8Array(coefficientCount); if (zeroBitPlanes !== 0) { - for (var i = 0; i < coefficientCount; i++) { + for (let i = 0; i < coefficientCount; i++) { bitsDecoded[i] = zeroBitPlanes; } } @@ -1819,12 +1819,12 @@ var JpxImage = (function JpxImageClosure() { column, index ) { - var neighborsSignificance = this.neighborsSignificance; - var width = this.width, + const neighborsSignificance = this.neighborsSignificance; + const width = this.width, height = this.height; - var left = column > 0; - var right = column + 1 < width; - var i; + const left = column > 0; + const right = column + 1 < width; + let i; if (row > 0) { i = index - width; @@ -1857,25 +1857,25 @@ var JpxImage = (function JpxImageClosure() { neighborsSignificance[index] |= 0x80; }, runSignificancePropagationPass: function BitModel_runSignificancePropagationPass() { - var decoder = this.decoder; - var width = this.width, + const decoder = this.decoder; + const width = this.width, height = this.height; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var neighborsSignificance = this.neighborsSignificance; - var processingFlags = this.processingFlags; - var contexts = this.contexts; - var labels = this.contextLabelTable; - var bitsDecoded = this.bitsDecoded; - var processedInverseMask = ~1; - var processedMask = 1; - var firstMagnitudeBitMask = 2; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + const neighborsSignificance = this.neighborsSignificance; + const processingFlags = this.processingFlags; + const contexts = this.contexts; + const labels = this.contextLabelTable; + const bitsDecoded = this.bitsDecoded; + const processedInverseMask = ~1; + const processedMask = 1; + const firstMagnitudeBitMask = 2; - for (var i0 = 0; i0 < height; i0 += 4) { - for (var j = 0; j < width; j++) { - var index = i0 * width + j; - for (var i1 = 0; i1 < 4; i1++, index += width) { - var i = i0 + i1; + for (let i0 = 0; i0 < height; i0 += 4) { + for (let j = 0; j < width; j++) { + let index = i0 * width + j; + for (let i1 = 0; i1 < 4; i1++, index += width) { + const i = i0 + i1; if (i >= height) { break; } @@ -1889,10 +1889,10 @@ var JpxImage = (function JpxImageClosure() { continue; } - var contextLabel = labels[neighborsSignificance[index]]; - var decision = decoder.readBit(contexts, contextLabel); + const contextLabel = labels[neighborsSignificance[index]]; + const decision = decoder.readBit(contexts, contextLabel); if (decision) { - var sign = this.decodeSignBit(i, j, index); + const sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; this.setNeighborsSignificance(i, j, index); @@ -1905,12 +1905,12 @@ var JpxImage = (function JpxImageClosure() { } }, decodeSignBit: function BitModel_decodeSignBit(row, column, index) { - var width = this.width, + const width = this.width, height = this.height; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var contribution, sign0, sign1, significance1; - var contextLabel, decoded; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + let contribution, sign0, sign1, significance1; + let contextLabel, decoded; // calculate horizontal contribution significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0; @@ -1928,7 +1928,7 @@ var JpxImage = (function JpxImageClosure() { } else { contribution = 0; } - var horizontalContribution = 3 * contribution; + const horizontalContribution = 3 * contribution; // calculate vertical contribution and combine with the horizontal significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0; @@ -1957,23 +1957,23 @@ var JpxImage = (function JpxImageClosure() { return decoded; }, runMagnitudeRefinementPass: function BitModel_runMagnitudeRefinementPass() { - var decoder = this.decoder; - var width = this.width, + const decoder = this.decoder; + const width = this.width, height = this.height; - var coefficentsMagnitude = this.coefficentsMagnitude; - var neighborsSignificance = this.neighborsSignificance; - var contexts = this.contexts; - var bitsDecoded = this.bitsDecoded; - var processingFlags = this.processingFlags; - var processedMask = 1; - var firstMagnitudeBitMask = 2; - var length = width * height; - var width4 = width * 4; + const coefficentsMagnitude = this.coefficentsMagnitude; + const neighborsSignificance = this.neighborsSignificance; + const contexts = this.contexts; + const bitsDecoded = this.bitsDecoded; + const processingFlags = this.processingFlags; + const processedMask = 1; + const firstMagnitudeBitMask = 2; + const length = width * height; + const width4 = width * 4; for (var index0 = 0, indexNext; index0 < length; index0 = indexNext) { indexNext = Math.min(length, index0 + width4); - for (var j = 0; j < width; j++) { - for (var index = index0 + j; index < indexNext; index += width) { + for (let j = 0; j < width; j++) { + for (let index = index0 + j; index < indexNext; index += width) { // significant but not those that have just become if ( !coefficentsMagnitude[index] || @@ -1982,15 +1982,15 @@ var JpxImage = (function JpxImageClosure() { continue; } - var contextLabel = 16; + let contextLabel = 16; if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { processingFlags[index] ^= firstMagnitudeBitMask; // first refinement - var significance = neighborsSignificance[index] & 127; + const significance = neighborsSignificance[index] & 127; contextLabel = significance === 0 ? 15 : 14; } - var bit = decoder.readBit(contexts, contextLabel); + const bit = decoder.readBit(contexts, contextLabel); coefficentsMagnitude[index] = (coefficentsMagnitude[index] << 1) | bit; bitsDecoded[index]++; @@ -2000,31 +2000,31 @@ var JpxImage = (function JpxImageClosure() { } }, runCleanupPass: function BitModel_runCleanupPass() { - var decoder = this.decoder; - var width = this.width, + const decoder = this.decoder; + const width = this.width, height = this.height; - var neighborsSignificance = this.neighborsSignificance; - var coefficentsMagnitude = this.coefficentsMagnitude; - var coefficentsSign = this.coefficentsSign; - var contexts = this.contexts; - var labels = this.contextLabelTable; - var bitsDecoded = this.bitsDecoded; - var processingFlags = this.processingFlags; - var processedMask = 1; - var firstMagnitudeBitMask = 2; - var oneRowDown = width; - var twoRowsDown = width * 2; - var threeRowsDown = width * 3; - var iNext; - for (var i0 = 0; i0 < height; i0 = iNext) { + const neighborsSignificance = this.neighborsSignificance; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + const contexts = this.contexts; + const labels = this.contextLabelTable; + const bitsDecoded = this.bitsDecoded; + const processingFlags = this.processingFlags; + const processedMask = 1; + const firstMagnitudeBitMask = 2; + const oneRowDown = width; + const twoRowsDown = width * 2; + const threeRowsDown = width * 3; + let iNext; + for (let i0 = 0; i0 < height; i0 = iNext) { iNext = Math.min(i0 + 4, height); - var indexBase = i0 * width; - var checkAllEmpty = i0 + 3 < height; - for (var j = 0; j < width; j++) { - var index0 = indexBase + j; + const indexBase = i0 * width; + const checkAllEmpty = i0 + 3 < height; + for (let j = 0; j < width; j++) { + const index0 = indexBase + j; // using the property: labels[neighborsSignificance[index]] === 0 // when neighborsSignificance[index] === 0 - var allEmpty = + const allEmpty = checkAllEmpty && processingFlags[index0] === 0 && processingFlags[index0 + oneRowDown] === 0 && @@ -2034,12 +2034,12 @@ var JpxImage = (function JpxImageClosure() { neighborsSignificance[index0 + oneRowDown] === 0 && neighborsSignificance[index0 + twoRowsDown] === 0 && neighborsSignificance[index0 + threeRowsDown] === 0; - var i1 = 0, + let i1 = 0, index = index0; var i = i0, sign; if (allEmpty) { - var hasSignificantCoefficent = decoder.readBit( + const hasSignificantCoefficent = decoder.readBit( contexts, RUNLENGTH_CONTEXT ); @@ -2065,7 +2065,7 @@ var JpxImage = (function JpxImageClosure() { processingFlags[index] |= firstMagnitudeBitMask; index = index0; - for (var i2 = i0; i2 <= i; i2++, index += width) { + for (let i2 = i0; i2 <= i; i2++, index += width) { bitsDecoded[index]++; } @@ -2079,8 +2079,8 @@ var JpxImage = (function JpxImageClosure() { continue; } - var contextLabel = labels[neighborsSignificance[index]]; - var decision = decoder.readBit(contexts, contextLabel); + const contextLabel = labels[neighborsSignificance[index]]; + const decision = decoder.readBit(contexts, contextLabel); if (decision === 1) { sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; @@ -2094,9 +2094,9 @@ var JpxImage = (function JpxImageClosure() { } }, checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() { - var decoder = this.decoder; - var contexts = this.contexts; - var symbol = + const decoder = this.decoder; + const contexts = this.contexts; + const symbol = (decoder.readBit(contexts, UNIFORM_CONTEXT) << 3) | (decoder.readBit(contexts, UNIFORM_CONTEXT) << 2) | (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | @@ -2111,7 +2111,7 @@ var JpxImage = (function JpxImageClosure() { })(); // Section F, Discrete wavelet transformation - var Transform = (function TransformClosure() { + const Transform = (function TransformClosure() { // eslint-disable-next-line no-shadow function Transform() {} @@ -2120,17 +2120,17 @@ var JpxImage = (function JpxImageClosure() { u0, v0 ) { - var ll = subbands[0]; - for (var i = 1, ii = subbands.length; i < ii; i++) { + let ll = subbands[0]; + for (let i = 1, ii = subbands.length; i < ii; i++) { ll = this.iterate(ll, subbands[i], u0, v0); } return ll; }; Transform.prototype.extend = function extend(buffer, offset, size) { // Section F.3.7 extending... using max extension of 4 - var i1 = offset - 1, + let i1 = offset - 1, j1 = offset + 1; - var i2 = offset + size - 2, + let i2 = offset + size - 2, j2 = offset + size; buffer[i1--] = buffer[j1++]; buffer[j2++] = buffer[i2--]; @@ -2147,13 +2147,13 @@ var JpxImage = (function JpxImageClosure() { u0, v0 ) { - var llWidth = ll.width, + let llWidth = ll.width, llHeight = ll.height, llItems = ll.items; - var width = hl_lh_hh.width; - var height = hl_lh_hh.height; - var items = hl_lh_hh.items; - var i, j, k, l, u, v; + const width = hl_lh_hh.width; + const height = hl_lh_hh.height; + const items = hl_lh_hh.items; + let i, j, k, l, u, v; // Interleave LL according to Section F.3.3 for (k = 0, i = 0; i < llHeight; i++) { @@ -2165,8 +2165,8 @@ var JpxImage = (function JpxImageClosure() { // The LL band is not needed anymore. llItems = ll.items = null; - var bufferPadding = 4; - var rowBuffer = new Float32Array(width + 2 * bufferPadding); + const bufferPadding = 4; + const rowBuffer = new Float32Array(width + 2 * bufferPadding); // Section F.3.4 HOR_SR if (width === 1) { @@ -2196,12 +2196,12 @@ var JpxImage = (function JpxImageClosure() { // have a cache miss every time. To reduce cache misses, get up to // 'numBuffers' items at a time and store them into the individual // buffers. The colBuffers should be small enough to fit into CPU cache. - var numBuffers = 16; - var colBuffers = []; + let numBuffers = 16; + const colBuffers = []; for (i = 0; i < numBuffers; i++) { colBuffers.push(new Float32Array(height + 2 * bufferPadding)); } - var b, + let b, currentBuffer = 0; ll = bufferPadding + height; @@ -2227,7 +2227,7 @@ var JpxImage = (function JpxImageClosure() { } currentBuffer--; - var buffer = colBuffers[currentBuffer]; + const buffer = colBuffers[currentBuffer]; this.extend(buffer, bufferPadding, height); this.filter(buffer, bufferPadding, height); @@ -2265,16 +2265,16 @@ var JpxImage = (function JpxImageClosure() { offset, length ) { - var len = length >> 1; + const len = length >> 1; offset = offset | 0; - var j, n, current, next; + let j, n, current, next; - var alpha = -1.586134342059924; - var beta = -0.052980118572961; - var gamma = 0.882911075530934; - var delta = 0.443506852043971; - var K = 1.230174104914001; - var K_ = 1 / K; + const alpha = -1.586134342059924; + const beta = -0.052980118572961; + const gamma = 0.882911075530934; + const delta = 0.443506852043971; + const K = 1.230174104914001; + const K_ = 1 / K; // step 1 is combined with step 3 @@ -2363,9 +2363,9 @@ var JpxImage = (function JpxImageClosure() { offset, length ) { - var len = length >> 1; + const len = length >> 1; offset = offset | 0; - var j, n; + let j, n; for (j = offset, n = len + 1; n--; j += 2) { x[j] -= (x[j - 1] + x[j + 1] + 2) >> 2; From cb65b762ebe4c8c8628bf34d1fc6fbe0b032ce33 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 12:47:01 +0200 Subject: [PATCH 8/9] Fix the remaining `no-var` failures, which couldn't be handled automatically, in the `src/core/jpx.js` file --- src/core/jpx.js | 94 ++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 48 deletions(-) diff --git a/src/core/jpx.js b/src/core/jpx.js index ade3d8bfb..e62ff0552 100644 --- a/src/core/jpx.js +++ b/src/core/jpx.js @@ -45,8 +45,8 @@ const JpxImage = (function JpxImageClosure() { return; } - let position = 0, - length = data.length; + const length = data.length; + let position = 0; while (position < length) { let headerSize = 8; let lbox = readUint32(data, position); @@ -75,7 +75,7 @@ const JpxImage = (function JpxImageClosure() { break; case 0x636f6c72: // 'colr' // Colorspaces are not used, the CS from the PDF is used. - var method = data[position]; + const method = data[position]; if (method === 1) { // enumerated colorspace const colorspace = readUint32(data, position + 3); @@ -108,7 +108,7 @@ const JpxImage = (function JpxImageClosure() { case 0x69686472: // 'ihdr' break; default: - var headerType = String.fromCharCode( + const headerType = String.fromCharCode( (tbox >> 24) & 0xff, (tbox >> 16) & 0xff, (tbox >> 8) & 0xff, @@ -156,7 +156,7 @@ const JpxImage = (function JpxImageClosure() { const code = readUint16(data, position); position += 2; - var length = 0, + let length = 0, j, sqcd, spqcds, @@ -171,7 +171,7 @@ const JpxImage = (function JpxImageClosure() { break; case 0xff51: // Image and tile size (SIZ) length = readUint16(data, position); - var siz = {}; + const siz = {}; siz.Xsiz = readUint32(data, position + 4); siz.Ysiz = readUint32(data, position + 8); siz.XOsiz = readUint32(data, position + 12); @@ -180,9 +180,9 @@ const JpxImage = (function JpxImageClosure() { siz.YTsiz = readUint32(data, position + 24); siz.XTOsiz = readUint32(data, position + 28); siz.YTOsiz = readUint32(data, position + 32); - var componentsCount = readUint16(data, position + 36); + const componentsCount = readUint16(data, position + 36); siz.Csiz = componentsCount; - var components = []; + const components = []; j = position + 38; for (let i = 0; i < componentsCount; i++) { const component = { @@ -203,7 +203,7 @@ const JpxImage = (function JpxImageClosure() { break; case 0xff5c: // Quantization default (QCD) length = readUint16(data, position); - var qcd = {}; + const qcd = {}; j = position + 2; sqcd = data[j++]; switch (sqcd & 0x1f) { @@ -227,7 +227,7 @@ const JpxImage = (function JpxImageClosure() { qcd.guardBits = sqcd >> 5; spqcds = []; while (j < length + position) { - var spqcd = {}; + const spqcd = {}; if (spqcdSize === 8) { spqcd.epsilon = data[j++] >> 3; spqcd.mu = 0; @@ -248,9 +248,9 @@ const JpxImage = (function JpxImageClosure() { break; case 0xff5d: // Quantization component (QCC) length = readUint16(data, position); - var qcc = {}; + const qcc = {}; j = position + 2; - var cqcc; + let cqcc; if (context.SIZ.Csiz < 257) { cqcc = data[j++]; } else { @@ -279,7 +279,7 @@ const JpxImage = (function JpxImageClosure() { qcc.guardBits = sqcd >> 5; spqcds = []; while (j < length + position) { - spqcd = {}; + const spqcd = {}; if (spqcdSize === 8) { spqcd.epsilon = data[j++] >> 3; spqcd.mu = 0; @@ -299,9 +299,9 @@ const JpxImage = (function JpxImageClosure() { break; case 0xff52: // Coding style default (COD) length = readUint16(data, position); - var cod = {}; + const cod = {}; j = position + 2; - var scod = data[j++]; + const scod = data[j++]; cod.entropyCoderWithCustomPrecincts = !!(scod & 1); cod.sopMarkerUsed = !!(scod & 2); cod.ephMarkerUsed = !!(scod & 4); @@ -313,7 +313,7 @@ const JpxImage = (function JpxImageClosure() { cod.decompositionLevelsCount = data[j++]; cod.xcb = (data[j++] & 0xf) + 2; cod.ycb = (data[j++] & 0xf) + 2; - var blockStyle = data[j++]; + const blockStyle = data[j++]; cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); cod.resetContextProbabilities = !!(blockStyle & 2); cod.terminationOnEachCodingPass = !!(blockStyle & 4); @@ -332,7 +332,7 @@ const JpxImage = (function JpxImageClosure() { } cod.precinctsSizes = precinctsSizes; } - var unsupported = []; + const unsupported = []; if (cod.selectiveArithmeticCodingBypass) { unsupported.push("selectiveArithmeticCodingBypass"); } @@ -431,8 +431,8 @@ const JpxImage = (function JpxImageClosure() { function calculateTileGrids(context, components) { const siz = context.SIZ; // Section B.3 Division into tile and tile-components - let tile, - tiles = []; + const tiles = []; + let tile; const numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); const numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); for (let q = 0; q < numYtiles; q++) { @@ -1006,7 +1006,7 @@ const JpxImage = (function JpxImageClosure() { buildPrecincts(context, resolution, blocksDimensions); resolutions.push(resolution); - var subband; + let subband; if (r === 0) { // one sub-band (LL) with last decomposition subband = {}; @@ -1177,9 +1177,9 @@ const JpxImage = (function JpxImageClosure() { if (!readBits(1)) { continue; } - const layerNumber = packet.layerNumber; - var queue = [], - codeblock; + const layerNumber = packet.layerNumber, + queue = []; + let codeblock; for (let i = 0, ii = packet.codeblocks.length; i < ii; i++) { codeblock = packet.codeblocks[i]; let precinct = codeblock.precinct; @@ -1187,13 +1187,13 @@ const JpxImage = (function JpxImageClosure() { const codeblockRow = codeblock.cby - precinct.cbyMin; let codeblockIncluded = false; let firstTimeInclusion = false; - var valueReady; + let valueReady, zeroBitPlanesTree; if (codeblock.included !== undefined) { codeblockIncluded = !!readBits(1); } else { // reading inclusion tree precinct = codeblock.precinct; - var inclusionTree, zeroBitPlanesTree; + let inclusionTree; if (precinct.inclusionTree !== undefined) { inclusionTree = precinct.inclusionTree; } else { @@ -1306,21 +1306,20 @@ const JpxImage = (function JpxImageClosure() { continue; } - var bitModel, currentCodingpassType; - bitModel = new BitModel( + const bitModel = new BitModel( blockWidth, blockHeight, codeblock.subbandType, codeblock.zeroBitPlanes, mb ); - currentCodingpassType = 2; // first bit plane starts from cleanup + let currentCodingpassType = 2; // first bit plane starts from cleanup // collect data - let data = codeblock.data, - totalLength = 0, + const data = codeblock.data; + let totalLength = 0, codingpasses = 0; - var j, jj, dataItem; + let j, jj, dataItem; for (j = 0, jj = data.length; j < jj; j++) { dataItem = data[j]; totalLength += dataItem.end - dataItem.start; @@ -1361,7 +1360,7 @@ const JpxImage = (function JpxImageClosure() { const magnitude = bitModel.coefficentsMagnitude; const bitsDecoded = bitModel.bitsDecoded; const magnitudeCorrection = reversible ? 0 : 0.5; - var k, n, nb; + let k, n, nb; position = 0; // Do the interleaving of Section F.3.3 here, so we do not need // to copy later. LL level is not interleaved, just copied. @@ -1419,7 +1418,7 @@ const JpxImage = (function JpxImageClosure() { const coefficients = new Float32Array(width * height); for (let j = 0, jj = resolution.subbands.length; j < jj; j++) { - var mu, epsilon; + let mu, epsilon; if (!scalarExpounded) { // formula E-5 mu = spqcds[0].mu; @@ -1483,8 +1482,7 @@ const JpxImage = (function JpxImageClosure() { for (let i = 0, ii = context.tiles.length; i < ii; i++) { const tile = context.tiles[i]; const transformedTiles = []; - var c; - for (c = 0; c < componentsCount; c++) { + for (let c = 0; c < componentsCount; c++) { transformedTiles[c] = transformTile(context, tile, c); } const tile0 = transformedTiles[0]; @@ -1498,8 +1496,8 @@ const JpxImage = (function JpxImageClosure() { }; // Section G.2.2 Inverse multi component transform - var shift, offset; - var pos = 0, + let shift, offset; + let pos = 0, j, jj, y0, @@ -1551,7 +1549,7 @@ const JpxImage = (function JpxImageClosure() { } } else { // no multi-component transform - for (c = 0; c < componentsCount; c++) { + for (let c = 0; c < componentsCount; c++) { const items = transformedTiles[c].items; shift = components[c].precision - 8; offset = (128 << shift) + 0.5; @@ -1586,7 +1584,7 @@ const JpxImage = (function JpxImageClosure() { } // Section B.10.2 Tag trees - var TagTree = (function TagTreeClosure() { + const TagTree = (function TagTreeClosure() { // eslint-disable-next-line no-shadow function TagTree(width, height) { const levelsLength = log2(Math.max(width, height)) + 1; @@ -1648,7 +1646,7 @@ const JpxImage = (function JpxImageClosure() { return TagTree; })(); - var InclusionTree = (function InclusionTreeClosure() { + const InclusionTree = (function InclusionTreeClosure() { // eslint-disable-next-line no-shadow function InclusionTree(width, height, defaultValue) { const levelsLength = log2(Math.max(width, height)) + 1; @@ -1731,7 +1729,7 @@ const JpxImage = (function JpxImageClosure() { })(); // Section D. Coefficient bit modeling - var BitModel = (function BitModelClosure() { + const BitModel = (function BitModelClosure() { const UNIFORM_CONTEXT = 17; const RUNLENGTH_CONTEXT = 18; // Table D-1 @@ -1970,7 +1968,7 @@ const JpxImage = (function JpxImageClosure() { const length = width * height; const width4 = width * 4; - for (var index0 = 0, indexNext; index0 < length; index0 = indexNext) { + for (let index0 = 0, indexNext; index0 < length; index0 = indexNext) { indexNext = Math.min(length, index0 + width4); for (let j = 0; j < width; j++) { for (let index = index0 + j; index < indexNext; index += width) { @@ -2036,7 +2034,7 @@ const JpxImage = (function JpxImageClosure() { neighborsSignificance[index0 + threeRowsDown] === 0; let i1 = 0, index = index0; - var i = i0, + let i = i0, sign; if (allEmpty) { const hasSignificantCoefficent = decoder.readBit( @@ -2147,9 +2145,9 @@ const JpxImage = (function JpxImageClosure() { u0, v0 ) { - let llWidth = ll.width, - llHeight = ll.height, - llItems = ll.items; + const llWidth = ll.width, + llHeight = ll.height; + let llItems = ll.items; const width = hl_lh_hh.width; const height = hl_lh_hh.height; const items = hl_lh_hh.items; @@ -2253,7 +2251,7 @@ const JpxImage = (function JpxImageClosure() { })(); // Section 3.8.2 Irreversible 9-7 filter - var IrreversibleTransform = (function IrreversibleTransformClosure() { + const IrreversibleTransform = (function IrreversibleTransformClosure() { // eslint-disable-next-line no-shadow function IrreversibleTransform() { Transform.call(this); @@ -2351,7 +2349,7 @@ const JpxImage = (function JpxImageClosure() { })(); // Section 3.8.1 Reversible 5-3 filter - var ReversibleTransform = (function ReversibleTransformClosure() { + const ReversibleTransform = (function ReversibleTransformClosure() { // eslint-disable-next-line no-shadow function ReversibleTransform() { Transform.call(this); From ce14171cf0b8a08b9e6f44bb22293712cd5f8684 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 5 May 2021 13:02:58 +0200 Subject: [PATCH 9/9] Convert `src/core/jpx.js` to use standard classes *Please note:* Ignoring whitespace-only changes is probably necessary in order to review this. --- src/core/jpx.js | 4247 +++++++++++++++++++++++------------------------ 1 file changed, 2105 insertions(+), 2142 deletions(-) diff --git a/src/core/jpx.js b/src/core/jpx.js index e62ff0552..5a6544b28 100644 --- a/src/core/jpx.js +++ b/src/core/jpx.js @@ -13,7 +13,7 @@ * limitations under the License. */ -import { BaseException, info, warn } from "../shared/util.js"; +import { BaseException, info, unreachable, warn } from "../shared/util.js"; import { log2, readUint16, readUint32 } from "./core_utils.js"; import { ArithmeticDecoder } from "./arithmetic_decoder.js"; @@ -23,1739 +23,1738 @@ class JpxError extends BaseException { } } -const JpxImage = (function JpxImageClosure() { - // Table E.1 - const SubbandsGainLog2 = { - LL: 0, - LH: 1, - HL: 1, - HH: 2, - }; +// Table E.1 +const SubbandsGainLog2 = { + LL: 0, + LH: 1, + HL: 1, + HH: 2, +}; - // eslint-disable-next-line no-shadow - function JpxImage() { +class JpxImage { + constructor() { this.failOnCorruptedImage = false; } - JpxImage.prototype = { - parse: function JpxImage_parse(data) { - const head = readUint16(data, 0); - // No box header, immediate start of codestream (SOC) - if (head === 0xff4f) { - this.parseCodestream(data, 0, data.length); + + parse(data) { + const head = readUint16(data, 0); + // No box header, immediate start of codestream (SOC) + if (head === 0xff4f) { + this.parseCodestream(data, 0, data.length); + return; + } + + const length = data.length; + let position = 0; + while (position < length) { + let headerSize = 8; + let lbox = readUint32(data, position); + const tbox = readUint32(data, position + 4); + position += headerSize; + if (lbox === 1) { + // XLBox: read UInt64 according to spec. + // JavaScript's int precision of 53 bit should be sufficient here. + lbox = + readUint32(data, position) * 4294967296 + + readUint32(data, position + 4); + position += 8; + headerSize += 8; + } + if (lbox === 0) { + lbox = length - position + headerSize; + } + if (lbox < headerSize) { + throw new JpxError("Invalid box field size"); + } + const dataLength = lbox - headerSize; + let jumpDataLength = true; + switch (tbox) { + case 0x6a703268: // 'jp2h' + jumpDataLength = false; // parsing child boxes + break; + case 0x636f6c72: // 'colr' + // Colorspaces are not used, the CS from the PDF is used. + const method = data[position]; + if (method === 1) { + // enumerated colorspace + const colorspace = readUint32(data, position + 3); + switch (colorspace) { + case 16: // this indicates a sRGB colorspace + case 17: // this indicates a grayscale colorspace + case 18: // this indicates a YUV colorspace + break; + default: + warn("Unknown colorspace " + colorspace); + break; + } + } else if (method === 2) { + info("ICC profile not supported"); + } + break; + case 0x6a703263: // 'jp2c' + this.parseCodestream(data, position, position + dataLength); + break; + case 0x6a502020: // 'jP\024\024' + if (readUint32(data, position) !== 0x0d0a870a) { + warn("Invalid JP2 signature"); + } + break; + // The following header types are valid but currently not used: + case 0x6a501a1a: // 'jP\032\032' + case 0x66747970: // 'ftyp' + case 0x72726571: // 'rreq' + case 0x72657320: // 'res ' + case 0x69686472: // 'ihdr' + break; + default: + const headerType = String.fromCharCode( + (tbox >> 24) & 0xff, + (tbox >> 16) & 0xff, + (tbox >> 8) & 0xff, + tbox & 0xff + ); + warn(`Unsupported header type ${tbox} (${headerType}).`); + break; + } + if (jumpDataLength) { + position += dataLength; + } + } + } + + parseImageProperties(stream) { + let newByte = stream.getByte(); + while (newByte >= 0) { + const oldByte = newByte; + newByte = stream.getByte(); + const code = (oldByte << 8) | newByte; + // Image and tile size (SIZ) + if (code === 0xff51) { + stream.skip(4); + const Xsiz = stream.getInt32() >>> 0; // Byte 4 + const Ysiz = stream.getInt32() >>> 0; // Byte 8 + const XOsiz = stream.getInt32() >>> 0; // Byte 12 + const YOsiz = stream.getInt32() >>> 0; // Byte 16 + stream.skip(16); + const Csiz = stream.getUint16(); // Byte 36 + this.width = Xsiz - XOsiz; + this.height = Ysiz - YOsiz; + this.componentsCount = Csiz; + // Results are always returned as `Uint8ClampedArray`s. + this.bitsPerComponent = 8; return; } + } + throw new JpxError("No size marker found in JPX stream"); + } - const length = data.length; - let position = 0; - while (position < length) { - let headerSize = 8; - let lbox = readUint32(data, position); - const tbox = readUint32(data, position + 4); - position += headerSize; - if (lbox === 1) { - // XLBox: read UInt64 according to spec. - // JavaScript's int precision of 53 bit should be sufficient here. - lbox = - readUint32(data, position) * 4294967296 + - readUint32(data, position + 4); - position += 8; - headerSize += 8; - } - if (lbox === 0) { - lbox = length - position + headerSize; - } - if (lbox < headerSize) { - throw new JpxError("Invalid box field size"); - } - const dataLength = lbox - headerSize; - let jumpDataLength = true; - switch (tbox) { - case 0x6a703268: // 'jp2h' - jumpDataLength = false; // parsing child boxes - break; - case 0x636f6c72: // 'colr' - // Colorspaces are not used, the CS from the PDF is used. - const method = data[position]; - if (method === 1) { - // enumerated colorspace - const colorspace = readUint32(data, position + 3); - switch (colorspace) { - case 16: // this indicates a sRGB colorspace - case 17: // this indicates a grayscale colorspace - case 18: // this indicates a YUV colorspace - break; - default: - warn("Unknown colorspace " + colorspace); - break; - } - } else if (method === 2) { - info("ICC profile not supported"); - } - break; - case 0x6a703263: // 'jp2c' - this.parseCodestream(data, position, position + dataLength); - break; - case 0x6a502020: // 'jP\024\024' - if (readUint32(data, position) !== 0x0d0a870a) { - warn("Invalid JP2 signature"); - } - break; - // The following header types are valid but currently not used: - case 0x6a501a1a: // 'jP\032\032' - case 0x66747970: // 'ftyp' - case 0x72726571: // 'rreq' - case 0x72657320: // 'res ' - case 0x69686472: // 'ihdr' - break; - default: - const headerType = String.fromCharCode( - (tbox >> 24) & 0xff, - (tbox >> 16) & 0xff, - (tbox >> 8) & 0xff, - tbox & 0xff - ); - warn("Unsupported header type " + tbox + " (" + headerType + ")"); - break; - } - if (jumpDataLength) { - position += dataLength; - } - } - }, - parseImageProperties: function JpxImage_parseImageProperties(stream) { - let newByte = stream.getByte(); - while (newByte >= 0) { - const oldByte = newByte; - newByte = stream.getByte(); - const code = (oldByte << 8) | newByte; - // Image and tile size (SIZ) - if (code === 0xff51) { - stream.skip(4); - const Xsiz = stream.getInt32() >>> 0; // Byte 4 - const Ysiz = stream.getInt32() >>> 0; // Byte 8 - const XOsiz = stream.getInt32() >>> 0; // Byte 12 - const YOsiz = stream.getInt32() >>> 0; // Byte 16 - stream.skip(16); - const Csiz = stream.getUint16(); // Byte 36 - this.width = Xsiz - XOsiz; - this.height = Ysiz - YOsiz; - this.componentsCount = Csiz; - // Results are always returned as `Uint8ClampedArray`s. - this.bitsPerComponent = 8; - return; - } - } - throw new JpxError("No size marker found in JPX stream"); - }, - parseCodestream: function JpxImage_parseCodestream(data, start, end) { - const context = {}; - let doNotRecover = false; - try { - let position = start; - while (position + 1 < end) { - const code = readUint16(data, position); - position += 2; + parseCodestream(data, start, end) { + const context = {}; + let doNotRecover = false; + try { + let position = start; + while (position + 1 < end) { + const code = readUint16(data, position); + position += 2; - let length = 0, - j, - sqcd, - spqcds, - spqcdSize, - scalarExpounded, - tile; - switch (code) { - case 0xff4f: // Start of codestream (SOC) - context.mainHeader = true; - break; - case 0xffd9: // End of codestream (EOC) - break; - case 0xff51: // Image and tile size (SIZ) - length = readUint16(data, position); - const siz = {}; - siz.Xsiz = readUint32(data, position + 4); - siz.Ysiz = readUint32(data, position + 8); - siz.XOsiz = readUint32(data, position + 12); - siz.YOsiz = readUint32(data, position + 16); - siz.XTsiz = readUint32(data, position + 20); - siz.YTsiz = readUint32(data, position + 24); - siz.XTOsiz = readUint32(data, position + 28); - siz.YTOsiz = readUint32(data, position + 32); - const componentsCount = readUint16(data, position + 36); - siz.Csiz = componentsCount; - const components = []; - j = position + 38; - for (let i = 0; i < componentsCount; i++) { - const component = { - precision: (data[j] & 0x7f) + 1, - isSigned: !!(data[j] & 0x80), - XRsiz: data[j + 1], - YRsiz: data[j + 2], - }; - j += 3; - calculateComponentDimensions(component, siz); - components.push(component); - } - context.SIZ = siz; - context.components = components; - calculateTileGrids(context, components); - context.QCC = []; - context.COC = []; - break; - case 0xff5c: // Quantization default (QCD) - length = readUint16(data, position); - const qcd = {}; - j = position + 2; - sqcd = data[j++]; - switch (sqcd & 0x1f) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - throw new Error("Invalid SQcd value " + sqcd); - } - qcd.noQuantization = spqcdSize === 8; - qcd.scalarExpounded = scalarExpounded; - qcd.guardBits = sqcd >> 5; - spqcds = []; - while (j < length + position) { - const spqcd = {}; - if (spqcdSize === 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; - } - spqcds.push(spqcd); - } - qcd.SPqcds = spqcds; - if (context.mainHeader) { - context.QCD = qcd; + let length = 0, + j, + sqcd, + spqcds, + spqcdSize, + scalarExpounded, + tile; + switch (code) { + case 0xff4f: // Start of codestream (SOC) + context.mainHeader = true; + break; + case 0xffd9: // End of codestream (EOC) + break; + case 0xff51: // Image and tile size (SIZ) + length = readUint16(data, position); + const siz = {}; + siz.Xsiz = readUint32(data, position + 4); + siz.Ysiz = readUint32(data, position + 8); + siz.XOsiz = readUint32(data, position + 12); + siz.YOsiz = readUint32(data, position + 16); + siz.XTsiz = readUint32(data, position + 20); + siz.YTsiz = readUint32(data, position + 24); + siz.XTOsiz = readUint32(data, position + 28); + siz.YTOsiz = readUint32(data, position + 32); + const componentsCount = readUint16(data, position + 36); + siz.Csiz = componentsCount; + const components = []; + j = position + 38; + for (let i = 0; i < componentsCount; i++) { + const component = { + precision: (data[j] & 0x7f) + 1, + isSigned: !!(data[j] & 0x80), + XRsiz: data[j + 1], + YRsiz: data[j + 2], + }; + j += 3; + calculateComponentDimensions(component, siz); + components.push(component); + } + context.SIZ = siz; + context.components = components; + calculateTileGrids(context, components); + context.QCC = []; + context.COC = []; + break; + case 0xff5c: // Quantization default (QCD) + length = readUint16(data, position); + const qcd = {}; + j = position + 2; + sqcd = data[j++]; + switch (sqcd & 0x1f) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw new Error("Invalid SQcd value " + sqcd); + } + qcd.noQuantization = spqcdSize === 8; + qcd.scalarExpounded = scalarExpounded; + qcd.guardBits = sqcd >> 5; + spqcds = []; + while (j < length + position) { + const spqcd = {}; + if (spqcdSize === 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; } else { - context.currentTile.QCD = qcd; - context.currentTile.QCC = []; - } - break; - case 0xff5d: // Quantization component (QCC) - length = readUint16(data, position); - const qcc = {}; - j = position + 2; - let cqcc; - if (context.SIZ.Csiz < 257) { - cqcc = data[j++]; - } else { - cqcc = readUint16(data, j); + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; j += 2; } - sqcd = data[j++]; - switch (sqcd & 0x1f) { - case 0: - spqcdSize = 8; - scalarExpounded = true; - break; - case 1: - spqcdSize = 16; - scalarExpounded = false; - break; - case 2: - spqcdSize = 16; - scalarExpounded = true; - break; - default: - throw new Error("Invalid SQcd value " + sqcd); - } - qcc.noQuantization = spqcdSize === 8; - qcc.scalarExpounded = scalarExpounded; - qcc.guardBits = sqcd >> 5; - spqcds = []; - while (j < length + position) { - const spqcd = {}; - if (spqcdSize === 8) { - spqcd.epsilon = data[j++] >> 3; - spqcd.mu = 0; - } else { - spqcd.epsilon = data[j] >> 3; - spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; - j += 2; - } - spqcds.push(spqcd); - } - qcc.SPqcds = spqcds; - if (context.mainHeader) { - context.QCC[cqcc] = qcc; - } else { - context.currentTile.QCC[cqcc] = qcc; - } - break; - case 0xff52: // Coding style default (COD) - length = readUint16(data, position); - const cod = {}; - j = position + 2; - const scod = data[j++]; - cod.entropyCoderWithCustomPrecincts = !!(scod & 1); - cod.sopMarkerUsed = !!(scod & 2); - cod.ephMarkerUsed = !!(scod & 4); - cod.progressionOrder = data[j++]; - cod.layersCount = readUint16(data, j); + spqcds.push(spqcd); + } + qcd.SPqcds = spqcds; + if (context.mainHeader) { + context.QCD = qcd; + } else { + context.currentTile.QCD = qcd; + context.currentTile.QCC = []; + } + break; + case 0xff5d: // Quantization component (QCC) + length = readUint16(data, position); + const qcc = {}; + j = position + 2; + let cqcc; + if (context.SIZ.Csiz < 257) { + cqcc = data[j++]; + } else { + cqcc = readUint16(data, j); j += 2; - cod.multipleComponentTransform = data[j++]; - - cod.decompositionLevelsCount = data[j++]; - cod.xcb = (data[j++] & 0xf) + 2; - cod.ycb = (data[j++] & 0xf) + 2; - const blockStyle = data[j++]; - cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); - cod.resetContextProbabilities = !!(blockStyle & 2); - cod.terminationOnEachCodingPass = !!(blockStyle & 4); - cod.verticallyStripe = !!(blockStyle & 8); - cod.predictableTermination = !!(blockStyle & 16); - cod.segmentationSymbolUsed = !!(blockStyle & 32); - cod.reversibleTransformation = data[j++]; - if (cod.entropyCoderWithCustomPrecincts) { - const precinctsSizes = []; - while (j < length + position) { - const precinctsSize = data[j++]; - precinctsSizes.push({ - PPx: precinctsSize & 0xf, - PPy: precinctsSize >> 4, - }); - } - cod.precinctsSizes = precinctsSizes; - } - const unsupported = []; - if (cod.selectiveArithmeticCodingBypass) { - unsupported.push("selectiveArithmeticCodingBypass"); - } - if (cod.resetContextProbabilities) { - unsupported.push("resetContextProbabilities"); - } - if (cod.terminationOnEachCodingPass) { - unsupported.push("terminationOnEachCodingPass"); - } - if (cod.verticallyStripe) { - unsupported.push("verticallyStripe"); - } - if (cod.predictableTermination) { - unsupported.push("predictableTermination"); - } - if (unsupported.length > 0) { - doNotRecover = true; - warn( - `JPX: Unsupported COD options (${unsupported.join(", ")}).` - ); - } - if (context.mainHeader) { - context.COD = cod; + } + sqcd = data[j++]; + switch (sqcd & 0x1f) { + case 0: + spqcdSize = 8; + scalarExpounded = true; + break; + case 1: + spqcdSize = 16; + scalarExpounded = false; + break; + case 2: + spqcdSize = 16; + scalarExpounded = true; + break; + default: + throw new Error("Invalid SQcd value " + sqcd); + } + qcc.noQuantization = spqcdSize === 8; + qcc.scalarExpounded = scalarExpounded; + qcc.guardBits = sqcd >> 5; + spqcds = []; + while (j < length + position) { + const spqcd = {}; + if (spqcdSize === 8) { + spqcd.epsilon = data[j++] >> 3; + spqcd.mu = 0; } else { - context.currentTile.COD = cod; - context.currentTile.COC = []; + spqcd.epsilon = data[j] >> 3; + spqcd.mu = ((data[j] & 0x7) << 8) | data[j + 1]; + j += 2; } - break; - case 0xff90: // Start of tile-part (SOT) - length = readUint16(data, position); - tile = {}; - tile.index = readUint16(data, position + 2); - tile.length = readUint32(data, position + 4); - tile.dataEnd = tile.length + position - 2; - tile.partIndex = data[position + 8]; - tile.partsCount = data[position + 9]; + spqcds.push(spqcd); + } + qcc.SPqcds = spqcds; + if (context.mainHeader) { + context.QCC[cqcc] = qcc; + } else { + context.currentTile.QCC[cqcc] = qcc; + } + break; + case 0xff52: // Coding style default (COD) + length = readUint16(data, position); + const cod = {}; + j = position + 2; + const scod = data[j++]; + cod.entropyCoderWithCustomPrecincts = !!(scod & 1); + cod.sopMarkerUsed = !!(scod & 2); + cod.ephMarkerUsed = !!(scod & 4); + cod.progressionOrder = data[j++]; + cod.layersCount = readUint16(data, j); + j += 2; + cod.multipleComponentTransform = data[j++]; - context.mainHeader = false; - if (tile.partIndex === 0) { - // reset component specific settings - tile.COD = context.COD; - tile.COC = context.COC.slice(0); // clone of the global COC - tile.QCD = context.QCD; - tile.QCC = context.QCC.slice(0); // clone of the global COC - } - context.currentTile = tile; - break; - case 0xff93: // Start of data (SOD) - tile = context.currentTile; - if (tile.partIndex === 0) { - initializeTile(context, tile.index); - buildPackets(context); + cod.decompositionLevelsCount = data[j++]; + cod.xcb = (data[j++] & 0xf) + 2; + cod.ycb = (data[j++] & 0xf) + 2; + const blockStyle = data[j++]; + cod.selectiveArithmeticCodingBypass = !!(blockStyle & 1); + cod.resetContextProbabilities = !!(blockStyle & 2); + cod.terminationOnEachCodingPass = !!(blockStyle & 4); + cod.verticallyStripe = !!(blockStyle & 8); + cod.predictableTermination = !!(blockStyle & 16); + cod.segmentationSymbolUsed = !!(blockStyle & 32); + cod.reversibleTransformation = data[j++]; + if (cod.entropyCoderWithCustomPrecincts) { + const precinctsSizes = []; + while (j < length + position) { + const precinctsSize = data[j++]; + precinctsSizes.push({ + PPx: precinctsSize & 0xf, + PPy: precinctsSize >> 4, + }); } + cod.precinctsSizes = precinctsSizes; + } + const unsupported = []; + if (cod.selectiveArithmeticCodingBypass) { + unsupported.push("selectiveArithmeticCodingBypass"); + } + if (cod.resetContextProbabilities) { + unsupported.push("resetContextProbabilities"); + } + if (cod.terminationOnEachCodingPass) { + unsupported.push("terminationOnEachCodingPass"); + } + if (cod.verticallyStripe) { + unsupported.push("verticallyStripe"); + } + if (cod.predictableTermination) { + unsupported.push("predictableTermination"); + } + if (unsupported.length > 0) { + doNotRecover = true; + warn(`JPX: Unsupported COD options (${unsupported.join(", ")}).`); + } + if (context.mainHeader) { + context.COD = cod; + } else { + context.currentTile.COD = cod; + context.currentTile.COC = []; + } + break; + case 0xff90: // Start of tile-part (SOT) + length = readUint16(data, position); + tile = {}; + tile.index = readUint16(data, position + 2); + tile.length = readUint32(data, position + 4); + tile.dataEnd = tile.length + position - 2; + tile.partIndex = data[position + 8]; + tile.partsCount = data[position + 9]; - // moving to the end of the data - length = tile.dataEnd - position; - parseTilePackets(context, data, position, length); - break; - case 0xff53: // Coding style component (COC) - warn("JPX: Codestream code 0xFF53 (COC) is not implemented."); - /* falls through */ - case 0xff55: // Tile-part lengths, main header (TLM) - case 0xff57: // Packet length, main header (PLM) - case 0xff58: // Packet length, tile-part header (PLT) - case 0xff64: // Comment (COM) - length = readUint16(data, position); - // skipping content - break; - default: - throw new Error("Unknown codestream code: " + code.toString(16)); - } - position += length; - } - } catch (e) { - if (doNotRecover || this.failOnCorruptedImage) { - throw new JpxError(e.message); - } else { - warn(`JPX: Trying to recover from: "${e.message}".`); + context.mainHeader = false; + if (tile.partIndex === 0) { + // reset component specific settings + tile.COD = context.COD; + tile.COC = context.COC.slice(0); // clone of the global COC + tile.QCD = context.QCD; + tile.QCC = context.QCC.slice(0); // clone of the global COC + } + context.currentTile = tile; + break; + case 0xff93: // Start of data (SOD) + tile = context.currentTile; + if (tile.partIndex === 0) { + initializeTile(context, tile.index); + buildPackets(context); + } + + // moving to the end of the data + length = tile.dataEnd - position; + parseTilePackets(context, data, position, length); + break; + case 0xff53: // Coding style component (COC) + warn("JPX: Codestream code 0xFF53 (COC) is not implemented."); + /* falls through */ + case 0xff55: // Tile-part lengths, main header (TLM) + case 0xff57: // Packet length, main header (PLM) + case 0xff58: // Packet length, tile-part header (PLT) + case 0xff64: // Comment (COM) + length = readUint16(data, position); + // skipping content + break; + default: + throw new Error("Unknown codestream code: " + code.toString(16)); } + position += length; } - this.tiles = transformComponents(context); - this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; - this.height = context.SIZ.Ysiz - context.SIZ.YOsiz; - this.componentsCount = context.SIZ.Csiz; - }, + } catch (e) { + if (doNotRecover || this.failOnCorruptedImage) { + throw new JpxError(e.message); + } else { + warn(`JPX: Trying to recover from: "${e.message}".`); + } + } + this.tiles = transformComponents(context); + this.width = context.SIZ.Xsiz - context.SIZ.XOsiz; + this.height = context.SIZ.Ysiz - context.SIZ.YOsiz; + this.componentsCount = context.SIZ.Csiz; + } +} + +function calculateComponentDimensions(component, siz) { + // Section B.2 Component mapping + component.x0 = Math.ceil(siz.XOsiz / component.XRsiz); + component.x1 = Math.ceil(siz.Xsiz / component.XRsiz); + component.y0 = Math.ceil(siz.YOsiz / component.YRsiz); + component.y1 = Math.ceil(siz.Ysiz / component.YRsiz); + component.width = component.x1 - component.x0; + component.height = component.y1 - component.y0; +} +function calculateTileGrids(context, components) { + const siz = context.SIZ; + // Section B.3 Division into tile and tile-components + const tiles = []; + let tile; + const numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); + const numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); + for (let q = 0; q < numYtiles; q++) { + for (let p = 0; p < numXtiles; p++) { + tile = {}; + tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); + tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); + tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz); + tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz); + tile.width = tile.tx1 - tile.tx0; + tile.height = tile.ty1 - tile.ty0; + tile.components = []; + tiles.push(tile); + } + } + context.tiles = tiles; + + const componentsCount = siz.Csiz; + for (let i = 0, ii = componentsCount; i < ii; i++) { + const component = components[i]; + for (let j = 0, jj = tiles.length; j < jj; j++) { + const tileComponent = {}; + tile = tiles[j]; + tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz); + tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz); + tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz); + tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz); + tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0; + tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0; + tile.components[i] = tileComponent; + } + } +} +function getBlocksDimensions(context, component, r) { + const codOrCoc = component.codingStyleParameters; + const result = {}; + if (!codOrCoc.entropyCoderWithCustomPrecincts) { + result.PPx = 15; + result.PPy = 15; + } else { + result.PPx = codOrCoc.precinctsSizes[r].PPx; + result.PPy = codOrCoc.precinctsSizes[r].PPy; + } + // calculate codeblock size as described in section B.7 + result.xcb_ = + r > 0 + ? Math.min(codOrCoc.xcb, result.PPx - 1) + : Math.min(codOrCoc.xcb, result.PPx); + result.ycb_ = + r > 0 + ? Math.min(codOrCoc.ycb, result.PPy - 1) + : Math.min(codOrCoc.ycb, result.PPy); + return result; +} +function buildPrecincts(context, resolution, dimensions) { + // Section B.6 Division resolution to precincts + const precinctWidth = 1 << dimensions.PPx; + const precinctHeight = 1 << dimensions.PPy; + // Jasper introduces codeblock groups for mapping each subband codeblocks + // to precincts. Precinct partition divides a resolution according to width + // and height parameters. The subband that belongs to the resolution level + // has a different size than the level, unless it is the zero resolution. + + // From Jasper documentation: jpeg2000.pdf, section K: Tier-2 coding: + // The precinct partitioning for a particular subband is derived from a + // partitioning of its parent LL band (i.e., the LL band at the next higher + // resolution level)... The LL band associated with each resolution level is + // divided into precincts... Each of the resulting precinct regions is then + // mapped into its child subbands (if any) at the next lower resolution + // level. This is accomplished by using the coordinate transformation + // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the + // coordinates of a point in the LL band and child subband, respectively. + const isZeroRes = resolution.resLevel === 0; + const precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1)); + const precinctHeightInSubband = 1 << (dimensions.PPy + (isZeroRes ? 0 : -1)); + const numprecinctswide = + resolution.trx1 > resolution.trx0 + ? Math.ceil(resolution.trx1 / precinctWidth) - + Math.floor(resolution.trx0 / precinctWidth) + : 0; + const numprecinctshigh = + resolution.try1 > resolution.try0 + ? Math.ceil(resolution.try1 / precinctHeight) - + Math.floor(resolution.try0 / precinctHeight) + : 0; + const numprecincts = numprecinctswide * numprecinctshigh; + + resolution.precinctParameters = { + precinctWidth, + precinctHeight, + numprecinctswide, + numprecinctshigh, + numprecincts, + precinctWidthInSubband, + precinctHeightInSubband, }; - function calculateComponentDimensions(component, siz) { - // Section B.2 Component mapping - component.x0 = Math.ceil(siz.XOsiz / component.XRsiz); - component.x1 = Math.ceil(siz.Xsiz / component.XRsiz); - component.y0 = Math.ceil(siz.YOsiz / component.YRsiz); - component.y1 = Math.ceil(siz.Ysiz / component.YRsiz); - component.width = component.x1 - component.x0; - component.height = component.y1 - component.y0; - } - function calculateTileGrids(context, components) { - const siz = context.SIZ; - // Section B.3 Division into tile and tile-components - const tiles = []; - let tile; - const numXtiles = Math.ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); - const numYtiles = Math.ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); - for (let q = 0; q < numYtiles; q++) { - for (let p = 0; p < numXtiles; p++) { - tile = {}; - tile.tx0 = Math.max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); - tile.ty0 = Math.max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); - tile.tx1 = Math.min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz); - tile.ty1 = Math.min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz); - tile.width = tile.tx1 - tile.tx0; - tile.height = tile.ty1 - tile.ty0; - tile.components = []; - tiles.push(tile); +} +function buildCodeblocks(context, subband, dimensions) { + // Section B.7 Division sub-band into code-blocks + const xcb_ = dimensions.xcb_; + const ycb_ = dimensions.ycb_; + const codeblockWidth = 1 << xcb_; + const codeblockHeight = 1 << ycb_; + const cbx0 = subband.tbx0 >> xcb_; + const cby0 = subband.tby0 >> ycb_; + const cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_; + const cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_; + const precinctParameters = subband.resolution.precinctParameters; + const codeblocks = []; + const precincts = []; + let i, j, codeblock, precinctNumber; + for (j = cby0; j < cby1; j++) { + for (i = cbx0; i < cbx1; i++) { + codeblock = { + cbx: i, + cby: j, + tbx0: codeblockWidth * i, + tby0: codeblockHeight * j, + tbx1: codeblockWidth * (i + 1), + tby1: codeblockHeight * (j + 1), + }; + + codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); + codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); + codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); + codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1); + + // Calculate precinct number for this codeblock, codeblock position + // should be relative to its subband, use actual dimension and position + // See comment about codeblock group width and height + const pi = Math.floor( + (codeblock.tbx0_ - subband.tbx0) / + precinctParameters.precinctWidthInSubband + ); + const pj = Math.floor( + (codeblock.tby0_ - subband.tby0) / + precinctParameters.precinctHeightInSubband + ); + precinctNumber = pi + pj * precinctParameters.numprecinctswide; + + codeblock.precinctNumber = precinctNumber; + codeblock.subbandType = subband.type; + codeblock.Lblock = 3; + + if ( + codeblock.tbx1_ <= codeblock.tbx0_ || + codeblock.tby1_ <= codeblock.tby0_ + ) { + continue; } - } - context.tiles = tiles; - - const componentsCount = siz.Csiz; - for (let i = 0, ii = componentsCount; i < ii; i++) { - const component = components[i]; - for (let j = 0, jj = tiles.length; j < jj; j++) { - const tileComponent = {}; - tile = tiles[j]; - tileComponent.tcx0 = Math.ceil(tile.tx0 / component.XRsiz); - tileComponent.tcy0 = Math.ceil(tile.ty0 / component.YRsiz); - tileComponent.tcx1 = Math.ceil(tile.tx1 / component.XRsiz); - tileComponent.tcy1 = Math.ceil(tile.ty1 / component.YRsiz); - tileComponent.width = tileComponent.tcx1 - tileComponent.tcx0; - tileComponent.height = tileComponent.tcy1 - tileComponent.tcy0; - tile.components[i] = tileComponent; - } - } - } - function getBlocksDimensions(context, component, r) { - const codOrCoc = component.codingStyleParameters; - const result = {}; - if (!codOrCoc.entropyCoderWithCustomPrecincts) { - result.PPx = 15; - result.PPy = 15; - } else { - result.PPx = codOrCoc.precinctsSizes[r].PPx; - result.PPy = codOrCoc.precinctsSizes[r].PPy; - } - // calculate codeblock size as described in section B.7 - result.xcb_ = - r > 0 - ? Math.min(codOrCoc.xcb, result.PPx - 1) - : Math.min(codOrCoc.xcb, result.PPx); - result.ycb_ = - r > 0 - ? Math.min(codOrCoc.ycb, result.PPy - 1) - : Math.min(codOrCoc.ycb, result.PPy); - return result; - } - function buildPrecincts(context, resolution, dimensions) { - // Section B.6 Division resolution to precincts - const precinctWidth = 1 << dimensions.PPx; - const precinctHeight = 1 << dimensions.PPy; - // Jasper introduces codeblock groups for mapping each subband codeblocks - // to precincts. Precinct partition divides a resolution according to width - // and height parameters. The subband that belongs to the resolution level - // has a different size than the level, unless it is the zero resolution. - - // From Jasper documentation: jpeg2000.pdf, section K: Tier-2 coding: - // The precinct partitioning for a particular subband is derived from a - // partitioning of its parent LL band (i.e., the LL band at the next higher - // resolution level)... The LL band associated with each resolution level is - // divided into precincts... Each of the resulting precinct regions is then - // mapped into its child subbands (if any) at the next lower resolution - // level. This is accomplished by using the coordinate transformation - // (u, v) = (ceil(x/2), ceil(y/2)) where (x, y) and (u, v) are the - // coordinates of a point in the LL band and child subband, respectively. - const isZeroRes = resolution.resLevel === 0; - const precinctWidthInSubband = 1 << (dimensions.PPx + (isZeroRes ? 0 : -1)); - const precinctHeightInSubband = - 1 << (dimensions.PPy + (isZeroRes ? 0 : -1)); - const numprecinctswide = - resolution.trx1 > resolution.trx0 - ? Math.ceil(resolution.trx1 / precinctWidth) - - Math.floor(resolution.trx0 / precinctWidth) - : 0; - const numprecinctshigh = - resolution.try1 > resolution.try0 - ? Math.ceil(resolution.try1 / precinctHeight) - - Math.floor(resolution.try0 / precinctHeight) - : 0; - const numprecincts = numprecinctswide * numprecinctshigh; - - resolution.precinctParameters = { - precinctWidth, - precinctHeight, - numprecinctswide, - numprecinctshigh, - numprecincts, - precinctWidthInSubband, - precinctHeightInSubband, - }; - } - function buildCodeblocks(context, subband, dimensions) { - // Section B.7 Division sub-band into code-blocks - const xcb_ = dimensions.xcb_; - const ycb_ = dimensions.ycb_; - const codeblockWidth = 1 << xcb_; - const codeblockHeight = 1 << ycb_; - const cbx0 = subband.tbx0 >> xcb_; - const cby0 = subband.tby0 >> ycb_; - const cbx1 = (subband.tbx1 + codeblockWidth - 1) >> xcb_; - const cby1 = (subband.tby1 + codeblockHeight - 1) >> ycb_; - const precinctParameters = subband.resolution.precinctParameters; - const codeblocks = []; - const precincts = []; - let i, j, codeblock, precinctNumber; - for (j = cby0; j < cby1; j++) { - for (i = cbx0; i < cbx1; i++) { - codeblock = { - cbx: i, - cby: j, - tbx0: codeblockWidth * i, - tby0: codeblockHeight * j, - tbx1: codeblockWidth * (i + 1), - tby1: codeblockHeight * (j + 1), + codeblocks.push(codeblock); + // building precinct for the sub-band + let precinct = precincts[precinctNumber]; + if (precinct !== undefined) { + if (i < precinct.cbxMin) { + precinct.cbxMin = i; + } else if (i > precinct.cbxMax) { + precinct.cbxMax = i; + } + if (j < precinct.cbyMin) { + precinct.cbxMin = j; + } else if (j > precinct.cbyMax) { + precinct.cbyMax = j; + } + } else { + precincts[precinctNumber] = precinct = { + cbxMin: i, + cbyMin: j, + cbxMax: i, + cbyMax: j, }; - - codeblock.tbx0_ = Math.max(subband.tbx0, codeblock.tbx0); - codeblock.tby0_ = Math.max(subband.tby0, codeblock.tby0); - codeblock.tbx1_ = Math.min(subband.tbx1, codeblock.tbx1); - codeblock.tby1_ = Math.min(subband.tby1, codeblock.tby1); - - // Calculate precinct number for this codeblock, codeblock position - // should be relative to its subband, use actual dimension and position - // See comment about codeblock group width and height - const pi = Math.floor( - (codeblock.tbx0_ - subband.tbx0) / - precinctParameters.precinctWidthInSubband - ); - const pj = Math.floor( - (codeblock.tby0_ - subband.tby0) / - precinctParameters.precinctHeightInSubband - ); - precinctNumber = pi + pj * precinctParameters.numprecinctswide; - - codeblock.precinctNumber = precinctNumber; - codeblock.subbandType = subband.type; - codeblock.Lblock = 3; - - if ( - codeblock.tbx1_ <= codeblock.tbx0_ || - codeblock.tby1_ <= codeblock.tby0_ - ) { - continue; - } - codeblocks.push(codeblock); - // building precinct for the sub-band - let precinct = precincts[precinctNumber]; - if (precinct !== undefined) { - if (i < precinct.cbxMin) { - precinct.cbxMin = i; - } else if (i > precinct.cbxMax) { - precinct.cbxMax = i; - } - if (j < precinct.cbyMin) { - precinct.cbxMin = j; - } else if (j > precinct.cbyMax) { - precinct.cbyMax = j; - } - } else { - precincts[precinctNumber] = precinct = { - cbxMin: i, - cbyMin: j, - cbxMax: i, - cbyMax: j, - }; - } - codeblock.precinct = precinct; } + codeblock.precinct = precinct; } - subband.codeblockParameters = { - codeblockWidth: xcb_, - codeblockHeight: ycb_, - numcodeblockwide: cbx1 - cbx0 + 1, - numcodeblockhigh: cby1 - cby0 + 1, - }; - subband.codeblocks = codeblocks; - subband.precincts = precincts; } - function createPacket(resolution, precinctNumber, layerNumber) { - const precinctCodeblocks = []; - // Section B.10.8 Order of info in packet - const subbands = resolution.subbands; - // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence - for (let i = 0, ii = subbands.length; i < ii; i++) { - const subband = subbands[i]; - const codeblocks = subband.codeblocks; - for (let j = 0, jj = codeblocks.length; j < jj; j++) { - const codeblock = codeblocks[j]; - if (codeblock.precinctNumber !== precinctNumber) { - continue; - } - precinctCodeblocks.push(codeblock); + subband.codeblockParameters = { + codeblockWidth: xcb_, + codeblockHeight: ycb_, + numcodeblockwide: cbx1 - cbx0 + 1, + numcodeblockhigh: cby1 - cby0 + 1, + }; + subband.codeblocks = codeblocks; + subband.precincts = precincts; +} +function createPacket(resolution, precinctNumber, layerNumber) { + const precinctCodeblocks = []; + // Section B.10.8 Order of info in packet + const subbands = resolution.subbands; + // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence + for (let i = 0, ii = subbands.length; i < ii; i++) { + const subband = subbands[i]; + const codeblocks = subband.codeblocks; + for (let j = 0, jj = codeblocks.length; j < jj; j++) { + const codeblock = codeblocks[j]; + if (codeblock.precinctNumber !== precinctNumber) { + continue; } + precinctCodeblocks.push(codeblock); } - return { - layerNumber, - codeblocks: precinctCodeblocks, - }; } - function LayerResolutionComponentPositionIterator(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const layersCount = tile.codingStyleDefaultParameters.layersCount; - const componentsCount = siz.Csiz; - let maxDecompositionLevelsCount = 0; - for (let q = 0; q < componentsCount; q++) { - maxDecompositionLevelsCount = Math.max( - maxDecompositionLevelsCount, - tile.components[q].codingStyleParameters.decompositionLevelsCount - ); - } - - let l = 0, - r = 0, - i = 0, - k = 0; - - this.nextPacket = function JpxImage_nextPacket() { - // Section B.12.1.1 Layer-resolution-component-position - for (; l < layersCount; l++) { - for (; r <= maxDecompositionLevelsCount; r++) { - for (; i < componentsCount; i++) { - const component = tile.components[i]; - if (r > component.codingStyleParameters.decompositionLevelsCount) { - continue; - } - - const resolution = component.resolutions[r]; - const numprecincts = resolution.precinctParameters.numprecincts; - for (; k < numprecincts; ) { - const packet = createPacket(resolution, k, l); - k++; - return packet; - } - k = 0; - } - i = 0; - } - r = 0; - } - throw new JpxError("Out of packets"); - }; - } - function ResolutionLayerComponentPositionIterator(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const layersCount = tile.codingStyleDefaultParameters.layersCount; - const componentsCount = siz.Csiz; - let maxDecompositionLevelsCount = 0; - for (let q = 0; q < componentsCount; q++) { - maxDecompositionLevelsCount = Math.max( - maxDecompositionLevelsCount, - tile.components[q].codingStyleParameters.decompositionLevelsCount - ); - } - - let r = 0, - l = 0, - i = 0, - k = 0; - - this.nextPacket = function JpxImage_nextPacket() { - // Section B.12.1.2 Resolution-layer-component-position - for (; r <= maxDecompositionLevelsCount; r++) { - for (; l < layersCount; l++) { - for (; i < componentsCount; i++) { - const component = tile.components[i]; - if (r > component.codingStyleParameters.decompositionLevelsCount) { - continue; - } - - const resolution = component.resolutions[r]; - const numprecincts = resolution.precinctParameters.numprecincts; - for (; k < numprecincts; ) { - const packet = createPacket(resolution, k, l); - k++; - return packet; - } - k = 0; - } - i = 0; - } - l = 0; - } - throw new JpxError("Out of packets"); - }; - } - function ResolutionPositionComponentLayerIterator(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const layersCount = tile.codingStyleDefaultParameters.layersCount; - const componentsCount = siz.Csiz; - let l, r, c, p; - let maxDecompositionLevelsCount = 0; - for (c = 0; c < componentsCount; c++) { - const component = tile.components[c]; - maxDecompositionLevelsCount = Math.max( - maxDecompositionLevelsCount, - component.codingStyleParameters.decompositionLevelsCount - ); - } - const maxNumPrecinctsInLevel = new Int32Array( - maxDecompositionLevelsCount + 1 + return { + layerNumber, + codeblocks: precinctCodeblocks, + }; +} +function LayerResolutionComponentPositionIterator(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let maxDecompositionLevelsCount = 0; + for (let q = 0; q < componentsCount; q++) { + maxDecompositionLevelsCount = Math.max( + maxDecompositionLevelsCount, + tile.components[q].codingStyleParameters.decompositionLevelsCount ); - for (r = 0; r <= maxDecompositionLevelsCount; ++r) { - let maxNumPrecincts = 0; - for (c = 0; c < componentsCount; ++c) { - const resolutions = tile.components[c].resolutions; - if (r < resolutions.length) { - maxNumPrecincts = Math.max( - maxNumPrecincts, - resolutions[r].precinctParameters.numprecincts - ); - } - } - maxNumPrecinctsInLevel[r] = maxNumPrecincts; - } - l = 0; - r = 0; - c = 0; - p = 0; + } - this.nextPacket = function JpxImage_nextPacket() { - // Section B.12.1.3 Resolution-position-component-layer + let l = 0, + r = 0, + i = 0, + k = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.1 Layer-resolution-component-position + for (; l < layersCount; l++) { for (; r <= maxDecompositionLevelsCount; r++) { - for (; p < maxNumPrecinctsInLevel[r]; p++) { - for (; c < componentsCount; c++) { - const component = tile.components[c]; - if (r > component.codingStyleParameters.decompositionLevelsCount) { - continue; - } + for (; i < componentsCount; i++) { + const component = tile.components[i]; + if (r > component.codingStyleParameters.decompositionLevelsCount) { + continue; + } + + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; + for (; k < numprecincts; ) { + const packet = createPacket(resolution, k, l); + k++; + return packet; + } + k = 0; + } + i = 0; + } + r = 0; + } + throw new JpxError("Out of packets"); + }; +} +function ResolutionLayerComponentPositionIterator(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let maxDecompositionLevelsCount = 0; + for (let q = 0; q < componentsCount; q++) { + maxDecompositionLevelsCount = Math.max( + maxDecompositionLevelsCount, + tile.components[q].codingStyleParameters.decompositionLevelsCount + ); + } + + let r = 0, + l = 0, + i = 0, + k = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.2 Resolution-layer-component-position + for (; r <= maxDecompositionLevelsCount; r++) { + for (; l < layersCount; l++) { + for (; i < componentsCount; i++) { + const component = tile.components[i]; + if (r > component.codingStyleParameters.decompositionLevelsCount) { + continue; + } + + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; + for (; k < numprecincts; ) { + const packet = createPacket(resolution, k, l); + k++; + return packet; + } + k = 0; + } + i = 0; + } + l = 0; + } + throw new JpxError("Out of packets"); + }; +} +function ResolutionPositionComponentLayerIterator(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + let l, r, c, p; + let maxDecompositionLevelsCount = 0; + for (c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + maxDecompositionLevelsCount = Math.max( + maxDecompositionLevelsCount, + component.codingStyleParameters.decompositionLevelsCount + ); + } + const maxNumPrecinctsInLevel = new Int32Array( + maxDecompositionLevelsCount + 1 + ); + for (r = 0; r <= maxDecompositionLevelsCount; ++r) { + let maxNumPrecincts = 0; + for (c = 0; c < componentsCount; ++c) { + const resolutions = tile.components[c].resolutions; + if (r < resolutions.length) { + maxNumPrecincts = Math.max( + maxNumPrecincts, + resolutions[r].precinctParameters.numprecincts + ); + } + } + maxNumPrecinctsInLevel[r] = maxNumPrecincts; + } + l = 0; + r = 0; + c = 0; + p = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.3 Resolution-position-component-layer + for (; r <= maxDecompositionLevelsCount; r++) { + for (; p < maxNumPrecinctsInLevel[r]; p++) { + for (; c < componentsCount; c++) { + const component = tile.components[c]; + if (r > component.codingStyleParameters.decompositionLevelsCount) { + continue; + } + const resolution = component.resolutions[r]; + const numprecincts = resolution.precinctParameters.numprecincts; + if (p >= numprecincts) { + continue; + } + for (; l < layersCount; ) { + const packet = createPacket(resolution, p, l); + l++; + return packet; + } + l = 0; + } + c = 0; + } + p = 0; + } + throw new JpxError("Out of packets"); + }; +} +function PositionComponentResolutionLayerIterator(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + const precinctsSizes = getPrecinctSizesInImageScale(tile); + const precinctsIterationSizes = precinctsSizes; + let l = 0, + r = 0, + c = 0, + px = 0, + py = 0; + + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.4 Position-component-resolution-layer + for (; py < precinctsIterationSizes.maxNumHigh; py++) { + for (; px < precinctsIterationSizes.maxNumWide; px++) { + for (; c < componentsCount; c++) { + const component = tile.components[c]; + const decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + for (; r <= decompositionLevelsCount; r++) { const resolution = component.resolutions[r]; - const numprecincts = resolution.precinctParameters.numprecincts; - if (p >= numprecincts) { + const sizeInImageScale = + precinctsSizes.components[c].resolutions[r]; + const k = getPrecinctIndexIfExist( + px, + py, + sizeInImageScale, + precinctsIterationSizes, + resolution + ); + if (k === null) { continue; } for (; l < layersCount; ) { - const packet = createPacket(resolution, p, l); + const packet = createPacket(resolution, k, l); l++; return packet; } l = 0; } - c = 0; + r = 0; } - p = 0; + c = 0; } - throw new JpxError("Out of packets"); - }; - } - function PositionComponentResolutionLayerIterator(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const layersCount = tile.codingStyleDefaultParameters.layersCount; - const componentsCount = siz.Csiz; - const precinctsSizes = getPrecinctSizesInImageScale(tile); - const precinctsIterationSizes = precinctsSizes; - let l = 0, - r = 0, - c = 0, - px = 0, - py = 0; + px = 0; + } + throw new JpxError("Out of packets"); + }; +} +function ComponentPositionResolutionLayerIterator(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const layersCount = tile.codingStyleDefaultParameters.layersCount; + const componentsCount = siz.Csiz; + const precinctsSizes = getPrecinctSizesInImageScale(tile); + let l = 0, + r = 0, + c = 0, + px = 0, + py = 0; - this.nextPacket = function JpxImage_nextPacket() { - // Section B.12.1.4 Position-component-resolution-layer + this.nextPacket = function JpxImage_nextPacket() { + // Section B.12.1.5 Component-position-resolution-layer + for (; c < componentsCount; ++c) { + const component = tile.components[c]; + const precinctsIterationSizes = precinctsSizes.components[c]; + const decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; for (; py < precinctsIterationSizes.maxNumHigh; py++) { for (; px < precinctsIterationSizes.maxNumWide; px++) { - for (; c < componentsCount; c++) { - const component = tile.components[c]; - const decompositionLevelsCount = - component.codingStyleParameters.decompositionLevelsCount; - for (; r <= decompositionLevelsCount; r++) { - const resolution = component.resolutions[r]; - const sizeInImageScale = - precinctsSizes.components[c].resolutions[r]; - const k = getPrecinctIndexIfExist( - px, - py, - sizeInImageScale, - precinctsIterationSizes, - resolution - ); - if (k === null) { - continue; - } - for (; l < layersCount; ) { - const packet = createPacket(resolution, k, l); - l++; - return packet; - } - l = 0; + for (; r <= decompositionLevelsCount; r++) { + const resolution = component.resolutions[r]; + const sizeInImageScale = precinctsIterationSizes.resolutions[r]; + const k = getPrecinctIndexIfExist( + px, + py, + sizeInImageScale, + precinctsIterationSizes, + resolution + ); + if (k === null) { + continue; } - r = 0; + for (; l < layersCount; ) { + const packet = createPacket(resolution, k, l); + l++; + return packet; + } + l = 0; } - c = 0; + r = 0; } px = 0; } - throw new JpxError("Out of packets"); - }; - } - function ComponentPositionResolutionLayerIterator(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const layersCount = tile.codingStyleDefaultParameters.layersCount; - const componentsCount = siz.Csiz; - const precinctsSizes = getPrecinctSizesInImageScale(tile); - let l = 0, - r = 0, - c = 0, - px = 0, py = 0; - - this.nextPacket = function JpxImage_nextPacket() { - // Section B.12.1.5 Component-position-resolution-layer - for (; c < componentsCount; ++c) { - const component = tile.components[c]; - const precinctsIterationSizes = precinctsSizes.components[c]; - const decompositionLevelsCount = - component.codingStyleParameters.decompositionLevelsCount; - for (; py < precinctsIterationSizes.maxNumHigh; py++) { - for (; px < precinctsIterationSizes.maxNumWide; px++) { - for (; r <= decompositionLevelsCount; r++) { - const resolution = component.resolutions[r]; - const sizeInImageScale = precinctsIterationSizes.resolutions[r]; - const k = getPrecinctIndexIfExist( - px, - py, - sizeInImageScale, - precinctsIterationSizes, - resolution - ); - if (k === null) { - continue; - } - for (; l < layersCount; ) { - const packet = createPacket(resolution, k, l); - l++; - return packet; - } - l = 0; - } - r = 0; - } - px = 0; - } - py = 0; - } - throw new JpxError("Out of packets"); - }; - } - function getPrecinctIndexIfExist( - pxIndex, - pyIndex, - sizeInImageScale, - precinctIterationSizes, - resolution + } + throw new JpxError("Out of packets"); + }; +} +function getPrecinctIndexIfExist( + pxIndex, + pyIndex, + sizeInImageScale, + precinctIterationSizes, + resolution +) { + const posX = pxIndex * precinctIterationSizes.minWidth; + const posY = pyIndex * precinctIterationSizes.minHeight; + if ( + posX % sizeInImageScale.width !== 0 || + posY % sizeInImageScale.height !== 0 ) { - const posX = pxIndex * precinctIterationSizes.minWidth; - const posY = pyIndex * precinctIterationSizes.minHeight; - if ( - posX % sizeInImageScale.width !== 0 || - posY % sizeInImageScale.height !== 0 - ) { - return null; - } - const startPrecinctRowIndex = - (posY / sizeInImageScale.width) * - resolution.precinctParameters.numprecinctswide; - return posX / sizeInImageScale.height + startPrecinctRowIndex; + return null; } - function getPrecinctSizesInImageScale(tile) { - const componentsCount = tile.components.length; - let minWidth = Number.MAX_VALUE; - let minHeight = Number.MAX_VALUE; - let maxNumWide = 0; - let maxNumHigh = 0; - const sizePerComponent = new Array(componentsCount); - for (let c = 0; c < componentsCount; c++) { - const component = tile.components[c]; - const decompositionLevelsCount = - component.codingStyleParameters.decompositionLevelsCount; - const sizePerResolution = new Array(decompositionLevelsCount + 1); - let minWidthCurrentComponent = Number.MAX_VALUE; - let minHeightCurrentComponent = Number.MAX_VALUE; - let maxNumWideCurrentComponent = 0; - let maxNumHighCurrentComponent = 0; - let scale = 1; - for (let r = decompositionLevelsCount; r >= 0; --r) { - const resolution = component.resolutions[r]; - const widthCurrentResolution = - scale * resolution.precinctParameters.precinctWidth; - const heightCurrentResolution = - scale * resolution.precinctParameters.precinctHeight; - minWidthCurrentComponent = Math.min( - minWidthCurrentComponent, - widthCurrentResolution - ); - minHeightCurrentComponent = Math.min( - minHeightCurrentComponent, - heightCurrentResolution - ); - maxNumWideCurrentComponent = Math.max( - maxNumWideCurrentComponent, - resolution.precinctParameters.numprecinctswide - ); - maxNumHighCurrentComponent = Math.max( - maxNumHighCurrentComponent, - resolution.precinctParameters.numprecinctshigh - ); - sizePerResolution[r] = { - width: widthCurrentResolution, - height: heightCurrentResolution, - }; - scale <<= 1; - } - minWidth = Math.min(minWidth, minWidthCurrentComponent); - minHeight = Math.min(minHeight, minHeightCurrentComponent); - maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent); - maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent); - sizePerComponent[c] = { - resolutions: sizePerResolution, - minWidth: minWidthCurrentComponent, - minHeight: minHeightCurrentComponent, - maxNumWide: maxNumWideCurrentComponent, - maxNumHigh: maxNumHighCurrentComponent, + const startPrecinctRowIndex = + (posY / sizeInImageScale.width) * + resolution.precinctParameters.numprecinctswide; + return posX / sizeInImageScale.height + startPrecinctRowIndex; +} +function getPrecinctSizesInImageScale(tile) { + const componentsCount = tile.components.length; + let minWidth = Number.MAX_VALUE; + let minHeight = Number.MAX_VALUE; + let maxNumWide = 0; + let maxNumHigh = 0; + const sizePerComponent = new Array(componentsCount); + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + const sizePerResolution = new Array(decompositionLevelsCount + 1); + let minWidthCurrentComponent = Number.MAX_VALUE; + let minHeightCurrentComponent = Number.MAX_VALUE; + let maxNumWideCurrentComponent = 0; + let maxNumHighCurrentComponent = 0; + let scale = 1; + for (let r = decompositionLevelsCount; r >= 0; --r) { + const resolution = component.resolutions[r]; + const widthCurrentResolution = + scale * resolution.precinctParameters.precinctWidth; + const heightCurrentResolution = + scale * resolution.precinctParameters.precinctHeight; + minWidthCurrentComponent = Math.min( + minWidthCurrentComponent, + widthCurrentResolution + ); + minHeightCurrentComponent = Math.min( + minHeightCurrentComponent, + heightCurrentResolution + ); + maxNumWideCurrentComponent = Math.max( + maxNumWideCurrentComponent, + resolution.precinctParameters.numprecinctswide + ); + maxNumHighCurrentComponent = Math.max( + maxNumHighCurrentComponent, + resolution.precinctParameters.numprecinctshigh + ); + sizePerResolution[r] = { + width: widthCurrentResolution, + height: heightCurrentResolution, }; + scale <<= 1; } - return { - components: sizePerComponent, - minWidth, - minHeight, - maxNumWide, - maxNumHigh, + minWidth = Math.min(minWidth, minWidthCurrentComponent); + minHeight = Math.min(minHeight, minHeightCurrentComponent); + maxNumWide = Math.max(maxNumWide, maxNumWideCurrentComponent); + maxNumHigh = Math.max(maxNumHigh, maxNumHighCurrentComponent); + sizePerComponent[c] = { + resolutions: sizePerResolution, + minWidth: minWidthCurrentComponent, + minHeight: minHeightCurrentComponent, + maxNumWide: maxNumWideCurrentComponent, + maxNumHigh: maxNumHighCurrentComponent, }; } - function buildPackets(context) { - const siz = context.SIZ; - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const componentsCount = siz.Csiz; - // Creating resolutions and sub-bands for each component - for (let c = 0; c < componentsCount; c++) { - const component = tile.components[c]; - const decompositionLevelsCount = - component.codingStyleParameters.decompositionLevelsCount; - // Section B.5 Resolution levels and sub-bands - const resolutions = []; - const subbands = []; - for (let r = 0; r <= decompositionLevelsCount; r++) { - const blocksDimensions = getBlocksDimensions(context, component, r); - const resolution = {}; - const scale = 1 << (decompositionLevelsCount - r); - resolution.trx0 = Math.ceil(component.tcx0 / scale); - resolution.try0 = Math.ceil(component.tcy0 / scale); - resolution.trx1 = Math.ceil(component.tcx1 / scale); - resolution.try1 = Math.ceil(component.tcy1 / scale); - resolution.resLevel = r; - buildPrecincts(context, resolution, blocksDimensions); - resolutions.push(resolution); + return { + components: sizePerComponent, + minWidth, + minHeight, + maxNumWide, + maxNumHigh, + }; +} +function buildPackets(context) { + const siz = context.SIZ; + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const componentsCount = siz.Csiz; + // Creating resolutions and sub-bands for each component + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const decompositionLevelsCount = + component.codingStyleParameters.decompositionLevelsCount; + // Section B.5 Resolution levels and sub-bands + const resolutions = []; + const subbands = []; + for (let r = 0; r <= decompositionLevelsCount; r++) { + const blocksDimensions = getBlocksDimensions(context, component, r); + const resolution = {}; + const scale = 1 << (decompositionLevelsCount - r); + resolution.trx0 = Math.ceil(component.tcx0 / scale); + resolution.try0 = Math.ceil(component.tcy0 / scale); + resolution.trx1 = Math.ceil(component.tcx1 / scale); + resolution.try1 = Math.ceil(component.tcy1 / scale); + resolution.resLevel = r; + buildPrecincts(context, resolution, blocksDimensions); + resolutions.push(resolution); - let subband; - if (r === 0) { - // one sub-band (LL) with last decomposition - subband = {}; - subband.type = "LL"; - subband.tbx0 = Math.ceil(component.tcx0 / scale); - subband.tby0 = Math.ceil(component.tcy0 / scale); - subband.tbx1 = Math.ceil(component.tcx1 / scale); - subband.tby1 = Math.ceil(component.tcy1 / scale); - subband.resolution = resolution; - buildCodeblocks(context, subband, blocksDimensions); - subbands.push(subband); - resolution.subbands = [subband]; - } else { - const bscale = 1 << (decompositionLevelsCount - r + 1); - const resolutionSubbands = []; - // three sub-bands (HL, LH and HH) with rest of decompositions - subband = {}; - subband.type = "HL"; - subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); - subband.tby0 = Math.ceil(component.tcy0 / bscale); - subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); - subband.tby1 = Math.ceil(component.tcy1 / bscale); - subband.resolution = resolution; - buildCodeblocks(context, subband, blocksDimensions); - subbands.push(subband); - resolutionSubbands.push(subband); + let subband; + if (r === 0) { + // one sub-band (LL) with last decomposition + subband = {}; + subband.type = "LL"; + subband.tbx0 = Math.ceil(component.tcx0 / scale); + subband.tby0 = Math.ceil(component.tcy0 / scale); + subband.tbx1 = Math.ceil(component.tcx1 / scale); + subband.tby1 = Math.ceil(component.tcy1 / scale); + subband.resolution = resolution; + buildCodeblocks(context, subband, blocksDimensions); + subbands.push(subband); + resolution.subbands = [subband]; + } else { + const bscale = 1 << (decompositionLevelsCount - r + 1); + const resolutionSubbands = []; + // three sub-bands (HL, LH and HH) with rest of decompositions + subband = {}; + subband.type = "HL"; + subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); + subband.tby0 = Math.ceil(component.tcy0 / bscale); + subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); + subband.tby1 = Math.ceil(component.tcy1 / bscale); + subband.resolution = resolution; + buildCodeblocks(context, subband, blocksDimensions); + subbands.push(subband); + resolutionSubbands.push(subband); - subband = {}; - subband.type = "LH"; - subband.tbx0 = Math.ceil(component.tcx0 / bscale); - subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); - subband.tbx1 = Math.ceil(component.tcx1 / bscale); - subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); - subband.resolution = resolution; - buildCodeblocks(context, subband, blocksDimensions); - subbands.push(subband); - resolutionSubbands.push(subband); + subband = {}; + subband.type = "LH"; + subband.tbx0 = Math.ceil(component.tcx0 / bscale); + subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); + subband.tbx1 = Math.ceil(component.tcx1 / bscale); + subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); + subband.resolution = resolution; + buildCodeblocks(context, subband, blocksDimensions); + subbands.push(subband); + resolutionSubbands.push(subband); - subband = {}; - subband.type = "HH"; - subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); - subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); - subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); - subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); - subband.resolution = resolution; - buildCodeblocks(context, subband, blocksDimensions); - subbands.push(subband); - resolutionSubbands.push(subband); + subband = {}; + subband.type = "HH"; + subband.tbx0 = Math.ceil(component.tcx0 / bscale - 0.5); + subband.tby0 = Math.ceil(component.tcy0 / bscale - 0.5); + subband.tbx1 = Math.ceil(component.tcx1 / bscale - 0.5); + subband.tby1 = Math.ceil(component.tcy1 / bscale - 0.5); + subband.resolution = resolution; + buildCodeblocks(context, subband, blocksDimensions); + subbands.push(subband); + resolutionSubbands.push(subband); - resolution.subbands = resolutionSubbands; - } + resolution.subbands = resolutionSubbands; } - component.resolutions = resolutions; - component.subbands = subbands; } - // Generate the packets sequence - const progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; - switch (progressionOrder) { - case 0: - tile.packetsIterator = new LayerResolutionComponentPositionIterator( - context - ); - break; - case 1: - tile.packetsIterator = new ResolutionLayerComponentPositionIterator( - context - ); - break; - case 2: - tile.packetsIterator = new ResolutionPositionComponentLayerIterator( - context - ); - break; - case 3: - tile.packetsIterator = new PositionComponentResolutionLayerIterator( - context - ); - break; - case 4: - tile.packetsIterator = new ComponentPositionResolutionLayerIterator( - context - ); - break; - default: - throw new JpxError(`Unsupported progression order ${progressionOrder}`); + component.resolutions = resolutions; + component.subbands = subbands; + } + // Generate the packets sequence + const progressionOrder = tile.codingStyleDefaultParameters.progressionOrder; + switch (progressionOrder) { + case 0: + tile.packetsIterator = new LayerResolutionComponentPositionIterator( + context + ); + break; + case 1: + tile.packetsIterator = new ResolutionLayerComponentPositionIterator( + context + ); + break; + case 2: + tile.packetsIterator = new ResolutionPositionComponentLayerIterator( + context + ); + break; + case 3: + tile.packetsIterator = new PositionComponentResolutionLayerIterator( + context + ); + break; + case 4: + tile.packetsIterator = new ComponentPositionResolutionLayerIterator( + context + ); + break; + default: + throw new JpxError(`Unsupported progression order ${progressionOrder}`); + } +} +function parseTilePackets(context, data, offset, dataLength) { + let position = 0; + let buffer, + bufferSize = 0, + skipNextBit = false; + function readBits(count) { + while (bufferSize < count) { + const b = data[offset + position]; + position++; + if (skipNextBit) { + buffer = (buffer << 7) | b; + bufferSize += 7; + skipNextBit = false; + } else { + buffer = (buffer << 8) | b; + bufferSize += 8; + } + if (b === 0xff) { + skipNextBit = true; + } + } + bufferSize -= count; + return (buffer >>> bufferSize) & ((1 << count) - 1); + } + function skipMarkerIfEqual(value) { + if ( + data[offset + position - 1] === 0xff && + data[offset + position] === value + ) { + skipBytes(1); + return true; + } else if ( + data[offset + position] === 0xff && + data[offset + position + 1] === value + ) { + skipBytes(2); + return true; + } + return false; + } + function skipBytes(count) { + position += count; + } + function alignToByte() { + bufferSize = 0; + if (skipNextBit) { + position++; + skipNextBit = false; } } - function parseTilePackets(context, data, offset, dataLength) { - let position = 0; - let buffer, - bufferSize = 0, - skipNextBit = false; - function readBits(count) { - while (bufferSize < count) { - const b = data[offset + position]; - position++; - if (skipNextBit) { - buffer = (buffer << 7) | b; - bufferSize += 7; - skipNextBit = false; + function readCodingpasses() { + if (readBits(1) === 0) { + return 1; + } + if (readBits(1) === 0) { + return 2; + } + let value = readBits(2); + if (value < 3) { + return value + 3; + } + value = readBits(5); + if (value < 31) { + return value + 6; + } + value = readBits(7); + return value + 37; + } + const tileIndex = context.currentTile.index; + const tile = context.tiles[tileIndex]; + const sopMarkerUsed = context.COD.sopMarkerUsed; + const ephMarkerUsed = context.COD.ephMarkerUsed; + const packetsIterator = tile.packetsIterator; + while (position < dataLength) { + alignToByte(); + if (sopMarkerUsed && skipMarkerIfEqual(0x91)) { + // Skip also marker segment length and packet sequence ID + skipBytes(4); + } + const packet = packetsIterator.nextPacket(); + if (!readBits(1)) { + continue; + } + const layerNumber = packet.layerNumber, + queue = []; + let codeblock; + for (let i = 0, ii = packet.codeblocks.length; i < ii; i++) { + codeblock = packet.codeblocks[i]; + let precinct = codeblock.precinct; + const codeblockColumn = codeblock.cbx - precinct.cbxMin; + const codeblockRow = codeblock.cby - precinct.cbyMin; + let codeblockIncluded = false; + let firstTimeInclusion = false; + let valueReady, zeroBitPlanesTree; + if (codeblock.included !== undefined) { + codeblockIncluded = !!readBits(1); + } else { + // reading inclusion tree + precinct = codeblock.precinct; + let inclusionTree; + if (precinct.inclusionTree !== undefined) { + inclusionTree = precinct.inclusionTree; } else { - buffer = (buffer << 8) | b; - bufferSize += 8; + // building inclusion and zero bit-planes trees + const width = precinct.cbxMax - precinct.cbxMin + 1; + const height = precinct.cbyMax - precinct.cbyMin + 1; + inclusionTree = new InclusionTree(width, height, layerNumber); + zeroBitPlanesTree = new TagTree(width, height); + precinct.inclusionTree = inclusionTree; + precinct.zeroBitPlanesTree = zeroBitPlanesTree; } - if (b === 0xff) { - skipNextBit = true; - } - } - bufferSize -= count; - return (buffer >>> bufferSize) & ((1 << count) - 1); - } - function skipMarkerIfEqual(value) { - if ( - data[offset + position - 1] === 0xff && - data[offset + position] === value - ) { - skipBytes(1); - return true; - } else if ( - data[offset + position] === 0xff && - data[offset + position + 1] === value - ) { - skipBytes(2); - return true; - } - return false; - } - function skipBytes(count) { - position += count; - } - function alignToByte() { - bufferSize = 0; - if (skipNextBit) { - position++; - skipNextBit = false; - } - } - function readCodingpasses() { - if (readBits(1) === 0) { - return 1; - } - if (readBits(1) === 0) { - return 2; - } - let value = readBits(2); - if (value < 3) { - return value + 3; - } - value = readBits(5); - if (value < 31) { - return value + 6; - } - value = readBits(7); - return value + 37; - } - const tileIndex = context.currentTile.index; - const tile = context.tiles[tileIndex]; - const sopMarkerUsed = context.COD.sopMarkerUsed; - const ephMarkerUsed = context.COD.ephMarkerUsed; - const packetsIterator = tile.packetsIterator; - while (position < dataLength) { - alignToByte(); - if (sopMarkerUsed && skipMarkerIfEqual(0x91)) { - // Skip also marker segment length and packet sequence ID - skipBytes(4); - } - const packet = packetsIterator.nextPacket(); - if (!readBits(1)) { - continue; - } - const layerNumber = packet.layerNumber, - queue = []; - let codeblock; - for (let i = 0, ii = packet.codeblocks.length; i < ii; i++) { - codeblock = packet.codeblocks[i]; - let precinct = codeblock.precinct; - const codeblockColumn = codeblock.cbx - precinct.cbxMin; - const codeblockRow = codeblock.cby - precinct.cbyMin; - let codeblockIncluded = false; - let firstTimeInclusion = false; - let valueReady, zeroBitPlanesTree; - if (codeblock.included !== undefined) { - codeblockIncluded = !!readBits(1); - } else { - // reading inclusion tree - precinct = codeblock.precinct; - let inclusionTree; - if (precinct.inclusionTree !== undefined) { - inclusionTree = precinct.inclusionTree; - } else { - // building inclusion and zero bit-planes trees - const width = precinct.cbxMax - precinct.cbxMin + 1; - const height = precinct.cbyMax - precinct.cbyMin + 1; - inclusionTree = new InclusionTree(width, height, layerNumber); - zeroBitPlanesTree = new TagTree(width, height); - precinct.inclusionTree = inclusionTree; - precinct.zeroBitPlanesTree = zeroBitPlanesTree; - } - if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) { - while (true) { - if (readBits(1)) { - valueReady = !inclusionTree.nextLevel(); - if (valueReady) { - codeblock.included = true; - codeblockIncluded = firstTimeInclusion = true; - break; - } - } else { - inclusionTree.incrementValue(layerNumber); - break; - } - } - } - } - if (!codeblockIncluded) { - continue; - } - if (firstTimeInclusion) { - zeroBitPlanesTree = precinct.zeroBitPlanesTree; - zeroBitPlanesTree.reset(codeblockColumn, codeblockRow); + if (inclusionTree.reset(codeblockColumn, codeblockRow, layerNumber)) { while (true) { if (readBits(1)) { - valueReady = !zeroBitPlanesTree.nextLevel(); + valueReady = !inclusionTree.nextLevel(); if (valueReady) { + codeblock.included = true; + codeblockIncluded = firstTimeInclusion = true; break; } } else { - zeroBitPlanesTree.incrementValue(); + inclusionTree.incrementValue(layerNumber); + break; } } - codeblock.zeroBitPlanes = zeroBitPlanesTree.value; } - const codingpasses = readCodingpasses(); - while (readBits(1)) { - codeblock.Lblock++; - } - const codingpassesLog2 = log2(codingpasses); - // rounding down log2 - const bits = - (codingpasses < 1 << codingpassesLog2 - ? codingpassesLog2 - 1 - : codingpassesLog2) + codeblock.Lblock; - const codedDataLength = readBits(bits); - queue.push({ - codeblock, - codingpasses, - dataLength: codedDataLength, - }); } - alignToByte(); - if (ephMarkerUsed) { - skipMarkerIfEqual(0x92); - } - while (queue.length > 0) { - const packetItem = queue.shift(); - codeblock = packetItem.codeblock; - if (codeblock.data === undefined) { - codeblock.data = []; - } - codeblock.data.push({ - data, - start: offset + position, - end: offset + position + packetItem.dataLength, - codingpasses: packetItem.codingpasses, - }); - position += packetItem.dataLength; - } - } - return position; - } - function copyCoefficients( - coefficients, - levelWidth, - levelHeight, - subband, - delta, - mb, - reversible, - segmentationSymbolUsed - ) { - const x0 = subband.tbx0; - const y0 = subband.tby0; - const width = subband.tbx1 - subband.tbx0; - const codeblocks = subband.codeblocks; - const right = subband.type.charAt(0) === "H" ? 1 : 0; - const bottom = subband.type.charAt(1) === "H" ? levelWidth : 0; - - for (let i = 0, ii = codeblocks.length; i < ii; ++i) { - const codeblock = codeblocks[i]; - const blockWidth = codeblock.tbx1_ - codeblock.tbx0_; - const blockHeight = codeblock.tby1_ - codeblock.tby0_; - if (blockWidth === 0 || blockHeight === 0) { + if (!codeblockIncluded) { continue; } - if (codeblock.data === undefined) { - continue; - } - - const bitModel = new BitModel( - blockWidth, - blockHeight, - codeblock.subbandType, - codeblock.zeroBitPlanes, - mb - ); - let currentCodingpassType = 2; // first bit plane starts from cleanup - - // collect data - const data = codeblock.data; - let totalLength = 0, - codingpasses = 0; - let j, jj, dataItem; - for (j = 0, jj = data.length; j < jj; j++) { - dataItem = data[j]; - totalLength += dataItem.end - dataItem.start; - codingpasses += dataItem.codingpasses; - } - const encodedData = new Uint8Array(totalLength); - let position = 0; - for (j = 0, jj = data.length; j < jj; j++) { - dataItem = data[j]; - const chunk = dataItem.data.subarray(dataItem.start, dataItem.end); - encodedData.set(chunk, position); - position += chunk.length; - } - // decoding the item - const decoder = new ArithmeticDecoder(encodedData, 0, totalLength); - bitModel.setDecoder(decoder); - - for (j = 0; j < codingpasses; j++) { - switch (currentCodingpassType) { - case 0: - bitModel.runSignificancePropagationPass(); - break; - case 1: - bitModel.runMagnitudeRefinementPass(); - break; - case 2: - bitModel.runCleanupPass(); - if (segmentationSymbolUsed) { - bitModel.checkSegmentationSymbol(); - } - break; - } - currentCodingpassType = (currentCodingpassType + 1) % 3; - } - - let offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width; - const sign = bitModel.coefficentsSign; - const magnitude = bitModel.coefficentsMagnitude; - const bitsDecoded = bitModel.bitsDecoded; - const magnitudeCorrection = reversible ? 0 : 0.5; - let k, n, nb; - position = 0; - // Do the interleaving of Section F.3.3 here, so we do not need - // to copy later. LL level is not interleaved, just copied. - const interleave = subband.type !== "LL"; - for (j = 0; j < blockHeight; j++) { - const row = (offset / width) | 0; // row in the non-interleaved subband - const levelOffset = 2 * row * (levelWidth - width) + right + bottom; - for (k = 0; k < blockWidth; k++) { - n = magnitude[position]; - if (n !== 0) { - n = (n + magnitudeCorrection) * delta; - if (sign[position] !== 0) { - n = -n; - } - nb = bitsDecoded[position]; - const pos = interleave ? levelOffset + (offset << 1) : offset; - if (reversible && nb >= mb) { - coefficients[pos] = n; - } else { - coefficients[pos] = n * (1 << (mb - nb)); + if (firstTimeInclusion) { + zeroBitPlanesTree = precinct.zeroBitPlanesTree; + zeroBitPlanesTree.reset(codeblockColumn, codeblockRow); + while (true) { + if (readBits(1)) { + valueReady = !zeroBitPlanesTree.nextLevel(); + if (valueReady) { + break; } + } else { + zeroBitPlanesTree.incrementValue(); } - offset++; - position++; } - offset += width - blockWidth; + codeblock.zeroBitPlanes = zeroBitPlanesTree.value; } - } - } - function transformTile(context, tile, c) { - const component = tile.components[c]; - const codingStyleParameters = component.codingStyleParameters; - const quantizationParameters = component.quantizationParameters; - const decompositionLevelsCount = - codingStyleParameters.decompositionLevelsCount; - const spqcds = quantizationParameters.SPqcds; - const scalarExpounded = quantizationParameters.scalarExpounded; - const guardBits = quantizationParameters.guardBits; - const segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; - const precision = context.components[c].precision; - - const reversible = codingStyleParameters.reversibleTransformation; - const transform = reversible - ? new ReversibleTransform() - : new IrreversibleTransform(); - - const subbandCoefficients = []; - let b = 0; - for (let i = 0; i <= decompositionLevelsCount; i++) { - const resolution = component.resolutions[i]; - - const width = resolution.trx1 - resolution.trx0; - const height = resolution.try1 - resolution.try0; - // Allocate space for the whole sublevel. - const coefficients = new Float32Array(width * height); - - for (let j = 0, jj = resolution.subbands.length; j < jj; j++) { - let mu, epsilon; - if (!scalarExpounded) { - // formula E-5 - mu = spqcds[0].mu; - epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0); - } else { - mu = spqcds[b].mu; - epsilon = spqcds[b].epsilon; - b++; - } - - const subband = resolution.subbands[j]; - const gainLog2 = SubbandsGainLog2[subband.type]; - - // calculate quantization coefficient (Section E.1.1.1) - const delta = reversible - ? 1 - : 2 ** (precision + gainLog2 - epsilon) * (1 + mu / 2048); - const mb = guardBits + epsilon - 1; - - // In the first resolution level, copyCoefficients will fill the - // whole array with coefficients. In the succeeding passes, - // copyCoefficients will consecutively fill in the values that belong - // to the interleaved positions of the HL, LH, and HH coefficients. - // The LL coefficients will then be interleaved in Transform.iterate(). - copyCoefficients( - coefficients, - width, - height, - subband, - delta, - mb, - reversible, - segmentationSymbolUsed - ); + const codingpasses = readCodingpasses(); + while (readBits(1)) { + codeblock.Lblock++; } - subbandCoefficients.push({ - width, - height, - items: coefficients, + const codingpassesLog2 = log2(codingpasses); + // rounding down log2 + const bits = + (codingpasses < 1 << codingpassesLog2 + ? codingpassesLog2 - 1 + : codingpassesLog2) + codeblock.Lblock; + const codedDataLength = readBits(bits); + queue.push({ + codeblock, + codingpasses, + dataLength: codedDataLength, }); } - - const result = transform.calculate( - subbandCoefficients, - component.tcx0, - component.tcy0 - ); - return { - left: component.tcx0, - top: component.tcy0, - width: result.width, - height: result.height, - items: result.items, - }; - } - function transformComponents(context) { - const siz = context.SIZ; - const components = context.components; - const componentsCount = siz.Csiz; - const resultImages = []; - for (let i = 0, ii = context.tiles.length; i < ii; i++) { - const tile = context.tiles[i]; - const transformedTiles = []; - for (let c = 0; c < componentsCount; c++) { - transformedTiles[c] = transformTile(context, tile, c); + alignToByte(); + if (ephMarkerUsed) { + skipMarkerIfEqual(0x92); + } + while (queue.length > 0) { + const packetItem = queue.shift(); + codeblock = packetItem.codeblock; + if (codeblock.data === undefined) { + codeblock.data = []; } - const tile0 = transformedTiles[0]; - const out = new Uint8ClampedArray(tile0.items.length * componentsCount); - const result = { - left: tile0.left, - top: tile0.top, - width: tile0.width, - height: tile0.height, - items: out, - }; + codeblock.data.push({ + data, + start: offset + position, + end: offset + position + packetItem.dataLength, + codingpasses: packetItem.codingpasses, + }); + position += packetItem.dataLength; + } + } + return position; +} +function copyCoefficients( + coefficients, + levelWidth, + levelHeight, + subband, + delta, + mb, + reversible, + segmentationSymbolUsed +) { + const x0 = subband.tbx0; + const y0 = subband.tby0; + const width = subband.tbx1 - subband.tbx0; + const codeblocks = subband.codeblocks; + const right = subband.type.charAt(0) === "H" ? 1 : 0; + const bottom = subband.type.charAt(1) === "H" ? levelWidth : 0; - // Section G.2.2 Inverse multi component transform - let shift, offset; - let pos = 0, - j, - jj, - y0, - y1, - y2; - if (tile.codingStyleDefaultParameters.multipleComponentTransform) { - const fourComponents = componentsCount === 4; - const y0items = transformedTiles[0].items; - const y1items = transformedTiles[1].items; - const y2items = transformedTiles[2].items; - const y3items = fourComponents ? transformedTiles[3].items : null; + for (let i = 0, ii = codeblocks.length; i < ii; ++i) { + const codeblock = codeblocks[i]; + const blockWidth = codeblock.tbx1_ - codeblock.tbx0_; + const blockHeight = codeblock.tby1_ - codeblock.tby0_; + if (blockWidth === 0 || blockHeight === 0) { + continue; + } + if (codeblock.data === undefined) { + continue; + } - // HACK: The multiple component transform formulas below assume that - // all components have the same precision. With this in mind, we - // compute shift and offset only once. - shift = components[0].precision - 8; - offset = (128 << shift) + 0.5; + const bitModel = new BitModel( + blockWidth, + blockHeight, + codeblock.subbandType, + codeblock.zeroBitPlanes, + mb + ); + let currentCodingpassType = 2; // first bit plane starts from cleanup - const component0 = tile.components[0]; - const alpha01 = componentsCount - 3; - jj = y0items.length; - if (!component0.codingStyleParameters.reversibleTransformation) { - // inverse irreversible multiple component transform - for (j = 0; j < jj; j++, pos += alpha01) { - y0 = y0items[j] + offset; - y1 = y1items[j]; - y2 = y2items[j]; - out[pos++] = (y0 + 1.402 * y2) >> shift; - out[pos++] = (y0 - 0.34413 * y1 - 0.71414 * y2) >> shift; - out[pos++] = (y0 + 1.772 * y1) >> shift; + // collect data + const data = codeblock.data; + let totalLength = 0, + codingpasses = 0; + let j, jj, dataItem; + for (j = 0, jj = data.length; j < jj; j++) { + dataItem = data[j]; + totalLength += dataItem.end - dataItem.start; + codingpasses += dataItem.codingpasses; + } + const encodedData = new Uint8Array(totalLength); + let position = 0; + for (j = 0, jj = data.length; j < jj; j++) { + dataItem = data[j]; + const chunk = dataItem.data.subarray(dataItem.start, dataItem.end); + encodedData.set(chunk, position); + position += chunk.length; + } + // decoding the item + const decoder = new ArithmeticDecoder(encodedData, 0, totalLength); + bitModel.setDecoder(decoder); + + for (j = 0; j < codingpasses; j++) { + switch (currentCodingpassType) { + case 0: + bitModel.runSignificancePropagationPass(); + break; + case 1: + bitModel.runMagnitudeRefinementPass(); + break; + case 2: + bitModel.runCleanupPass(); + if (segmentationSymbolUsed) { + bitModel.checkSegmentationSymbol(); } - } else { - // inverse reversible multiple component transform - for (j = 0; j < jj; j++, pos += alpha01) { - y0 = y0items[j] + offset; - y1 = y1items[j]; - y2 = y2items[j]; - const g = y0 - ((y2 + y1) >> 2); + break; + } + currentCodingpassType = (currentCodingpassType + 1) % 3; + } - out[pos++] = (g + y2) >> shift; - out[pos++] = g >> shift; - out[pos++] = (g + y1) >> shift; + let offset = codeblock.tbx0_ - x0 + (codeblock.tby0_ - y0) * width; + const sign = bitModel.coefficentsSign; + const magnitude = bitModel.coefficentsMagnitude; + const bitsDecoded = bitModel.bitsDecoded; + const magnitudeCorrection = reversible ? 0 : 0.5; + let k, n, nb; + position = 0; + // Do the interleaving of Section F.3.3 here, so we do not need + // to copy later. LL level is not interleaved, just copied. + const interleave = subband.type !== "LL"; + for (j = 0; j < blockHeight; j++) { + const row = (offset / width) | 0; // row in the non-interleaved subband + const levelOffset = 2 * row * (levelWidth - width) + right + bottom; + for (k = 0; k < blockWidth; k++) { + n = magnitude[position]; + if (n !== 0) { + n = (n + magnitudeCorrection) * delta; + if (sign[position] !== 0) { + n = -n; + } + nb = bitsDecoded[position]; + const pos = interleave ? levelOffset + (offset << 1) : offset; + if (reversible && nb >= mb) { + coefficients[pos] = n; + } else { + coefficients[pos] = n * (1 << (mb - nb)); } } - if (fourComponents) { - for (j = 0, pos = 3; j < jj; j++, pos += 4) { - out[pos] = (y3items[j] + offset) >> shift; - } + offset++; + position++; + } + offset += width - blockWidth; + } + } +} +function transformTile(context, tile, c) { + const component = tile.components[c]; + const codingStyleParameters = component.codingStyleParameters; + const quantizationParameters = component.quantizationParameters; + const decompositionLevelsCount = + codingStyleParameters.decompositionLevelsCount; + const spqcds = quantizationParameters.SPqcds; + const scalarExpounded = quantizationParameters.scalarExpounded; + const guardBits = quantizationParameters.guardBits; + const segmentationSymbolUsed = codingStyleParameters.segmentationSymbolUsed; + const precision = context.components[c].precision; + + const reversible = codingStyleParameters.reversibleTransformation; + const transform = reversible + ? new ReversibleTransform() + : new IrreversibleTransform(); + + const subbandCoefficients = []; + let b = 0; + for (let i = 0; i <= decompositionLevelsCount; i++) { + const resolution = component.resolutions[i]; + + const width = resolution.trx1 - resolution.trx0; + const height = resolution.try1 - resolution.try0; + // Allocate space for the whole sublevel. + const coefficients = new Float32Array(width * height); + + for (let j = 0, jj = resolution.subbands.length; j < jj; j++) { + let mu, epsilon; + if (!scalarExpounded) { + // formula E-5 + mu = spqcds[0].mu; + epsilon = spqcds[0].epsilon + (i > 0 ? 1 - i : 0); + } else { + mu = spqcds[b].mu; + epsilon = spqcds[b].epsilon; + b++; + } + + const subband = resolution.subbands[j]; + const gainLog2 = SubbandsGainLog2[subband.type]; + + // calculate quantization coefficient (Section E.1.1.1) + const delta = reversible + ? 1 + : 2 ** (precision + gainLog2 - epsilon) * (1 + mu / 2048); + const mb = guardBits + epsilon - 1; + + // In the first resolution level, copyCoefficients will fill the + // whole array with coefficients. In the succeeding passes, + // copyCoefficients will consecutively fill in the values that belong + // to the interleaved positions of the HL, LH, and HH coefficients. + // The LL coefficients will then be interleaved in Transform.iterate(). + copyCoefficients( + coefficients, + width, + height, + subband, + delta, + mb, + reversible, + segmentationSymbolUsed + ); + } + subbandCoefficients.push({ + width, + height, + items: coefficients, + }); + } + + const result = transform.calculate( + subbandCoefficients, + component.tcx0, + component.tcy0 + ); + return { + left: component.tcx0, + top: component.tcy0, + width: result.width, + height: result.height, + items: result.items, + }; +} +function transformComponents(context) { + const siz = context.SIZ; + const components = context.components; + const componentsCount = siz.Csiz; + const resultImages = []; + for (let i = 0, ii = context.tiles.length; i < ii; i++) { + const tile = context.tiles[i]; + const transformedTiles = []; + for (let c = 0; c < componentsCount; c++) { + transformedTiles[c] = transformTile(context, tile, c); + } + const tile0 = transformedTiles[0]; + const out = new Uint8ClampedArray(tile0.items.length * componentsCount); + const result = { + left: tile0.left, + top: tile0.top, + width: tile0.width, + height: tile0.height, + items: out, + }; + + // Section G.2.2 Inverse multi component transform + let shift, offset; + let pos = 0, + j, + jj, + y0, + y1, + y2; + if (tile.codingStyleDefaultParameters.multipleComponentTransform) { + const fourComponents = componentsCount === 4; + const y0items = transformedTiles[0].items; + const y1items = transformedTiles[1].items; + const y2items = transformedTiles[2].items; + const y3items = fourComponents ? transformedTiles[3].items : null; + + // HACK: The multiple component transform formulas below assume that + // all components have the same precision. With this in mind, we + // compute shift and offset only once. + shift = components[0].precision - 8; + offset = (128 << shift) + 0.5; + + const component0 = tile.components[0]; + const alpha01 = componentsCount - 3; + jj = y0items.length; + if (!component0.codingStyleParameters.reversibleTransformation) { + // inverse irreversible multiple component transform + for (j = 0; j < jj; j++, pos += alpha01) { + y0 = y0items[j] + offset; + y1 = y1items[j]; + y2 = y2items[j]; + out[pos++] = (y0 + 1.402 * y2) >> shift; + out[pos++] = (y0 - 0.34413 * y1 - 0.71414 * y2) >> shift; + out[pos++] = (y0 + 1.772 * y1) >> shift; } } else { - // no multi-component transform - for (let c = 0; c < componentsCount; c++) { - const items = transformedTiles[c].items; - shift = components[c].precision - 8; - offset = (128 << shift) + 0.5; - for (pos = c, j = 0, jj = items.length; j < jj; j++) { - out[pos] = (items[j] + offset) >> shift; - pos += componentsCount; - } + // inverse reversible multiple component transform + for (j = 0; j < jj; j++, pos += alpha01) { + y0 = y0items[j] + offset; + y1 = y1items[j]; + y2 = y2items[j]; + const g = y0 - ((y2 + y1) >> 2); + + out[pos++] = (g + y2) >> shift; + out[pos++] = g >> shift; + out[pos++] = (g + y1) >> shift; + } + } + if (fourComponents) { + for (j = 0, pos = 3; j < jj; j++, pos += 4) { + out[pos] = (y3items[j] + offset) >> shift; + } + } + } else { + // no multi-component transform + for (let c = 0; c < componentsCount; c++) { + const items = transformedTiles[c].items; + shift = components[c].precision - 8; + offset = (128 << shift) + 0.5; + for (pos = c, j = 0, jj = items.length; j < jj; j++) { + out[pos] = (items[j] + offset) >> shift; + pos += componentsCount; } } - resultImages.push(result); } - return resultImages; + resultImages.push(result); } - function initializeTile(context, tileIndex) { - const siz = context.SIZ; - const componentsCount = siz.Csiz; - const tile = context.tiles[tileIndex]; - for (let c = 0; c < componentsCount; c++) { - const component = tile.components[c]; - const qcdOrQcc = - context.currentTile.QCC[c] !== undefined - ? context.currentTile.QCC[c] - : context.currentTile.QCD; - component.quantizationParameters = qcdOrQcc; - const codOrCoc = - context.currentTile.COC[c] !== undefined - ? context.currentTile.COC[c] - : context.currentTile.COD; - component.codingStyleParameters = codOrCoc; + return resultImages; +} +function initializeTile(context, tileIndex) { + const siz = context.SIZ; + const componentsCount = siz.Csiz; + const tile = context.tiles[tileIndex]; + for (let c = 0; c < componentsCount; c++) { + const component = tile.components[c]; + const qcdOrQcc = + context.currentTile.QCC[c] !== undefined + ? context.currentTile.QCC[c] + : context.currentTile.QCD; + component.quantizationParameters = qcdOrQcc; + const codOrCoc = + context.currentTile.COC[c] !== undefined + ? context.currentTile.COC[c] + : context.currentTile.COD; + component.codingStyleParameters = codOrCoc; + } + tile.codingStyleDefaultParameters = context.currentTile.COD; +} + +// Section B.10.2 Tag trees +class TagTree { + constructor(width, height) { + const levelsLength = log2(Math.max(width, height)) + 1; + this.levels = []; + for (let i = 0; i < levelsLength; i++) { + const level = { + width, + height, + items: [], + }; + this.levels.push(level); + width = Math.ceil(width / 2); + height = Math.ceil(height / 2); } - tile.codingStyleDefaultParameters = context.currentTile.COD; } - // Section B.10.2 Tag trees - const TagTree = (function TagTreeClosure() { - // eslint-disable-next-line no-shadow - function TagTree(width, height) { - const levelsLength = log2(Math.max(width, height)) + 1; - this.levels = []; - for (let i = 0; i < levelsLength; i++) { - const level = { - width, - height, - items: [], - }; - this.levels.push(level); - width = Math.ceil(width / 2); - height = Math.ceil(height / 2); + reset(i, j) { + let currentLevel = 0, + value = 0, + level; + while (currentLevel < this.levels.length) { + level = this.levels[currentLevel]; + const index = i + j * level.width; + if (level.items[index] !== undefined) { + value = level.items[index]; + break; } + level.index = index; + i >>= 1; + j >>= 1; + currentLevel++; } - TagTree.prototype = { - reset: function TagTree_reset(i, j) { - let currentLevel = 0, - value = 0, - level; - while (currentLevel < this.levels.length) { - level = this.levels[currentLevel]; - const index = i + j * level.width; - if (level.items[index] !== undefined) { - value = level.items[index]; - break; - } - level.index = index; - i >>= 1; - j >>= 1; - currentLevel++; - } - currentLevel--; - level = this.levels[currentLevel]; - level.items[level.index] = value; - this.currentLevel = currentLevel; - delete this.value; - }, - incrementValue: function TagTree_incrementValue() { - const level = this.levels[this.currentLevel]; - level.items[level.index]++; - }, - nextLevel: function TagTree_nextLevel() { - let currentLevel = this.currentLevel; - let level = this.levels[currentLevel]; - const value = level.items[level.index]; - currentLevel--; - if (currentLevel < 0) { - this.value = value; - return false; - } + currentLevel--; + level = this.levels[currentLevel]; + level.items[level.index] = value; + this.currentLevel = currentLevel; + delete this.value; + } - this.currentLevel = currentLevel; - level = this.levels[currentLevel]; - level.items[level.index] = value; - return true; - }, - }; - return TagTree; - })(); + incrementValue() { + const level = this.levels[this.currentLevel]; + level.items[level.index]++; + } - const InclusionTree = (function InclusionTreeClosure() { - // eslint-disable-next-line no-shadow - function InclusionTree(width, height, defaultValue) { - const levelsLength = log2(Math.max(width, height)) + 1; - this.levels = []; - for (let i = 0; i < levelsLength; i++) { - const items = new Uint8Array(width * height); - for (let j = 0, jj = items.length; j < jj; j++) { - items[j] = defaultValue; - } + nextLevel() { + let currentLevel = this.currentLevel; + let level = this.levels[currentLevel]; + const value = level.items[level.index]; + currentLevel--; + if (currentLevel < 0) { + this.value = value; + return false; + } - const level = { - width, - height, - items, - }; - this.levels.push(level); + this.currentLevel = currentLevel; + level = this.levels[currentLevel]; + level.items[level.index] = value; + return true; + } +} - width = Math.ceil(width / 2); - height = Math.ceil(height / 2); +class InclusionTree { + constructor(width, height, defaultValue) { + const levelsLength = log2(Math.max(width, height)) + 1; + this.levels = []; + for (let i = 0; i < levelsLength; i++) { + const items = new Uint8Array(width * height); + for (let j = 0, jj = items.length; j < jj; j++) { + items[j] = defaultValue; } + + const level = { + width, + height, + items, + }; + this.levels.push(level); + + width = Math.ceil(width / 2); + height = Math.ceil(height / 2); } - InclusionTree.prototype = { - reset: function InclusionTree_reset(i, j, stopValue) { - let currentLevel = 0; - while (currentLevel < this.levels.length) { - const level = this.levels[currentLevel]; - const index = i + j * level.width; - level.index = index; - const value = level.items[index]; + } - if (value === 0xff) { - break; - } + reset(i, j, stopValue) { + let currentLevel = 0; + while (currentLevel < this.levels.length) { + const level = this.levels[currentLevel]; + const index = i + j * level.width; + level.index = index; + const value = level.items[index]; - if (value > stopValue) { - this.currentLevel = currentLevel; - // already know about this one, propagating the value to top levels - this.propagateValues(); - return false; - } + if (value === 0xff) { + break; + } - i >>= 1; - j >>= 1; - currentLevel++; - } - this.currentLevel = currentLevel - 1; - return true; - }, - incrementValue: function InclusionTree_incrementValue(stopValue) { - const level = this.levels[this.currentLevel]; - level.items[level.index] = stopValue + 1; + if (value > stopValue) { + this.currentLevel = currentLevel; + // already know about this one, propagating the value to top levels this.propagateValues(); - }, - propagateValues: function InclusionTree_propagateValues() { - let levelIndex = this.currentLevel; - let level = this.levels[levelIndex]; - const currentValue = level.items[level.index]; - while (--levelIndex >= 0) { - level = this.levels[levelIndex]; - level.items[level.index] = currentValue; - } - }, - nextLevel: function InclusionTree_nextLevel() { - let currentLevel = this.currentLevel; - let level = this.levels[currentLevel]; - const value = level.items[level.index]; - level.items[level.index] = 0xff; - currentLevel--; - if (currentLevel < 0) { - return false; - } + return false; + } - this.currentLevel = currentLevel; - level = this.levels[currentLevel]; - level.items[level.index] = value; - return true; - }, - }; - return InclusionTree; - })(); + i >>= 1; + j >>= 1; + currentLevel++; + } + this.currentLevel = currentLevel - 1; + return true; + } - // Section D. Coefficient bit modeling - const BitModel = (function BitModelClosure() { - const UNIFORM_CONTEXT = 17; - const RUNLENGTH_CONTEXT = 18; - // Table D-1 - // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), - // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) - // prettier-ignore - const LLAndLHContextsLabel = new Uint8Array([ + incrementValue(stopValue) { + const level = this.levels[this.currentLevel]; + level.items[level.index] = stopValue + 1; + this.propagateValues(); + } + + propagateValues() { + let levelIndex = this.currentLevel; + let level = this.levels[levelIndex]; + const currentValue = level.items[level.index]; + while (--levelIndex >= 0) { + level = this.levels[levelIndex]; + level.items[level.index] = currentValue; + } + } + + nextLevel() { + let currentLevel = this.currentLevel; + let level = this.levels[currentLevel]; + const value = level.items[level.index]; + level.items[level.index] = 0xff; + currentLevel--; + if (currentLevel < 0) { + return false; + } + + this.currentLevel = currentLevel; + level = this.levels[currentLevel]; + level.items[level.index] = value; + return true; + } +} + +// Section D. Coefficient bit modeling +const BitModel = (function BitModelClosure() { + const UNIFORM_CONTEXT = 17; + const RUNLENGTH_CONTEXT = 18; + // Table D-1 + // The index is binary presentation: 0dddvvhh, ddd - sum of Di (0..4), + // vv - sum of Vi (0..2), and hh - sum of Hi (0..2) + // prettier-ignore + const LLAndLHContextsLabel = new Uint8Array([ 0, 5, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 1, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8, 0, 0, 0, 0, 0, 2, 6, 8, 0, 3, 7, 8, 0, 4, 7, 8 ]); - // prettier-ignore - const HLContextLabel = new Uint8Array([ + // prettier-ignore + const HLContextLabel = new Uint8Array([ 0, 3, 4, 0, 5, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 1, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8, 0, 0, 0, 0, 0, 2, 3, 4, 0, 6, 7, 7, 0, 8, 8, 8 ]); - // prettier-ignore - const HHContextLabel = new Uint8Array([ + // prettier-ignore + const HHContextLabel = new Uint8Array([ 0, 1, 2, 0, 1, 2, 2, 0, 2, 2, 2, 0, 0, 0, 0, 0, 3, 4, 5, 0, 4, 5, 5, 0, 5, 5, 5, 0, 0, 0, 0, 0, 6, 7, 7, 0, 7, 7, 7, 0, 7, 7, 7, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8, 0, 0, 0, 0, 0, 8, 8, 8, 0, 8, 8, 8, 0, 8, 8, 8 ]); - // eslint-disable-next-line no-shadow - function BitModel(width, height, subband, zeroBitPlanes, mb) { + // eslint-disable-next-line no-shadow + class BitModel { + constructor(width, height, subband, zeroBitPlanes, mb) { this.width = width; this.height = height; @@ -1797,587 +1796,551 @@ const JpxImage = (function JpxImageClosure() { this.reset(); } - BitModel.prototype = { - setDecoder: function BitModel_setDecoder(decoder) { - this.decoder = decoder; - }, - reset: function BitModel_reset() { - // We have 17 contexts that are accessed via context labels, - // plus the uniform and runlength context. - this.contexts = new Int8Array(19); + setDecoder(decoder) { + this.decoder = decoder; + } - // Contexts are packed into 1 byte: - // highest 7 bits carry the index, lowest bit carries mps - this.contexts[0] = (4 << 1) | 0; - this.contexts[UNIFORM_CONTEXT] = (46 << 1) | 0; - this.contexts[RUNLENGTH_CONTEXT] = (3 << 1) | 0; - }, - setNeighborsSignificance: function BitModel_setNeighborsSignificance( - row, - column, - index - ) { - const neighborsSignificance = this.neighborsSignificance; - const width = this.width, - height = this.height; - const left = column > 0; - const right = column + 1 < width; - let i; + reset() { + // We have 17 contexts that are accessed via context labels, + // plus the uniform and runlength context. + this.contexts = new Int8Array(19); - if (row > 0) { - i = index - width; - if (left) { - neighborsSignificance[i - 1] += 0x10; - } - if (right) { - neighborsSignificance[i + 1] += 0x10; - } - neighborsSignificance[i] += 0x04; - } + // Contexts are packed into 1 byte: + // highest 7 bits carry the index, lowest bit carries mps + this.contexts[0] = (4 << 1) | 0; + this.contexts[UNIFORM_CONTEXT] = (46 << 1) | 0; + this.contexts[RUNLENGTH_CONTEXT] = (3 << 1) | 0; + } - if (row + 1 < height) { - i = index + width; - if (left) { - neighborsSignificance[i - 1] += 0x10; - } - if (right) { - neighborsSignificance[i + 1] += 0x10; - } - neighborsSignificance[i] += 0x04; - } + setNeighborsSignificance(row, column, index) { + const neighborsSignificance = this.neighborsSignificance; + const width = this.width, + height = this.height; + const left = column > 0; + const right = column + 1 < width; + let i; + if (row > 0) { + i = index - width; if (left) { - neighborsSignificance[index - 1] += 0x01; + neighborsSignificance[i - 1] += 0x10; } if (right) { - neighborsSignificance[index + 1] += 0x01; + neighborsSignificance[i + 1] += 0x10; } - neighborsSignificance[index] |= 0x80; - }, - runSignificancePropagationPass: function BitModel_runSignificancePropagationPass() { - const decoder = this.decoder; - const width = this.width, - height = this.height; - const coefficentsMagnitude = this.coefficentsMagnitude; - const coefficentsSign = this.coefficentsSign; - const neighborsSignificance = this.neighborsSignificance; - const processingFlags = this.processingFlags; - const contexts = this.contexts; - const labels = this.contextLabelTable; - const bitsDecoded = this.bitsDecoded; - const processedInverseMask = ~1; - const processedMask = 1; - const firstMagnitudeBitMask = 2; + neighborsSignificance[i] += 0x04; + } - for (let i0 = 0; i0 < height; i0 += 4) { - for (let j = 0; j < width; j++) { - let index = i0 * width + j; - for (let i1 = 0; i1 < 4; i1++, index += width) { - const i = i0 + i1; - if (i >= height) { - break; - } - // clear processed flag first - processingFlags[index] &= processedInverseMask; + if (row + 1 < height) { + i = index + width; + if (left) { + neighborsSignificance[i - 1] += 0x10; + } + if (right) { + neighborsSignificance[i + 1] += 0x10; + } + neighborsSignificance[i] += 0x04; + } - if ( - coefficentsMagnitude[index] || - !neighborsSignificance[index] - ) { - continue; - } + if (left) { + neighborsSignificance[index - 1] += 0x01; + } + if (right) { + neighborsSignificance[index + 1] += 0x01; + } + neighborsSignificance[index] |= 0x80; + } - const contextLabel = labels[neighborsSignificance[index]]; - const decision = decoder.readBit(contexts, contextLabel); - if (decision) { - const sign = this.decodeSignBit(i, j, index); - coefficentsSign[index] = sign; - coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j, index); - processingFlags[index] |= firstMagnitudeBitMask; - } - bitsDecoded[index]++; - processingFlags[index] |= processedMask; + runSignificancePropagationPass() { + const decoder = this.decoder; + const width = this.width, + height = this.height; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + const neighborsSignificance = this.neighborsSignificance; + const processingFlags = this.processingFlags; + const contexts = this.contexts; + const labels = this.contextLabelTable; + const bitsDecoded = this.bitsDecoded; + const processedInverseMask = ~1; + const processedMask = 1; + const firstMagnitudeBitMask = 2; + + for (let i0 = 0; i0 < height; i0 += 4) { + for (let j = 0; j < width; j++) { + let index = i0 * width + j; + for (let i1 = 0; i1 < 4; i1++, index += width) { + const i = i0 + i1; + if (i >= height) { + break; } + // clear processed flag first + processingFlags[index] &= processedInverseMask; + + if (coefficentsMagnitude[index] || !neighborsSignificance[index]) { + continue; + } + + const contextLabel = labels[neighborsSignificance[index]]; + const decision = decoder.readBit(contexts, contextLabel); + if (decision) { + const sign = this.decodeSignBit(i, j, index); + coefficentsSign[index] = sign; + coefficentsMagnitude[index] = 1; + this.setNeighborsSignificance(i, j, index); + processingFlags[index] |= firstMagnitudeBitMask; + } + bitsDecoded[index]++; + processingFlags[index] |= processedMask; } } - }, - decodeSignBit: function BitModel_decodeSignBit(row, column, index) { - const width = this.width, - height = this.height; - const coefficentsMagnitude = this.coefficentsMagnitude; - const coefficentsSign = this.coefficentsSign; - let contribution, sign0, sign1, significance1; - let contextLabel, decoded; + } + } - // calculate horizontal contribution - significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0; - if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) { - sign1 = coefficentsSign[index + 1]; - if (significance1) { - sign0 = coefficentsSign[index - 1]; - contribution = 1 - sign1 - sign0; - } else { - contribution = 1 - sign1 - sign1; - } - } else if (significance1) { + decodeSignBit(row, column, index) { + const width = this.width, + height = this.height; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + let contribution, sign0, sign1, significance1; + let contextLabel, decoded; + + // calculate horizontal contribution + significance1 = column > 0 && coefficentsMagnitude[index - 1] !== 0; + if (column + 1 < width && coefficentsMagnitude[index + 1] !== 0) { + sign1 = coefficentsSign[index + 1]; + if (significance1) { sign0 = coefficentsSign[index - 1]; - contribution = 1 - sign0 - sign0; + contribution = 1 - sign1 - sign0; } else { - contribution = 0; + contribution = 1 - sign1 - sign1; } - const horizontalContribution = 3 * contribution; + } else if (significance1) { + sign0 = coefficentsSign[index - 1]; + contribution = 1 - sign0 - sign0; + } else { + contribution = 0; + } + const horizontalContribution = 3 * contribution; - // calculate vertical contribution and combine with the horizontal - significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0; - if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) { - sign1 = coefficentsSign[index + width]; - if (significance1) { - sign0 = coefficentsSign[index - width]; - contribution = 1 - sign1 - sign0 + horizontalContribution; - } else { - contribution = 1 - sign1 - sign1 + horizontalContribution; - } - } else if (significance1) { + // calculate vertical contribution and combine with the horizontal + significance1 = row > 0 && coefficentsMagnitude[index - width] !== 0; + if (row + 1 < height && coefficentsMagnitude[index + width] !== 0) { + sign1 = coefficentsSign[index + width]; + if (significance1) { sign0 = coefficentsSign[index - width]; - contribution = 1 - sign0 - sign0 + horizontalContribution; + contribution = 1 - sign1 - sign0 + horizontalContribution; } else { - contribution = horizontalContribution; + contribution = 1 - sign1 - sign1 + horizontalContribution; } + } else if (significance1) { + sign0 = coefficentsSign[index - width]; + contribution = 1 - sign0 - sign0 + horizontalContribution; + } else { + contribution = horizontalContribution; + } - if (contribution >= 0) { - contextLabel = 9 + contribution; - decoded = this.decoder.readBit(this.contexts, contextLabel); - } else { - contextLabel = 9 - contribution; - decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1; - } - return decoded; - }, - runMagnitudeRefinementPass: function BitModel_runMagnitudeRefinementPass() { - const decoder = this.decoder; - const width = this.width, - height = this.height; - const coefficentsMagnitude = this.coefficentsMagnitude; - const neighborsSignificance = this.neighborsSignificance; - const contexts = this.contexts; - const bitsDecoded = this.bitsDecoded; - const processingFlags = this.processingFlags; - const processedMask = 1; - const firstMagnitudeBitMask = 2; - const length = width * height; - const width4 = width * 4; + if (contribution >= 0) { + contextLabel = 9 + contribution; + decoded = this.decoder.readBit(this.contexts, contextLabel); + } else { + contextLabel = 9 - contribution; + decoded = this.decoder.readBit(this.contexts, contextLabel) ^ 1; + } + return decoded; + } - for (let index0 = 0, indexNext; index0 < length; index0 = indexNext) { - indexNext = Math.min(length, index0 + width4); - for (let j = 0; j < width; j++) { - for (let index = index0 + j; index < indexNext; index += width) { - // significant but not those that have just become - if ( - !coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) !== 0 - ) { - continue; - } + runMagnitudeRefinementPass() { + const decoder = this.decoder; + const width = this.width, + height = this.height; + const coefficentsMagnitude = this.coefficentsMagnitude; + const neighborsSignificance = this.neighborsSignificance; + const contexts = this.contexts; + const bitsDecoded = this.bitsDecoded; + const processingFlags = this.processingFlags; + const processedMask = 1; + const firstMagnitudeBitMask = 2; + const length = width * height; + const width4 = width * 4; - let contextLabel = 16; - if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { - processingFlags[index] ^= firstMagnitudeBitMask; - // first refinement - const significance = neighborsSignificance[index] & 127; - contextLabel = significance === 0 ? 15 : 14; - } - - const bit = decoder.readBit(contexts, contextLabel); - coefficentsMagnitude[index] = - (coefficentsMagnitude[index] << 1) | bit; - bitsDecoded[index]++; - processingFlags[index] |= processedMask; + for (let index0 = 0, indexNext; index0 < length; index0 = indexNext) { + indexNext = Math.min(length, index0 + width4); + for (let j = 0; j < width; j++) { + for (let index = index0 + j; index < indexNext; index += width) { + // significant but not those that have just become + if ( + !coefficentsMagnitude[index] || + (processingFlags[index] & processedMask) !== 0 + ) { + continue; } + + let contextLabel = 16; + if ((processingFlags[index] & firstMagnitudeBitMask) !== 0) { + processingFlags[index] ^= firstMagnitudeBitMask; + // first refinement + const significance = neighborsSignificance[index] & 127; + contextLabel = significance === 0 ? 15 : 14; + } + + const bit = decoder.readBit(contexts, contextLabel); + coefficentsMagnitude[index] = + (coefficentsMagnitude[index] << 1) | bit; + bitsDecoded[index]++; + processingFlags[index] |= processedMask; } } - }, - runCleanupPass: function BitModel_runCleanupPass() { - const decoder = this.decoder; - const width = this.width, - height = this.height; - const neighborsSignificance = this.neighborsSignificance; - const coefficentsMagnitude = this.coefficentsMagnitude; - const coefficentsSign = this.coefficentsSign; - const contexts = this.contexts; - const labels = this.contextLabelTable; - const bitsDecoded = this.bitsDecoded; - const processingFlags = this.processingFlags; - const processedMask = 1; - const firstMagnitudeBitMask = 2; - const oneRowDown = width; - const twoRowsDown = width * 2; - const threeRowsDown = width * 3; - let iNext; - for (let i0 = 0; i0 < height; i0 = iNext) { - iNext = Math.min(i0 + 4, height); - const indexBase = i0 * width; - const checkAllEmpty = i0 + 3 < height; - for (let j = 0; j < width; j++) { - const index0 = indexBase + j; - // using the property: labels[neighborsSignificance[index]] === 0 - // when neighborsSignificance[index] === 0 - const allEmpty = - checkAllEmpty && - processingFlags[index0] === 0 && - processingFlags[index0 + oneRowDown] === 0 && - processingFlags[index0 + twoRowsDown] === 0 && - processingFlags[index0 + threeRowsDown] === 0 && - neighborsSignificance[index0] === 0 && - neighborsSignificance[index0 + oneRowDown] === 0 && - neighborsSignificance[index0 + twoRowsDown] === 0 && - neighborsSignificance[index0 + threeRowsDown] === 0; - let i1 = 0, - index = index0; - let i = i0, - sign; - if (allEmpty) { - const hasSignificantCoefficent = decoder.readBit( - contexts, - RUNLENGTH_CONTEXT - ); - if (!hasSignificantCoefficent) { - bitsDecoded[index0]++; - bitsDecoded[index0 + oneRowDown]++; - bitsDecoded[index0 + twoRowsDown]++; - bitsDecoded[index0 + threeRowsDown]++; - continue; // next column - } - i1 = - (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | - decoder.readBit(contexts, UNIFORM_CONTEXT); - if (i1 !== 0) { - i = i0 + i1; - index += i1 * width; - } + } + } + runCleanupPass() { + const decoder = this.decoder; + const width = this.width, + height = this.height; + const neighborsSignificance = this.neighborsSignificance; + const coefficentsMagnitude = this.coefficentsMagnitude; + const coefficentsSign = this.coefficentsSign; + const contexts = this.contexts; + const labels = this.contextLabelTable; + const bitsDecoded = this.bitsDecoded; + const processingFlags = this.processingFlags; + const processedMask = 1; + const firstMagnitudeBitMask = 2; + const oneRowDown = width; + const twoRowsDown = width * 2; + const threeRowsDown = width * 3; + let iNext; + for (let i0 = 0; i0 < height; i0 = iNext) { + iNext = Math.min(i0 + 4, height); + const indexBase = i0 * width; + const checkAllEmpty = i0 + 3 < height; + for (let j = 0; j < width; j++) { + const index0 = indexBase + j; + // using the property: labels[neighborsSignificance[index]] === 0 + // when neighborsSignificance[index] === 0 + const allEmpty = + checkAllEmpty && + processingFlags[index0] === 0 && + processingFlags[index0 + oneRowDown] === 0 && + processingFlags[index0 + twoRowsDown] === 0 && + processingFlags[index0 + threeRowsDown] === 0 && + neighborsSignificance[index0] === 0 && + neighborsSignificance[index0 + oneRowDown] === 0 && + neighborsSignificance[index0 + twoRowsDown] === 0 && + neighborsSignificance[index0 + threeRowsDown] === 0; + let i1 = 0, + index = index0; + let i = i0, + sign; + if (allEmpty) { + const hasSignificantCoefficent = decoder.readBit( + contexts, + RUNLENGTH_CONTEXT + ); + if (!hasSignificantCoefficent) { + bitsDecoded[index0]++; + bitsDecoded[index0 + oneRowDown]++; + bitsDecoded[index0 + twoRowsDown]++; + bitsDecoded[index0 + threeRowsDown]++; + continue; // next column + } + i1 = + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | + decoder.readBit(contexts, UNIFORM_CONTEXT); + if (i1 !== 0) { + i = i0 + i1; + index += i1 * width; + } + + sign = this.decodeSignBit(i, j, index); + coefficentsSign[index] = sign; + coefficentsMagnitude[index] = 1; + this.setNeighborsSignificance(i, j, index); + processingFlags[index] |= firstMagnitudeBitMask; + + index = index0; + for (let i2 = i0; i2 <= i; i2++, index += width) { + bitsDecoded[index]++; + } + + i1++; + } + for (i = i0 + i1; i < iNext; i++, index += width) { + if ( + coefficentsMagnitude[index] || + (processingFlags[index] & processedMask) !== 0 + ) { + continue; + } + + const contextLabel = labels[neighborsSignificance[index]]; + const decision = decoder.readBit(contexts, contextLabel); + if (decision === 1) { sign = this.decodeSignBit(i, j, index); coefficentsSign[index] = sign; coefficentsMagnitude[index] = 1; this.setNeighborsSignificance(i, j, index); processingFlags[index] |= firstMagnitudeBitMask; - - index = index0; - for (let i2 = i0; i2 <= i; i2++, index += width) { - bitsDecoded[index]++; - } - - i1++; - } - for (i = i0 + i1; i < iNext; i++, index += width) { - if ( - coefficentsMagnitude[index] || - (processingFlags[index] & processedMask) !== 0 - ) { - continue; - } - - const contextLabel = labels[neighborsSignificance[index]]; - const decision = decoder.readBit(contexts, contextLabel); - if (decision === 1) { - sign = this.decodeSignBit(i, j, index); - coefficentsSign[index] = sign; - coefficentsMagnitude[index] = 1; - this.setNeighborsSignificance(i, j, index); - processingFlags[index] |= firstMagnitudeBitMask; - } - bitsDecoded[index]++; - } - } - } - }, - checkSegmentationSymbol: function BitModel_checkSegmentationSymbol() { - const decoder = this.decoder; - const contexts = this.contexts; - const symbol = - (decoder.readBit(contexts, UNIFORM_CONTEXT) << 3) | - (decoder.readBit(contexts, UNIFORM_CONTEXT) << 2) | - (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | - decoder.readBit(contexts, UNIFORM_CONTEXT); - if (symbol !== 0xa) { - throw new JpxError("Invalid segmentation symbol"); - } - }, - }; - - return BitModel; - })(); - - // Section F, Discrete wavelet transformation - const Transform = (function TransformClosure() { - // eslint-disable-next-line no-shadow - function Transform() {} - - Transform.prototype.calculate = function transformCalculate( - subbands, - u0, - v0 - ) { - let ll = subbands[0]; - for (let i = 1, ii = subbands.length; i < ii; i++) { - ll = this.iterate(ll, subbands[i], u0, v0); - } - return ll; - }; - Transform.prototype.extend = function extend(buffer, offset, size) { - // Section F.3.7 extending... using max extension of 4 - let i1 = offset - 1, - j1 = offset + 1; - let i2 = offset + size - 2, - j2 = offset + size; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1--] = buffer[j1++]; - buffer[j2++] = buffer[i2--]; - buffer[i1] = buffer[j1]; - buffer[j2] = buffer[i2]; - }; - Transform.prototype.iterate = function Transform_iterate( - ll, - hl_lh_hh, - u0, - v0 - ) { - const llWidth = ll.width, - llHeight = ll.height; - let llItems = ll.items; - const width = hl_lh_hh.width; - const height = hl_lh_hh.height; - const items = hl_lh_hh.items; - let i, j, k, l, u, v; - - // Interleave LL according to Section F.3.3 - for (k = 0, i = 0; i < llHeight; i++) { - l = i * 2 * width; - for (j = 0; j < llWidth; j++, k++, l += 2) { - items[l] = llItems[k]; - } - } - // The LL band is not needed anymore. - llItems = ll.items = null; - - const bufferPadding = 4; - const rowBuffer = new Float32Array(width + 2 * bufferPadding); - - // Section F.3.4 HOR_SR - if (width === 1) { - // if width = 1, when u0 even keep items as is, when odd divide by 2 - if ((u0 & 1) !== 0) { - for (v = 0, k = 0; v < height; v++, k += width) { - items[k] *= 0.5; - } - } - } else { - for (v = 0, k = 0; v < height; v++, k += width) { - rowBuffer.set(items.subarray(k, k + width), bufferPadding); - - this.extend(rowBuffer, bufferPadding, width); - this.filter(rowBuffer, bufferPadding, width); - - items.set( - rowBuffer.subarray(bufferPadding, bufferPadding + width), - k - ); - } - } - - // Accesses to the items array can take long, because it may not fit into - // CPU cache and has to be fetched from main memory. Since subsequent - // accesses to the items array are not local when reading columns, we - // have a cache miss every time. To reduce cache misses, get up to - // 'numBuffers' items at a time and store them into the individual - // buffers. The colBuffers should be small enough to fit into CPU cache. - let numBuffers = 16; - const colBuffers = []; - for (i = 0; i < numBuffers; i++) { - colBuffers.push(new Float32Array(height + 2 * bufferPadding)); - } - let b, - currentBuffer = 0; - ll = bufferPadding + height; - - // Section F.3.5 VER_SR - if (height === 1) { - // if height = 1, when v0 even keep items as is, when odd divide by 2 - if ((v0 & 1) !== 0) { - for (u = 0; u < width; u++) { - items[u] *= 0.5; - } - } - } else { - for (u = 0; u < width; u++) { - // if we ran out of buffers, copy several image columns at once - if (currentBuffer === 0) { - numBuffers = Math.min(width - u, numBuffers); - for (k = u, l = bufferPadding; l < ll; k += width, l++) { - for (b = 0; b < numBuffers; b++) { - colBuffers[b][l] = items[k + b]; - } - } - currentBuffer = numBuffers; - } - - currentBuffer--; - const buffer = colBuffers[currentBuffer]; - this.extend(buffer, bufferPadding, height); - this.filter(buffer, bufferPadding, height); - - // If this is last buffer in this group of buffers, flush all buffers. - if (currentBuffer === 0) { - k = u - numBuffers + 1; - for (l = bufferPadding; l < ll; k += width, l++) { - for (b = 0; b < numBuffers; b++) { - items[k + b] = colBuffers[b][l]; - } } + bitsDecoded[index]++; } } } - - return { - width, - height, - items, - }; - }; - return Transform; - })(); - - // Section 3.8.2 Irreversible 9-7 filter - const IrreversibleTransform = (function IrreversibleTransformClosure() { - // eslint-disable-next-line no-shadow - function IrreversibleTransform() { - Transform.call(this); } - IrreversibleTransform.prototype = Object.create(Transform.prototype); - IrreversibleTransform.prototype.filter = function irreversibleTransformFilter( - x, - offset, - length - ) { - const len = length >> 1; - offset = offset | 0; - let j, n, current, next; - - const alpha = -1.586134342059924; - const beta = -0.052980118572961; - const gamma = 0.882911075530934; - const delta = 0.443506852043971; - const K = 1.230174104914001; - const K_ = 1 / K; - - // step 1 is combined with step 3 - - // step 2 - j = offset - 3; - for (n = len + 4; n--; j += 2) { - x[j] *= K_; + checkSegmentationSymbol() { + const decoder = this.decoder; + const contexts = this.contexts; + const symbol = + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 3) | + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 2) | + (decoder.readBit(contexts, UNIFORM_CONTEXT) << 1) | + decoder.readBit(contexts, UNIFORM_CONTEXT); + if (symbol !== 0xa) { + throw new JpxError("Invalid segmentation symbol"); } - - // step 1 & 3 - j = offset - 2; - current = delta * x[j - 1]; - for (n = len + 3; n--; j += 2) { - next = delta * x[j + 1]; - x[j] = K * x[j] - current - next; - if (n--) { - j += 2; - current = delta * x[j + 1]; - x[j] = K * x[j] - current - next; - } else { - break; - } - } - - // step 4 - j = offset - 1; - current = gamma * x[j - 1]; - for (n = len + 2; n--; j += 2) { - next = gamma * x[j + 1]; - x[j] -= current + next; - if (n--) { - j += 2; - current = gamma * x[j + 1]; - x[j] -= current + next; - } else { - break; - } - } - - // step 5 - j = offset; - current = beta * x[j - 1]; - for (n = len + 1; n--; j += 2) { - next = beta * x[j + 1]; - x[j] -= current + next; - if (n--) { - j += 2; - current = beta * x[j + 1]; - x[j] -= current + next; - } else { - break; - } - } - - // step 6 - if (len !== 0) { - j = offset + 1; - current = alpha * x[j - 1]; - for (n = len; n--; j += 2) { - next = alpha * x[j + 1]; - x[j] -= current + next; - if (n--) { - j += 2; - current = alpha * x[j + 1]; - x[j] -= current + next; - } else { - break; - } - } - } - }; - - return IrreversibleTransform; - })(); - - // Section 3.8.1 Reversible 5-3 filter - const ReversibleTransform = (function ReversibleTransformClosure() { - // eslint-disable-next-line no-shadow - function ReversibleTransform() { - Transform.call(this); } + } - ReversibleTransform.prototype = Object.create(Transform.prototype); - ReversibleTransform.prototype.filter = function reversibleTransformFilter( - x, - offset, - length - ) { - const len = length >> 1; - offset = offset | 0; - let j, n; - - for (j = offset, n = len + 1; n--; j += 2) { - x[j] -= (x[j - 1] + x[j + 1] + 2) >> 2; - } - - for (j = offset + 1, n = len; n--; j += 2) { - x[j] += (x[j - 1] + x[j + 1]) >> 1; - } - }; - - return ReversibleTransform; - })(); - - return JpxImage; + return BitModel; })(); +// Section F, Discrete wavelet transformation +class Transform { + constructor() { + if (this.constructor === Transform) { + unreachable("Cannot initialize Transform."); + } + } + + calculate(subbands, u0, v0) { + let ll = subbands[0]; + for (let i = 1, ii = subbands.length; i < ii; i++) { + ll = this.iterate(ll, subbands[i], u0, v0); + } + return ll; + } + + extend(buffer, offset, size) { + // Section F.3.7 extending... using max extension of 4 + let i1 = offset - 1, + j1 = offset + 1; + let i2 = offset + size - 2, + j2 = offset + size; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1--] = buffer[j1++]; + buffer[j2++] = buffer[i2--]; + buffer[i1] = buffer[j1]; + buffer[j2] = buffer[i2]; + } + + filter(x, offset, length) { + unreachable("Abstract method `filter` called"); + } + + iterate(ll, hl_lh_hh, u0, v0) { + const llWidth = ll.width, + llHeight = ll.height; + let llItems = ll.items; + const width = hl_lh_hh.width; + const height = hl_lh_hh.height; + const items = hl_lh_hh.items; + let i, j, k, l, u, v; + + // Interleave LL according to Section F.3.3 + for (k = 0, i = 0; i < llHeight; i++) { + l = i * 2 * width; + for (j = 0; j < llWidth; j++, k++, l += 2) { + items[l] = llItems[k]; + } + } + // The LL band is not needed anymore. + llItems = ll.items = null; + + const bufferPadding = 4; + const rowBuffer = new Float32Array(width + 2 * bufferPadding); + + // Section F.3.4 HOR_SR + if (width === 1) { + // if width = 1, when u0 even keep items as is, when odd divide by 2 + if ((u0 & 1) !== 0) { + for (v = 0, k = 0; v < height; v++, k += width) { + items[k] *= 0.5; + } + } + } else { + for (v = 0, k = 0; v < height; v++, k += width) { + rowBuffer.set(items.subarray(k, k + width), bufferPadding); + + this.extend(rowBuffer, bufferPadding, width); + this.filter(rowBuffer, bufferPadding, width); + + items.set(rowBuffer.subarray(bufferPadding, bufferPadding + width), k); + } + } + + // Accesses to the items array can take long, because it may not fit into + // CPU cache and has to be fetched from main memory. Since subsequent + // accesses to the items array are not local when reading columns, we + // have a cache miss every time. To reduce cache misses, get up to + // 'numBuffers' items at a time and store them into the individual + // buffers. The colBuffers should be small enough to fit into CPU cache. + let numBuffers = 16; + const colBuffers = []; + for (i = 0; i < numBuffers; i++) { + colBuffers.push(new Float32Array(height + 2 * bufferPadding)); + } + let b, + currentBuffer = 0; + ll = bufferPadding + height; + + // Section F.3.5 VER_SR + if (height === 1) { + // if height = 1, when v0 even keep items as is, when odd divide by 2 + if ((v0 & 1) !== 0) { + for (u = 0; u < width; u++) { + items[u] *= 0.5; + } + } + } else { + for (u = 0; u < width; u++) { + // if we ran out of buffers, copy several image columns at once + if (currentBuffer === 0) { + numBuffers = Math.min(width - u, numBuffers); + for (k = u, l = bufferPadding; l < ll; k += width, l++) { + for (b = 0; b < numBuffers; b++) { + colBuffers[b][l] = items[k + b]; + } + } + currentBuffer = numBuffers; + } + + currentBuffer--; + const buffer = colBuffers[currentBuffer]; + this.extend(buffer, bufferPadding, height); + this.filter(buffer, bufferPadding, height); + + // If this is last buffer in this group of buffers, flush all buffers. + if (currentBuffer === 0) { + k = u - numBuffers + 1; + for (l = bufferPadding; l < ll; k += width, l++) { + for (b = 0; b < numBuffers; b++) { + items[k + b] = colBuffers[b][l]; + } + } + } + } + } + + return { width, height, items }; + } +} + +// Section 3.8.2 Irreversible 9-7 filter +class IrreversibleTransform extends Transform { + filter(x, offset, length) { + const len = length >> 1; + offset = offset | 0; + let j, n, current, next; + + const alpha = -1.586134342059924; + const beta = -0.052980118572961; + const gamma = 0.882911075530934; + const delta = 0.443506852043971; + const K = 1.230174104914001; + const K_ = 1 / K; + + // step 1 is combined with step 3 + + // step 2 + j = offset - 3; + for (n = len + 4; n--; j += 2) { + x[j] *= K_; + } + + // step 1 & 3 + j = offset - 2; + current = delta * x[j - 1]; + for (n = len + 3; n--; j += 2) { + next = delta * x[j + 1]; + x[j] = K * x[j] - current - next; + if (n--) { + j += 2; + current = delta * x[j + 1]; + x[j] = K * x[j] - current - next; + } else { + break; + } + } + + // step 4 + j = offset - 1; + current = gamma * x[j - 1]; + for (n = len + 2; n--; j += 2) { + next = gamma * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = gamma * x[j + 1]; + x[j] -= current + next; + } else { + break; + } + } + + // step 5 + j = offset; + current = beta * x[j - 1]; + for (n = len + 1; n--; j += 2) { + next = beta * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = beta * x[j + 1]; + x[j] -= current + next; + } else { + break; + } + } + + // step 6 + if (len !== 0) { + j = offset + 1; + current = alpha * x[j - 1]; + for (n = len; n--; j += 2) { + next = alpha * x[j + 1]; + x[j] -= current + next; + if (n--) { + j += 2; + current = alpha * x[j + 1]; + x[j] -= current + next; + } else { + break; + } + } + } + } +} + +// Section 3.8.1 Reversible 5-3 filter +class ReversibleTransform extends Transform { + filter(x, offset, length) { + const len = length >> 1; + offset = offset | 0; + let j, n; + + for (j = offset, n = len + 1; n--; j += 2) { + x[j] -= (x[j - 1] + x[j + 1] + 2) >> 2; + } + + for (j = offset + 1, n = len; n--; j += 2) { + x[j] += (x[j - 1] + x[j + 1]) >> 1; + } + } +} + export { JpxImage };