@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.
Files changed (41) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/build/__benchmark__/cog.read.benchmark.js +4 -2
  3. package/build/__benchmark__/source.file.d.ts +4 -0
  4. package/build/__benchmark__/source.file.d.ts.map +1 -1
  5. package/build/__benchmark__/source.file.js +2 -0
  6. package/build/__test__/cog.read.test.js +94 -1
  7. package/build/const/tiff.mime.d.ts +3 -0
  8. package/build/const/tiff.mime.d.ts.map +1 -1
  9. package/build/const/tiff.mime.js +23 -0
  10. package/build/const/tiff.tag.id.d.ts +35 -8
  11. package/build/const/tiff.tag.id.d.ts.map +1 -1
  12. package/build/const/tiff.tag.id.js +30 -2
  13. package/build/index.d.ts +3 -1
  14. package/build/index.d.ts.map +1 -1
  15. package/build/index.js +1 -1
  16. package/build/read/endian.d.ts +2 -0
  17. package/build/read/endian.d.ts.map +1 -0
  18. package/build/read/endian.js +5 -0
  19. package/build/read/tiff.tag.d.ts +1 -1
  20. package/build/read/tiff.tag.d.ts.map +1 -1
  21. package/build/read/tiff.tag.factory.d.ts +14 -1
  22. package/build/read/tiff.tag.factory.d.ts.map +1 -1
  23. package/build/read/tiff.tag.factory.js +60 -3
  24. package/build/tiff.d.ts +6 -2
  25. package/build/tiff.d.ts.map +1 -1
  26. package/build/tiff.image.d.ts +3 -0
  27. package/build/tiff.image.d.ts.map +1 -1
  28. package/build/tiff.image.js +23 -6
  29. package/build/tiff.js +4 -3
  30. package/package.json +2 -2
  31. package/src/__benchmark__/cog.read.benchmark.ts +4 -2
  32. package/src/__benchmark__/source.file.ts +2 -0
  33. package/src/__test__/cog.read.test.ts +127 -2
  34. package/src/const/tiff.mime.ts +23 -0
  35. package/src/const/tiff.tag.id.ts +35 -7
  36. package/src/index.ts +3 -0
  37. package/src/read/endian.ts +6 -0
  38. package/src/read/tiff.tag.factory.ts +73 -3
  39. package/src/read/tiff.tag.ts +1 -1
  40. package/src/tiff.image.ts +23 -6
  41. 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
- // Some offsets are quite long and don't need to read them often, so only read the tags we are interested in when we need to
137
- if (tagId === TiffTag.TileOffsets && hasBytes(view, dataOffset, dataLength)) setBytes(tag, view);
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<number[]> {
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
+ }
@@ -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
- return getValueAt(tiff, x, index);
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(source: Source): Promise<Tiff> {
45
- return new Tiff(source).init();
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
  /**