@cogeotiff/core 9.2.0 → 9.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/build/__benchmark__/cog.read.benchmark.js +4 -2
- package/build/__benchmark__/source.file.d.ts +4 -0
- package/build/__benchmark__/source.file.d.ts.map +1 -1
- package/build/__benchmark__/source.file.js +2 -0
- package/build/__test__/cog.read.test.js +94 -1
- package/build/const/tiff.mime.d.ts +3 -0
- package/build/const/tiff.mime.d.ts.map +1 -1
- package/build/const/tiff.mime.js +23 -0
- package/build/const/tiff.tag.id.d.ts +35 -8
- package/build/const/tiff.tag.id.d.ts.map +1 -1
- package/build/const/tiff.tag.id.js +30 -2
- package/build/index.d.ts +3 -1
- package/build/index.d.ts.map +1 -1
- package/build/index.js +1 -1
- package/build/read/endian.d.ts +2 -0
- package/build/read/endian.d.ts.map +1 -0
- package/build/read/endian.js +5 -0
- package/build/read/tiff.tag.d.ts +1 -1
- package/build/read/tiff.tag.d.ts.map +1 -1
- package/build/read/tiff.tag.factory.d.ts +14 -1
- package/build/read/tiff.tag.factory.d.ts.map +1 -1
- package/build/read/tiff.tag.factory.js +60 -3
- package/build/tiff.d.ts +6 -2
- package/build/tiff.d.ts.map +1 -1
- package/build/tiff.image.d.ts +3 -0
- package/build/tiff.image.d.ts.map +1 -1
- package/build/tiff.image.js +23 -6
- package/build/tiff.js +4 -3
- package/package.json +2 -2
- package/src/__benchmark__/cog.read.benchmark.ts +4 -2
- package/src/__benchmark__/source.file.ts +2 -0
- package/src/__test__/cog.read.test.ts +127 -2
- package/src/const/tiff.mime.ts +23 -0
- package/src/const/tiff.tag.id.ts +35 -7
- package/src/index.ts +3 -0
- package/src/read/endian.ts +6 -0
- package/src/read/tiff.tag.factory.ts +73 -3
- package/src/read/tiff.tag.ts +1 -1
- package/src/tiff.image.ts +23 -6
- package/src/tiff.ts +12 -3
|
@@ -5,6 +5,7 @@ import type { Tiff } from '../tiff.js';
|
|
|
5
5
|
import { getUint, getUint64 } from '../util/bytes.js';
|
|
6
6
|
import type { DataViewOffset } from './data.view.offset.js';
|
|
7
7
|
import { hasBytes } from './data.view.offset.js';
|
|
8
|
+
import { isLittleEndian } from './endian.js';
|
|
8
9
|
import type { Tag, TagLazy, TagOffset } from './tiff.tag.js';
|
|
9
10
|
import { getTiffTagSize } from './tiff.value.reader.js';
|
|
10
11
|
|
|
@@ -57,6 +58,42 @@ function readTagValue(
|
|
|
57
58
|
}
|
|
58
59
|
}
|
|
59
60
|
|
|
61
|
+
/**
|
|
62
|
+
* Convert a tiff tag value to a typed array if the local endian matches the tiff endian
|
|
63
|
+
*
|
|
64
|
+
* @param tiff
|
|
65
|
+
* @param bytes
|
|
66
|
+
* @param offset Offset in the data view to read from
|
|
67
|
+
* @param length Number of bytes to read
|
|
68
|
+
* @param type type of tiff tag
|
|
69
|
+
* @returns the value if the type is a typed array and the endianness matches otherwise null
|
|
70
|
+
*/
|
|
71
|
+
export function readTypedValue<T>(
|
|
72
|
+
tiff: Tiff,
|
|
73
|
+
bytes: DataView,
|
|
74
|
+
offset: number,
|
|
75
|
+
length: number,
|
|
76
|
+
type: TiffTagValueType,
|
|
77
|
+
): T | null {
|
|
78
|
+
switch (type) {
|
|
79
|
+
case TiffTagValueType.Uint8:
|
|
80
|
+
return new Uint8Array(
|
|
81
|
+
bytes.buffer.slice(bytes.byteOffset + offset, bytes.byteOffset + offset + length),
|
|
82
|
+
) as unknown as T;
|
|
83
|
+
case TiffTagValueType.Uint16:
|
|
84
|
+
if (tiff.isLittleEndian !== isLittleEndian) return null;
|
|
85
|
+
return new Uint16Array(
|
|
86
|
+
bytes.buffer.slice(bytes.byteOffset + offset, bytes.byteOffset + offset + length),
|
|
87
|
+
) as unknown as T;
|
|
88
|
+
case TiffTagValueType.Uint32:
|
|
89
|
+
if (tiff.isLittleEndian !== isLittleEndian) return null;
|
|
90
|
+
return new Uint32Array(
|
|
91
|
+
bytes.buffer.slice(bytes.byteOffset + offset, bytes.byteOffset + offset + length),
|
|
92
|
+
) as unknown as T;
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
60
97
|
function readValue<T>(
|
|
61
98
|
tiff: Tiff,
|
|
62
99
|
tagId: TiffTag | undefined,
|
|
@@ -84,6 +121,17 @@ function readValue<T>(
|
|
|
84
121
|
) as unknown as T;
|
|
85
122
|
}
|
|
86
123
|
|
|
124
|
+
// TODO should we convert all tag values to typed arrays if possible?
|
|
125
|
+
if (
|
|
126
|
+
tagId === TiffTag.TileOffsets ||
|
|
127
|
+
tagId === TiffTag.TileByteCounts ||
|
|
128
|
+
tagId === TiffTag.StripOffsets ||
|
|
129
|
+
tagId === TiffTag.StripByteCounts
|
|
130
|
+
) {
|
|
131
|
+
const typedOutput = readTypedValue(tiff, bytes, offset, dataLength, type);
|
|
132
|
+
if (typedOutput) return typedOutput as unknown as T;
|
|
133
|
+
}
|
|
134
|
+
|
|
87
135
|
const output = [];
|
|
88
136
|
for (let i = 0; i < dataLength; i += typeSize) {
|
|
89
137
|
output.push(readTagValue(type, bytes, offset + i, tiff.isLittleEndian));
|
|
@@ -133,8 +181,17 @@ export function createTag(tiff: Tiff, view: DataViewOffset, offset: number): Tag
|
|
|
133
181
|
value: [],
|
|
134
182
|
tagOffset: offset,
|
|
135
183
|
};
|
|
136
|
-
|
|
137
|
-
if (
|
|
184
|
+
|
|
185
|
+
if (hasBytes(view, dataOffset, dataLength)) {
|
|
186
|
+
const val = readTypedValue(tiff, view, dataOffset - view.sourceOffset, dataLength, dataType);
|
|
187
|
+
if (val) {
|
|
188
|
+
tag.value = val as number[] | Uint32Array | Uint16Array;
|
|
189
|
+
tag.isLoaded = true;
|
|
190
|
+
} else {
|
|
191
|
+
setBytes(tag, view);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
138
195
|
return tag;
|
|
139
196
|
}
|
|
140
197
|
|
|
@@ -161,7 +218,7 @@ export async function fetchLazy<T>(tag: TagLazy<T>, tiff: Tiff): Promise<T> {
|
|
|
161
218
|
/**
|
|
162
219
|
* Fetch all the values from a {@link TagOffset}
|
|
163
220
|
*/
|
|
164
|
-
export async function fetchAllOffsets(tiff: Tiff, tag: TagOffset): Promise<
|
|
221
|
+
export async function fetchAllOffsets(tiff: Tiff, tag: TagOffset): Promise<TagOffset['value']> {
|
|
165
222
|
const dataTypeSize = getTiffTagSize(tag.dataType);
|
|
166
223
|
|
|
167
224
|
if (tag.view == null) {
|
|
@@ -171,6 +228,7 @@ export async function fetchAllOffsets(tiff: Tiff, tag: TagOffset): Promise<numbe
|
|
|
171
228
|
}
|
|
172
229
|
|
|
173
230
|
tag.value = readValue(tiff, tag.id, tag.view, 0, tag.dataType, tag.count);
|
|
231
|
+
tag.view = undefined;
|
|
174
232
|
tag.isLoaded = true;
|
|
175
233
|
return tag.value;
|
|
176
234
|
}
|
|
@@ -204,3 +262,15 @@ export async function getValueAt(tiff: Tiff, tag: TagOffset, index: number): Pro
|
|
|
204
262
|
tag.value[index] = value;
|
|
205
263
|
return value;
|
|
206
264
|
}
|
|
265
|
+
|
|
266
|
+
export function getValueAtSync(tiff: Tiff, tag: TagOffset, index: number): number | null {
|
|
267
|
+
if (index > tag.count || index < 0) throw new Error('TagOffset: out of bounds :' + index);
|
|
268
|
+
if (tag.value[index] != null) return tag.value[index];
|
|
269
|
+
if (tag.view == null) return null;
|
|
270
|
+
const dataTypeSize = getTiffTagSize(tag.dataType);
|
|
271
|
+
|
|
272
|
+
const value = readValue(tiff, undefined, tag.view, index * dataTypeSize, tag.dataType, 1);
|
|
273
|
+
if (typeof value !== 'number') throw new Error('Value is not a number');
|
|
274
|
+
tag.value[index] = value;
|
|
275
|
+
return value;
|
|
276
|
+
}
|
package/src/read/tiff.tag.ts
CHANGED
|
@@ -38,7 +38,7 @@ export interface TagInline<T> extends TagBase {
|
|
|
38
38
|
export interface TagOffset extends TagBase {
|
|
39
39
|
type: 'offset';
|
|
40
40
|
/** Values of the offsets this is a sparse array unless @see {TagOffset.isLoaded} is true */
|
|
41
|
-
value: number[];
|
|
41
|
+
value: number[] | Uint32Array | Uint16Array;
|
|
42
42
|
/** Have all the values been read */
|
|
43
43
|
isLoaded: boolean;
|
|
44
44
|
/** Raw buffer of the values for lazy decoding, as reading 100,000s of uint64s can take quite a long time */
|
package/src/tiff.image.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getCompressionMimeType, TiffCompressionMimeType, TiffMimeType } from './const/tiff.mime.js';
|
|
2
2
|
import type { TiffTagGeoType, TiffTagType } from './const/tiff.tag.id.js';
|
|
3
3
|
import { Compression, ModelTypeCode, SubFileType, TiffTag, TiffTagGeo } from './const/tiff.tag.id.js';
|
|
4
|
-
import { fetchAllOffsets, fetchLazy, getValueAt } from './read/tiff.tag.factory.js';
|
|
4
|
+
import { fetchAllOffsets, fetchLazy, getValueAt, getValueAtSync } from './read/tiff.tag.factory.js';
|
|
5
5
|
import type { Tag, TagInline, TagOffset } from './read/tiff.tag.js';
|
|
6
6
|
import type { Tiff } from './tiff.js';
|
|
7
7
|
import { getUint } from './util/bytes.js';
|
|
@@ -556,15 +556,25 @@ export class TiffImage {
|
|
|
556
556
|
|
|
557
557
|
/**
|
|
558
558
|
* Load the offset and byteCount of a tile
|
|
559
|
+
*
|
|
560
|
+
* if the tiff is sparse, offset and byteCount will be zero if the tile is empty
|
|
561
|
+
*
|
|
559
562
|
* @param index index in the tile array
|
|
560
563
|
* @returns Offset and byteCount for the tile
|
|
561
564
|
*/
|
|
562
565
|
async getTileSize(index: number): Promise<{ offset: number; imageSize: number }> {
|
|
566
|
+
// If both the tile offset and tile byte counts are loaded,
|
|
567
|
+
// we can get the offset and byte count synchronously without needing to fetch any additional data
|
|
568
|
+
const byteCounts = this.tags.get(TiffTag.TileByteCounts) as TagOffset | TagInline<number[]>;
|
|
569
|
+
const tileOffset = getOffsetSync(this.tiff, this.tileOffset, index);
|
|
570
|
+
const tileSize = getOffsetSync(this.tiff, byteCounts, index);
|
|
571
|
+
if (tileOffset != null && tileSize != null) return { offset: tileOffset, imageSize: tileSize };
|
|
572
|
+
|
|
563
573
|
// GDAL optimizes tiles by storing the size of the tile in
|
|
564
574
|
// the few bytes leading up to the tile
|
|
565
575
|
const leaderBytes = this.tiff.options?.tileLeaderByteSize;
|
|
566
576
|
if (leaderBytes) {
|
|
567
|
-
const offset = await getOffset(this.tiff, this.tileOffset, index);
|
|
577
|
+
const offset = tileOffset ?? (await getOffset(this.tiff, this.tileOffset, index));
|
|
568
578
|
// Sparse tiff no data found
|
|
569
579
|
if (offset === 0) return { offset: 0, imageSize: 0 };
|
|
570
580
|
|
|
@@ -574,22 +584,29 @@ export class TiffImage {
|
|
|
574
584
|
return { offset, imageSize: getUint(new DataView(bytes), 0, leaderBytes, this.tiff.isLittleEndian) };
|
|
575
585
|
}
|
|
576
586
|
|
|
577
|
-
const byteCounts = this.tags.get(TiffTag.TileByteCounts) as TagOffset;
|
|
578
587
|
if (byteCounts == null) throw new Error('No tile byte counts found');
|
|
579
588
|
const [offset, imageSize] = await Promise.all([
|
|
580
|
-
getOffset(this.tiff, this.tileOffset, index),
|
|
581
|
-
getOffset(this.tiff, byteCounts, index),
|
|
589
|
+
tileOffset ?? getOffset(this.tiff, this.tileOffset, index),
|
|
590
|
+
tileSize ?? getOffset(this.tiff, byteCounts, index),
|
|
582
591
|
]);
|
|
583
592
|
return { offset, imageSize };
|
|
584
593
|
}
|
|
585
594
|
}
|
|
586
595
|
|
|
587
596
|
function getOffset(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | Promise<number> {
|
|
597
|
+
const val = getOffsetSync(tiff, x, index);
|
|
598
|
+
if (val != null) return Promise.resolve(val);
|
|
599
|
+
return getValueAt(tiff, x as TagOffset, index);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
function getOffsetSync(tiff: Tiff, x: TagOffset | TagInline<number[]>, index: number): number | null {
|
|
588
603
|
if (index < 0) {
|
|
589
604
|
throw new Error(`Tiff: ${tiff.source.url.href} out of bounds ${TiffTag[x.id]} index:${index} total:${x.count}`);
|
|
590
605
|
}
|
|
591
606
|
// Sparse tiffs may not have the full tileWidth * tileHeight in their offset arrays
|
|
592
607
|
if (index >= x.count) return 0;
|
|
593
608
|
if (x.type === 'inline') return x.value[index];
|
|
594
|
-
|
|
609
|
+
if (x.isLoaded) return x.value[index];
|
|
610
|
+
|
|
611
|
+
return getValueAtSync(tiff, x, index);
|
|
595
612
|
}
|
package/src/tiff.ts
CHANGED
|
@@ -13,6 +13,11 @@ import { TiffImage } from './tiff.image.js';
|
|
|
13
13
|
import { getUint } from './util/bytes.js';
|
|
14
14
|
import { toHex } from './util/util.hex.js';
|
|
15
15
|
|
|
16
|
+
export interface TiffCreationOptions {
|
|
17
|
+
/** When initializing the tiff, read data in blocks of this size (in KB) */
|
|
18
|
+
defaultReadSize: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
16
21
|
export class Tiff {
|
|
17
22
|
/** Read 16KB blocks at a time */
|
|
18
23
|
static DefaultReadSize = 16 * 1024;
|
|
@@ -36,13 +41,17 @@ export class Tiff {
|
|
|
36
41
|
private _initPromise?: Promise<Tiff>;
|
|
37
42
|
|
|
38
43
|
/** A Tiff constructed from a source will not be pre-initialized with {@link init}. */
|
|
39
|
-
constructor(source: Source) {
|
|
44
|
+
constructor(source: Source, options: TiffCreationOptions = { defaultReadSize: Tiff.DefaultReadSize }) {
|
|
40
45
|
this.source = source;
|
|
46
|
+
this.defaultReadSize = options.defaultReadSize;
|
|
41
47
|
}
|
|
42
48
|
|
|
43
49
|
/** Create a tiff and initialize it by reading the tiff headers */
|
|
44
|
-
static create(
|
|
45
|
-
|
|
50
|
+
static create(
|
|
51
|
+
source: Source,
|
|
52
|
+
options: TiffCreationOptions = { defaultReadSize: Tiff.DefaultReadSize },
|
|
53
|
+
): Promise<Tiff> {
|
|
54
|
+
return new Tiff(source, options).init();
|
|
46
55
|
}
|
|
47
56
|
|
|
48
57
|
/**
|