@fideus-labs/fiff 0.1.2 → 0.2.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.
@@ -0,0 +1,210 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
2
+ // SPDX-License-Identifier: MIT
3
+ import { zarrToOmePixelType } from "./dtypes.js";
4
+ /**
5
+ * Generate an OME-XML metadata string from a Multiscales object.
6
+ *
7
+ * @param multiscales - The ngff-zarr Multiscales to generate XML for.
8
+ * @param dtype - The Zarr data type of the pixel data.
9
+ * @param options - Writer options.
10
+ * @returns A complete OME-XML string suitable for a TIFF ImageDescription tag.
11
+ */
12
+ export function buildOmeXml(multiscales, dtype, options = {}) {
13
+ const dimensionOrder = options.dimensionOrder ?? "XYZCT";
14
+ const creator = options.creator ?? "fiff";
15
+ const dims = extractDimensions(multiscales);
16
+ const omeType = zarrToOmePixelType(dtype);
17
+ const imageName = options.imageName ?? multiscales.metadata.name ?? "image";
18
+ const channels = buildChannelElements(multiscales, dims.sizeC);
19
+ const physAttrs = buildPhysicalSizeAttrs(dims);
20
+ return `<?xml version="1.0" encoding="UTF-8"?>
21
+ <OME xmlns="http://www.openmicroscopy.org/Schemas/OME/2016-06"
22
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
23
+ xsi:schemaLocation="http://www.openmicroscopy.org/Schemas/OME/2016-06
24
+ http://www.openmicroscopy.org/Schemas/OME/2016-06/ome.xsd"
25
+ Creator="${escapeXml(creator)}">
26
+ <Image ID="Image:0" Name="${escapeXml(imageName)}">
27
+ <Pixels ID="Pixels:0" DimensionOrder="${dimensionOrder}" Type="${omeType}"
28
+ SizeX="${dims.sizeX}" SizeY="${dims.sizeY}" SizeZ="${dims.sizeZ}" SizeC="${dims.sizeC}" SizeT="${dims.sizeT}"
29
+ BigEndian="false"${physAttrs}>
30
+ ${channels} <TiffData/>
31
+ </Pixels>
32
+ </Image>
33
+ </OME>`;
34
+ }
35
+ /**
36
+ * Extract dimension sizes and physical sizes from a Multiscales object.
37
+ * Uses the highest-resolution image (images[0]).
38
+ */
39
+ export function extractDimensions(multiscales) {
40
+ const image = multiscales.images[0];
41
+ const shape = image.data.shape;
42
+ const dimNames = image.dims;
43
+ const info = {
44
+ sizeX: 1,
45
+ sizeY: 1,
46
+ sizeZ: 1,
47
+ sizeC: 1,
48
+ sizeT: 1,
49
+ };
50
+ for (let i = 0; i < dimNames.length; i++) {
51
+ const dim = dimNames[i];
52
+ switch (dim) {
53
+ case "x":
54
+ info.sizeX = shape[i];
55
+ break;
56
+ case "y":
57
+ info.sizeY = shape[i];
58
+ break;
59
+ case "z":
60
+ info.sizeZ = shape[i];
61
+ break;
62
+ case "c":
63
+ info.sizeC = shape[i];
64
+ break;
65
+ case "t":
66
+ info.sizeT = shape[i];
67
+ break;
68
+ }
69
+ }
70
+ // Extract physical sizes from scale and units
71
+ const axes = multiscales.metadata.axes;
72
+ for (const axis of axes) {
73
+ const scaleFactor = image.scale[axis.name];
74
+ if (scaleFactor === undefined || scaleFactor <= 0)
75
+ continue;
76
+ const unit = axis.unit ? ngffUnitToOmeUnit(axis.unit) : undefined;
77
+ switch (axis.name) {
78
+ case "x":
79
+ info.physicalSizeX = scaleFactor;
80
+ info.physicalSizeXUnit = unit;
81
+ break;
82
+ case "y":
83
+ info.physicalSizeY = scaleFactor;
84
+ info.physicalSizeYUnit = unit;
85
+ break;
86
+ case "z":
87
+ info.physicalSizeZ = scaleFactor;
88
+ info.physicalSizeZUnit = unit;
89
+ break;
90
+ }
91
+ }
92
+ return info;
93
+ }
94
+ // ── Internal helpers ────────────────────────────────────────────────
95
+ /**
96
+ * Build <Channel> XML elements from Multiscales omero metadata.
97
+ * Falls back to generic channel names if omero metadata is not available.
98
+ */
99
+ function buildChannelElements(multiscales, sizeC) {
100
+ const omero = multiscales.metadata.omero;
101
+ const lines = [];
102
+ for (let c = 0; c < sizeC; c++) {
103
+ const omeroChannel = omero?.channels?.[c];
104
+ const name = omeroChannel?.label ?? `Ch${c}`;
105
+ const colorAttr = omeroChannel
106
+ ? ` Color="${hexColorToOmeInt(omeroChannel.color)}"`
107
+ : "";
108
+ lines.push(` <Channel ID="Channel:0:${c}" Name="${escapeXml(name)}" SamplesPerPixel="1"${colorAttr}/>`);
109
+ }
110
+ return lines.length > 0 ? lines.join("\n") + "\n" : "";
111
+ }
112
+ /**
113
+ * Build physical size XML attributes string.
114
+ */
115
+ function buildPhysicalSizeAttrs(dims) {
116
+ let attrs = "";
117
+ if (dims.physicalSizeX !== undefined) {
118
+ attrs += ` PhysicalSizeX="${dims.physicalSizeX}"`;
119
+ if (dims.physicalSizeXUnit) {
120
+ attrs += ` PhysicalSizeXUnit="${escapeXml(dims.physicalSizeXUnit)}"`;
121
+ }
122
+ }
123
+ if (dims.physicalSizeY !== undefined) {
124
+ attrs += ` PhysicalSizeY="${dims.physicalSizeY}"`;
125
+ if (dims.physicalSizeYUnit) {
126
+ attrs += ` PhysicalSizeYUnit="${escapeXml(dims.physicalSizeYUnit)}"`;
127
+ }
128
+ }
129
+ if (dims.physicalSizeZ !== undefined) {
130
+ attrs += ` PhysicalSizeZ="${dims.physicalSizeZ}"`;
131
+ if (dims.physicalSizeZUnit) {
132
+ attrs += ` PhysicalSizeZUnit="${escapeXml(dims.physicalSizeZUnit)}"`;
133
+ }
134
+ }
135
+ return attrs;
136
+ }
137
+ /**
138
+ * Convert a 6-digit hex color string (e.g. "FF0000") to a signed 32-bit RGBA int.
139
+ * Alpha defaults to 0xFF.
140
+ */
141
+ export function hexColorToOmeInt(hex) {
142
+ // Strip leading # if present
143
+ const h = hex.startsWith("#") ? hex.slice(1) : hex;
144
+ // Parse as RGBA (alpha defaults to FF)
145
+ const r = parseInt(h.substring(0, 2), 16);
146
+ const g = parseInt(h.substring(2, 4), 16);
147
+ const b = parseInt(h.substring(4, 6), 16);
148
+ const a = h.length >= 8 ? parseInt(h.substring(6, 8), 16) : 0xff;
149
+ // Combine as RGBA and convert to signed 32-bit int
150
+ const unsigned = ((r << 24) | (g << 16) | (b << 8) | a) >>> 0;
151
+ return unsigned > 0x7fffffff ? unsigned - 0x100000000 : unsigned;
152
+ }
153
+ /**
154
+ * Convert an OME signed 32-bit RGBA int to a 6-digit hex color string.
155
+ */
156
+ export function omeIntToHexColor(value) {
157
+ const unsigned = value < 0 ? value + 0x100000000 : value;
158
+ const r = (unsigned >>> 24) & 0xff;
159
+ const g = (unsigned >>> 16) & 0xff;
160
+ const b = (unsigned >>> 8) & 0xff;
161
+ return (r.toString(16).padStart(2, "0") +
162
+ g.toString(16).padStart(2, "0") +
163
+ b.toString(16).padStart(2, "0")).toUpperCase();
164
+ }
165
+ /**
166
+ * Map NGFF unit names to OME-XML PhysicalSize unit symbols.
167
+ *
168
+ * NGFF uses full names (e.g. "micrometer"), OME-XML uses symbols (e.g. "µm").
169
+ */
170
+ function ngffUnitToOmeUnit(unit) {
171
+ const map = {
172
+ angstrom: "\u00C5",
173
+ attometer: "am",
174
+ centimeter: "cm",
175
+ decimeter: "dm",
176
+ exameter: "Em",
177
+ femtometer: "fm",
178
+ foot: "ft",
179
+ gigameter: "Gm",
180
+ hectometer: "hm",
181
+ inch: "in",
182
+ kilometer: "km",
183
+ megameter: "Mm",
184
+ meter: "m",
185
+ micrometer: "\u00B5m",
186
+ mile: "mi",
187
+ millimeter: "mm",
188
+ nanometer: "nm",
189
+ parsec: "pc",
190
+ petameter: "Pm",
191
+ picometer: "pm",
192
+ terameter: "Tm",
193
+ yard: "yd",
194
+ yoctometer: "ym",
195
+ yottameter: "Ym",
196
+ zeptometer: "zm",
197
+ zettameter: "Zm",
198
+ };
199
+ return map[unit] ?? unit;
200
+ }
201
+ /** Escape XML special characters. */
202
+ function escapeXml(str) {
203
+ return str
204
+ .replace(/&/g, "&amp;")
205
+ .replace(/</g, "&lt;")
206
+ .replace(/>/g, "&gt;")
207
+ .replace(/"/g, "&quot;")
208
+ .replace(/'/g, "&apos;");
209
+ }
210
+ //# sourceMappingURL=ome-xml-writer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ome-xml-writer.js","sourceRoot":"","sources":["../src/ome-xml-writer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /** TIFF tag data types and their byte sizes. */
2
+ export declare const TIFF_TYPE_BYTE = 1;
3
+ export declare const TIFF_TYPE_ASCII = 2;
4
+ export declare const TIFF_TYPE_SHORT = 3;
5
+ export declare const TIFF_TYPE_LONG = 4;
6
+ export declare const TIFF_TYPE_RATIONAL = 5;
7
+ /** Well-known TIFF tags. */
8
+ export declare const TAG_NEW_SUBFILE_TYPE = 254;
9
+ export declare const TAG_IMAGE_WIDTH = 256;
10
+ export declare const TAG_IMAGE_LENGTH = 257;
11
+ export declare const TAG_BITS_PER_SAMPLE = 258;
12
+ export declare const TAG_COMPRESSION = 259;
13
+ export declare const TAG_PHOTOMETRIC = 262;
14
+ export declare const TAG_IMAGE_DESCRIPTION = 270;
15
+ export declare const TAG_STRIP_OFFSETS = 273;
16
+ export declare const TAG_SAMPLES_PER_PIXEL = 277;
17
+ export declare const TAG_ROWS_PER_STRIP = 278;
18
+ export declare const TAG_STRIP_BYTE_COUNTS = 279;
19
+ export declare const TAG_PLANAR_CONFIGURATION = 284;
20
+ export declare const TAG_SAMPLE_FORMAT = 339;
21
+ export declare const TAG_SUB_IFDS = 330;
22
+ /** TIFF compression codes. */
23
+ export declare const COMPRESSION_NONE = 1;
24
+ export declare const COMPRESSION_DEFLATE = 8;
25
+ /** A single TIFF tag entry. */
26
+ export interface TiffTag {
27
+ /** TIFF tag number (e.g. 256 for ImageWidth). */
28
+ tag: number;
29
+ /** TIFF data type (e.g. TIFF_TYPE_SHORT = 3). */
30
+ type: number;
31
+ /** Tag values. For ASCII tags, pass a string. */
32
+ values: number[] | string;
33
+ }
34
+ /** Describes one IFD (image) to be written. */
35
+ export interface WritableIfd {
36
+ /** Tags for this IFD (excluding StripOffsets/StripByteCounts — those are auto-generated). */
37
+ tags: TiffTag[];
38
+ /** Raw (possibly compressed) strip data. Each entry is one strip. */
39
+ strips: Uint8Array[];
40
+ /** Optional SubIFDs attached to this IFD (for pyramid sub-resolutions). */
41
+ subIfds?: WritableIfd[];
42
+ }
43
+ /** Options for buildTiff. */
44
+ export interface BuildTiffOptions {
45
+ /** Compression to apply to strip data before writing. Default: "none". */
46
+ compression?: "none" | "deflate";
47
+ /** Deflate compression level (1-9). Default: 6. */
48
+ compressionLevel?: number;
49
+ }
50
+ /**
51
+ * Build a complete TIFF file from a list of IFDs.
52
+ *
53
+ * @param ifds - Main IFD chain (linked via next-IFD pointers).
54
+ * @param options - Build options (compression, etc.).
55
+ * @returns A complete TIFF file as an ArrayBuffer.
56
+ */
57
+ export declare function buildTiff(ifds: WritableIfd[], options?: BuildTiffOptions): ArrayBuffer;
58
+ /**
59
+ * Compress a Uint8Array using deflate (zlib-wrapped, RFC 1950).
60
+ * Compatible with TIFF compression code 8 and geotiff.js's pako.inflate().
61
+ */
62
+ export declare function compressDeflate(data: Uint8Array, level?: number): Uint8Array;
63
+ /** Create a standard set of tags for a grayscale image plane. */
64
+ export declare function makeImageTags(width: number, height: number, bitsPerSample: number, sampleFormat: number, compression?: "none" | "deflate", imageDescription?: string, isSubResolution?: boolean): TiffTag[];
65
+ //# sourceMappingURL=tiff-writer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tiff-writer.d.ts","sourceRoot":"","sources":["../src/tiff-writer.ts"],"names":[],"mappings":"AA2BA,gDAAgD;AAChD,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,eAAe,IAAI,CAAC;AACjC,eAAO,MAAM,cAAc,IAAI,CAAC;AAChC,eAAO,MAAM,kBAAkB,IAAI,CAAC;AAUpC,4BAA4B;AAC5B,eAAO,MAAM,oBAAoB,MAAM,CAAC;AACxC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,gBAAgB,MAAM,CAAC;AACpC,eAAO,MAAM,mBAAmB,MAAM,CAAC;AACvC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,kBAAkB,MAAM,CAAC;AACtC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AACzC,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAC5C,eAAO,MAAM,iBAAiB,MAAM,CAAC;AACrC,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,8BAA8B;AAC9B,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAClC,eAAO,MAAM,mBAAmB,IAAI,CAAC;AAIrC,+BAA+B;AAC/B,MAAM,WAAW,OAAO;IACtB,iDAAiD;IACjD,GAAG,EAAE,MAAM,CAAC;IACZ,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;CAC3B;AAED,+CAA+C;AAC/C,MAAM,WAAW,WAAW;IAC1B,6FAA6F;IAC7F,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,qEAAqE;IACrE,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;CACzB;AAED,6BAA6B;AAC7B,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,mDAAmD;IACnD,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAmCD;;;;;;GAMG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,WAAW,EAAE,EACnB,OAAO,GAAE,gBAAqB,GAC7B,WAAW,CA4Bb;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,KAAK,GAAE,MAAU,GAChB,UAAU,CAEZ;AA8TD,iEAAiE;AACjE,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,MAAM,EACrB,YAAY,EAAE,MAAM,EACpB,WAAW,GAAE,MAAM,GAAG,SAAkB,EACxC,gBAAgB,CAAC,EAAE,MAAM,EACzB,eAAe,CAAC,EAAE,OAAO,GACxB,OAAO,EAAE,CA0BX"}
@@ -0,0 +1,373 @@
1
+ // SPDX-FileCopyrightText: Copyright (c) Fideus Labs LLC
2
+ // SPDX-License-Identifier: MIT
3
+ /**
4
+ * Low-level TIFF binary builder.
5
+ *
6
+ * Assembles IFD entries, tag values, strip data, and SubIFD chains into
7
+ * a valid classic TIFF file (little-endian, magic 42).
8
+ *
9
+ * Layout strategy (two-pass):
10
+ * Pass 1: Collect all IFDs and pixel data, compute sizes.
11
+ * Pass 2: Compute offsets and write into a pre-allocated ArrayBuffer.
12
+ *
13
+ * File layout:
14
+ * [Header 8 bytes]
15
+ * [Main IFD 0 entries + overflow data + strip data]
16
+ * [Main IFD 1 entries + overflow data + strip data]
17
+ * ...
18
+ * [SubIFD 0,0 entries + overflow data + strip data]
19
+ * [SubIFD 0,1 entries + overflow data + strip data]
20
+ * ...
21
+ */
22
+ import { deflate } from "pako";
23
+ // ── TIFF constants ──────────────────────────────────────────────────
24
+ /** TIFF tag data types and their byte sizes. */
25
+ export const TIFF_TYPE_BYTE = 1; // 1 byte
26
+ export const TIFF_TYPE_ASCII = 2; // 1 byte (null-terminated)
27
+ export const TIFF_TYPE_SHORT = 3; // 2 bytes
28
+ export const TIFF_TYPE_LONG = 4; // 4 bytes
29
+ export const TIFF_TYPE_RATIONAL = 5; // 8 bytes (two LONGs)
30
+ const TYPE_SIZES = {
31
+ [TIFF_TYPE_BYTE]: 1,
32
+ [TIFF_TYPE_ASCII]: 1,
33
+ [TIFF_TYPE_SHORT]: 2,
34
+ [TIFF_TYPE_LONG]: 4,
35
+ [TIFF_TYPE_RATIONAL]: 8,
36
+ };
37
+ /** Well-known TIFF tags. */
38
+ export const TAG_NEW_SUBFILE_TYPE = 254;
39
+ export const TAG_IMAGE_WIDTH = 256;
40
+ export const TAG_IMAGE_LENGTH = 257;
41
+ export const TAG_BITS_PER_SAMPLE = 258;
42
+ export const TAG_COMPRESSION = 259;
43
+ export const TAG_PHOTOMETRIC = 262;
44
+ export const TAG_IMAGE_DESCRIPTION = 270;
45
+ export const TAG_STRIP_OFFSETS = 273;
46
+ export const TAG_SAMPLES_PER_PIXEL = 277;
47
+ export const TAG_ROWS_PER_STRIP = 278;
48
+ export const TAG_STRIP_BYTE_COUNTS = 279;
49
+ export const TAG_PLANAR_CONFIGURATION = 284;
50
+ export const TAG_SAMPLE_FORMAT = 339;
51
+ export const TAG_SUB_IFDS = 330;
52
+ /** TIFF compression codes. */
53
+ export const COMPRESSION_NONE = 1;
54
+ export const COMPRESSION_DEFLATE = 8;
55
+ // ── Public API ──────────────────────────────────────────────────────
56
+ /**
57
+ * Build a complete TIFF file from a list of IFDs.
58
+ *
59
+ * @param ifds - Main IFD chain (linked via next-IFD pointers).
60
+ * @param options - Build options (compression, etc.).
61
+ * @returns A complete TIFF file as an ArrayBuffer.
62
+ */
63
+ export function buildTiff(ifds, options = {}) {
64
+ const compression = options.compression ?? "none";
65
+ const compressionLevel = options.compressionLevel ?? 6;
66
+ // Compress strips if needed
67
+ const processedIfds = ifds.map((ifd) => processIfd(ifd, compression, compressionLevel));
68
+ // Pass 1: compute sizes and place all IFDs
69
+ const placed = placeIfds(processedIfds);
70
+ // Pass 2: compute total file size
71
+ const totalSize = computeTotalSize(placed);
72
+ // Pass 3: write into buffer
73
+ const buffer = new ArrayBuffer(totalSize);
74
+ const view = new DataView(buffer);
75
+ // Write TIFF header (little-endian)
76
+ view.setUint16(0, 0x4949, true); // "II" byte order
77
+ view.setUint16(2, 42, true); // magic number
78
+ view.setUint32(4, placed.length > 0 ? placed[0].ifdOffset : 0, true); // offset to first IFD
79
+ // Write all placed IFDs
80
+ for (const p of placed) {
81
+ writeIfd(view, buffer, p);
82
+ }
83
+ return buffer;
84
+ }
85
+ /**
86
+ * Compress a Uint8Array using deflate (zlib-wrapped, RFC 1950).
87
+ * Compatible with TIFF compression code 8 and geotiff.js's pako.inflate().
88
+ */
89
+ export function compressDeflate(data, level = 6) {
90
+ return deflate(data, { level: level });
91
+ }
92
+ // ── Internal helpers ────────────────────────────────────────────────
93
+ /**
94
+ * Process an IFD: compress strips if needed, add compression tag,
95
+ * and recursively process SubIFDs.
96
+ */
97
+ function processIfd(ifd, compression, level) {
98
+ let strips = ifd.strips;
99
+ const tags = [...ifd.tags];
100
+ if (compression === "deflate") {
101
+ strips = strips.map((s) => compressDeflate(s, level));
102
+ // Replace or add Compression tag
103
+ const compIdx = tags.findIndex((t) => t.tag === TAG_COMPRESSION);
104
+ if (compIdx >= 0) {
105
+ tags[compIdx] = { tag: TAG_COMPRESSION, type: TIFF_TYPE_SHORT, values: [COMPRESSION_DEFLATE] };
106
+ }
107
+ else {
108
+ tags.push({ tag: TAG_COMPRESSION, type: TIFF_TYPE_SHORT, values: [COMPRESSION_DEFLATE] });
109
+ }
110
+ }
111
+ const subIfds = ifd.subIfds?.map((sub) => processIfd(sub, compression, level));
112
+ return { tags, strips, subIfds };
113
+ }
114
+ /** Resolve a TiffTag to its byte representation. */
115
+ function resolveTag(tag) {
116
+ if (typeof tag.values === "string") {
117
+ // ASCII: null-terminated
118
+ const encoder = new TextEncoder();
119
+ const strBytes = encoder.encode(tag.values);
120
+ const valueBytes = new Uint8Array(strBytes.length + 1); // +1 for null terminator
121
+ valueBytes.set(strBytes);
122
+ valueBytes[strBytes.length] = 0;
123
+ return { tag: tag.tag, type: TIFF_TYPE_ASCII, count: valueBytes.length, valueBytes };
124
+ }
125
+ const typeSize = TYPE_SIZES[tag.type];
126
+ if (!typeSize) {
127
+ throw new Error(`Unknown TIFF type: ${tag.type}`);
128
+ }
129
+ const count = tag.type === TIFF_TYPE_RATIONAL ? tag.values.length / 2 : tag.values.length;
130
+ const totalBytes = count * typeSize;
131
+ const valueBytes = new Uint8Array(totalBytes);
132
+ const dv = new DataView(valueBytes.buffer, valueBytes.byteOffset, valueBytes.byteLength);
133
+ for (let i = 0; i < tag.values.length; i++) {
134
+ switch (tag.type) {
135
+ case TIFF_TYPE_BYTE:
136
+ dv.setUint8(i, tag.values[i]);
137
+ break;
138
+ case TIFF_TYPE_SHORT:
139
+ dv.setUint16(i * 2, tag.values[i], true);
140
+ break;
141
+ case TIFF_TYPE_LONG:
142
+ dv.setUint32(i * 4, tag.values[i], true);
143
+ break;
144
+ case TIFF_TYPE_RATIONAL:
145
+ // Rationals are stored as two LONGs (numerator, denominator)
146
+ dv.setUint32(i * 4, tag.values[i], true);
147
+ break;
148
+ }
149
+ }
150
+ return { tag: tag.tag, type: tag.type, count, valueBytes };
151
+ }
152
+ /**
153
+ * Compute the byte size of an IFD entry block:
154
+ * 2 bytes (entry count) + 12 bytes per entry + 4 bytes (next IFD offset)
155
+ */
156
+ function ifdEntryBlockSize(numTags) {
157
+ return 2 + numTags * 12 + 4;
158
+ }
159
+ /** Compute overflow size for resolved tags (values that don't fit in 4 bytes). */
160
+ function overflowSize(tags) {
161
+ let size = 0;
162
+ for (const t of tags) {
163
+ if (t.valueBytes.length > 4) {
164
+ size += t.valueBytes.length;
165
+ // Pad to word boundary (2-byte alignment)
166
+ if (t.valueBytes.length % 2 !== 0)
167
+ size += 1;
168
+ }
169
+ }
170
+ return size;
171
+ }
172
+ /** Compute total strip data size. */
173
+ function totalStripSize(strips) {
174
+ return strips.reduce((sum, s) => sum + s.length, 0);
175
+ }
176
+ /**
177
+ * Place all IFDs sequentially, computing absolute offsets.
178
+ * Returns a flat list of PlacedIfds (main chain first, then SubIFDs).
179
+ */
180
+ function placeIfds(ifds) {
181
+ const allPlaced = [];
182
+ function resolveIfdInfo(ifd) {
183
+ // Build tags list — we'll add StripOffsets and StripByteCounts as placeholders
184
+ // They'll be patched during the write phase
185
+ const userTags = ifd.tags.map(resolveTag);
186
+ // Add StripOffsets (placeholder — count = number of strips, values will be patched)
187
+ const stripOffsetsTag = {
188
+ tag: TAG_STRIP_OFFSETS,
189
+ type: TIFF_TYPE_LONG,
190
+ count: ifd.strips.length,
191
+ valueBytes: new Uint8Array(ifd.strips.length * 4),
192
+ };
193
+ // Add StripByteCounts
194
+ const stripByteCountsBytes = new Uint8Array(ifd.strips.length * 4);
195
+ const sbcView = new DataView(stripByteCountsBytes.buffer);
196
+ for (let i = 0; i < ifd.strips.length; i++) {
197
+ sbcView.setUint32(i * 4, ifd.strips[i].length, true);
198
+ }
199
+ const stripByteCountsTag = {
200
+ tag: TAG_STRIP_BYTE_COUNTS,
201
+ type: TIFF_TYPE_LONG,
202
+ count: ifd.strips.length,
203
+ valueBytes: stripByteCountsBytes,
204
+ };
205
+ const allTags = [...userTags, stripOffsetsTag, stripByteCountsTag];
206
+ // Add SubIFDs tag if there are SubIFDs
207
+ const subIfdInfos = (ifd.subIfds ?? []).map(resolveIfdInfo);
208
+ if (subIfdInfos.length > 0) {
209
+ const subIfdsTag = {
210
+ tag: TAG_SUB_IFDS,
211
+ type: TIFF_TYPE_LONG,
212
+ count: subIfdInfos.length,
213
+ valueBytes: new Uint8Array(subIfdInfos.length * 4), // placeholder offsets
214
+ };
215
+ allTags.push(subIfdsTag);
216
+ }
217
+ // Sort by tag number (TIFF spec requires this)
218
+ allTags.sort((a, b) => a.tag - b.tag);
219
+ return {
220
+ tags: allTags,
221
+ strips: ifd.strips,
222
+ subIfdInfos,
223
+ entryBlockSize: ifdEntryBlockSize(allTags.length),
224
+ overflow: overflowSize(allTags),
225
+ stripSize: totalStripSize(ifd.strips),
226
+ };
227
+ }
228
+ const mainInfos = ifds.map(resolveIfdInfo);
229
+ // Second pass: place IFDs sequentially
230
+ let cursor = 8; // Start after TIFF header
231
+ function placeIfdInfo(info) {
232
+ const ifdOffset = cursor;
233
+ cursor += info.entryBlockSize;
234
+ const overflowOffset = cursor;
235
+ cursor += info.overflow;
236
+ const stripDataOffset = cursor;
237
+ cursor += info.stripSize;
238
+ const placed = {
239
+ ifdOffset,
240
+ tags: info.tags,
241
+ overflowOffset,
242
+ overflowSize: info.overflow,
243
+ stripDataOffset,
244
+ strips: info.strips,
245
+ subIfds: [], // will be filled after SubIFD placement
246
+ nextIfdOffset: 0, // will be patched below for main chain
247
+ };
248
+ info.placed = placed;
249
+ allPlaced.push(placed);
250
+ // Place SubIFDs immediately after this IFD's strip data
251
+ for (const subInfo of info.subIfdInfos) {
252
+ placed.subIfds.push(placeIfdInfo(subInfo));
253
+ }
254
+ return placed;
255
+ }
256
+ // Place main chain IFDs (SubIFDs are placed within each main IFD's placeIfdInfo call)
257
+ const mainPlaced = [];
258
+ for (let i = 0; i < mainInfos.length; i++) {
259
+ mainPlaced.push(placeIfdInfo(mainInfos[i]));
260
+ }
261
+ // Link main chain next-IFD pointers
262
+ for (let i = 0; i < mainPlaced.length - 1; i++) {
263
+ mainPlaced[i].nextIfdOffset = mainPlaced[i + 1].ifdOffset;
264
+ }
265
+ return allPlaced;
266
+ }
267
+ /** Compute total file size from placed IFDs. */
268
+ function computeTotalSize(placed) {
269
+ if (placed.length === 0)
270
+ return 8;
271
+ let maxEnd = 8;
272
+ for (const p of placed) {
273
+ const end = p.stripDataOffset + totalStripSize(p.strips);
274
+ if (end > maxEnd)
275
+ maxEnd = end;
276
+ }
277
+ return maxEnd;
278
+ }
279
+ /** Write a single placed IFD (and its SubIFDs) into the buffer. */
280
+ function writeIfd(view, buffer, placed) {
281
+ let pos = placed.ifdOffset;
282
+ // Write entry count
283
+ view.setUint16(pos, placed.tags.length, true);
284
+ pos += 2;
285
+ // Compute strip offsets for this IFD
286
+ const stripOffsets = [];
287
+ let stripCursor = placed.stripDataOffset;
288
+ for (const strip of placed.strips) {
289
+ stripOffsets.push(stripCursor);
290
+ stripCursor += strip.length;
291
+ }
292
+ // Compute SubIFD offsets
293
+ const subIfdOffsets = placed.subIfds.map((s) => s.ifdOffset);
294
+ // Track overflow cursor
295
+ let overflowCursor = placed.overflowOffset;
296
+ // Write each tag entry (12 bytes each)
297
+ for (const tag of placed.tags) {
298
+ view.setUint16(pos, tag.tag, true);
299
+ view.setUint16(pos + 2, tag.type, true);
300
+ view.setUint32(pos + 4, tag.count, true);
301
+ // Determine the value bytes to write
302
+ let valueBytes = tag.valueBytes;
303
+ // Patch StripOffsets
304
+ if (tag.tag === TAG_STRIP_OFFSETS) {
305
+ valueBytes = new Uint8Array(stripOffsets.length * 4);
306
+ const dv = new DataView(valueBytes.buffer, valueBytes.byteOffset, valueBytes.byteLength);
307
+ for (let i = 0; i < stripOffsets.length; i++) {
308
+ dv.setUint32(i * 4, stripOffsets[i], true);
309
+ }
310
+ }
311
+ // Patch SubIFDs offsets
312
+ if (tag.tag === TAG_SUB_IFDS && subIfdOffsets.length > 0) {
313
+ valueBytes = new Uint8Array(subIfdOffsets.length * 4);
314
+ const dv = new DataView(valueBytes.buffer, valueBytes.byteOffset, valueBytes.byteLength);
315
+ for (let i = 0; i < subIfdOffsets.length; i++) {
316
+ dv.setUint32(i * 4, subIfdOffsets[i], true);
317
+ }
318
+ }
319
+ if (valueBytes.length <= 4) {
320
+ // Inline: write value bytes directly in the 4-byte value/offset field
321
+ const dest = new Uint8Array(buffer, pos + 8, 4);
322
+ dest.fill(0);
323
+ dest.set(valueBytes);
324
+ }
325
+ else {
326
+ // Overflow: write offset to overflow area, then write bytes there
327
+ view.setUint32(pos + 8, overflowCursor, true);
328
+ const dest = new Uint8Array(buffer, overflowCursor, valueBytes.length);
329
+ dest.set(valueBytes);
330
+ overflowCursor += valueBytes.length;
331
+ // Pad to word boundary
332
+ if (valueBytes.length % 2 !== 0)
333
+ overflowCursor += 1;
334
+ }
335
+ pos += 12;
336
+ }
337
+ // Write next IFD offset
338
+ view.setUint32(pos, placed.nextIfdOffset, true);
339
+ // Write strip data
340
+ let stripPos = placed.stripDataOffset;
341
+ for (const strip of placed.strips) {
342
+ const dest = new Uint8Array(buffer, stripPos, strip.length);
343
+ dest.set(strip);
344
+ stripPos += strip.length;
345
+ }
346
+ // SubIFDs are already in allPlaced and will be written by the caller's loop
347
+ }
348
+ // ── Convenience helpers for building IFD tags ───────────────────────
349
+ /** Create a standard set of tags for a grayscale image plane. */
350
+ export function makeImageTags(width, height, bitsPerSample, sampleFormat, compression = "none", imageDescription, isSubResolution) {
351
+ const tags = [];
352
+ if (isSubResolution) {
353
+ tags.push({ tag: TAG_NEW_SUBFILE_TYPE, type: TIFF_TYPE_LONG, values: [1] });
354
+ }
355
+ tags.push({ tag: TAG_IMAGE_WIDTH, type: TIFF_TYPE_LONG, values: [width] });
356
+ tags.push({ tag: TAG_IMAGE_LENGTH, type: TIFF_TYPE_LONG, values: [height] });
357
+ tags.push({ tag: TAG_BITS_PER_SAMPLE, type: TIFF_TYPE_SHORT, values: [bitsPerSample] });
358
+ tags.push({
359
+ tag: TAG_COMPRESSION,
360
+ type: TIFF_TYPE_SHORT,
361
+ values: [compression === "deflate" ? COMPRESSION_DEFLATE : COMPRESSION_NONE],
362
+ });
363
+ tags.push({ tag: TAG_PHOTOMETRIC, type: TIFF_TYPE_SHORT, values: [1] }); // MinIsBlack
364
+ tags.push({ tag: TAG_SAMPLES_PER_PIXEL, type: TIFF_TYPE_SHORT, values: [1] });
365
+ tags.push({ tag: TAG_ROWS_PER_STRIP, type: TIFF_TYPE_LONG, values: [height] }); // single strip
366
+ tags.push({ tag: TAG_PLANAR_CONFIGURATION, type: TIFF_TYPE_SHORT, values: [1] }); // chunky
367
+ tags.push({ tag: TAG_SAMPLE_FORMAT, type: TIFF_TYPE_SHORT, values: [sampleFormat] });
368
+ if (imageDescription) {
369
+ tags.push({ tag: TAG_IMAGE_DESCRIPTION, type: TIFF_TYPE_ASCII, values: imageDescription });
370
+ }
371
+ return tags;
372
+ }
373
+ //# sourceMappingURL=tiff-writer.js.map