@photivra/engine 2.2.1 → 2.4.1
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 +38 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/dist/capture/native-environment-raw.d.ts +18 -0
- package/dist/capture/native-environment-raw.js +17 -1
- package/dist/capture/native-environment-raw.js.map +1 -1
- package/dist/capture/native-raw-dng.d.ts +3 -0
- package/dist/capture/native-raw-dng.js +23 -3
- package/dist/capture/native-raw-dng.js.map +1 -1
- package/dist/capture/native-raw.d.ts +3 -0
- package/dist/capture/native-raw.js +8 -5
- package/dist/capture/native-raw.js.map +1 -1
- package/dist/core/version.d.ts +1 -1
- package/dist/core/version.js +1 -1
- package/dist/core/version.js.map +1 -1
- package/dist/equipment/generic-prime-lenses.d.ts +46 -0
- package/dist/equipment/generic-prime-lenses.js +59 -0
- package/dist/equipment/generic-prime-lenses.js.map +1 -0
- package/dist/facade/chart-scenes.d.ts +270 -1
- package/dist/facade/chart-scenes.js +443 -17
- package/dist/facade/chart-scenes.js.map +1 -1
- package/dist/facade/scene-radiance.d.ts +47 -0
- package/dist/facade/scene-radiance.js +65 -5
- package/dist/facade/scene-radiance.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +158 -4
- package/dist/facade/simulate-capture.js +324 -56
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/subject-framing.d.ts +55 -0
- package/dist/facade/subject-framing.js +66 -0
- package/dist/facade/subject-framing.js.map +1 -0
- package/dist/gpu/chart-radiance-gpu.d.ts +8 -0
- package/dist/gpu/chart-radiance-gpu.js +20 -1
- package/dist/gpu/chart-radiance-gpu.js.map +1 -1
- package/dist/index.d.ts +6 -3
- package/dist/index.js +6 -3
- package/dist/index.js.map +1 -1
- package/dist/output/text-metadata.d.ts +32 -0
- package/dist/output/text-metadata.js +201 -0
- package/dist/output/text-metadata.js.map +1 -0
- package/docs/API_REFERENCE.md +56 -1
- package/docs/QUICK_START.md +325 -4
- package/docs/api/capture-native-raw-dng.md +2 -0
- package/docs/api/core-version.md +1 -1
- package/docs/api/equipment-generic-prime-lenses.md +85 -0
- package/docs/api/exports.json +172 -2
- package/docs/api/facade-chart-scenes.md +280 -4
- package/docs/api/facade-scene-radiance.md +42 -0
- package/docs/api/facade-simulate-capture.md +113 -2
- package/docs/api/facade-subject-framing.md +72 -0
- package/docs/api/output-text-metadata.md +20 -0
- package/package.json +1 -1
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
/**
|
|
3
|
+
* Optional caller-supplied text metadata for encoded images (2.4): a description, the creating software, a
|
|
4
|
+
* copyright notice and a comment. The engine writes the caller's text unchanged and adds no text of its own
|
|
5
|
+
* (in a DNG the description follows the existing simulated-RAW statement).
|
|
6
|
+
* Independently written from these free specifications; no third-party source was used:
|
|
7
|
+
*
|
|
8
|
+
* - JPEG: the existing XMP packet in APP1 (Adobe XMP Specification Part 3, 2020, §1.1.3: identifier
|
|
9
|
+
* "http://ns.adobe.com/xap/1.0/" and a UTF-8 packet of at most 65 502 bytes, §1.1.3.1). The fields are added
|
|
10
|
+
* as one more top-level `rdf:Description` of the same resource (`rdf:about=""`): XMP Part 1 (2012, identical
|
|
11
|
+
* to ISO 16684-1:2011) §7.4 lets properties be "arbitrarily apportioned among the rdf:Description elements"
|
|
12
|
+
* of the single `rdf:RDF`. Mapping: `dc:description` and `dc:rights` as language alternatives with
|
|
13
|
+
* `x-default` (Part 1 §8.3), `xmp:CreatorTool` as text (Part 1 §8.4) and `exif:UserComment` as a language
|
|
14
|
+
* alternative (Part 2, 2022, §3.4, the Exif namespace). The comment is also a COM marker segment (ITU-T T.81
|
|
15
|
+
* §B.2.4.5, "the interpretation is left to the application"; written as UTF-8) after the APPn segments.
|
|
16
|
+
* - PNG: one text chunk per field with the predefined keywords Description, Software, Copyright and Comment
|
|
17
|
+
* (W3C PNG Specification, Third Edition, §11.3.3.1, Table 21). `tEXt` (§11.3.3.2; printable Latin-1 plus line
|
|
18
|
+
* feed) when every character fits, otherwise an uncompressed `iTXt` (§11.3.3.4; UTF-8, empty language tag and
|
|
19
|
+
* translated keyword). The chunks go before the first IDAT, as §12.9 recommends for small text.
|
|
20
|
+
* - DNG: `serializeNativeRawDng` writes the same XMP fields as a packet in TIFF tag 700 (XMP Part 3 §1.1.6,
|
|
21
|
+
* Table 13) plus the TIFF ASCII tags that XMP Part 3 §3.2.3 maps to them.
|
|
22
|
+
*
|
|
23
|
+
* Validation: each given field is a non-empty string of at most `OUTPUT_TEXT_METADATA_MAXIMUM_BYTES` UTF-8
|
|
24
|
+
* bytes, with no unpaired UTF-16 surrogate (so it encodes to valid UTF-8 without replacement), no NUL or other
|
|
25
|
+
* C0/C1 control character except line feed, and no U+FFFE/U+FFFF (not XML 1.0 characters). Line breaks are a
|
|
26
|
+
* single line feed, as the PNG text chunks require.
|
|
27
|
+
* @see docs/QUICK_START.md "Text metadata (2.4)".
|
|
28
|
+
*/
|
|
29
|
+
import { InvalidConfigurationError } from "../core/configuration-error.js";
|
|
30
|
+
import { pngCrc32 } from "./png-encoder.js";
|
|
31
|
+
/** Largest UTF-8 length of one metadata field, in bytes. Four escaped fields still fit one JPEG APP1 segment. */
|
|
32
|
+
export const OUTPUT_TEXT_METADATA_MAXIMUM_BYTES = 2048;
|
|
33
|
+
const FIELDS = ["description", "software", "copyright", "comment"];
|
|
34
|
+
/** PNG Third Edition §11.3.3.1 Table 21 keyword for each field: the field name with an initial capital. */
|
|
35
|
+
const pngKeyword = (field) => field[0].toUpperCase() + field.slice(1);
|
|
36
|
+
const XMP_IDENTIFIER = "http://ns.adobe.com/xap/1.0/\0";
|
|
37
|
+
/** Largest XMP packet in one JPEG APP1 segment (XMP Part 3 §1.1.3.1). */
|
|
38
|
+
const JPEG_XMP_MAXIMUM_PACKET_BYTES = 65502;
|
|
39
|
+
const encoder = new TextEncoder();
|
|
40
|
+
/**
|
|
41
|
+
* Checks caller text metadata. Returns undefined when `value` is undefined, otherwise a frozen copy with only
|
|
42
|
+
* the given fields. `path` prefixes the error messages, for example "output.metadata".
|
|
43
|
+
*/
|
|
44
|
+
export function parseOutputTextMetadata(value, path) {
|
|
45
|
+
if (value === undefined)
|
|
46
|
+
return undefined;
|
|
47
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
48
|
+
throw new InvalidConfigurationError(path + " must be an object.");
|
|
49
|
+
const out = {};
|
|
50
|
+
for (const key of Object.keys(value))
|
|
51
|
+
if (!FIELDS.includes(key))
|
|
52
|
+
throw new InvalidConfigurationError("Unknown " + path + " field: " + key + ".");
|
|
53
|
+
for (const field of FIELDS) {
|
|
54
|
+
const text = value[field];
|
|
55
|
+
if (text === undefined)
|
|
56
|
+
continue;
|
|
57
|
+
const name = path + "." + field;
|
|
58
|
+
if (typeof text !== "string" || text.length === 0)
|
|
59
|
+
throw new InvalidConfigurationError(name + " must be a non-empty string.");
|
|
60
|
+
if (/\p{Cs}/u.test(text))
|
|
61
|
+
throw new InvalidConfigurationError(name + " must be valid Unicode text (it has an unpaired UTF-16 surrogate).");
|
|
62
|
+
if (text.includes("\0"))
|
|
63
|
+
throw new InvalidConfigurationError(name + " must not contain NUL characters.");
|
|
64
|
+
// eslint-disable-next-line no-control-regex
|
|
65
|
+
if (/[\u0001-\u0009\u000b-\u001f\u007f-\u009f]/u.test(text))
|
|
66
|
+
throw new InvalidConfigurationError(name + " must not contain control characters other than line feed (\\n), or U+FFFE/U+FFFF.");
|
|
67
|
+
const bytes = encoder.encode(text).length;
|
|
68
|
+
if (bytes > OUTPUT_TEXT_METADATA_MAXIMUM_BYTES)
|
|
69
|
+
throw new InvalidConfigurationError(name + " must be at most " + OUTPUT_TEXT_METADATA_MAXIMUM_BYTES + " UTF-8 bytes (it has " + bytes + ").");
|
|
70
|
+
out[field] = text;
|
|
71
|
+
}
|
|
72
|
+
if (Object.keys(out).length === 0)
|
|
73
|
+
throw new InvalidConfigurationError(path + " must set at least one of description, software, copyright or comment.");
|
|
74
|
+
return Object.freeze(out);
|
|
75
|
+
}
|
|
76
|
+
function xmlText(text) {
|
|
77
|
+
return text.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
78
|
+
}
|
|
79
|
+
const alt = (text) => '<rdf:Alt><rdf:li xml:lang="x-default">' + xmlText(text) + "</rdf:li></rdf:Alt>";
|
|
80
|
+
/** One `rdf:Description` of the current resource with the given fields, declaring only the namespaces it uses. */
|
|
81
|
+
export function xmpTextMetadataDescription(metadata) {
|
|
82
|
+
const dc = metadata.description !== undefined || metadata.copyright !== undefined;
|
|
83
|
+
return '<rdf:Description rdf:about=""' + (dc ? ' xmlns:dc="http://purl.org/dc/elements/1.1/"' : "") +
|
|
84
|
+
(metadata.software !== undefined ? ' xmlns:xmp="http://ns.adobe.com/xap/1.0/"' : "") +
|
|
85
|
+
(metadata.comment !== undefined ? ' xmlns:exif="http://ns.adobe.com/exif/1.0/"' : "") + ">" +
|
|
86
|
+
(metadata.description !== undefined ? "<dc:description>" + alt(metadata.description) + "</dc:description>" : "") +
|
|
87
|
+
(metadata.copyright !== undefined ? "<dc:rights>" + alt(metadata.copyright) + "</dc:rights>" : "") +
|
|
88
|
+
(metadata.software !== undefined ? "<xmp:CreatorTool>" + xmlText(metadata.software) + "</xmp:CreatorTool>" : "") +
|
|
89
|
+
(metadata.comment !== undefined ? "<exif:UserComment>" + alt(metadata.comment) + "</exif:UserComment>" : "") + "</rdf:Description>";
|
|
90
|
+
}
|
|
91
|
+
/** A complete XMP packet (`x:xmpmeta` wrapper, no `xpacket` processing instructions) holding only the text fields. */
|
|
92
|
+
export function xmpTextMetadataPacket(metadata) {
|
|
93
|
+
return encoder.encode('<x:xmpmeta xmlns:x="adobe:ns:meta/"><rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">' +
|
|
94
|
+
xmpTextMetadataDescription(metadata) + "</rdf:RDF></x:xmpmeta>");
|
|
95
|
+
}
|
|
96
|
+
function concat(parts) {
|
|
97
|
+
const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
|
|
98
|
+
let offset = 0;
|
|
99
|
+
for (const part of parts) {
|
|
100
|
+
out.set(part, offset);
|
|
101
|
+
offset += part.length;
|
|
102
|
+
}
|
|
103
|
+
return out;
|
|
104
|
+
}
|
|
105
|
+
function segment(marker, payload) {
|
|
106
|
+
return concat([Uint8Array.of(0xff, marker, (payload.length + 2) >> 8, (payload.length + 2) & 255), payload]);
|
|
107
|
+
}
|
|
108
|
+
function startsWith(bytes, offset, prefix) {
|
|
109
|
+
if (offset + prefix.length > bytes.length)
|
|
110
|
+
return false;
|
|
111
|
+
for (let i = 0; i < prefix.length; i++)
|
|
112
|
+
if (bytes[offset + i] !== prefix[i])
|
|
113
|
+
return false;
|
|
114
|
+
return true;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Returns a copy of a baseline JPEG with the text fields added: merged into its XMP APP1 packet (or a new packet
|
|
118
|
+
* after the last leading APPn segment when it has none), plus a COM segment for `comment`. Every other byte,
|
|
119
|
+
* including the entropy-coded data, is unchanged. Reads marker segments only up to SOS.
|
|
120
|
+
*/
|
|
121
|
+
export function addJpegTextMetadata(jpeg, metadata) {
|
|
122
|
+
if (jpeg.length < 4 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8)
|
|
123
|
+
throw new InvalidConfigurationError("JPEG text metadata needs a JPEG stream starting with SOI.");
|
|
124
|
+
const identifier = encoder.encode(XMP_IDENTIFIER);
|
|
125
|
+
let offset = 2, xmp, appEnd = 2, inLeadingApps = true;
|
|
126
|
+
for (;;) {
|
|
127
|
+
if (offset + 4 > jpeg.length || jpeg[offset] !== 0xff)
|
|
128
|
+
throw new InvalidConfigurationError("JPEG text metadata found a malformed marker segment.");
|
|
129
|
+
const marker = jpeg[offset + 1];
|
|
130
|
+
if (marker === 0xda)
|
|
131
|
+
break;
|
|
132
|
+
const end = offset + 2 + ((jpeg[offset + 2] << 8) | jpeg[offset + 3]);
|
|
133
|
+
if (end > jpeg.length)
|
|
134
|
+
throw new InvalidConfigurationError("JPEG text metadata found a truncated marker segment.");
|
|
135
|
+
if (marker === 0xe1 && !xmp && startsWith(jpeg, offset + 4, identifier))
|
|
136
|
+
xmp = { start: offset, end };
|
|
137
|
+
if (inLeadingApps && marker >= 0xe0 && marker <= 0xef)
|
|
138
|
+
appEnd = end;
|
|
139
|
+
else
|
|
140
|
+
inLeadingApps = false;
|
|
141
|
+
offset = end;
|
|
142
|
+
}
|
|
143
|
+
const description = xmpTextMetadataDescription(metadata);
|
|
144
|
+
let xmpPacket;
|
|
145
|
+
if (xmp) {
|
|
146
|
+
const packet = new TextDecoder("utf-8", { fatal: true }).decode(jpeg.subarray(xmp.start + 4 + identifier.length, xmp.end));
|
|
147
|
+
const close = packet.lastIndexOf("</rdf:RDF>");
|
|
148
|
+
if (close < 0)
|
|
149
|
+
throw new InvalidConfigurationError("JPEG text metadata found an XMP packet without rdf:RDF.");
|
|
150
|
+
xmpPacket = encoder.encode(packet.slice(0, close) + description + packet.slice(close));
|
|
151
|
+
}
|
|
152
|
+
else
|
|
153
|
+
xmpPacket = xmpTextMetadataPacket(metadata);
|
|
154
|
+
if (xmpPacket.length > JPEG_XMP_MAXIMUM_PACKET_BYTES)
|
|
155
|
+
throw new InvalidConfigurationError("The text metadata makes the JPEG XMP packet " + xmpPacket.length + " bytes; one APP1 segment holds at most " + JPEG_XMP_MAXIMUM_PACKET_BYTES + ".");
|
|
156
|
+
const xmpSegment = segment(0xe1, concat([identifier, xmpPacket]));
|
|
157
|
+
const com = metadata.comment === undefined ? new Uint8Array(0) : segment(0xfe, encoder.encode(metadata.comment));
|
|
158
|
+
if (xmp)
|
|
159
|
+
return concat([jpeg.subarray(0, xmp.start), xmpSegment, jpeg.subarray(xmp.end, appEnd), com, jpeg.subarray(appEnd)]);
|
|
160
|
+
return concat([jpeg.subarray(0, appEnd), xmpSegment, com, jpeg.subarray(appEnd)]);
|
|
161
|
+
}
|
|
162
|
+
function latin1Text(text) {
|
|
163
|
+
for (const ch of text) {
|
|
164
|
+
const c = ch.codePointAt(0);
|
|
165
|
+
if (!(c === 10 || (c >= 0x20 && c <= 0x7e) || (c >= 0xa1 && c <= 0xff)))
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
function pngChunk(type, data) {
|
|
171
|
+
const out = new Uint8Array(12 + data.length), view = new DataView(out.buffer), typeBytes = Uint8Array.from(type, (c) => c.charCodeAt(0));
|
|
172
|
+
view.setUint32(0, data.length);
|
|
173
|
+
out.set(typeBytes, 4);
|
|
174
|
+
out.set(data, 8);
|
|
175
|
+
view.setUint32(8 + data.length, pngCrc32(data, pngCrc32(typeBytes)));
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
/** The text chunks for the given fields, in the order description, software, copyright, comment. */
|
|
179
|
+
export function pngTextMetadataChunks(metadata) {
|
|
180
|
+
return FIELDS.filter((field) => metadata[field] !== undefined).map((field) => {
|
|
181
|
+
const keyword = Uint8Array.from(pngKeyword(field), (c) => c.charCodeAt(0)), text = metadata[field];
|
|
182
|
+
// tEXt: keyword, NUL, Latin-1 text. iTXt: keyword, NUL, compression flag 0, method 0, empty language tag, NUL, empty translated keyword, NUL, UTF-8 text.
|
|
183
|
+
return latin1Text(text) ? pngChunk("tEXt", concat([keyword, Uint8Array.of(0), Uint8Array.from(text, (c) => c.charCodeAt(0))]))
|
|
184
|
+
: pngChunk("iTXt", concat([keyword, Uint8Array.of(0, 0, 0, 0, 0), encoder.encode(text)]));
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/** Returns a copy of a PNG with one text chunk per field inserted before the first IDAT chunk. Other chunks are unchanged. */
|
|
188
|
+
export function addPngTextMetadata(png, metadata) {
|
|
189
|
+
const signature = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
190
|
+
if (png.length < 8 || signature.some((b, i) => png[i] !== b))
|
|
191
|
+
throw new InvalidConfigurationError("PNG text metadata needs a PNG signature.");
|
|
192
|
+
const view = new DataView(png.buffer, png.byteOffset, png.byteLength);
|
|
193
|
+
for (let offset = 8; offset + 12 <= png.length;) {
|
|
194
|
+
const length = view.getUint32(offset), type = String.fromCharCode(...png.subarray(offset + 4, offset + 8));
|
|
195
|
+
if (type === "IDAT")
|
|
196
|
+
return concat([png.subarray(0, offset), ...pngTextMetadataChunks(metadata), png.subarray(offset)]);
|
|
197
|
+
offset += 12 + length;
|
|
198
|
+
}
|
|
199
|
+
throw new InvalidConfigurationError("PNG text metadata found no IDAT chunk.");
|
|
200
|
+
}
|
|
201
|
+
//# sourceMappingURL=text-metadata.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"text-metadata.js","sourceRoot":"","sources":["../../src/output/text-metadata.ts"],"names":[],"mappings":"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","sourcesContent":["// SPDX-License-Identifier: Apache-2.0\n\n/**\n * Optional caller-supplied text metadata for encoded images (2.4): a description, the creating software, a\n * copyright notice and a comment. The engine writes the caller's text unchanged and adds no text of its own\n * (in a DNG the description follows the existing simulated-RAW statement).\n * Independently written from these free specifications; no third-party source was used:\n *\n * - JPEG: the existing XMP packet in APP1 (Adobe XMP Specification Part 3, 2020, §1.1.3: identifier\n * \"http://ns.adobe.com/xap/1.0/\" and a UTF-8 packet of at most 65 502 bytes, §1.1.3.1). The fields are added\n * as one more top-level `rdf:Description` of the same resource (`rdf:about=\"\"`): XMP Part 1 (2012, identical\n * to ISO 16684-1:2011) §7.4 lets properties be \"arbitrarily apportioned among the rdf:Description elements\"\n * of the single `rdf:RDF`. Mapping: `dc:description` and `dc:rights` as language alternatives with\n * `x-default` (Part 1 §8.3), `xmp:CreatorTool` as text (Part 1 §8.4) and `exif:UserComment` as a language\n * alternative (Part 2, 2022, §3.4, the Exif namespace). The comment is also a COM marker segment (ITU-T T.81\n * §B.2.4.5, \"the interpretation is left to the application\"; written as UTF-8) after the APPn segments.\n * - PNG: one text chunk per field with the predefined keywords Description, Software, Copyright and Comment\n * (W3C PNG Specification, Third Edition, §11.3.3.1, Table 21). `tEXt` (§11.3.3.2; printable Latin-1 plus line\n * feed) when every character fits, otherwise an uncompressed `iTXt` (§11.3.3.4; UTF-8, empty language tag and\n * translated keyword). The chunks go before the first IDAT, as §12.9 recommends for small text.\n * - DNG: `serializeNativeRawDng` writes the same XMP fields as a packet in TIFF tag 700 (XMP Part 3 §1.1.6,\n * Table 13) plus the TIFF ASCII tags that XMP Part 3 §3.2.3 maps to them.\n *\n * Validation: each given field is a non-empty string of at most `OUTPUT_TEXT_METADATA_MAXIMUM_BYTES` UTF-8\n * bytes, with no unpaired UTF-16 surrogate (so it encodes to valid UTF-8 without replacement), no NUL or other\n * C0/C1 control character except line feed, and no U+FFFE/U+FFFF (not XML 1.0 characters). Line breaks are a\n * single line feed, as the PNG text chunks require.\n * @see docs/QUICK_START.md \"Text metadata (2.4)\".\n */\n\nimport { InvalidConfigurationError } from \"../core/configuration-error.js\";\nimport { pngCrc32 } from \"./png-encoder.js\";\n\n/** Caller-supplied text written into JPEG, PNG and DNG outputs. Every field is optional; at least one must be set. */\nexport interface OutputTextMetadata {\n /** What the image shows. JPEG/DNG `dc:description`, PNG Description, DNG ImageDescription (appended when ASCII). */\n description?: string;\n /** Name and version of the caller's software. JPEG/DNG `xmp:CreatorTool`, PNG Software. */\n software?: string;\n /** Rights statement. JPEG/DNG `dc:rights`; the PNG keyword and DNG tag 33432 (when ASCII) of the same name. */\n copyright?: string;\n /** Free-text comment. JPEG COM segment and `exif:UserComment`, PNG Comment, DNG `exif:UserComment`. */\n comment?: string;\n}\n\n/** Largest UTF-8 length of one metadata field, in bytes. Four escaped fields still fit one JPEG APP1 segment. */\nexport const OUTPUT_TEXT_METADATA_MAXIMUM_BYTES = 2048;\nconst FIELDS = [\"description\", \"software\", \"copyright\", \"comment\"] as const;\ntype Field = typeof FIELDS[number];\n/** PNG Third Edition §11.3.3.1 Table 21 keyword for each field: the field name with an initial capital. */\nconst pngKeyword = (field: Field): string => field[0]!.toUpperCase() + field.slice(1);\nconst XMP_IDENTIFIER = \"http://ns.adobe.com/xap/1.0/\\0\";\n/** Largest XMP packet in one JPEG APP1 segment (XMP Part 3 §1.1.3.1). */\nconst JPEG_XMP_MAXIMUM_PACKET_BYTES = 65502;\nconst encoder = new TextEncoder();\n\n/**\n * Checks caller text metadata. Returns undefined when `value` is undefined, otherwise a frozen copy with only\n * the given fields. `path` prefixes the error messages, for example \"output.metadata\".\n */\nexport function parseOutputTextMetadata(value: unknown, path: string): Readonly<OutputTextMetadata> | undefined {\n if (value === undefined) return undefined;\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) throw new InvalidConfigurationError(path + \" must be an object.\");\n const out: OutputTextMetadata = {};\n for (const key of Object.keys(value)) if (!(FIELDS as readonly string[]).includes(key)) throw new InvalidConfigurationError(\"Unknown \" + path + \" field: \" + key + \".\");\n for (const field of FIELDS) {\n const text = (value as Record<string, unknown>)[field];\n if (text === undefined) continue;\n const name = path + \".\" + field;\n if (typeof text !== \"string\" || text.length === 0) throw new InvalidConfigurationError(name + \" must be a non-empty string.\");\n if (/\\p{Cs}/u.test(text)) throw new InvalidConfigurationError(name + \" must be valid Unicode text (it has an unpaired UTF-16 surrogate).\");\n if (text.includes(\"\\0\")) throw new InvalidConfigurationError(name + \" must not contain NUL characters.\");\n // eslint-disable-next-line no-control-regex\n if (/[\\u0001-\\u0009\\u000b-\\u001f\\u007f-\\u009f]/u.test(text)) throw new InvalidConfigurationError(name + \" must not contain control characters other than line feed (\\\\n), or U+FFFE/U+FFFF.\");\n const bytes = encoder.encode(text).length;\n if (bytes > OUTPUT_TEXT_METADATA_MAXIMUM_BYTES) throw new InvalidConfigurationError(name + \" must be at most \" + OUTPUT_TEXT_METADATA_MAXIMUM_BYTES + \" UTF-8 bytes (it has \" + bytes + \").\");\n out[field] = text;\n }\n if (Object.keys(out).length === 0) throw new InvalidConfigurationError(path + \" must set at least one of description, software, copyright or comment.\");\n return Object.freeze(out);\n}\n\nfunction xmlText(text: string): string {\n return text.replaceAll(\"&\", \"&\").replaceAll(\"<\", \"<\").replaceAll(\">\", \">\");\n}\nconst alt = (text: string): string => '<rdf:Alt><rdf:li xml:lang=\"x-default\">' + xmlText(text) + \"</rdf:li></rdf:Alt>\";\n\n/** One `rdf:Description` of the current resource with the given fields, declaring only the namespaces it uses. */\nexport function xmpTextMetadataDescription(metadata: Readonly<OutputTextMetadata>): string {\n const dc = metadata.description !== undefined || metadata.copyright !== undefined;\n return '<rdf:Description rdf:about=\"\"' + (dc ? ' xmlns:dc=\"http://purl.org/dc/elements/1.1/\"' : \"\") +\n (metadata.software !== undefined ? ' xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"' : \"\") +\n (metadata.comment !== undefined ? ' xmlns:exif=\"http://ns.adobe.com/exif/1.0/\"' : \"\") + \">\" +\n (metadata.description !== undefined ? \"<dc:description>\" + alt(metadata.description) + \"</dc:description>\" : \"\") +\n (metadata.copyright !== undefined ? \"<dc:rights>\" + alt(metadata.copyright) + \"</dc:rights>\" : \"\") +\n (metadata.software !== undefined ? \"<xmp:CreatorTool>\" + xmlText(metadata.software) + \"</xmp:CreatorTool>\" : \"\") +\n (metadata.comment !== undefined ? \"<exif:UserComment>\" + alt(metadata.comment) + \"</exif:UserComment>\" : \"\") + \"</rdf:Description>\";\n}\n\n/** A complete XMP packet (`x:xmpmeta` wrapper, no `xpacket` processing instructions) holding only the text fields. */\nexport function xmpTextMetadataPacket(metadata: Readonly<OutputTextMetadata>): Uint8Array {\n return encoder.encode('<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"><rdf:RDF xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">' +\n xmpTextMetadataDescription(metadata) + \"</rdf:RDF></x:xmpmeta>\");\n}\n\nfunction concat(parts: readonly Uint8Array[]): Uint8Array {\n const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));\n let offset = 0;\n for (const part of parts) { out.set(part, offset); offset += part.length; }\n return out;\n}\nfunction segment(marker: number, payload: Uint8Array): Uint8Array {\n return concat([Uint8Array.of(0xff, marker, (payload.length + 2) >> 8, (payload.length + 2) & 255), payload]);\n}\nfunction startsWith(bytes: Uint8Array, offset: number, prefix: Uint8Array): boolean {\n if (offset + prefix.length > bytes.length) return false;\n for (let i = 0; i < prefix.length; i++) if (bytes[offset + i] !== prefix[i]) return false;\n return true;\n}\n\n/**\n * Returns a copy of a baseline JPEG with the text fields added: merged into its XMP APP1 packet (or a new packet\n * after the last leading APPn segment when it has none), plus a COM segment for `comment`. Every other byte,\n * including the entropy-coded data, is unchanged. Reads marker segments only up to SOS.\n */\nexport function addJpegTextMetadata(jpeg: Uint8Array, metadata: Readonly<OutputTextMetadata>): Uint8Array {\n if (jpeg.length < 4 || jpeg[0] !== 0xff || jpeg[1] !== 0xd8) throw new InvalidConfigurationError(\"JPEG text metadata needs a JPEG stream starting with SOI.\");\n const identifier = encoder.encode(XMP_IDENTIFIER);\n let offset = 2, xmp: { start: number; end: number } | undefined, appEnd = 2, inLeadingApps = true;\n for (;;) {\n if (offset + 4 > jpeg.length || jpeg[offset] !== 0xff) throw new InvalidConfigurationError(\"JPEG text metadata found a malformed marker segment.\");\n const marker = jpeg[offset + 1]!;\n if (marker === 0xda) break;\n const end = offset + 2 + ((jpeg[offset + 2]! << 8) | jpeg[offset + 3]!);\n if (end > jpeg.length) throw new InvalidConfigurationError(\"JPEG text metadata found a truncated marker segment.\");\n if (marker === 0xe1 && !xmp && startsWith(jpeg, offset + 4, identifier)) xmp = { start: offset, end };\n if (inLeadingApps && marker >= 0xe0 && marker <= 0xef) appEnd = end; else inLeadingApps = false;\n offset = end;\n }\n const description = xmpTextMetadataDescription(metadata);\n let xmpPacket: Uint8Array;\n if (xmp) {\n const packet = new TextDecoder(\"utf-8\", { fatal: true }).decode(jpeg.subarray(xmp.start + 4 + identifier.length, xmp.end));\n const close = packet.lastIndexOf(\"</rdf:RDF>\");\n if (close < 0) throw new InvalidConfigurationError(\"JPEG text metadata found an XMP packet without rdf:RDF.\");\n xmpPacket = encoder.encode(packet.slice(0, close) + description + packet.slice(close));\n } else xmpPacket = xmpTextMetadataPacket(metadata);\n if (xmpPacket.length > JPEG_XMP_MAXIMUM_PACKET_BYTES) throw new InvalidConfigurationError(\"The text metadata makes the JPEG XMP packet \" + xmpPacket.length + \" bytes; one APP1 segment holds at most \" + JPEG_XMP_MAXIMUM_PACKET_BYTES + \".\");\n const xmpSegment = segment(0xe1, concat([identifier, xmpPacket]));\n const com = metadata.comment === undefined ? new Uint8Array(0) : segment(0xfe, encoder.encode(metadata.comment));\n if (xmp) return concat([jpeg.subarray(0, xmp.start), xmpSegment, jpeg.subarray(xmp.end, appEnd), com, jpeg.subarray(appEnd)]);\n return concat([jpeg.subarray(0, appEnd), xmpSegment, com, jpeg.subarray(appEnd)]);\n}\n\nfunction latin1Text(text: string): boolean {\n for (const ch of text) { const c = ch.codePointAt(0)!; if (!(c === 10 || (c >= 0x20 && c <= 0x7e) || (c >= 0xa1 && c <= 0xff))) return false; }\n return true;\n}\nfunction pngChunk(type: string, data: Uint8Array): Uint8Array {\n const out = new Uint8Array(12 + data.length), view = new DataView(out.buffer), typeBytes = Uint8Array.from(type, (c) => c.charCodeAt(0));\n view.setUint32(0, data.length); out.set(typeBytes, 4); out.set(data, 8); view.setUint32(8 + data.length, pngCrc32(data, pngCrc32(typeBytes)));\n return out;\n}\n/** The text chunks for the given fields, in the order description, software, copyright, comment. */\nexport function pngTextMetadataChunks(metadata: Readonly<OutputTextMetadata>): Uint8Array[] {\n return FIELDS.filter((field) => metadata[field] !== undefined).map((field) => {\n const keyword = Uint8Array.from(pngKeyword(field), (c) => c.charCodeAt(0)), text = metadata[field]!;\n // tEXt: keyword, NUL, Latin-1 text. iTXt: keyword, NUL, compression flag 0, method 0, empty language tag, NUL, empty translated keyword, NUL, UTF-8 text.\n return latin1Text(text) ? pngChunk(\"tEXt\", concat([keyword, Uint8Array.of(0), Uint8Array.from(text, (c) => c.charCodeAt(0))]))\n : pngChunk(\"iTXt\", concat([keyword, Uint8Array.of(0, 0, 0, 0, 0), encoder.encode(text)]));\n });\n}\n\n/** Returns a copy of a PNG with one text chunk per field inserted before the first IDAT chunk. Other chunks are unchanged. */\nexport function addPngTextMetadata(png: Uint8Array, metadata: Readonly<OutputTextMetadata>): Uint8Array {\n const signature = [137, 80, 78, 71, 13, 10, 26, 10];\n if (png.length < 8 || signature.some((b, i) => png[i] !== b)) throw new InvalidConfigurationError(\"PNG text metadata needs a PNG signature.\");\n const view = new DataView(png.buffer, png.byteOffset, png.byteLength);\n for (let offset = 8; offset + 12 <= png.length;) {\n const length = view.getUint32(offset), type = String.fromCharCode(...png.subarray(offset + 4, offset + 8));\n if (type === \"IDAT\") return concat([png.subarray(0, offset), ...pngTextMetadataChunks(metadata), png.subarray(offset)]);\n offset += 12 + length;\n }\n throw new InvalidConfigurationError(\"PNG text metadata found no IDAT chunk.\");\n}\n"]}
|
package/docs/API_REFERENCE.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Public API reference
|
|
2
2
|
|
|
3
|
-
Generated from the exact root exports for **@photivra/engine 2.
|
|
3
|
+
Generated from the exact root exports for **@photivra/engine 2.4.1 / root contract 2.4.1** by `node scripts/generate-api-reference.mjs`. No module deep import is supported.
|
|
4
4
|
|
|
5
5
|
Use [the developer guide](https://github.com/Photivra/photivra/blob/main/docs/DEVELOPERS.md) for executable paths and scientific boundaries. Types are contracts, not runtime validation: parse untrusted data, preserve units/reference frames, read assessment blockers, and retain evidence/limitations. Optional properties do not imply a universal default. Model/schema IDs remain independent of the distribution.
|
|
6
6
|
|
|
@@ -824,6 +824,20 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
824
824
|
| [`ResolveGenericEquipmentExposureCapabilitiesInput`](api/equipment-exposure-capabilities.md#resolvegenericequipmentexposurecapabilitiesinput) | Type |
|
|
825
825
|
| [`WidestAvailableFNumberCapability`](api/equipment-exposure-capabilities.md#widestavailablefnumbercapability) | Type |
|
|
826
826
|
|
|
827
|
+
## equipment/generic-prime-lenses.ts
|
|
828
|
+
|
|
829
|
+
[Detailed contracts](api/equipment-generic-prime-lenses.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/equipment/generic-prime-lenses.ts)
|
|
830
|
+
|
|
831
|
+
| Export | Kind |
|
|
832
|
+
| --- | --- |
|
|
833
|
+
| [`calculateGenericPrimeMinimumFocusDistanceM`](api/equipment-generic-prime-lenses.md#calculategenericprimeminimumfocusdistancem) | Runtime |
|
|
834
|
+
| [`GENERIC_PRIME_EQUIVALENT_FOCAL_LENGTHS_MM`](api/equipment-generic-prime-lenses.md#generic_prime_equivalent_focal_lengths_mm) | Runtime |
|
|
835
|
+
| [`GENERIC_PRIME_MINIMUM_FOCUS`](api/equipment-generic-prime-lenses.md#generic_prime_minimum_focus) | Runtime |
|
|
836
|
+
| [`GENERIC_PRIME_THIRD_STOP_F_NUMBERS`](api/equipment-generic-prime-lenses.md#generic_prime_third_stop_f_numbers) | Runtime |
|
|
837
|
+
| [`GenericPrimeEquivalentFocalLengthMm`](api/equipment-generic-prime-lenses.md#genericprimeequivalentfocallengthmm) | Type |
|
|
838
|
+
| [`GenericPrimeLens`](api/equipment-generic-prime-lenses.md#genericprimelens) | Type |
|
|
839
|
+
| [`resolveGenericPrimeLens`](api/equipment-generic-prime-lenses.md#resolvegenericprimelens) | Runtime |
|
|
840
|
+
|
|
827
841
|
## equipment/generic-tier-assets.ts
|
|
828
842
|
|
|
829
843
|
[Detailed contracts](api/equipment-generic-tier-assets.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/equipment/generic-tier-assets.ts)
|
|
@@ -1106,24 +1120,40 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1106
1120
|
|
|
1107
1121
|
| Export | Kind |
|
|
1108
1122
|
| --- | --- |
|
|
1123
|
+
| [`BarGroupsChartScene`](api/facade-chart-scenes.md#bargroupschartscene) | Type |
|
|
1124
|
+
| [`calculateBarGroupTargets`](api/facade-chart-scenes.md#calculatebargrouptargets) | Runtime |
|
|
1109
1125
|
| [`calculatePointGridCenters`](api/facade-chart-scenes.md#calculatepointgridcenters) | Runtime |
|
|
1126
|
+
| [`calculateSlantedSquareEdges`](api/facade-chart-scenes.md#calculateslantedsquareedges) | Runtime |
|
|
1110
1127
|
| [`calculateStepWedgeReflectances`](api/facade-chart-scenes.md#calculatestepwedgereflectances) | Runtime |
|
|
1128
|
+
| [`CHART_CONTINUOUS_LEVELS`](api/facade-chart-scenes.md#chart_continuous_levels) | Runtime |
|
|
1111
1129
|
| [`CHART_SCENE_DEFAULTS`](api/facade-chart-scenes.md#chart_scene_defaults) | Runtime |
|
|
1112
1130
|
| [`CHART_SCENE_PRESETS`](api/facade-chart-scenes.md#chart_scene_presets) | Runtime |
|
|
1113
1131
|
| [`CHART_SPECTRAL_RANGE_NANOMETERS`](api/facade-chart-scenes.md#chart_spectral_range_nanometers) | Runtime |
|
|
1132
|
+
| [`ChartBarGroupTarget`](api/facade-chart-scenes.md#chartbargrouptarget) | Type |
|
|
1133
|
+
| [`ChartElementScene`](api/facade-chart-scenes.md#chartelementscene) | Type |
|
|
1134
|
+
| [`ChartRectangle`](api/facade-chart-scenes.md#chartrectangle) | Type |
|
|
1114
1135
|
| [`ChartReflectance`](api/facade-chart-scenes.md#chartreflectance) | Type |
|
|
1115
1136
|
| [`ChartScene`](api/facade-chart-scenes.md#chartscene) | Type |
|
|
1116
1137
|
| [`ChartScenePresetId`](api/facade-chart-scenes.md#chartscenepresetid) | Type |
|
|
1138
|
+
| [`ChartSlantedSquare`](api/facade-chart-scenes.md#chartslantedsquare) | Type |
|
|
1117
1139
|
| [`CheckerboardChartScene`](api/facade-chart-scenes.md#checkerboardchartscene) | Type |
|
|
1140
|
+
| [`CompositeChartElement`](api/facade-chart-scenes.md#compositechartelement) | Type |
|
|
1141
|
+
| [`CompositeChartScene`](api/facade-chart-scenes.md#compositechartscene) | Type |
|
|
1142
|
+
| [`DeadLeavesChartScene`](api/facade-chart-scenes.md#deadleaveschartscene) | Type |
|
|
1143
|
+
| [`FiducialChartScene`](api/facade-chart-scenes.md#fiducialchartscene) | Type |
|
|
1118
1144
|
| [`GreyRampChartScene`](api/facade-chart-scenes.md#greyrampchartscene) | Type |
|
|
1145
|
+
| [`HyperbolicWedgeChartScene`](api/facade-chart-scenes.md#hyperbolicwedgechartscene) | Type |
|
|
1146
|
+
| [`LineGridChartScene`](api/facade-chart-scenes.md#linegridchartscene) | Type |
|
|
1119
1147
|
| [`PatchChartScene`](api/facade-chart-scenes.md#patchchartscene) | Type |
|
|
1120
1148
|
| [`PointGridChartScene`](api/facade-chart-scenes.md#pointgridchartscene) | Type |
|
|
1121
1149
|
| [`resolveChartScene`](api/facade-chart-scenes.md#resolvechartscene) | Runtime |
|
|
1122
1150
|
| [`ResolvedChartScene`](api/facade-chart-scenes.md#resolvedchartscene) | Type |
|
|
1123
1151
|
| [`SiemensStarChartScene`](api/facade-chart-scenes.md#siemensstarchartscene) | Type |
|
|
1124
1152
|
| [`SlantedEdgeChartScene`](api/facade-chart-scenes.md#slantededgechartscene) | Type |
|
|
1153
|
+
| [`SlantedSquaresChartScene`](api/facade-chart-scenes.md#slantedsquareschartscene) | Type |
|
|
1125
1154
|
| [`StepWedgeChartScene`](api/facade-chart-scenes.md#stepwedgechartscene) | Type |
|
|
1126
1155
|
| [`UniformChartScene`](api/facade-chart-scenes.md#uniformchartscene) | Type |
|
|
1156
|
+
| [`ZonePlateChartScene`](api/facade-chart-scenes.md#zoneplatechartscene) | Type |
|
|
1127
1157
|
|
|
1128
1158
|
## facade/scene-radiance.ts
|
|
1129
1159
|
|
|
@@ -1134,6 +1164,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1134
1164
|
| [`CallerRadianceScene`](api/facade-scene-radiance.md#callerradiancescene) | Type |
|
|
1135
1165
|
| [`CallerReflectanceScene`](api/facade-scene-radiance.md#callerreflectancescene) | Type |
|
|
1136
1166
|
| [`CaptureIlluminant`](api/facade-scene-radiance.md#captureilluminant) | Type |
|
|
1167
|
+
| [`CHART_SIZE_DEFAULT_SURROUND_REFLECTANCE`](api/facade-scene-radiance.md#chart_size_default_surround_reflectance) | Runtime |
|
|
1168
|
+
| [`ChartSizeOption`](api/facade-scene-radiance.md#chartsizeoption) | Type |
|
|
1137
1169
|
| [`createPlanarSceneRadianceProvider`](api/facade-scene-radiance.md#createplanarsceneradianceprovider) | Runtime |
|
|
1138
1170
|
| [`PHOTOPIC_MAXIMUM_LUMINOUS_EFFICACY_LM_PER_W`](api/facade-scene-radiance.md#photopic_maximum_luminous_efficacy_lm_per_w) | Runtime |
|
|
1139
1171
|
| [`PlanarSceneRadianceProvider`](api/facade-scene-radiance.md#planarsceneradianceprovider) | Type |
|
|
@@ -1147,6 +1179,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1147
1179
|
|
|
1148
1180
|
| Export | Kind |
|
|
1149
1181
|
| --- | --- |
|
|
1182
|
+
| [`IdealReferenceResult`](api/facade-simulate-capture.md#idealreferenceresult) | Type |
|
|
1183
|
+
| [`SIMULATE_CAPTURE_BOX_APERTURE_DEFAULT_SAMPLES`](api/facade-simulate-capture.md#simulate_capture_box_aperture_default_samples) | Runtime |
|
|
1150
1184
|
| [`SIMULATE_CAPTURE_COST_OPTIONS`](api/facade-simulate-capture.md#simulate_capture_cost_options) | Runtime |
|
|
1151
1185
|
| [`SIMULATE_CAPTURE_DEFAULT_ILLUMINANCE_LUX`](api/facade-simulate-capture.md#simulate_capture_default_illuminance_lux) | Runtime |
|
|
1152
1186
|
| [`SIMULATE_CAPTURE_FACADE_VERSION`](api/facade-simulate-capture.md#simulate_capture_facade_version) | Runtime |
|
|
@@ -1161,10 +1195,23 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1161
1195
|
| [`SimulateCaptureMotion`](api/facade-simulate-capture.md#simulatecapturemotion) | Type |
|
|
1162
1196
|
| [`SimulateCaptureOptions`](api/facade-simulate-capture.md#simulatecaptureoptions) | Type |
|
|
1163
1197
|
| [`SimulateCaptureOutputFormat`](api/facade-simulate-capture.md#simulatecaptureoutputformat) | Type |
|
|
1198
|
+
| [`SimulateCapturePixelAperture`](api/facade-simulate-capture.md#simulatecapturepixelaperture) | Type |
|
|
1164
1199
|
| [`SimulateCaptureProgress`](api/facade-simulate-capture.md#simulatecaptureprogress) | Type |
|
|
1165
1200
|
| [`SimulateCaptureResult`](api/facade-simulate-capture.md#simulatecaptureresult) | Type |
|
|
1166
1201
|
| [`SimulateCaptureSensorAntiAliasing`](api/facade-simulate-capture.md#simulatecapturesensorantialiasing) | Type |
|
|
1167
1202
|
| [`SimulateCaptureWhiteBalance`](api/facade-simulate-capture.md#simulatecapturewhitebalance) | Type |
|
|
1203
|
+
| [`simulateIdealReference`](api/facade-simulate-capture.md#simulateidealreference) | Runtime |
|
|
1204
|
+
|
|
1205
|
+
## facade/subject-framing.ts
|
|
1206
|
+
|
|
1207
|
+
[Detailed contracts](api/facade-subject-framing.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/facade/subject-framing.ts)
|
|
1208
|
+
|
|
1209
|
+
| Export | Kind |
|
|
1210
|
+
| --- | --- |
|
|
1211
|
+
| [`calculateSubjectDistanceForFraming`](api/facade-subject-framing.md#calculatesubjectdistanceforframing) | Runtime |
|
|
1212
|
+
| [`CalculateSubjectDistanceForFramingInput`](api/facade-subject-framing.md#calculatesubjectdistanceforframinginput) | Type |
|
|
1213
|
+
| [`SubjectFraming`](api/facade-subject-framing.md#subjectframing) | Type |
|
|
1214
|
+
| [`SubjectFramingFit`](api/facade-subject-framing.md#subjectframingfit) | Type |
|
|
1168
1215
|
|
|
1169
1216
|
## gpu/image-kernel-accelerator.ts
|
|
1170
1217
|
|
|
@@ -2030,6 +2077,14 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
2030
2077
|
| [`CalculateSubjectFramingCropInput`](api/output-subject-framing-crop.md#calculatesubjectframingcropinput) | Type |
|
|
2031
2078
|
| [`SubjectFramingCrop`](api/output-subject-framing-crop.md#subjectframingcrop) | Type |
|
|
2032
2079
|
|
|
2080
|
+
## output/text-metadata.ts
|
|
2081
|
+
|
|
2082
|
+
[Detailed contracts](api/output-text-metadata.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/output/text-metadata.ts)
|
|
2083
|
+
|
|
2084
|
+
| Export | Kind |
|
|
2085
|
+
| --- | --- |
|
|
2086
|
+
| [`OutputTextMetadata`](api/output-text-metadata.md#outputtextmetadata) | Type |
|
|
2087
|
+
|
|
2033
2088
|
## processing/demosaic.ts
|
|
2034
2089
|
|
|
2035
2090
|
[Detailed contracts](api/processing-demosaic.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/processing/demosaic.ts)
|