s2-tools
Version:
A collection of geospatial tools primarily designed for WGS84, Web Mercator, and S2.
1,230 lines • 106 kB
JavaScript
/** 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