UNPKG

s2-tools

Version:

A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.

1,230 lines 106 kB
/** Table E.1 */ const SubbandsGainLog2 = { LL: 0, LH: 1, HL: 1, HH: 2, }; /** * Default SIZ container * @returns - the default SIZ */ function defaultSIZ() { return { Xsiz: 0, Ysiz: 0, XOsiz: 0, YOsiz: 0, XTsiz: 0, YTsiz: 0, XTOsiz: 0, YTOsiz: 0, Csiz: 0, }; } /** * Default codeblock parameters * @returns - the default codeblock parameters */ function defaultCodeBlockParams() { return { codeblockWidth: 0, codeblockHeight: 0, numcodeblockwide: 0, numcodeblockhigh: 0, }; } /** * Default coding style parameters * @returns - the default coding style parameters */ function defaultCodingStyleParameters() { return { entropyCoderWithCustomPrecincts: false, sopMarkerUsed: false, ephMarkerUsed: false, progressionOrder: 0, layersCount: 0, multipleComponentTransform: 0, decompositionLevelsCount: 0, xcb: 0, ycb: 0, selectiveArithmeticCodingBypass: false, resetContextProbabilities: false, terminationOnEachCodingPass: false, verticallyStripe: false, predictableTermination: false, segmentationSymbolUsed: false, reversibleTransformation: false, precinctsSizes: [], }; } /** * Default tile * @returns - the default tile */ function defaultTile() { return { index: 0, length: 0, dataEnd: 0, partIndex: 0, partsCount: 0, COD: defaultCodingStyleParameters(), QCD: defaultQuantizationParameters(), QCC: [], COC: [], components: [], }; } /** * Default quantization parameters * @returns - the default quantization parameters */ function defaultQuantizationParameters() { return { noQuantization: false, scalarExpounded: false, guardBits: 0, spqcds: [], }; } /** * Precinct parameters * @returns - the default precinct parameters */ function defaultPrecinctParameters() { return { precinctWidth: 0, precinctHeight: 0, numprecinctswide: 0, numprecinctshigh: 0, numprecincts: 0, precinctWidthInSubband: 0, precinctHeightInSubband: 0, }; } /** * Jpeg2000 image decoder * @returns - the Jpeg2000 image */ export class JpxImage { failOnCorruptedImage = false; width = 0; height = 0; componentsCount = 0; tiles = []; /** @param reader - the reader to parse from */ constructor(reader) { if (reader !== undefined) this.parse(reader); } /** * parse the input data into components * @param reader - the reader to parse from */ parse(reader) { const head = reader.getUint16(0); // No box header, immediate start of codestream (SOC) if (head === 0xff4f) { this.parseCodestream(reader, 0, reader.byteLength); return; } let position = 0; const length = reader.byteLength; while (position < length) { let headerSize = 8; let lbox = reader.getUint16(position); const tbox = reader.getUint16(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 = reader.getUint16(position) * 4294967296 + reader.getUint16(position + 4); position += 8; headerSize += 8; } if (lbox === 0) { lbox = length - position + headerSize; } if (lbox < headerSize) { throw new Error('Invalid box field size'); } const dataLength = lbox - headerSize; let jumpDataLength = true; switch (tbox) { case 0x6a703268: // 'jp2h' jumpDataLength = false; // parsing child boxes break; case 0x636f6c72: { // Colorspaces are not used, the CS from the PDF is used. // 'colr' // const method = data[position]; const method = reader.getUint8(position); if (method === 1) { // enumerated colorspace const colorspace = reader.getUint32(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: console.warn('Unknown colorspace ' + colorspace); break; } } else if (method === 2) { console.info('ICC profile not supported'); } break; } case 0x6a703263: // 'jp2c' this.parseCodestream(reader, position, position + dataLength); break; case 0x6a502020: // 'jP\024\024' if (reader.getUint32(position) !== 0x0d0a870a) { console.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); console.warn('Unsupported header type ' + tbox + ' (' + headerType + ')'); break; } } if (jumpDataLength) { position += dataLength; } } } /** * parse the input data into components * @param data - compressed data * @param start - start index * @param end - end index */ parseCodestream(data, start, end) { const context = { mainHeader: false, components: [], QCD: defaultQuantizationParameters(), QCC: [], COC: [], SIZ: defaultSIZ(), tiles: [], currentTile: defaultTile(), COD: defaultCodingStyleParameters(), }; let doNotRecover = false; try { let position = start; while (position + 1 < end) { const code = data.getUint16(position); position += 2; let length = 0; 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 = data.getUint16(position); const siz = { Xsiz: data.getUint32(position + 4), Ysiz: data.getUint32(position + 8), XOsiz: data.getUint32(position + 12), YOsiz: data.getUint32(position + 16), XTsiz: data.getUint32(position + 20), YTsiz: data.getUint32(position + 24), XTOsiz: data.getUint32(position + 28), YTOsiz: data.getUint32(position + 32), Csiz: data.getUint16(position + 36), }; let j = position + 38; for (let i = 0; i < siz.Csiz; i++) { const component = { precision: (data.getUint8(j) & 0x7f) + 1, isSigned: !((data.getUint8(j) & 0x80) === 0), XRsiz: data.getUint8(j + 1), YRsiz: data.getUint8(j + 2), x0: 0, y0: 0, x1: 0, y1: 0, width: 0, height: 0, }; j += 3; calculateComponentDimensions(component, siz); context.components.push(component); } context.SIZ = siz; calculateTileGrids(context, context.components); context.QCC = []; context.COC = []; break; } case 0xff5c: { // Quantization default (QCD) length = data.getUint16(position); let j = position + 2; const sqcd = data.getUint8(j++); let spqcdSize = 0; let scalarExpounded = false; 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); } const noQuantization = spqcdSize === 8; const guardBits = sqcd >> 5; const spqcds = []; while (j < length + position) { let epsilon = 0; let mu = 0; if (spqcdSize === 8) { epsilon = data.getUint8(j++) >> 3; mu = 0; } else { epsilon = data.getUint8(j) >> 3; mu = ((data.getUint8(j) & 0x7) << 8) | data.getUint8(j + 1); j += 2; } spqcds.push({ epsilon, mu }); } const qcd = { noQuantization, scalarExpounded, guardBits, spqcds, }; if (context.mainHeader) { context.QCD = qcd; } else { context.currentTile.QCD = qcd; context.currentTile.QCC = []; } break; } case 0xff5d: { // Quantization component (QCC) length = data.getUint16(position); let j = position + 2; let cqcc = 0; if (context.SIZ.Csiz < 257) { cqcc = data.getUint8(j++); } else { cqcc = data.getUint16(j); j += 2; } const sqcd = data.getUint8(j++); let spqcdSize = 0; let scalarExpounded = false; 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); } const noQuantization = spqcdSize === 8; const guardBits = sqcd >> 5; const spqcds = []; while (j < length + position) { let epsilon = 0; let mu = 0; if (spqcdSize === 8) { epsilon = data.getUint8(j++) >> 3; mu = 0; } else { epsilon = data.getUint8(j) >> 3; mu = ((data.getUint8(j) & 0x7) << 8) | data.getUint8(j + 1); j += 2; } spqcds.push({ epsilon, mu }); } const qcc = { noQuantization, scalarExpounded, guardBits, spqcds, }; if (context.mainHeader) { context.QCC[cqcc] = qcc; } else { context.currentTile.QCC[cqcc] = qcc; } break; } case 0xff52: { // Coding style default (COD) length = data.getUint16(position); let j = position + 2; const scod = data.getUint8(j++); const entropyCoderWithCustomPrecincts = !((scod & 1) === 0); const sopMarkerUsed = !((scod & 2) === 0); const ephMarkerUsed = !((scod & 4) === 0); const progressionOrder = data.getUint8(j++); const layersCount = data.getUint16(j); j += 2; const multipleComponentTransform = data.getUint8(j++); const decompositionLevelsCount = data.getUint8(j++); const xcb = (data.getUint8(j++) & 0xf) + 2; const ycb = (data.getUint8(j++) & 0xf) + 2; const blockStyle = data.getUint8(j++); const selectiveArithmeticCodingBypass = !((blockStyle & 1) === 0); const resetContextProbabilities = !((blockStyle & 2) === 0); const terminationOnEachCodingPass = !((blockStyle & 4) === 0); const verticallyStripe = !((blockStyle & 8) === 0); const predictableTermination = !((blockStyle & 16) === 0); const segmentationSymbolUsed = !((blockStyle & 32) === 0); const reversibleTransformation = !(data.getUint8(j++) === 0); const precinctsSizes = []; if (entropyCoderWithCustomPrecincts) { while (j < length + position) { const precinctsSize = data.getUint8(j++); precinctsSizes.push({ PPx: precinctsSize & 0xf, PPy: precinctsSize >> 4, }); } } const unsupported = []; if (selectiveArithmeticCodingBypass) unsupported.push('selectiveArithmeticCodingBypass'); if (resetContextProbabilities) unsupported.push('resetContextProbabilities'); if (terminationOnEachCodingPass) unsupported.push('terminationOnEachCodingPass'); if (verticallyStripe) unsupported.push('verticallyStripe'); if (predictableTermination) unsupported.push('predictableTermination'); if (unsupported.length > 0) { doNotRecover = true; console.warn(`JPX: Unsupported COD options (${unsupported.join(', ')}).`); } const cod = { entropyCoderWithCustomPrecincts, sopMarkerUsed, ephMarkerUsed, progressionOrder, layersCount, multipleComponentTransform, decompositionLevelsCount, xcb, ycb, selectiveArithmeticCodingBypass, resetContextProbabilities, terminationOnEachCodingPass, verticallyStripe, predictableTermination, segmentationSymbolUsed, reversibleTransformation, precinctsSizes, }; if (context.mainHeader) { context.COD = cod; } else { context.currentTile.COD = cod; context.currentTile.COC = []; } break; } case 0xff90: { // Start of tile-part (SOT) length = data.getUint16(position); const index = data.getUint16(position + 2); const tileLength = data.getUint32(position + 4); const dataEnd = tileLength + position - 2; const partIndex = data.getUint8(position + 8); const partsCount = data.getUint8(position + 9); const tile = { index, length: tileLength, dataEnd, partIndex, partsCount, COD: defaultCodingStyleParameters(), COC: [], QCD: defaultQuantizationParameters(), QCC: [], components: [], }; 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) const 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) console.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 = data.getUint16(position); // skipping content break; default: throw new Error('Unknown codestream code: ' + code.toString(16)); } position += length; } } catch (e) { if (e instanceof Error) { if (doNotRecover || this.failOnCorruptedImage) { throw new Error(e.message); } else { console.error(`JPX: Trying to recover from: "${e.message}".`); } } else { console.error(e); } } 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; } } /** * @param component - component to update the dimensions * @param siz - SIZ parameters */ function calculateComponentDimensions(component, siz) { const { ceil } = Math; // Section B.2 Component mapping component.x0 = ceil(siz.XOsiz / component.XRsiz); component.x1 = ceil(siz.Xsiz / component.XRsiz); component.y0 = ceil(siz.YOsiz / component.YRsiz); component.y1 = ceil(siz.Ysiz / component.YRsiz); component.width = component.x1 - component.x0; component.height = component.y1 - component.y0; } /** * Section B.3 Division into tile and tile-components * @param context - global context to extract parameters * @param components - components to update */ function calculateTileGrids(context, components) { const { ceil, min, max } = Math; const siz = context.SIZ; const tiles = []; const numXtiles = ceil((siz.Xsiz - siz.XTOsiz) / siz.XTsiz); const numYtiles = ceil((siz.Ysiz - siz.YTOsiz) / siz.YTsiz); for (let q = 0; q < numYtiles; q++) { for (let p = 0; p < numXtiles; p++) { const tx0 = max(siz.XTOsiz + p * siz.XTsiz, siz.XOsiz); const ty0 = max(siz.YTOsiz + q * siz.YTsiz, siz.YOsiz); const tx1 = min(siz.XTOsiz + (p + 1) * siz.XTsiz, siz.Xsiz); const ty1 = min(siz.YTOsiz + (q + 1) * siz.YTsiz, siz.Ysiz); tiles.push({ tx0, ty0, tx1, ty1, width: tx1 - tx0, height: ty1 - ty0, components: [], codingStyleDefaultParameters: defaultCodingStyleParameters(), }); } } 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 tile = tiles[j]; const tcx0 = ceil(tile.tx0 / component.XRsiz); const tcy0 = ceil(tile.ty0 / component.YRsiz); const tcx1 = ceil(tile.tx1 / component.XRsiz); const tcy1 = ceil(tile.ty1 / component.YRsiz); tile.components[i] = { tcx0, tcy0, tcx1, tcy1, width: tcx1 - tcx0, height: tcy1 - tcy0, resolutions: [], subbands: [], quantizationParameters: defaultQuantizationParameters(), codingStyleParameters: defaultCodingStyleParameters(), }; } } } /** * @param component - component to update * @param r - resolution * @returns - block dimensions */ function getBlocksDimensions(component, r) { const { min } = Math; const codOrCoc = component.codingStyleParameters; let PPx = 0; let PPy = 0; let xcb_ = 0; let ycb_ = 0; if (!codOrCoc.entropyCoderWithCustomPrecincts) { PPx = 15; PPy = 15; } else { PPx = codOrCoc.precinctsSizes[r].PPx; PPy = codOrCoc.precinctsSizes[r].PPy; } // calculate codeblock size as described in section B.7 xcb_ = r > 0 ? min(codOrCoc.xcb, PPx - 1) : min(codOrCoc.xcb, PPx); ycb_ = r > 0 ? min(codOrCoc.ycb, PPy - 1) : min(codOrCoc.ycb, PPy); return { PPx, PPy, xcb_, ycb_, }; } /** * @param resolution - resolution to update precincts * @param dimensions - block dimensions */ function buildPrecincts(resolution, dimensions) { const { ceil, floor } = Math; // 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 ? ceil(resolution.trx1 / precinctWidth) - floor(resolution.trx0 / precinctWidth) : 0; const numprecinctshigh = resolution.try1 > resolution.try0 ? ceil(resolution.try1 / precinctHeight) - floor(resolution.try0 / precinctHeight) : 0; const numprecincts = numprecinctswide * numprecinctshigh; resolution.precinctParameters = { precinctWidth, precinctHeight, numprecinctswide, numprecinctshigh, numprecincts, precinctWidthInSubband, precinctHeightInSubband, }; } /** * @param subband - subband to update * @param dimensions - block dimensions to use */ function buildCodeblocks(subband, dimensions) { const { max, min, floor } = Math; // 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 = []; for (let j = cby0; j < cby1; j++) { for (let i = cbx0; i < cbx1; i++) { const codeblock = { cbx: i, cby: j, tbx0: codeblockWidth * i, tby0: codeblockHeight * j, tbx1: codeblockWidth * (i + 1), tby1: codeblockHeight * (j + 1), tbx0_: 0, tby0_: 0, tbx1_: 0, tby1_: 0, precinctNumber: 0, subbandType: 'HH', Lblock: 0, }; codeblock.tbx0_ = max(subband.tbx0, codeblock.tbx0); codeblock.tby0_ = max(subband.tby0, codeblock.tby0); codeblock.tbx1_ = min(subband.tbx1, codeblock.tbx1); codeblock.tby1_ = 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 = floor((codeblock.tbx0_ - subband.tbx0) / precinctParameters.precinctWidthInSubband); const pj = floor((codeblock.tby0_ - subband.tby0) / precinctParameters.precinctHeightInSubband); const 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; } } subband.codeblockParameters = { codeblockWidth: xcb_, codeblockHeight: ycb_, numcodeblockwide: cbx1 - cbx0 + 1, numcodeblockhigh: cby1 - cby0 + 1, }; subband.codeblocks = codeblocks; subband.precincts = precincts; } /** * @param resolution - the resolution of the packet * @param precinctNumber - the precinct number in the resolution * @param layerNumber - the associated layer * @returns - the built packet */ function createPacket(resolution, precinctNumber, layerNumber) { const precinctCodeblocks = []; // Section B.10.8 Order of info in packet // sub-bands already ordered in 'LL', 'HL', 'LH', and 'HH' sequence for (const { codeblocks } of resolution.subbands) { for (const codeblock of codeblocks) { if (codeblock.precinctNumber !== precinctNumber) continue; precinctCodeblocks.push(codeblock); } } return { layerNumber, codeblocks: precinctCodeblocks, }; } /** * Layer Resolution Component Position Iterator */ class LayerResolutionComponentPositionIterator { layersCount; componentsCount; maxDecompositionLevelsCount = 0; tile; l = 0; r = 0; i = 0; k = 0; /** * @param context - global context to pull data from */ constructor(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; this.tile = context.tiles[tileIndex]; this.layersCount = this.tile.codingStyleDefaultParameters.layersCount; this.componentsCount = siz.Csiz; for (let q = 0; q < this.componentsCount; q++) { this.maxDecompositionLevelsCount = Math.max(this.maxDecompositionLevelsCount, this.tile.components[q].codingStyleParameters.decompositionLevelsCount); } } /** * Section B.12.1.1 Layer-resolution-component-position * @returns - the next packet in the layer resolution */ nextPacket() { for (; this.l < this.layersCount; this.l++) { for (; this.r <= this.maxDecompositionLevelsCount; this.r++) { for (; this.i < this.componentsCount; this.i++) { const component = this.tile.components[this.i]; if (this.r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[this.r]; const numprecincts = resolution.precinctParameters.numprecincts; for (; this.k < numprecincts;) { const packet = createPacket(resolution, this.k, this.l); this.k++; return packet; } this.k = 0; } this.i = 0; } this.r = 0; } throw new Error('Out of packets'); } } /** * Resolution Layer Component Position Iterator */ class ResolutionLayerComponentPositionIterator { layersCount; componentsCount; maxDecompositionLevelsCount; tile; l = 0; r = 0; i = 0; k = 0; /** * @param context - global context to pull data from */ constructor(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; this.tile = context.tiles[tileIndex]; this.layersCount = this.tile.codingStyleDefaultParameters.layersCount; this.componentsCount = siz.Csiz; this.maxDecompositionLevelsCount = 0; for (let q = 0; q < this.componentsCount; q++) { this.maxDecompositionLevelsCount = Math.max(this.maxDecompositionLevelsCount, this.tile.components[q].codingStyleParameters.decompositionLevelsCount); } } /** * Section B.12.1.2 Resolution-layer-component-position * @returns - the next packet in the resolution layer */ nextPacket() { // Section B.12.1.2 Resolution-layer-component-position for (; this.r <= this.maxDecompositionLevelsCount; this.r++) { for (; this.l < this.layersCount; this.l++) { for (; this.i < this.componentsCount; this.i++) { const component = this.tile.components[this.i]; if (this.r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[this.r]; const numprecincts = resolution.precinctParameters.numprecincts; for (; this.k < numprecincts;) { const packet = createPacket(resolution, this.k, this.l); this.k++; return packet; } this.k = 0; } this.i = 0; } this.l = 0; } throw new Error('Out of packets'); } } /** * Resolution Position Component Layer Iterator */ class ResolutionPositionComponentLayerIterator { maxDecompositionLevelsCount = 0; layersCount; componentsCount; maxNumPrecinctsInLevel; tile; l = 0; r = 0; c = 0; p = 0; /** * @param context - global context to pull data from */ constructor(context) { const { max } = Math; const siz = context.SIZ; const tileIndex = context.currentTile.index; this.tile = context.tiles[tileIndex]; this.layersCount = this.tile.codingStyleDefaultParameters.layersCount; const componentsCount = (this.componentsCount = siz.Csiz); for (let c = 0; c < componentsCount; c++) { const component = this.tile.components[c]; this.maxDecompositionLevelsCount = max(this.maxDecompositionLevelsCount, component.codingStyleParameters.decompositionLevelsCount); } this.maxNumPrecinctsInLevel = new Int32Array(this.maxDecompositionLevelsCount + 1); for (let r = 0; r <= this.maxDecompositionLevelsCount; ++r) { let maxNumPrecincts = 0; for (let c = 0; c < componentsCount; ++c) { const resolutions = this.tile.components[c].resolutions; if (r < resolutions.length) { maxNumPrecincts = max(maxNumPrecincts, resolutions[r].precinctParameters.numprecincts); } } this.maxNumPrecinctsInLevel[r] = maxNumPrecincts; } } /** * Section B.12.1.3 Resolution-position-component-layer * @returns - the next packet in the resolution layer */ nextPacket() { for (; this.r <= this.maxDecompositionLevelsCount; this.r++) { for (; this.p < this.maxNumPrecinctsInLevel[this.r]; this.p++) { for (; this.c < this.componentsCount; this.c++) { const component = this.tile.components[this.c]; if (this.r > component.codingStyleParameters.decompositionLevelsCount) { continue; } const resolution = component.resolutions[this.r]; const numprecincts = resolution.precinctParameters.numprecincts; if (this.p >= numprecincts) continue; for (; this.l < this.layersCount;) { const packet = createPacket(resolution, this.p, this.l); this.l++; return packet; } this.l = 0; } this.c = 0; } this.p = 0; } throw new Error('Out of packets'); } } /** * Position Component Resolution Layer Iterator */ class PositionComponentResolutionLayerIterator { layersCount; componentsCount; precinctsSizes; precinctsIterationSizes; tile; l = 0; r = 0; c = 0; px = 0; py = 0; /** @param context - global context to pull data from */ constructor(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; this.tile = context.tiles[tileIndex]; this.layersCount = this.tile.codingStyleDefaultParameters.layersCount; this.componentsCount = siz.Csiz; this.precinctsSizes = getPrecinctSizesInImageScale(this.tile); this.precinctsIterationSizes = this.precinctsSizes; } /** * Section B.12.1.4 Position-component-resolution-layer * @returns - the next packet in the position layer */ nextPacket() { for (; this.py < this.precinctsIterationSizes.maxNumHigh; this.py++) { for (; this.px < this.precinctsIterationSizes.maxNumWide; this.px++) { for (; this.c < this.componentsCount; this.c++) { const component = this.tile.components[this.c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; this.r <= decompositionLevelsCount; this.r++) { const resolution = component.resolutions[this.r]; const sizeInImageScale = this.precinctsSizes.components[this.c].resolutions[this.r]; const k = getPrecinctIndexIfExist(this.px, this.py, sizeInImageScale, this.precinctsIterationSizes, resolution); if (k === null) continue; for (; this.l < this.layersCount;) { const packet = createPacket(resolution, k, this.l); this.l++; return packet; } this.l = 0; } this.r = 0; } this.c = 0; } this.px = 0; } throw new Error('Out of packets'); } } /** * Component Position Resolution Layer Iterator */ class ComponentPositionResolutionLayerIterator { layersCount; componentsCount; precinctsSizes; tile; l = 0; r = 0; c = 0; px = 0; py = 0; /** * @param context - global context to pull data from */ constructor(context) { const siz = context.SIZ; const tileIndex = context.currentTile.index; this.tile = context.tiles[tileIndex]; this.layersCount = this.tile.codingStyleDefaultParameters.layersCount; this.componentsCount = siz.Csiz; this.precinctsSizes = getPrecinctSizesInImageScale(this.tile); } /** * Section B.12.1.5 Component-position-resolution-layer * @returns - the next packet in the position layer */ nextPacket() { for (; this.c < this.componentsCount; ++this.c) { const component = this.tile.components[this.c]; const precinctsIterationSizes = this.precinctsSizes.components[this.c]; const decompositionLevelsCount = component.codingStyleParameters.decompositionLevelsCount; for (; this.py < precinctsIterationSizes.maxNumHigh; this.py++) { for (; this.px < precinctsIterationSizes.maxNumWide; this.px++) { for (; this.r <= decompositionLevelsCount; this.r++) { const resolution = component.resolutions[this.r]; const sizeInImageScale = precinctsIterationSizes.resolutions[this.r]; const k = getPrecinctIndexIfExist(this.px, this.py, sizeInImageScale, this.precinctsSizes, resolution); if (k === null) continue; for (; this.l < this.layersCount;) { const packet = createPacket(resolution, k, this.l); this.l++; return packet; } this.l = 0; } this.r = 0; } this.px = 0; } this.py = 0; } throw new Error('Out of packets'); } } /** * @param pxIndex - x position index * @param pyIndex - y position index * @param sizeInImageScale - size in image scale * @param precinctIterationSizes - precinct iteration sizes * @param resolution - resolution to check row size * @returns - precinct index if it exists */ 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) { return null; } const startPrecinctRowIndex = (posY / sizeInImageScale.width) * resolution.precinctParameters.numprecinctswide; return posX / sizeInImageScale.height + startPrecinctRowIndex; } /** * @param tile - tile to get precinct sizes from * @returns - precinct sizes */ function getPrecinctSizesInImageScale(tile) { const { min, max } = Math; const { components } = tile; const componentsCount = 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 = 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 = min(minWidthCurrentComponent, widthCurrentResolution); minHeightCurrentComponent = min(minHeightCurrentComponent, heightCurrentResolution); maxNumWideCurrentComponent = max(maxNumWideCurrentComponent, resolution.precinctParameters.numprecinctswide); maxNumHighCurrentComponent = max(maxNumHighCurrentComponent, resolution.precinctParameters.numprecinctshigh); sizePerResolution[r] = { width: widthCurrentResolution, height: heightCurrentResolution, }; scale <<= 1; } minWidth = min(minWidth, minWidthCurrentComponent); minHeight = min(minHeight, minHeightCurrentComponent); maxNumWide = max(maxNumWide, maxNumWideCurrentComponent); maxNumHigh = max(maxNumHigh, maxNumHighCurrentComponent); sizePerComponent[c] = { resolutions: sizePerResolution, minWidth: minWidthCurrentComponent, minHeight: minHeightCurrentComponent, maxNumWide: maxNumWideCurrentComponent, maxNumHigh: maxNumHighCurrentComponent, }; } return { components: sizePerComponent, minWidth, minHeight, maxNumWide, maxNumHigh, }; } /** * Build packets from our global context tile data * @param context - context to build packets from */ function buildPackets(context) { const { ceil } = Math; 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(component, r); const scale = 1 << (decompositionLevelsCount - r); const resolution = { trx0: ceil(component.tcx0 / scale), try0: ceil(component.tcy0 / scale), trx1: ceil(component.tcx1 / scale), try1: ceil(component.tcy1 / scale), resLevel: r, precinctParameters: defaultPrecinctParameters(), subbands: [], }; buildPrecincts(resolution, blocksDimensions); resolutions.push(resolution); if (r === 0) { // one sub-band (LL) with last decomposition const subband = { type: 'LL', tbx0: ceil(component.tcx0 / scale), tby0: ceil(component.tcy0 / scale), tbx1: ceil(component.tcx1 / scale), tby1: ceil(component.tcy1 / scale), resolution, codeblocks: [], precincts: [], codeblockParameters: defaultCodeBlockParams(), }; buildCodeblocks(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 const subbandHL = { type: 'HL', tbx0: ceil(component.tcx0 / bscale - 0.5), tby0: ceil(component.tcy0 / bscale), tbx1: ceil(component.tcx1 / bscale - 0.5), tby1: ceil(component.tcy1 / bscale), resolution, codeblocks: [], precincts: [], codeblockParameters: defaultCodeBlockParams(), }; buildCodeblocks(subbandHL, blocksDimensions); subbands.push(subbandHL); resolutionSubbands.push(subbandHL); const subbandLH = { type: 'LH', tbx0: ceil(component.tcx0 / bscale), tby0: ceil(component.tcy0 / bscale - 0.5), tbx1: c