Skip to main content

WAI Extension: Exact Tone Mapping

Mirrored from the canonical text at commit 260cbd09 ().

Status: Draft. Registers two companions and a record. wai.meta.gainmap carries a gain map of ISO 21496-1:2025 (its metadata and its gain map image) as a companion of an image payload, and wai.meta.st2094_50 carries the dynamic metadata of SMPTE ST 2094-50:2026-08 (Application #5) as a companion of a video payload. A sink presents the picture it decoded at its display’s headroom, the ratio of its peak to the luminance of HDR reference white, as a log2 value. Both standards state the computation from the base, the companion and the headroom in real numbers, so two sinks at one headroom compute it in their own floating point and present different pixels. This extension pins it in integer arithmetic: wai-tonemap:gainmap and wai-tonemap:st2094-50 map the base’s RGB8 samples, the companion’s bytes and a headroom, an integer in units of 1/10000 of a stop, to one RGB16 rendition, with a stated fixed-point log2, a stated fixed-point exp2, and a stated rounding at every step. Every sink at a headroom presents the same bytes, and a verifier holding the base and the companion recomputes them. A sink that presents a rendition signs a rendition record naming the base, the companion, each headroom and the digest of each rendition. The derivation is a convert step of synthesis: it rebuilds what the base and the companion hold and generates nothing. No new transport. The reference implementation is the exact_tonemap module of wai-rs (feature exact_tonemap); the corpus is live-conformance/tonemap/, and its second implementation is live-conformance/tonemap_verify.py. Keywords MUST, MUST NOT, SHOULD, MAY are RFC 2119/8174.

Texts read for this revision: SMPTE ST 2094-50:2026-08, Dynamic Metadata for Color Volume Transform — Application #5, approved 2026-08-24, in its published text, every clause and annex (its figures were not read); the WAI texts this one cites; RFC 8032 and RFC 8785. ISO 21496-1:2025 (first edition, Digital photography — Gain map metadata for image conversion — Part 1: Dynamic range conversion) was not read: its text is sold, and no part of it was obtained for this revision. What this extension states of it is what ST 2094-50:2026-08 says of it (its terms 3.1 to 3.11 cite ISO 21496-1:2025 clauses 3.1, 3.2, 3.4, 3.6, 5.2.6, 5.2.7 and 6.3) and what public, open-source readers of its binary metadata implement: the field list of §4.2 and the computation of §4.5. Each is marked where it is used. A revision that reads the standard confirms each against its text or corrects it.

1. Scope

In scope: the payloads of the two companions (§4.1, §5.1) and how their metadata reads (§4.2, §5.2); the integer arithmetic the derivations share (§3); the two derivations (§4.4, §5.3 to §5.6), each step mapped onto the standard’s own computation (§4.5, §5.7); their error against a floating-point evaluation of the same formulas (§6); the rendition record and its place in synthesis’s class lattice (§7); verification (§8).

Out of scope: a transport; how a sink measures its own headroom; a base in another transfer than sRGB, a gain application colour space whose primaries differ from the base’s, and the HDR reference white’s luminance in candelas, each of which this revision does not compute (§4.2, §5.2); how a gain map is made from an HDR original, which is not part of the derivation (the corpus’s encoder is the reference’s own, §10); the presentation of a rendition on a panel, which takes the rendition’s code values as linear drive values relative to the display’s peak (§3.5) and does with them what the panel does.

Relationship to the core spec: wai.meta.gainmap and wai.meta.st2094_50 are companion-only capabilities (SPEC §5, §7.1): each is applied with the rendition it binds to and never decoded on its own. wai.meta.gainmap applies to a payload of the image media class, wai.meta.st2094_50 to one of the video media class, as SPEC §7.1’s rule 2 states. wai.media.tonemap is a record (SPEC §5): a signed record a WAI tool emits and verifies, which names no payload format and is not dispatchable. The two derivations are terms of this extension, not wai.derive.* strings: the registry’s derivation kind is the operations of ISO/IEC 23001-16 (derived-tracks), and a tone map is none of them.

Relationship to synthesis: a rendition is a convert of its inputs (synthesis §3.2): it rebuilds, at the display’s headroom, what the base and the companion together hold, names no model and generates nothing; its class is the greatest of reconstructed and its inputs’ classes, so a rendition of a synthesized picture is synthesized (§7.2).

Relationship to determinism-tiers: display adaptation changes the decoded medium, so a tier covers a rendition only if its computation is pinned (determinism-tiers §2, What a tier covers). The derivations are integer throughout, so a rendition is a pure function of the base’s samples, the companion’s bytes and the headroom, and a sink that presents it holds the tier of the decode it ran on.

2. Records and buffers

The record of this extension (§7) is a JSON object under the encodings of synthesis §2: byte strings in lowercase hexadecimal, unsigned integers of at most 2^53 − 1 in plain decimal, no other number, no boolean, terms and date-times as jwp-receipts §2.4.3 defines them, no member named twice, at most 32 levels of nesting, exactly the members §7.1 lists at every level, and a kind naming the record; one that does not is record_malformed. It carries signer_pubkey (32 bytes) and sig (64 bytes):

sig       = Ed25519(signer_key, "wai:tonemap\x01" || JCS(record without "sig"))
link_hash = BLAKE3("wai:tonemap-link\x01" || JCS(record))

JCS is RFC 8785. The signature is verified strictly (SPEC §7 Signatures): a key that fails step 1 or 2 is bad_key, and a signature that fails a later step bad_signature.

A base picture is an RGB8 buffer (rows top to bottom, pixels left to right, three bytes a pixel, R, G, B, no padding), read as sRGB-coded (IEC 61966-2-1, as ST 2094-50:2026-08 Formula (A.2) restates it). A rendition is an RGB16 buffer: the same order, three unsigned 16-bit samples a pixel, each written little-endian. A buffer’s digest is SHA-256 (FIPS 180-4) over it. An envelope’s content hash is jwp-receipts §2.1’s.

3. Integer arithmetic

3.1 Notation

Every value is an integer. ⌊a / b⌋, for b > 0, is the floor of the quotient, also for a negative a. rnd(a, b) = ⌊(a + ⌊b / 2⌋) / b⌋ for b > 0: the integer nearest a / b, halves upward, for either sign of a. clamp(v, lo, hi) = min(max(v, lo), hi). A value in Qn stands for itself divided by 2^n: a log2 value in Q16 is in units of 1/65536 of a stop, a linear light value in Q30 is in units of 2^−30 of HDR reference white. No intermediate value of this section or of §4 and §5 needs more than 127 bits and a sign; an implementation computes each exactly.

3.2 L2: a fixed-point log2

For an integer v ≥ 1, L2(v) is a Q16 approximation of log2(v) from below:

  1. n is the bit length of v less 1, so 2^n ≤ v < 2^(n+1).
  2. m = v · 2^(30 − n) where n ≤ 30, and ⌊v / 2^(n − 30)⌋ otherwise: the mantissa in Q30, 2^30 ≤ m < 2^31.
  3. f = 0; for i = 1 to 16 in turn: m = ⌊m² / 2^30⌋; where m ≥ 2^31, m = ⌊m / 2⌋ and f = f + 2^(16 − i).
  4. L2(v) = n · 2^16 + f.

Step 3 is the bitwise logarithm: each squaring doubles the fractional part, and a mantissa past 2 gives the next bit. Every squaring truncates.

3.3 E2: a fixed-point exp2, and scaling by it

For a Q16 integer y, E2(y) is a pair (p, e) with 2^(y / 2^16) ≈ p · 2^(e − 30):

  1. e = ⌊y / 2^16⌋ and f = y − 2^16 · e, 0 ≤ f < 2^16.
  2. p = 2^30; for i = 1 to 16 in turn, where bit 16 − i of f is 1, p = rnd(p · Kᵢ, 2^30).

Kᵢ is 2^(2^−i) in Q30, made by integer square roots: R₀ = 2^61, Rᵢ = ⌊√(Rᵢ₋₁ · 2^60)⌋ (each a Q60 value, the integer square root rounded down), and Kᵢ = rnd(Rᵢ, 2^30):

iKᵢiKᵢiKᵢiKᵢ
115185002505109725370891075196443131073832680
2127690141761085434106101074468888141073787251
3117092376271079572136111074105294151073764537
4112128043681076653033121073923544161073753181

scale(X, y, F), for an integer X and a Q16 y, is X · 2^(y / 2^16) / 2^F as an integer: with (p, e) = E2(y) and t = e − 30 − F, it is X · p · 2^t where t ≥ 0, and rnd(X · p, 2^−t) otherwise.

Since both functions are functions of one integer, an implementation MAY tabulate them, or compute them any other way that gives the same integers: a table of E2 over the 2^16 values of f is the same function.

3.4 S: the base’s samples in linear light

S[k], for each sample value k from 0 to 255, is the sRGB decode of k / 255 in Q30, with HDR reference white at 2^30 (an sRGB-coded sample of 255 is reference white, ST 2094-50:2026-08 Note 4 to Formula (A.1)):

S[255] = 2^30 exactly.

3.5 The rendition’s code values

A sink presents a rendition to a display of headroom h, an integer from 0 to 60000 in units of 1/10000 of a stop (the unit ST 2094-50:2026-08 Annex C encodes headrooms in): the display’s peak is 2^(h / 10000) times HDR reference white. Hₜ = rnd(h · 2^16, 10000) is h in Q16. A rendition’s code value is linear light relative to the display’s peak, at 16 bits: a linear value T (Q30, relative to reference white) is presented as

code(T) = clamp(scale(T · 65535, −Hₜ, 30), 0, 65535),

so the display’s peak is 65535 and a value past it is clamped, the projection of ST 2094-50:2026-08 Clause A.4, Note. Each derivation forms its value and its exponent together and rounds once (§4.4 step 7, §5.6).

4. Gain maps: wai.meta.gainmap and wai-tonemap:gainmap

4.1 The payload: WGM1

A wai.meta.gainmap payload is a WGM1 frame. WAI’s own fields are little-endian.

offsizefieldrule
04magic WGM1
41version1
51reserved0
62md_len1 to 4096: the metadata’s length
8md_lenmetadataISO 21496-1:2025 metadata in its binary form (§4.2), as written
8 + md_lenthe restgain map imageone PNG file (W3C PNG, Third Edition) of 8-bit samples, colour type 0 (one channel) or 2 (three), no interlace

A frame that breaks a rule of this table, or whose image is not such a PNG, or does not decode, is companion_malformed. Nothing follows the PNG’s IEND chunk.

4.2 The metadata

The metadata is ISO 21496-1:2025’s binary form. This revision did not read ISO 21496-1; the fields below are the ones public, open-source readers of the form read, in their order, with the names they give. Its multi-byte fields are big-endian.

sizefieldtype
2minimum_versionunsigned
2writer_versionunsigned
1is_multichannel (bit 7), use_base_colour_space (bit 6), six reserved bits
4 + 4base_hdr_headroom numerator, denominatorunsigned, unsigned
4 + 4alternate_hdr_headroom numerator, denominatorunsigned, unsigned
per channel, 1 or 3 times (3 where is_multichannel is 1)
4 + 4gain_map_min numerator, denominatorsigned, unsigned
4 + 4gain_map_max numerator, denominatorsigned, unsigned
4 + 4gamma numerator, denominatorunsigned, unsigned
4 + 4base_offset numerator, denominatorsigned, unsigned
4 + 4alternate_offset numerator, denominatorsigned, unsigned

Each pair is the rational numerator / denominator. Headrooms, gain_map_min and gain_map_max are log2 values. A reader, in this order:

  1. refuses metadata whose minimum_version is not 0 with metadata_unsupported: a reader does not apply metadata whose minimum version it does not know;
  2. refuses metadata shorter than its fields, or whose writer_version is below its minimum_version, or, with a writer_version of 0, longer than its fields, with metadata_malformed; bytes after the fields of metadata of a later writer_version are read past, and the reserved bits are not read;
  3. refuses with metadata_out_of_range metadata outside the range this revision computes: a denominator of 0; a headroom past 6 (the range of ST 2094-50:2026-08 §6.2.2); two equal headrooms, between which no weight is defined; a gain_map_min or gain_map_max past 16 in magnitude, or a gain_map_min greater than its gain_map_max; a gamma of 0 or outside 1/64 to 64; a base_offset or alternate_offset past 1 in magnitude;
  4. refuses metadata whose use_base_colour_space is 0 with gain_space_unsupported: its gain is applied in the alternate image’s colour space, which a conversion of primaries reaches, and this revision computes none.

Then, for each channel j: minⱼ and maxⱼ in Q16 are rnd(n · 2^16, d) of gain_map_min and gain_map_max; boⱼ and aoⱼ in Q30 are rnd(n · 2^30, d) of base_offset and alternate_offset; γⱼ is the rational gamma itself. A single-channel map applies its one set to all three channels.

4.3 The gain map image

The PNG’s colour type is 0 for metadata whose is_multichannel is 0, and 2 where it is 1 (map_channels_mismatch). Its size is gw × gh, and the base’s w × h: s = w / gw is an integer of 1, 2, 4 or 8, and h = s · gh (map_dimensions_mismatch). g(x, y) is its sample at column x and row y, of channel j.

4.4 wai-tonemap:gainmap

wai-tonemap:gainmap maps a base of w × h, a wai.meta.gainmap payload that reads (§4.1 to §4.3) and a headroom hᵈ (§3.5) to a rendition of w × h. With Hb = base_hdr_headroom = bn / bd, Ha = alternate_hdr_headroom = an / ad and Hₜ = rnd(hᵈ · 2^16, 10000):

  1. Weight. A = hᵈ · bd · ad − 10000 · bn · ad and D = 10000 · (an · bd − bn · ad), so A / D = (hᵈ / 10000 − Hb) / (Ha − Hb). Where D > 0, W = clamp(rnd(A · 2^16, D), 0, 2^16); where D < 0, W = −clamp(rnd(−A · 2^16, −D), 0, 2^16). W is in Q16, and is negative where the alternate’s headroom is below the base’s.

For each pixel (x, y) and each of its channels c, with j = c for a three-channel map and j = 0 for a single-channel one:

  1. Base. B = S[base(x, y)[c]].
  2. The map’s sample at the pixel. X = max(0, 2x + 1 − s), x₀ = ⌊X / 2s⌋, ax = X − 2s · x₀ and x₁ = min(x₀ + 1, gw − 1); Y, y₀, ay and y₁ the same from y and gh. Then N = g(x₀, y₀) · (2s − ax) · (2s − ay) + g(x₁, y₀) · ax · (2s − ay) + g(x₀, y₁) · (2s − ax) · ay + g(x₁, y₁) · ax · ay, and Q = 255 · 4s²: the map’s normalised sample is N / Q, a centre-aligned bilinear interpolation of its codes, the edges repeated. With s = 1 it is the sample itself.
  3. Recovery. v (Q30) = rnd(N · 2^30, Q) where γⱼ = 1; 0 where N = 0; otherwise, with γⱼ = γn / γd, λ = L2(N) − L2(Q) and v = scale(2^30, rnd(λ · γd, γn), 0): the sample raised to 1/γⱼ.
  4. Log gain. L (Q16) = rnd(minⱼ · (2^30 − v) + maxⱼ · v, 2^30).
  5. Weighted gain. G (Q16) = rnd(L · W, 2^16).
  6. Output. P = scale((B + boⱼ) · 65535, G − Hₜ, 0) − scale(aoⱼ · 65535, −Hₜ, 0), and the rendition’s sample is clamp(rnd(P, 2^30), 0, 65535): code((B + bo) · 2^(L · W) − ao) of §3.5, both terms in Q30 of a code value and rounded once.

The rendition at a headroom at or past the alternate’s is the alternate image the map was made from, within the error of §6; at the base’s headroom (W = 0) it is the base, offsets cancelling where they are equal.

4.5 Against ISO 21496-1’s computation

The computation the integer steps reproduce is the one the public readers of §4.2 implement: with the base’s linear value B, the map’s sample g in [0, 1] at the pixel, and the weight W = clamp((Hₜ − Hb) / (Ha − Hb), 0, 1), negated where Ha < Hb,

L = min + (max − min) · g^(1/γ), T = (B + bo) · 2^(L · W) − ao.

ST 2094-50:2026-08 term 3.7 names ISO 21496-1:2025 clause 6.3 as the algorithm that produces an image at a targeted HDR headroom from per-pixel metadata; this revision did not read that clause (header note). Step by step:

stepreal-number forminteger form (§4.4)what is pinned
base to linearsRGB decode of k / 255S[k] (§3.4)L2, E2, one rounding each
the map at the pixelupsampled by a filter each implementation choosesstep 3: bilinear, centre-aligned, edges repeated, exactthe filter itself
recoveryg^(1/γ)step 4Q30, through Q16 log2
log gainmin + (max − min) · g^(1/γ)step 5Q16
weightclamp of a ratio of headroomsstep 1, exact rational, rounded onceQ16
gain and offsets(B + bo) · 2^(L · W) − aostep 7Q30, through Q16 exp2
presentationT / 2^Hₜ at the display’s peak§3.516-bit code values

The upsampling filter is where two floating-point sinks differ most, and the standard, as far as this revision knows it, leaves it to the implementation. The derivation fixes it.

5. ST 2094-50: wai.meta.st2094_50 and wai-tonemap:st2094-50

5.1 The payload: WD50

A wai.meta.st2094_50 payload is a WD50 frame: the metadata sets of a video, each with the frames it applies to. ST 2094-50:2026-08 carries one metadata set with the frames it applies to by ITU-T T.35 messages in a codec or a container (its §7.3), and its Annex C encoding holds no time; the frame gives each set its frames, the TimeInterval of its §7.1, in frames of the base. WAI’s own fields are little-endian.

offsizefieldrule
04magic WD50
41version1
51reserved0
62n_sets1 to 256
8…the setsn_sets sets, one after another; nothing follows the last

A set:

sizefieldrule
4first_framethe index of its first frame, frames counted from 0
4frame_count1 or more
2len1 to 4096
lenthe setone smpte_st_2094_50_application_info() of ST 2094-50:2026-08 Annex C, as written

Each set’s first_frame is at least the end of the set before it (its first_frame plus its frame_count), and no end passes 2^32 − 1. A frame that breaks a rule of these tables is companion_malformed.

5.2 Reading a metadata set

A set reads as ST 2094-50:2026-08 Annex C has it: each syntax element of type u(N) an N-bit unsigned integer, most significant bit first, and the structures of its Tables C.1 to C.5 in order. The reader, in this order:

  1. refuses a set whose minimum_application_version is not 0 with metadata_unsupported: Annex C.2.1 has a parser ignore the whole bitstream;
  2. refuses a set that ends inside a structure, or holds a reserved_zero element that is not 0 (Annex C.1), with metadata_malformed. Bytes after the last element are padding and are not read (Annex C.2.1);
  3. assigns the metadata items as Annex C.3 does: HDR reference white, 203 or clamp(·, 1, 50000) / 5 (C.3.3); headrooms min(·, 60000) / 10000 (C.3.4); NumAlternateImages min(·, 4); component mixing (C.3.6); gain curves, with the sign s of C.3.7, x = min(·, 64000) / 1000 and y = s · min(·, 60000) / 10000, and each slope from its angle or by C.3.9 (§5.4);
  4. refuses with metadata_out_of_range a set that breaks a constraint of ST 2094-50:2026-08 §6.2.2, §6.4.2 or §6.5.2 (an alternate headroom equal to the baseline’s, alternate headrooms not increasing, mixing weights that sum to 0, control points whose x decreases, or two of one x with different y), or whose gain curve’s last control point has x = 0, beyond which Formula (12) is not finite;
  5. refuses with gain_space_unsupported a set whose use_reference_white_tone_mapping_flag is 1, whose gain application colour space C.3.8 sets to the primaries of ITU-R BT.2020, or whose gain_application_space_chromaticities_mode is not 0, the primaries and white point of ITU-R BT.709: this revision applies gains in the base’s primaries, those of sRGB, and converts none.

The recommended constraints of §6.2.3 are not checked: §6.2.3 lets an implementation adjust metadata to meet them, and the derivation applies the metadata as it reads.

For a base read as sRGB-coded (§2), the conversion of ST 2094-50:2026-08 Formula (A.3) divides the luminance of Formula (A.2) by the HDR reference white it was formed with, so the value in the gain application colour space is S[k] of §3.4 and the reference white’s value in candelas does not enter. A set with no headroom-adaptive tone map (has_adaptive_tone_map_flag 0) leaves the choice to the output system (its Clause A.3); the derivation then applies none, as a set with no alternate images does (its Clause B.4): the rendition is the base, projected (§3.5).

5.3 The parameters as integers

5.4 Slopes

From an angle (C.3.7, gain_curve_use_pchip_slope_flag 0): m = tan((clamp(θ, 1, 35999) − 18000) · π / 36000). With d = clamp(θ, 1, 35999) − 18000, P = ⌊π · 2^62⌋ = 14488038916154245684 and the angle in Q62 a = rnd(d · P, 36000), and a² in Q62 q = rnd(a · a, 2^62):

The angle is within π/2 of 0, so κ > 0, and fifteen terms of each series leave a remainder below 2^−62.

By PCHIP (C.3.9, flag 1): each slope of C.3.9 is a rational function of the control points, computed exactly, and rounded once to Q30. With hᵢ = Xᵢ₊₁ − Xᵢ and Δᵢ = Yᵢ₊₁ − Yᵢ, the slope of interval i is sᵢ = Δᵢ / (10 · hᵢ) stops per unit of x, and 0 where hᵢ = 0 (Formula (C.7)). The interior rule (C.8), the endpoint rules (C.9, C.10) with their sign tests and their cap at 3 · |s|, and the degenerate case are C.3.9’s, each comparison made on the exact rationals; the slope chosen, a rational, is m = rnd(numerator · 2^30, denominator) with a positive denominator.

5.5 Component mixing and the gain curve

Mixing (Formulas (9), (10)): for a colour C = (cr, cg, cb) in Q30,

Mⱼ = rnd(cⱼ · kcomponent + kred · cr + kgreen · cg + kblue · cb + kmax · max(C) + kmin · min(C), ksum), in Q30.

The gain curve (Formulas (11), (12)) at M in Q30, against 1000 · M and Xᵢ · 2^30, its value G in Q16:

The colour gain function of an alternate image is the curve at each of M’s three components (Formula (7)); the zero function’s is 0 (Formula (8)).

5.6 wai-tonemap:st2094-50

wai-tonemap:st2094-50 maps a frame of a base of w × h, a wai.meta.st2094_50 payload that reads (§5.1, §5.2), the set whose frames include the frame’s index (frame_uncovered where none does), and a headroom hᵈ (§3.5) to a rendition of w × h. Following §6.2.5: the baseline headroom is placed among the alternates’ in sorted position, H₀ < … < Hₙ₋₁, with the zero gain function at its place; hc = clamp(hᵈ, H₀, Hₙ₋₁). For each pixel, C = (S[r], S[g], S[b]) of its samples, and the gain G of each component (Q16) is:

Each component of the rendition is clamp(scale(c · 65535, G − Hₜ, 30), 0, 65535) with Hₜ = rnd(hᵈ · 2^16, 10000): code(C · 2^G) of §3.5 (Formula (5)), rounded once.

5.7 Against ST 2094-50’s computation

ST 2094-50:2026-08real-number forminteger formwhat is pinned
Formula (A.2), (A.3)sRGB decode, over reference whiteS[k] (§3.4)L2, E2
C.3.7m = tan of the angle§5.4, series in Q62, m in Q30the series and its rounding
C.3.9PCHIP slopes§5.4, exact rationals, rounded onceQ30
(9), (10)weighted mix over ksum§5.5, rounded onceQ30
(11)cubic Hermite in t§5.5, Horner in Q30each product’s rounding
(12)y + log2(xₙ₋₁ / x)L2 differenceQ16
(2) to (4)weights of headrooms§5.6, exact, rounded onceQ16
(5) and A.4C · 2^G, projected at the display§5.6 and §3.516-bit code values

Every formula of §6 of ST 2094-50:2026-08 has an integer step above, and every value in it is the one Annex C assigns from the bits.

6. Error against floating point

The derivations round where the standards compute in real numbers, so a rendition differs from a floating-point evaluation of the same formulas by a little. The reference measures it over every rendition of the corpus: the same steps in IEEE 754 binary64 (sRGB decoding by pow, log2, exp2 and tan of the platform library, the same bilinear weights, the PCHIP slopes in binary64), each code value rounded to nearest and clamped as §3.5 has it, against the integer rendition’s:

derivationrenditionssampleslargest differencemean difference
wai-tonemap:gainmap104147220.1269
wai-tonemap:st2094-502517280020.1642

in code values of 65535. The corpus states the same figures (measurement), and its test fails when they change. A difference is the integer derivation’s distance from that one floating-point evaluation; another floating-point implementation differs from it as well, by its own rounding and its own upsampling filter, and it is that spread which the derivation removes.

7. The rendition record

7.1 The record

A sink that presents renditions signs a rendition record, kind "wai-tonemap", for them, under a key of its session or of its own (the record names no viewer). A party that publishes a rendition signs one for what it published.

membertypemeaning
kindstring"wai-tonemap"
derivationtermwai-tonemap:gainmap or wai-tonemap:st2094-50
baseobject{"object", "rendition", "width", "height"}: the content hash of the envelope that carries the base and the companion; the index of the base’s entry among its renditions (0 for a WAI1); and the base’s size, each 1 to 4096
companionobject{"index", "sha256"}: the index of the companion’s entry, and the SHA-256 of its payload
picturesarray1 to 256 entries, each {"frame", "headroom", "input", "output"}: the frame’s index (0 for a still), the headroom (§3.5, 0 to 60000), the digest of the base frame’s RGB8 samples, and the digest of the rendition’s RGB16 samples; no two entries with the same frame and headroom, and entries in increasing order of frame, then of headroom
classstringthe class of the renditions (§7.2)
inputsarray1 to 16 entries, each {"content_hash", "origin", "class"}: an object the derivation read, the link hash of its origin claim (synthesis §3.1) or null, and that claim’s class, restated; one of them is base.object, and no content hash appears twice
issued_atdate-timewhen it signed it
signer_pubkey, sig§2

A wai-tonemap:gainmap record’s pictures are all of frame 0. class and an input’s class are one of captured, reconstructed, assistive and synthesized. A record that breaks a rule of this table is record_malformed; one whose headroom passes 60000 is headroom_out_of_range.

7.2 Provenance

A rendition is a convert step of synthesis §3.2. It rebuilds what its inputs hold: the base’s samples, and, at a headroom, the alternate rendering the companion states of them. It names no model, asserts no source of its own and makes no evidence claim: there is no edit to check, only a computation to repeat, and §8 repeats it. Its class is the greatest of reconstructed and its inputs’ classes, so a rendition never stands lower than what it was made from: a rendition of a captured photograph is reconstructed, and one of a synthesized picture is synthesized. A record whose class is another is class_mismatch.

Where a rendition is published as an object, its origin claim is a convert claim whose inputs are the inputs of its record (synthesis §3.4, the row for a decoder’s reconstruction), and its record is the claim’s record.

8. Verification

8.1 Inputs

A verifier is given, each in a list: rendition records; the envelopes it holds, each named by its content hash; the decoded frames it holds, each the RGB8 samples of one frame of the base of an envelope, named by the envelope’s content hash and the frame’s index (a verifier that decodes a base itself holds its frames; the base of a wai.image.png entry is decoded by its PNG); and a policy:

membervaluesdefaultmeaning
tonemapaccept-unrecomputed, require-proofaccept-unrecomputedrequire-proof: a picture the verifier does not recompute is refused (step 6)

8.2 Steps

The verifier runs these steps over each record in order and stops at the first refusal, which it reports with the reason and where: the record by its index (["record", i]), a picture of it by both (["record", i, "picture", j]).

  1. Reading. In this order: the record reads (§2, §7.1, record_malformed), its signature verifies (bad_key, bad_signature), and no picture’s headroom passes 60000 (headroom_out_of_range).

  2. Class. The record’s class is §7.2’s: class_mismatch.

  3. The envelope, where the verifier holds the one base.object names:

    1. it parses (SPEC §2, §7.1) and every component conforms to SPEC §7.1’s rules and to the role SPEC §5 gives its capability (the reference WaiMulti::check_component_roles): otherwise envelope_nonconforming;
    2. entry base.rendition is a selectable rendition, entry companion.index a companion bound to it (SPEC §7.1, Companion components) whose capability is the derivation’s (wai.meta.gainmap for wai-tonemap:gainmap, wai.meta.st2094_50 for wai-tonemap:st2094-50), and the SHA-256 of the companion’s payload is companion.sha256: otherwise companion_mismatch;
    3. the companion reads: §4.1 to §4.3, or §5.1 and §5.2, in their order, each with its reason.
  4. Each picture, in order, where the verifier holds its frame:

    1. for wai-tonemap:st2094-50, a set of the companion covers the frame (frame_uncovered);
    2. the frame’s samples are width · height · 3 bytes and their digest is input (input_samples_mismatch); for wai-tonemap:gainmap, the map’s size fits the base’s (§4.3, map_dimensions_mismatch);
    3. the derivation of §4.4 or §5.6 at the picture’s headroom gives a rendition whose digest is output (rendition_mismatch). The picture is then proved.

    A picture whose frame or companion the verifier does not hold is not recomputed.

  5. Nothing else is read: a picture’s output is never compared with anything but its recomputation.

  6. Under tonemap require-proof, a picture that is not proved: rendition_not_proved.

8.3 What an accepted case states

For each record, in order: its link hash, its class, and for each picture, in order, proved or not-recomputed.

9. Reasons

The codes this extension adds, each with where it is raised:

reasonraised at
record_malformed§2, §7.1, §8.2 step 1
bad_key§2
bad_signature§2
headroom_out_of_range§7.1, §8.2 step 1
class_mismatch§7.2, §8.2 step 2
envelope_nonconforming§8.2 step 3.1
companion_mismatch§8.2 step 3.2
companion_malformed§4.1, §5.1
metadata_unsupported§4.2 step 1, §5.2 step 1
metadata_malformed§4.2 step 2, §5.2 step 2
metadata_out_of_range§4.2 step 3, §5.2 step 4
gain_space_unsupported§4.2 step 4, §5.2 step 5
map_channels_mismatch§4.3
frame_uncovered§5.6, §8.2 step 4.1
input_samples_mismatch§8.2 step 4.2
map_dimensions_mismatch§4.3, §8.2 step 4.2
rendition_mismatch§8.2 step 4.3
rendition_not_proved§8.2 step 6

A sink that is given an envelope which breaks SPEC §7.1’s component rules reports SPEC’s own code for the break (companion-only-as-capability, companion-only-as-rendition, incompatible-base, …); a verifier reports the record over it as envelope_nonconforming.

10. Conformance

wai tonemap run renders a still’s gain map and writes its record, and wai tonemap verify checks them under require-proof.

live-conformance/tonemap/ holds the corpus, written by wai_tonemap_vectors gen from fixed seeds and from the integer video corpus: vectors.json (with the arithmetic of §3 and §5.4 at stated points and over whole ranges: L2, E2, the table S, tan of every angle and the slopes of the video’s curves, which a rendition of the corpus would not always expose), the envelopes it names in envelopes/, and frames.rgb8, the five frames of the int_video clip of wai-rs/tests/vectors/int_video/ as wai.video.int_motion decodes them (their digests are that clip’s sidecar’s). Its stills are generated: an HDR scene made from integer formulas, rendered to an sRGB base and a gain map by the reference’s own encoder, which is integer throughout and pinned by its code, not by this text. It has:

A conforming verifier gives every case the outcome, the reason, the place and the statements the corpus states. wai_tonemap_vectors verify and the test tests/exact_tonemap.rs check that the corpus regenerates byte for byte, that wai-rs gives every case its outcome, that §9 lists exactly the reference’s codes, that §6’s figures are the corpus’s, and that frames.rgb8 is the decode of the int_video clip. live-conformance/tonemap_verify.py is the second implementation, written from this text: it shares no code with wai-rs; it computes both derivations in integer arithmetic of its own, reads PNG, the envelopes and both companions itself, recomputes §6’s figures within one code value of the largest and 0.01 of the mean, reads §9’s codes from this text, mutates copies of the corpus and requires every copy to be refused, and drops each of its own checks in turn and requires the corpus to catch the weakened verifier.

11. Security and privacy considerations