@photivra/engine 2.12.0 → 2.13.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 +77 -1
- package/CITATION.cff +1 -1
- package/README.md +6 -6
- package/SECURITY.md +1 -1
- package/THIRD_PARTY.md +1 -0
- package/dist/capture/export-jpeg.d.ts +1 -1
- package/dist/capture/export-jpeg.js +6 -4
- package/dist/capture/export-jpeg.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/exposure/auto-iso-slowest-shutter.d.ts +120 -0
- package/dist/exposure/auto-iso-slowest-shutter.js +216 -0
- package/dist/exposure/auto-iso-slowest-shutter.js.map +1 -0
- package/dist/exposure/exposure-mode-resolver.d.ts +57 -1
- package/dist/exposure/exposure-mode-resolver.js +238 -33
- package/dist/exposure/exposure-mode-resolver.js.map +1 -1
- package/dist/facade/diffraction-provider.js +12 -13
- package/dist/facade/diffraction-provider.js.map +1 -1
- package/dist/facade/display-view.d.ts +27 -0
- package/dist/facade/display-view.js +234 -65
- package/dist/facade/display-view.js.map +1 -1
- package/dist/facade/simulate-capture.d.ts +93 -10
- package/dist/facade/simulate-capture.js +146 -22
- package/dist/facade/simulate-capture.js.map +1 -1
- package/dist/facade/simulate-display-view.js +2 -2
- package/dist/facade/simulate-display-view.js.map +1 -1
- package/dist/gpu/display-view-quality-gpu.d.ts +2 -1
- package/dist/gpu/display-view-quality-gpu.js +24 -14
- package/dist/gpu/display-view-quality-gpu.js.map +1 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.d.ts +13 -1
- package/dist/gpu/wgsl-display-view-quality-kernel.js +36 -2
- package/dist/gpu/wgsl-display-view-quality-kernel.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/optics/irradiance-convolution.d.ts +10 -0
- package/dist/optics/irradiance-convolution.js +48 -13
- package/dist/optics/irradiance-convolution.js.map +1 -1
- package/dist/output/print-jpeg.js +12 -9
- package/dist/output/print-jpeg.js.map +1 -1
- package/dist/output/text-metadata.d.ts +22 -2
- package/dist/output/text-metadata.js +58 -9
- package/dist/output/text-metadata.js.map +1 -1
- package/docs/API_REFERENCE.md +21 -1
- package/docs/QUICK_START.md +65 -5
- package/docs/USAGE.md +207 -2
- package/docs/api/core-version.md +1 -1
- package/docs/api/exports.json +67 -2
- package/docs/api/exposure-auto-iso-slowest-shutter.md +139 -0
- package/docs/api/exposure-exposure-mode-resolver.md +68 -0
- package/docs/api/facade-simulate-capture.md +90 -9
- package/docs/api/output-text-metadata.md +34 -0
- package/package.json +1 -1
|
@@ -1 +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"]}
|
|
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 * Root exports (#388): `addJpegTextMetadata` and `addPngTextMetadata` add the text to a JPEG or PNG that the caller\n * encoded (for example a file assembled from `output.region` parts and `composePrintJpegStrips` or `serializePngImage`),\n * with the same bytes as `simulateCapture`'s `output.metadata`, which calls the same two functions.\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/** The checked metadata of a root writer: an object of the given fields, `metadata` in the messages. */\nfunction requireTextMetadata(value: unknown, writer: string): Readonly<OutputTextMetadata> {\n if (value === undefined) throw new InvalidConfigurationError(writer + \" metadata must be an object.\");\n return parseOutputTextMetadata(value, writer + \" metadata\")!;\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. The engine's own\n * `output.metadata` path calls this function, so for the same JPEG and text the bytes are the same.\n *\n * Root export (#388). `metadata` is checked as `output.metadata` is (see the module notes); a JPEG without SOI,\n * a malformed or truncated segment before SOS, an XMP APP1 segment after a non-APPn segment, an XMP packet that is\n * not UTF-8 or has no `rdf:RDF`, or text that makes the packet larger than one APP1 segment fails with\n * `InvalidConfigurationError`. The input is not changed.\n * @param jpeg A baseline JPEG stream, for example `createPrintJpegTask`'s or `composePrintJpegStrips`'s bytes.\n * @param metadata The text: at least one of description, software, copyright and comment.\n * @returns A new array holding the JPEG with the text.\n */\nexport function addJpegTextMetadata(jpeg: Uint8Array, metadata: Readonly<OutputTextMetadata>): Uint8Array {\n if (!(jpeg instanceof Uint8Array)) throw new InvalidConfigurationError(\"addJpegTextMetadata jpeg must be a Uint8Array.\");\n const text = requireTextMetadata(metadata, \"addJpegTextMetadata\");\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 // Before SOS every marker has a length (T.81 B.1.1.4): SOI, EOI, RSTm, TEM and fill bytes there are malformed.\n const length = (jpeg[offset + 2]! << 8) | jpeg[offset + 3]!;\n if (marker === 0xff || marker === 0x01 || (marker >= 0xd0 && marker <= 0xd9) || length < 2) throw new InvalidConfigurationError(\"JPEG text metadata found a malformed marker segment.\");\n const end = offset + 2 + length;\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)) {\n // The packet is rewritten in place, so it must lie in the leading APPn run that the COM segment follows.\n if (!inLeadingApps) throw new InvalidConfigurationError(\"JPEG text metadata found an XMP APP1 segment after a non-APPn segment.\");\n xmp = { start: offset, end };\n }\n if (inLeadingApps && marker >= 0xe0 && marker <= 0xef) appEnd = end; else inLeadingApps = false;\n offset = end;\n }\n const description = xmpTextMetadataDescription(text);\n let xmpPacket: Uint8Array;\n if (xmp) {\n let packet: string;\n try { packet = new TextDecoder(\"utf-8\", { fatal: true, ignoreBOM: true }).decode(jpeg.subarray(xmp.start + 4 + identifier.length, xmp.end)); }\n catch { throw new InvalidConfigurationError(\"JPEG text metadata found an XMP packet that is not valid UTF-8.\"); }\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(text);\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 = text.comment === undefined ? new Uint8Array(0) : segment(0xfe, encoder.encode(text.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/**\n * Returns a copy of a PNG with one text chunk per field inserted before the first IDAT chunk. Other chunks are\n * unchanged. The engine's own `output.metadata` path calls this function, so for the same PNG and text the bytes\n * are the same.\n *\n * Root export (#388). `metadata` is checked as `output.metadata` is (see the module notes); a file without the PNG\n * signature, or without an IDAT chunk inside its bytes, fails with `InvalidConfigurationError`. Chunk CRCs before\n * IDAT are not checked. The input is not changed.\n * @param png A PNG file, for example `serializePngImage`'s bytes.\n * @param metadata The text: at least one of description, software, copyright and comment.\n * @returns A new array holding the PNG with the text.\n */\nexport function addPngTextMetadata(png: Uint8Array, metadata: Readonly<OutputTextMetadata>): Uint8Array {\n if (!(png instanceof Uint8Array)) throw new InvalidConfigurationError(\"addPngTextMetadata png must be a Uint8Array.\");\n const text = requireTextMetadata(metadata, \"addPngTextMetadata\");\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(text), 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.13.0 / root contract 2.13.0** 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
|
|
|
@@ -918,6 +918,20 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
918
918
|
| [`GenericReleaseDriveMode`](api/equipment-release-capabilities.md#genericreleasedrivemode) | Type |
|
|
919
919
|
| [`parseGenericReleaseCapabilityProfile`](api/equipment-release-capabilities.md#parsegenericreleasecapabilityprofile) | Runtime |
|
|
920
920
|
|
|
921
|
+
## exposure/auto-iso-slowest-shutter.ts
|
|
922
|
+
|
|
923
|
+
[Detailed contracts](api/exposure-auto-iso-slowest-shutter.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/exposure/auto-iso-slowest-shutter.ts)
|
|
924
|
+
|
|
925
|
+
| Export | Kind |
|
|
926
|
+
| --- | --- |
|
|
927
|
+
| [`AUTO_ISO_SLOWEST_SHUTTER_MAXIMUM_BIAS_STOPS`](api/exposure-auto-iso-slowest-shutter.md#auto_iso_slowest_shutter_maximum_bias_stops) | Runtime |
|
|
928
|
+
| [`AUTO_ISO_SLOWEST_SHUTTER_MODEL_VERSION`](api/exposure-auto-iso-slowest-shutter.md#auto_iso_slowest_shutter_model_version) | Runtime |
|
|
929
|
+
| [`AutoIsoSlowestShutterBlurBudget`](api/exposure-auto-iso-slowest-shutter.md#autoisoslowestshutterblurbudget) | Type |
|
|
930
|
+
| [`AutoIsoSlowestShutterEstimate`](api/exposure-auto-iso-slowest-shutter.md#autoisoslowestshutterestimate) | Type |
|
|
931
|
+
| [`estimateAutoIsoSlowestShutter`](api/exposure-auto-iso-slowest-shutter.md#estimateautoisoslowestshutter) | Runtime |
|
|
932
|
+
| [`EstimateAutoIsoSlowestShutterInput`](api/exposure-auto-iso-slowest-shutter.md#estimateautoisoslowestshutterinput) | Type |
|
|
933
|
+
| [`GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC`](api/exposure-auto-iso-slowest-shutter.md#generic_handheld_angular_velocity_rad_per_sec) | Runtime |
|
|
934
|
+
|
|
921
935
|
## exposure/exposure-duration-control.ts
|
|
922
936
|
|
|
923
937
|
[Detailed contracts](api/exposure-exposure-duration-control.md) · [Source](https://github.com/Photivra/photivra/blob/main/src/exposure/exposure-duration-control.ts)
|
|
@@ -941,6 +955,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
941
955
|
| [`AperturePriorityAutoIsoPolicy`](api/exposure-exposure-mode-resolver.md#aperturepriorityautoisopolicy) | Type |
|
|
942
956
|
| [`AperturePriorityExposureModeResolution`](api/exposure-exposure-mode-resolver.md#aperturepriorityexposuremoderesolution) | Type |
|
|
943
957
|
| [`AutoIsoBaselinePolicy`](api/exposure-exposure-mode-resolver.md#autoisobaselinepolicy) | Type |
|
|
958
|
+
| [`AutoIsoLimits`](api/exposure-exposure-mode-resolver.md#autoisolimits) | Type |
|
|
944
959
|
| [`EXPOSURE_MODE_RESOLVER_VERSION`](api/exposure-exposure-mode-resolver.md#exposure_mode_resolver_version) | Runtime |
|
|
945
960
|
| [`ExposureResolutionConstraint`](api/exposure-exposure-mode-resolver.md#exposureresolutionconstraint) | Type |
|
|
946
961
|
| [`ExposureTargetResidual`](api/exposure-exposure-mode-resolver.md#exposuretargetresidual) | Type |
|
|
@@ -952,6 +967,7 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
952
967
|
| [`ProgramAutoExposureModeResolution`](api/exposure-exposure-mode-resolver.md#programautoexposuremoderesolution) | Type |
|
|
953
968
|
| [`ProgramAutoIsoControl`](api/exposure-exposure-mode-resolver.md#programautoisocontrol) | Type |
|
|
954
969
|
| [`ProgramLineSelectionDiagnostics`](api/exposure-exposure-mode-resolver.md#programlineselectiondiagnostics) | Type |
|
|
970
|
+
| [`ProgramSlowestPreferredShutterPolicy`](api/exposure-exposure-mode-resolver.md#programslowestpreferredshutterpolicy) | Type |
|
|
955
971
|
| [`RelativeExposureControlAnchor`](api/exposure-exposure-mode-resolver.md#relativeexposurecontrolanchor) | Type |
|
|
956
972
|
| [`resolveAperturePriorityAutoIsoExposureMode`](api/exposure-exposure-mode-resolver.md#resolveaperturepriorityautoisoexposuremode) | Runtime |
|
|
957
973
|
| [`ResolveAperturePriorityAutoIsoExposureModeInput`](api/exposure-exposure-mode-resolver.md#resolveaperturepriorityautoisoexposuremodeinput) | Type |
|
|
@@ -1239,6 +1255,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
1239
1255
|
| [`SimulateCaptureAssumption`](api/facade-simulate-capture.md#simulatecaptureassumption) | Type |
|
|
1240
1256
|
| [`SimulateCaptureAutoIsoAfterMaximum`](api/facade-simulate-capture.md#simulatecaptureautoisoaftermaximum) | Type |
|
|
1241
1257
|
| [`SimulateCaptureAutoIsoReport`](api/facade-simulate-capture.md#simulatecaptureautoisoreport) | Type |
|
|
1258
|
+
| [`SimulateCaptureAutomaticSlowestShutterOptions`](api/facade-simulate-capture.md#simulatecaptureautomaticslowestshutteroptions) | Type |
|
|
1259
|
+
| [`SimulateCaptureAutomaticSlowestShutterReport`](api/facade-simulate-capture.md#simulatecaptureautomaticslowestshutterreport) | Type |
|
|
1242
1260
|
| [`SimulateCaptureBorderHandling`](api/facade-simulate-capture.md#simulatecaptureborderhandling) | Type |
|
|
1243
1261
|
| [`SimulateCaptureColorEncoding`](api/facade-simulate-capture.md#simulatecapturecolorencoding) | Type |
|
|
1244
1262
|
| [`SimulateCaptureCostOption`](api/facade-simulate-capture.md#simulatecapturecostoption) | Type |
|
|
@@ -2172,6 +2190,8 @@ Start here to find a function by task. [Using Photivra from an agent](AGENT_GUID
|
|
|
2172
2190
|
|
|
2173
2191
|
| Export | Kind |
|
|
2174
2192
|
| --- | --- |
|
|
2193
|
+
| [`addJpegTextMetadata`](api/output-text-metadata.md#addjpegtextmetadata) | Runtime |
|
|
2194
|
+
| [`addPngTextMetadata`](api/output-text-metadata.md#addpngtextmetadata) | Runtime |
|
|
2175
2195
|
| [`OutputTextMetadata`](api/output-text-metadata.md#outputtextmetadata) | Type |
|
|
2176
2196
|
|
|
2177
2197
|
## processing/demosaic.ts
|
package/docs/QUICK_START.md
CHANGED
|
@@ -48,7 +48,7 @@ the example above is [simulate-capture.mjs](examples/simulate-capture.mjs).
|
|
|
48
48
|
| `raw` | The packed native RAW: codes, black levels, saturation flags and per-phase white levels |
|
|
49
49
|
| `plan` | The native RAW plan the engine executed |
|
|
50
50
|
| `request` | The complete underlying request: `environmentRaw` (the native environment RAW input), `siteInput(index)` (rebuilds the physical input of any sensor site), `development` (the RAW development input) and `whiteBalanceState` |
|
|
51
|
-
| `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`) and the exposure-mode resolver's full output |
|
|
51
|
+
| `exposure` | Mode, resolved settings, EV100, `isoControl` (`"manual"` or `"automatic"`) and, with `iso: "auto"`, `autoIso` (how the ISO was chosen and what limited it), metered scene luminance, the deviation from the standard exposure in stops (`standardExposureDeviationStops`), the illuminance at which the settings give the standard exposure (`standardExposureIlluminanceLux`) and the exposure-mode resolver's full output |
|
|
52
52
|
| `scene` | Chart parameters, illuminant, distance, framed size and the scene's SHA-256 digest (it covers caller illuminant and reflectance spectra; see `sourceSha256`) |
|
|
53
53
|
| `diagnostics` | Radiance query count, highlight and rendering counters, per-stage timings (`timingsMs`) and, from 2.1, the opt-ins that ran (`options`); see [Cost of each option](#cost-of-each-option) |
|
|
54
54
|
| `provenance` | Status (`approximation`), camera preset identity, evidence, the composed contracts in order, and limitations |
|
|
@@ -76,7 +76,7 @@ modelling choices (such as "no lens PSF") as `kind: "model"`.
|
|
|
76
76
|
| `lens.diffraction` | off | 2.1: `"circular-aperture"` or `{ kind: "circular-aperture", kernelRadiusSamples, fast }`: diffraction blur of an ideal lens at the resolved f-number (see [Optics](#optics)). `aperture` is accepted as a deprecated alias of `kind` until 3.0.0. |
|
|
77
77
|
| `exposure.mode` | `"manual"` | Also `"aperture-priority"`, `"shutter-priority"`, `"program"`. |
|
|
78
78
|
| `exposure.iso` | 100 | Selects the body's ISO readout regime (gain and read noise). `"auto"` lets Auto ISO choose it from the meter in any mode (see [Auto ISO](#auto-iso-516)). |
|
|
79
|
-
| `exposure.autoIso` | none | `aperture-priority`
|
|
79
|
+
| `exposure.autoIso` | none | With `iso: "auto"` only. `aperture-priority` requires `{ slowestPreferredShutterSeconds, afterMaximumIso? }`, and `program` takes them optionally; the slowest shutter may be `"auto"`, set by `automaticSlowestShutter`. Every mode takes `limits: { maximum, minimum? }`, the photographer's Auto ISO range (see [Auto ISO](#auto-iso-516)). |
|
|
80
80
|
| `exposure.shutterSeconds` | 1/125 | Used in `manual` and `shutter-priority`. |
|
|
81
81
|
| `exposure.compensationStops` | 0 | Applied to the meter target. |
|
|
82
82
|
| `scene` | required | A chart preset or a caller function (below). |
|
|
@@ -368,6 +368,13 @@ The facade meters the scene like a reflected-light meter:
|
|
|
368
368
|
used: minus the compensation (−⅔ for +⅔ stop), plus any step or limit the resolver hit (the slowest shutter, the ISO
|
|
369
369
|
range). It is `null` only when the metered luminance is 0, which has no standard exposure. The display view reports
|
|
370
370
|
the same value.
|
|
371
|
+
- `exposure.standardExposureIlluminanceLux` is the illuminance at which the settings used give that standard exposure:
|
|
372
|
+
E·L_ref/L = E·2^Δ, with E the request's `illuminanceLux` and L_ref = 10·N²/(0.65·t·S). A chart, reflectance function
|
|
373
|
+
or material scene has radiance reflectance × E-scaled illuminant / π, so its metered luminance is proportional to E,
|
|
374
|
+
and the same request at this illuminance meters at Δ = 0 (to rounding: the metering quadrature is the same at both
|
|
375
|
+
illuminances). With the defaults it is the default illuminance; the marked f/11 gives 2^0.919 times it, about
|
|
376
|
+
40 612 lux. It is `null` for a black scene and for a `radiance-function` scene, whose radiance does not depend on the
|
|
377
|
+
illuminance. It can exceed the 1e7 lux `illuminanceLux` accepts. The display view reports the same value.
|
|
371
378
|
|
|
372
379
|
DC-004 ties the reference level to an 18 % grey (Explanation 1 and 2.2). The default illuminance
|
|
373
380
|
puts an 18 % Lambertian grey card, whose luminance is L = 0.18·E/π, exactly at that luminance:
|
|
@@ -400,9 +407,9 @@ Exposure compensation applies to the meter target first, as with a numeric ISO.
|
|
|
400
407
|
| Mode | With `iso: "auto"` |
|
|
401
408
|
| --- | --- |
|
|
402
409
|
| `manual` | Your aperture and shutter; the ISO makes up the exposure (`resolveManualExposureMode`, automatic ISO). |
|
|
403
|
-
| `aperture-priority` | Your aperture. The shutter follows the meter at the lowest ISO, but no slower than `exposure.autoIso.slowestPreferredShutterSeconds` (required; seconds within the body's shutter range); the ISO makes up the rest. At the Auto ISO maximum, `afterMaximumIso` decides: `"allow-slower-shutter"` (the default) lengthens the shutter to reach the target, `"hold-preferred-shutter"` keeps it and leaves the exposure under target (`resolveAperturePriorityAutoIsoExposureMode`).
|
|
410
|
+
| `aperture-priority` | Your aperture. The shutter follows the meter at the lowest ISO, but no slower than `exposure.autoIso.slowestPreferredShutterSeconds` (required; seconds within the body's shutter range, or `"auto"`, see below); the ISO makes up the rest. At the Auto ISO maximum, `afterMaximumIso` decides: `"allow-slower-shutter"` (the default) lengthens the shutter to reach the target, `"hold-preferred-shutter"` keeps it and leaves the exposure under target (`resolveAperturePriorityAutoIsoExposureMode`). |
|
|
404
411
|
| `shutter-priority` | Your shutter. The aperture is chosen at the lowest ISO, no wider than the target needs, so it stops at the widest aperture; the ISO makes up the rest (`resolveShutterPriorityExposureMode`, policy `minimum-iso-aperture-first`). |
|
|
405
|
-
| `program` | The generic program line at the lowest ISO; beyond the line's end (f/4, 1.024 s) the ISO rises (`resolveProgramAutoExposureMode`, automatic ISO). |
|
|
412
|
+
| `program` | The generic program line at the lowest ISO; beyond the line's end (f/4, 1.024 s) the ISO rises (`resolveProgramAutoExposureMode`, automatic ISO). With `exposure.autoIso.slowestPreferredShutterSeconds` (optional, a number or `"auto"`, #504) the line's shutter is held at it before the ISO rises, and `afterMaximumIso` applies as in aperture priority, never past the line's own shutter. |
|
|
406
413
|
|
|
407
414
|
The result says what happened. `exposure.settings.iso` is the ISO used and `exposure.isoControl` is `"automatic"`.
|
|
408
415
|
`exposure.autoIso` reports `idealIsoBeforeConstraints`, the Auto ISO range (`autoIsoMinimum`, `autoIsoMaximum`),
|
|
@@ -414,6 +421,14 @@ A display view, an ideal reference and every `output.region` part of the same re
|
|
|
414
421
|
they resolve the same exposure and report the same values. Requests without `"auto"` are unchanged: their
|
|
415
422
|
`isoControl` is `"manual"` and they have no `autoIso`.
|
|
416
423
|
|
|
424
|
+
**Your own limits (#505).** `exposure.autoIso.limits: { maximum, minimum? }` caps Auto ISO, and optionally raises its
|
|
425
|
+
floor, in any mode, as cameras' "maximum sensitivity" settings do. It is your choice, kept apart from the body's Auto
|
|
426
|
+
ISO range: both limits must be selectable ISOs of the body inside that range (`minimum` at most `maximum`, default the
|
|
427
|
+
body's minimum), or the request fails with `InvalidConfigurationError`; nothing is clipped. When the ISO stops at your
|
|
428
|
+
limit, `limitingConstraint` is `"user-iso-maximum"` (or `"user-iso-minimum"`), distinct from the body's `"iso-maximum"`,
|
|
429
|
+
and `exposure.autoIso.limits` repeats the range used. In aperture priority, `afterMaximumIso` applies at your maximum
|
|
430
|
+
just as at the body's. Without `limits` nothing changes. A separate cap for flash is not part of this.
|
|
431
|
+
|
|
417
432
|
```ts
|
|
418
433
|
import { simulateCapture } from "@photivra/engine";
|
|
419
434
|
|
|
@@ -427,6 +442,42 @@ const dusk = await simulateCapture({
|
|
|
427
442
|
console.log(dusk.exposure.settings, dusk.exposure.isoControl, dusk.exposure.autoIso?.limitingConstraint);
|
|
428
443
|
```
|
|
429
444
|
|
|
445
|
+
```ts
|
|
446
|
+
import { simulateCapture } from "@photivra/engine";
|
|
447
|
+
|
|
448
|
+
const night = await simulateCapture({
|
|
449
|
+
exposure: { mode: "program", iso: "auto", autoIso: { limits: { maximum: 6400 } } },
|
|
450
|
+
scene: { preset: "uniform" },
|
|
451
|
+
illuminanceLux: 0.5,
|
|
452
|
+
output: { width: 8, height: 8 }
|
|
453
|
+
});
|
|
454
|
+
// ISO 6400, "user-iso-maximum", { minimum: 100, maximum: 6400 }
|
|
455
|
+
console.log(night.exposure.settings.iso, night.exposure.autoIso?.limitingConstraint, night.exposure.autoIso?.limits);
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
**An automatic slowest shutter (#504).** `slowestPreferredShutterSeconds: "auto"` lets the engine choose it from the lens,
|
|
459
|
+
the pixel pitch and the hand's shake, through its camera-shake model (`estimateAutoIsoSlowestShutter`, see
|
|
460
|
+
[USAGE](USAGE.md#automatic-slowest-shutter-504)). It does not use the 1/focal-length rule of thumb: the shutter is the time
|
|
461
|
+
at which the shake blur equals the budget. `exposure.autoIso.automaticSlowestShutter` sets `fasterByStops` (−2 to +2, as
|
|
462
|
+
cameras' faster/slower bias; default 0), `blurBudgetPixels` (default 1), `angularVelocityRadPerSec` and
|
|
463
|
+
`stabilizationStopsEquivalent` (default 0). The default hand, 4.39 °/s, is a cited generic value for a phone held in two
|
|
464
|
+
hands, not a measurement of you, so pass your own when you know it. `exposure.autoIso.automaticSlowestShutter` in the result
|
|
465
|
+
reports the derived, biased and used times.
|
|
466
|
+
|
|
467
|
+
```ts
|
|
468
|
+
import { simulateCapture } from "@photivra/engine";
|
|
469
|
+
|
|
470
|
+
const handheld = await simulateCapture({
|
|
471
|
+
exposure: { mode: "aperture-priority", iso: "auto",
|
|
472
|
+
autoIso: { slowestPreferredShutterSeconds: "auto", automaticSlowestShutter: { fasterByStops: -1, stabilizationStopsEquivalent: 3 } } },
|
|
473
|
+
lens: { fNumber: 4 },
|
|
474
|
+
scene: { preset: "uniform" },
|
|
475
|
+
illuminanceLux: 20,
|
|
476
|
+
output: { width: 8, height: 8 }
|
|
477
|
+
});
|
|
478
|
+
console.log(handheld.exposure.autoIso?.automaticSlowestShutter, handheld.exposure.settings);
|
|
479
|
+
```
|
|
480
|
+
|
|
430
481
|
## Scene presets
|
|
431
482
|
|
|
432
483
|
All charts are analytic patterns written by the engine; no third-party chart data is bundled. Each
|
|
@@ -689,6 +740,10 @@ console.log(shot.image.png?.length, shot.assumptions.find((a) => a.id === "outpu
|
|
|
689
740
|
| `copyright` | XMP `dc:rights` | `Copyright` | Copyright tag (ASCII only); XMP `dc:rights` |
|
|
690
741
|
| `comment` | COM segment and XMP `exif:UserComment` | `Comment` | XMP `exif:UserComment` |
|
|
691
742
|
|
|
743
|
+
To add the same text to a JPEG or PNG you encoded yourself (for example a full-size file joined from
|
|
744
|
+
`output.region` parts), call `addJpegTextMetadata(jpeg, metadata)` or `addPngTextMetadata(png, metadata)` (#388).
|
|
745
|
+
They are the functions `output.metadata` uses, so the bytes are the same, and they check the text by the same rules.
|
|
746
|
+
|
|
692
747
|
Each field is optional, but at least one must be set. A field is a non-empty string of at most
|
|
693
748
|
2048 UTF-8 bytes. Lone UTF-16 surrogates (which have no UTF-8 form), NUL, U+FFFE, U+FFFF and control
|
|
694
749
|
characters other than line feed (`\n`) are rejected with an `InvalidConfigurationError` that names the
|
|
@@ -803,6 +858,11 @@ The optics options feed only what the environment site contract accepts:
|
|
|
803
858
|
scene; a test checks it to 1e-12 per site. With camera rotation, a projection or distortion, or
|
|
804
859
|
vignetting, the per-tap quadrature runs instead (much slower). `request.diffraction` holds the profile,
|
|
805
860
|
the equivalent `sampled-local` PSF and which execution ran.
|
|
861
|
+
- The convolution runs in 32-row bands and computes only each band's own values, not the 2R rows and
|
|
862
|
+
columns of padding around it (#419; the values are bit-identical to the full-grid convolution's). On a
|
|
863
|
+
function scene with the Quality options at 1 µm and f/16 (R 32), 96 × 72, `diagnostics.timingsMs.diffraction`
|
|
864
|
+
went from 6.3 s to 0.62 s (medians of 5, one thread, a shared Mac); the tabulated capture's four
|
|
865
|
+
verification sites went from medians of 0.41–0.54 s to 4–5 ms at R 29–32.
|
|
806
866
|
- Not modelled: defocus, aberrations, pupil clipping, a polygonal (bladed) aperture (the tiers declare
|
|
807
867
|
no blade count) and, unless `sensor.pixelAperture` is `"box"` (2.3), the pixel aperture (each site
|
|
808
868
|
is sampled at its centre). It cannot be
|
|
@@ -894,7 +954,7 @@ The optics options feed only what the environment site contract accepts:
|
|
|
894
954
|
request fails and names the radius it needed.
|
|
895
955
|
- A 24 MP frame costs proportionally more. Render it in parts across workers, as for diffraction.
|
|
896
956
|
- Off the tabulated path (caller reflectance or radiance functions), the provider gathers only each 32-row band's
|
|
897
|
-
own values, not the 2R extra rows
|
|
957
|
+
own values, not the 2R extra rows a full-grid convolution would return (#419). On a 256 × 171
|
|
898
958
|
function scene the convolution took 0.3 s at R 8, 3.5 s at R 32 and 12.9 s at R 64.
|
|
899
959
|
- It cannot be combined with `lens.pupilSampling` (the rays already render defocus) or `lens.psf` (a caller PSF
|
|
900
960
|
declares its own defocus). It can be combined with `lens.diffraction`.
|
package/docs/USAGE.md
CHANGED
|
@@ -5089,10 +5089,72 @@ The limiting constraint is reported as one of:
|
|
|
5089
5089
|
- `none`;
|
|
5090
5090
|
- `iso-minimum`;
|
|
5091
5091
|
- `iso-maximum`;
|
|
5092
|
+
- `user-iso-minimum` and `user-iso-maximum` (#505, [photographer-set limits](#photographer-set-auto-iso-limits-505));
|
|
5092
5093
|
- `iso-grid-quantization`.
|
|
5093
5094
|
|
|
5094
5095
|
If Auto ISO capability is `unsupported` or `unknown`, the resolver returns a blocked result. Unknown is not treated as supported.
|
|
5095
5096
|
|
|
5097
|
+
### Photographer-set Auto ISO limits (#505)
|
|
5098
|
+
|
|
5099
|
+
Cameras let the photographer cap Auto ISO, and some also raise its floor: the Nikon D850 has a maximum sensitivity
|
|
5100
|
+
([Auto ISO control](https://imaging.nikon.com/support/digitutor/d850/functions/autoisocontrol.html)), Fujifilm a default and a
|
|
5101
|
+
maximum sensitivity per preset ([ISO Auto setting](https://fujifilm-dsc.com/en/manual/x-pro2/menu_shooting/iso_auto_setting/index.html)),
|
|
5102
|
+
and Panasonic separate upper and lower Auto ISO limits
|
|
5103
|
+
([GH5 II manual](https://eww.pavc.panasonic.co.jp/dscoi/DC-GH5M2/html/DC-GH5M2_DVQP2464_eng/0062.html)). That is the
|
|
5104
|
+
photographer's choice, so the engine keeps it apart from what the body allows (`capabilities.iso.autoIsoMinimum` and
|
|
5105
|
+
`autoIsoMaximum`, or the ISO range when the body declares none).
|
|
5106
|
+
|
|
5107
|
+
Every Auto ISO entry point takes an optional `autoIsoLimits: { maximum, minimum? }` (`AutoIsoLimits`):
|
|
5108
|
+
|
|
5109
|
+
| Resolver | Where |
|
|
5110
|
+
| --- | --- |
|
|
5111
|
+
| `resolveManualExposureMode` | `isoControl.autoIsoLimits` (automatic ISO) |
|
|
5112
|
+
| `resolveAperturePriorityAutoIsoExposureMode` | `policy.autoIsoLimits` |
|
|
5113
|
+
| `resolveShutterPriorityExposureMode` | `isoControl.policy.autoIsoLimits` (automatic ISO) |
|
|
5114
|
+
| `resolveProgramAutoExposureMode` | `isoControl.autoIsoLimits` (automatic ISO) |
|
|
5115
|
+
| `resolveFullAutoExposureMode` | `policy.autoIsoLimits` |
|
|
5116
|
+
|
|
5117
|
+
Composition fragment: requires previously validated bindings `capabilities`, `target`. This is an integration sketch, not a standalone runnable program.
|
|
5118
|
+
|
|
5119
|
+
```ts
|
|
5120
|
+
import {
|
|
5121
|
+
resolveManualExposureMode
|
|
5122
|
+
} from "@photivra/engine";
|
|
5123
|
+
|
|
5124
|
+
const capped =
|
|
5125
|
+
resolveManualExposureMode({
|
|
5126
|
+
target,
|
|
5127
|
+
capabilities,
|
|
5128
|
+
referenceExposure: {
|
|
5129
|
+
aperture: 4,
|
|
5130
|
+
shutterSeconds: 1 / 125,
|
|
5131
|
+
iso: 100
|
|
5132
|
+
},
|
|
5133
|
+
manualAperture: 4,
|
|
5134
|
+
manualShutterSeconds: 1 / 125,
|
|
5135
|
+
isoControl: {
|
|
5136
|
+
kind: "automatic",
|
|
5137
|
+
quantizationPolicy:
|
|
5138
|
+
"nearest-log2-lower-on-tie",
|
|
5139
|
+
autoIsoLimits: { maximum: 6400 }
|
|
5140
|
+
}
|
|
5141
|
+
});
|
|
5142
|
+
```
|
|
5143
|
+
|
|
5144
|
+
The rules:
|
|
5145
|
+
|
|
5146
|
+
- Both limits are ISO settings: inside the body's Auto ISO range and, on a discrete ISO grid, one of its values, with
|
|
5147
|
+
`minimum` at most `maximum`. `minimum` defaults to the body's Auto ISO minimum. A limit outside the body's range or off
|
|
5148
|
+
the grid fails with `InvalidScientificInputError`, and a malformed record (not an object, an unknown field) with
|
|
5149
|
+
`InvalidConfigurationError`. The engine never clips a limit silently.
|
|
5150
|
+
- The ISO is chosen in the narrowed range, and the baseline of the priority, program and Full Auto policies (the lowest
|
|
5151
|
+
selectable ISO) is the first selectable ISO at or above `minimum`.
|
|
5152
|
+
- When the ISO is held at a limit strictly inside the body's range, the limiting constraint is `user-iso-maximum` or
|
|
5153
|
+
`user-iso-minimum`, with the residual to the target as usual. A limit equal to the body's bound adds nothing; the body's
|
|
5154
|
+
`iso-maximum` or `iso-minimum` is reported.
|
|
5155
|
+
- Without `autoIsoLimits` every result is unchanged.
|
|
5156
|
+
- A separate, lower cap for flash is not part of this: it belongs with flash-aware exposure.
|
|
5157
|
+
|
|
5096
5158
|
A `no-signal` meter target also blocks Auto ISO, instead of selecting maximum ISO or infinity.
|
|
5097
5159
|
|
|
5098
5160
|
### Manual ISO
|
|
@@ -5258,6 +5320,12 @@ The policy is explicit:
|
|
|
5258
5320
|
- `allow-slower-shutter`; or
|
|
5259
5321
|
- `hold-preferred-shutter` and return residual underexposure.
|
|
5260
5322
|
|
|
5323
|
+
With `policy.autoIsoLimits` ([photographer-set limits](#photographer-set-auto-iso-limits-505), #505) the maximum in step 4
|
|
5324
|
+
is the photographer's `maximum`, and both choices behave there exactly as at the body's maximum: the same settings and
|
|
5325
|
+
residual, with `user-iso-maximum` in place of `iso-maximum` when the policy holds the shutter. Shutter Priority
|
|
5326
|
+
(`isoControl.policy.autoIsoLimits`), Program Auto (`isoControl.autoIsoLimits`) and Full Auto (`policy.autoIsoLimits`) take
|
|
5327
|
+
the same limits.
|
|
5328
|
+
|
|
5261
5329
|
For discrete shutter grids, the shutter chosen before ISO is raised is never longer than the requested or preferred duration. Otherwise rounding to the grid could overexpose, and undoing that would need an ISO below the supported minimum.
|
|
5262
5330
|
|
|
5263
5331
|
### Shutter Priority + manual ISO
|
|
@@ -5320,6 +5388,107 @@ This policy:
|
|
|
5320
5388
|
|
|
5321
5389
|
That direction is deliberate. Choosing an aperture wider than the ideal at minimum ISO could overexpose, with no lower ISO left to compensate.
|
|
5322
5390
|
|
|
5391
|
+
### Automatic slowest shutter (#504)
|
|
5392
|
+
|
|
5393
|
+
Cameras let the photographer set the slowest shutter before Auto ISO raises the ISO to "Auto", chosen from the focal
|
|
5394
|
+
length, with a bias in 1-stop steps (Sony's ISO AUTO Min. SS: Faster, Fast, Standard, Slow and Slower, 1 EV apart:
|
|
5395
|
+
[help guide](https://helpguide.sony.net/ilc/2020/v1/en/contents/TP1000139958.html); Nikon and Fujifilm: see the manuals
|
|
5396
|
+
cited above). Those cameras base it on the old reciprocal rule, about 1/focal length. That is a rule of thumb, not physics,
|
|
5397
|
+
and the engine does not use it. `estimateAutoIsoSlowestShutter()` derives the threshold from the engine's own camera-shake
|
|
5398
|
+
model, `estimateCameraShakeBlur()` ([camera shake](https://github.com/Photivra/photivra/blob/main/docs/STABILIZATION.md#controlled-camera-shake)), the same physics the preview
|
|
5399
|
+
shows.
|
|
5400
|
+
|
|
5401
|
+
The camera turns at a constant angular velocity ω = (ω_yaw, ω_pitch), reduced by stabilization to r·ω with r = 2^−stops. A
|
|
5402
|
+
still point on the optical axis then moves
|
|
5403
|
+
|
|
5404
|
+
```text
|
|
5405
|
+
b(t) = v · hypot(tan(r·ω_yaw·t), tan(r·ω_pitch·t))
|
|
5406
|
+
```
|
|
5407
|
+
|
|
5408
|
+
on the sensor, where v is the focal length (or, with `focusDistanceM`, the thin-lens image distance). The derived shutter is
|
|
5409
|
+
the time t at which b(t) equals the blur budget. On one axis that is `t = atan(budget / v) / (r·|ω|)` exactly; on two axes
|
|
5410
|
+
the engine bisects, which works because b grows strictly with t. Then:
|
|
5411
|
+
|
|
5412
|
+
1. the bias: `biased = derived · 2^−fasterByStops`, with `fasterByStops` a whole number from −2 to +2 (+1 is one stop faster);
|
|
5413
|
+
2. the grid, optional: with `shutterCapability`, the biased time snaps to the body's shutter grid, never longer, held at its
|
|
5414
|
+
ends.
|
|
5415
|
+
|
|
5416
|
+
Composition fragment: requires previously validated bindings `capabilities`. This is an integration sketch, not a standalone runnable program.
|
|
5417
|
+
|
|
5418
|
+
```ts
|
|
5419
|
+
import {
|
|
5420
|
+
estimateAutoIsoSlowestShutter,
|
|
5421
|
+
GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC
|
|
5422
|
+
} from "@photivra/engine";
|
|
5423
|
+
|
|
5424
|
+
const slowest =
|
|
5425
|
+
estimateAutoIsoSlowestShutter({
|
|
5426
|
+
focalLengthMm: 50,
|
|
5427
|
+
angularVelocityRadPerSec:
|
|
5428
|
+
GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC,
|
|
5429
|
+
stabilizationStopsEquivalent: 0,
|
|
5430
|
+
blurBudget: {
|
|
5431
|
+
kind: "pixels",
|
|
5432
|
+
pixels: 1,
|
|
5433
|
+
pixelPitchMicrometers: 6
|
|
5434
|
+
},
|
|
5435
|
+
fasterByStops: 0,
|
|
5436
|
+
shutterCapability:
|
|
5437
|
+
capabilities.shutter
|
|
5438
|
+
});
|
|
5439
|
+
// slowest.value.shutterSeconds: about 1/640 s for 50 mm, 6 µm pixels and the generic hand.
|
|
5440
|
+
```
|
|
5441
|
+
|
|
5442
|
+
The result reports `derivedShutterSeconds`, `biasedShutterSeconds`, `shutterSeconds` (after the grid), the blur budget and
|
|
5443
|
+
the shake model's blur at the derived and the final shutter, the projection distance and the residual-motion factor, as an
|
|
5444
|
+
`approximation` with its assumptions. The blur budget is `{ kind: "pixels", pixels, pixelPitchMicrometers }` or
|
|
5445
|
+
`{ kind: "image-diagonal-fraction", fraction, imageWidthMm, imageHeightMm }`.
|
|
5446
|
+
|
|
5447
|
+
What the tests check: longer focal lengths give faster thresholds; each stabilization stop doubles the time exactly; the
|
|
5448
|
+
bias moves it by exact whole stops; the shake model's blur at the derived time equals the budget; nothing is NaN or
|
|
5449
|
+
infinite; invalid input fails with a domain error (no shake, a fractional or out-of-range bias, a non-positive budget, a
|
|
5450
|
+
quarter turn of motion).
|
|
5451
|
+
|
|
5452
|
+
**The hand-shake profile is an input.** No free source gives one angular velocity for every photographer and camera.
|
|
5453
|
+
`GENERIC_HANDHELD_ANGULAR_VELOCITY_RAD_PER_SEC` is a **labelled generic default**: 4.39 °/s (≈ 0.0766 rad/s), on yaw. It is the
|
|
5454
|
+
linear camera-motion rate F. Xiao, J. Farrell, P. Catrysse and B. Wandell use for an average user holding a typical 100 g
|
|
5455
|
+
mobile handset with two hands ("Mobile imaging: the big challenge of the small pixel", Proc. SPIE 7250, Digital
|
|
5456
|
+
Photography V, 2009, §2; [author copy](https://web.stanford.edu/~wandell/data/papers/2009-CameraShake-Xiao.pdf)). They
|
|
5457
|
+
take it from their earlier gyroscope measurements, F. Xiao et al., "Camera-motion and mobile imaging", Proc. SPIE 6502
|
|
5458
|
+
(2007), which report that the motion is close to linear for exposures under about 100 ms. It is one magnitude, so the
|
|
5459
|
+
engine puts it on yaw; for small angles the blur length does not depend on the direction. A heavier camera, another grip
|
|
5460
|
+
or another photographer moves differently, so pass your own profile when you have one.
|
|
5461
|
+
|
|
5462
|
+
**Approximations.** Constant angular velocity, blur on the optical axis only, stabilization as 2^−stops (the educational
|
|
5463
|
+
model of `estimateCameraShakeBlur()`, not a CIPA DC-011 rating), no roll, translation or subject motion. The blur budget is
|
|
5464
|
+
your choice of sharpness, not a measured threshold.
|
|
5465
|
+
|
|
5466
|
+
#### Through `simulateCapture`
|
|
5467
|
+
|
|
5468
|
+
`exposure.autoIso.slowestPreferredShutterSeconds: "auto"` (aperture priority; optional in program) calls
|
|
5469
|
+
`estimateAutoIsoSlowestShutter()` with the request's focal length, focus distance, pixel pitch and the body's shutter range.
|
|
5470
|
+
`exposure.autoIso.automaticSlowestShutter` sets `fasterByStops` (default 0), `blurBudgetPixels` (default 1, a generic
|
|
5471
|
+
pixel-level budget), `angularVelocityRadPerSec` (default the generic hand above) and `stabilizationStopsEquivalent` (default 0:
|
|
5472
|
+
the tiers declare none). Each default is recorded as a `default` assumption. The report's `exposure.autoIso.automaticSlowestShutter`
|
|
5473
|
+
gives the derived, biased and used times and the blur at the used time in pixels.
|
|
5474
|
+
|
|
5475
|
+
### Program Auto with Auto ISO: a slowest preferred shutter (#504)
|
|
5476
|
+
|
|
5477
|
+
`resolveProgramAutoExposureMode()` optionally takes
|
|
5478
|
+
`isoControl.slowestPreferredShutter: { slowestPreferredShutterSeconds, afterMaximumIso }`
|
|
5479
|
+
(`ProgramSlowestPreferredShutterPolicy`), the same two-axis policy as Aperture Priority + Auto ISO:
|
|
5480
|
+
|
|
5481
|
+
1. Follow the program line at the lowest selectable ISO.
|
|
5482
|
+
2. If the line's shutter is longer than `slowestPreferredShutterSeconds`, hold the shutter there (snapped, never longer),
|
|
5483
|
+
keep the line's aperture, and raise the ISO.
|
|
5484
|
+
3. At the Auto ISO maximum (the body's or the photographer's `autoIsoLimits`), `"allow-slower-shutter"` lengthens the shutter
|
|
5485
|
+
toward the target, but never past the line's own shutter. `"hold-preferred-shutter"` keeps the shutter there and reports
|
|
5486
|
+
the residual.
|
|
5487
|
+
|
|
5488
|
+
The result adds `slowestPreferredShutter: { ..., applied, afterMaximumIsoFallbackUsed }`; `programLineSelection` stays the
|
|
5489
|
+
line's own selection. Without the option the resolver behaves as before and reports no such field. Full Auto exposure does
|
|
5490
|
+
not take the option.
|
|
5491
|
+
|
|
5323
5492
|
### Shared boundaries
|
|
5324
5493
|
|
|
5325
5494
|
These are control policies over the same relative exposure equation. They create no new optics or sensor physics.
|
|
@@ -6882,6 +7051,31 @@ try { parseDngRawImage({ bytes: png }); } catch { rejected = true; }
|
|
|
6882
7051
|
if (!rejected) throw new Error('A PNG must not parse as a DNG');
|
|
6883
7052
|
```
|
|
6884
7053
|
|
|
7054
|
+
## Text metadata on a file you encoded
|
|
7055
|
+
|
|
7056
|
+
`addJpegTextMetadata(jpeg, metadata)` and `addPngTextMetadata(png, metadata)` (#388) add the caller's text to a JPEG
|
|
7057
|
+
or PNG that was encoded without `output.metadata`: for example a full-size file joined from `output.region` parts
|
|
7058
|
+
and encoded with `composePrintJpegStrips` or `serializePngImage`. They are the functions that `output.metadata`
|
|
7059
|
+
calls, so for the same file and text the bytes are the same as a whole-frame `simulateCapture` with
|
|
7060
|
+
`output.metadata`. The text is checked as `output.metadata` is (see
|
|
7061
|
+
[Text metadata](https://github.com/Photivra/photivra/blob/main/docs/QUICK_START.md#text-metadata-24)); invalid text or a
|
|
7062
|
+
malformed file fails with `InvalidConfigurationError`. The input array is not changed.
|
|
7063
|
+
|
|
7064
|
+
```ts
|
|
7065
|
+
import { addJpegTextMetadata, addPngTextMetadata, serializePngImage, simulateCapture } from '@photivra/engine';
|
|
7066
|
+
|
|
7067
|
+
const text = { description: 'Siemens star at f/8', software: 'My Viewer 1.0', comment: 'Test run 3' };
|
|
7068
|
+
// A PNG the caller encoded (here one sRGB pixel) gets the PNG text chunks.
|
|
7069
|
+
const png = addPngTextMetadata(serializePngImage({ width: 1, height: 1, bitDepth: 8, samples: Uint8Array.of(255, 128, 0),
|
|
7070
|
+
colourEncoding: { kind: 'srgb', renderingIntent: 'perceptual' } }), text);
|
|
7071
|
+
// A JPEG written without output.metadata and tagged afterwards has the bytes of one written with it.
|
|
7072
|
+
const plain = await simulateCapture({ scene: { preset: 'siemens-star' }, output: { width: 8, height: 8, format: 'jpeg' } });
|
|
7073
|
+
const direct = await simulateCapture({ scene: { preset: 'siemens-star' }, output: { width: 8, height: 8, format: 'jpeg', metadata: text } });
|
|
7074
|
+
const tagged = addJpegTextMetadata(plain.image.jpeg!, text);
|
|
7075
|
+
const same = tagged.length === direct.image.jpeg!.length && tagged.every((byte, i) => byte === direct.image.jpeg![i]);
|
|
7076
|
+
console.log(png.length, same);
|
|
7077
|
+
```
|
|
7078
|
+
|
|
6885
7079
|
## Native-only Print planning
|
|
6886
7080
|
|
|
6887
7081
|
- [Registered linear statistics](https://github.com/Photivra/photivra/blob/main/docs/PRINT_REGION_STATISTICS.md) tell apart changes caused by processing the same capture from variation between repeated captures at each site.
|
|
@@ -7507,12 +7701,17 @@ In `aperture-priority` mode, the shutter comes from the meter (CIPA DC-004 relat
|
|
|
7507
7701
|
`exposure.standardExposureDeviationStops` is the exposure used minus the standard exposure for the metered luminance L, in stops,
|
|
7508
7702
|
log₂(N²/t) − log₂(0.65·L·S/10) from the same relations: positive is darker (under), negative brighter (over). In an automatic
|
|
7509
7703
|
mode it is minus the compensation plus any limit the resolver hit; it is `null` only for a scene with no metered luminance.
|
|
7510
|
-
|
|
7704
|
+
`exposure.standardExposureIlluminanceLux` is the illuminance at which those settings give the standard exposure, E·2^Δ for the
|
|
7705
|
+
request's illuminance E (a reflective scene's metered luminance is proportional to E); `null` for a black scene or a
|
|
7706
|
+
`radiance-function` scene. The display view's `exposure` carries the same values.
|
|
7511
7707
|
|
|
7512
7708
|
With `exposure.iso: "auto"` (#516) the ISO is chosen too, in every mode, by the resolvers' Auto ISO branches
|
|
7513
7709
|
([Priority modes with Auto ISO](#priority-modes-with-auto-iso)). Aperture priority then needs the slowest shutter
|
|
7514
7710
|
before the ISO rises, and takes the policy at the maximum ISO; the result reports the ISO, that it was automatic and
|
|
7515
|
-
what limited it.
|
|
7711
|
+
what limited it. `exposure.autoIso.limits: { maximum, minimum? }` (#505) sets the photographer's Auto ISO range in any
|
|
7712
|
+
mode ([photographer-set limits](#photographer-set-auto-iso-limits-505)). `slowestPreferredShutterSeconds: "auto"` (#504)
|
|
7713
|
+
derives the slowest shutter from the camera-shake model, in aperture priority and, optionally, program
|
|
7714
|
+
([automatic slowest shutter](#automatic-slowest-shutter-504)). See [Auto ISO](QUICK_START.md#auto-iso-516).
|
|
7516
7715
|
|
|
7517
7716
|
```ts
|
|
7518
7717
|
import { prepareDisplayView, simulateCapture, type SimulateCaptureOptions } from "@photivra/engine";
|
|
@@ -7575,6 +7774,12 @@ console.log(ran.backend, ran.fallbackReason, ran.assurance, ran.errorBound, ran.
|
|
|
7575
7774
|
prepared.dispose(); // releases a device the view acquired; a shared accelerator's device stays its owner's
|
|
7576
7775
|
```
|
|
7577
7776
|
|
|
7777
|
+
The GPU code (the session, the renderers and their WGSL, about 52 kB gzip) is imported only when `gpu` is `"auto"` or
|
|
7778
|
+
`"require"`, so a bundler that splits dynamic imports leaves it out of a page's first load. A build that cannot split inlines
|
|
7779
|
+
it: Vite builds workers as `iife` by default, so a worker that calls `prepareDisplayView` carries it; build workers as ES
|
|
7780
|
+
modules (`worker: { format: "es" }`, with `new Worker(url, { type: "module" })`) to keep it out. Measured sizes:
|
|
7781
|
+
[Bundle size](https://github.com/Photivra/photivra/blob/main/docs/DISPLAY_VIEW.md#bundle-size).
|
|
7782
|
+
|
|
7578
7783
|
#### Sharing AHD correction states between workers
|
|
7579
7784
|
|
|
7580
7785
|
With `output.demosaic: "ahd"`, low-signal display pixels read correction states that each realm (a page, a Web Worker)
|
package/docs/api/core-version.md
CHANGED
|
@@ -11,5 +11,5 @@ version they were made with. Proof-of-concept (POC), schema and model contracts
|
|
|
11
11
|
own versions. Release checks confirm it matches package.json and the packed package.
|
|
12
12
|
|
|
13
13
|
```ts
|
|
14
|
-
ENGINE_API_VERSION = "2.
|
|
14
|
+
ENGINE_API_VERSION = "2.13.0" as const
|
|
15
15
|
```
|