1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
//! Pure-Rust pixel-format conversions for the oxideav framework.
//!
//! This crate extends [`oxideav_core::PixelFormat`] with the converters
//! that the rest of the codec/container ecosystem depends on — RGB/BGR
//! swizzles, YUV↔RGB (BT.601 and BT.709, limited and full range), chroma
//! subsampling changes (4:2:0 ↔ 4:2:2 ↔ 4:4:4), NV12/NV21 ↔ Yuv420P,
//! grayscale expansion, 16-bit/8-bit bit-depth changes, and palette
//! generation + Pal8 encode/decode with optional dithering.
//!
//! # Entry points
//!
//! - [`convert`] — the single conversion function; dispatches on
//! `(src_info.format, dst_format)` (the caller provides the source
//! format / dimensions via [`FrameInfo`]) and returns a freshly
//! allocated [`VideoFrame`].
//! - [`generate_palette`] — build a [`Palette`] from one or more source
//! frames, honouring the selected [`PaletteStrategy`].
//! - [`convert_in_place_if_same`] — trivial passthrough helper for the
//! "no conversion needed" case so callers don't duplicate the check.
//!
//! # Colour science
//!
//! The YUV↔RGB paths are written as scalar integer pipelines against
//! BT.601 and BT.709 weights. The "limited" variants use the studio
//! range (Y in 16..=235, chroma in 16..=240); "full" variants use the
//! full 0..=255 range, matching JPEG / "J" YUV. Every converter clamps
//! to `[0, 255]` after reconstruction. See [`yuv`] for the exact matrix
//! coefficients.
//!
//! Range is a property of the pixel format (`YuvJ*` full, every other
//! YUV layout limited); [`ConvertOptions::color_space`] picks the
//! matrix weights. A colour signal overrides both: the source frame's
//! [`oxideav_core::VideoFrame::color_signal`] record (e.g. a HEIF
//! `nclx` box declaring a `Yuva420P` or `Yuv420P10Le` payload full range
//! BT.709), or a caller override in [`ConvertContext::signal`] passed to
//! [`convert_with`]. Signalled H.273 matrices 1 (BT.709), 4 (FCC),
//! 5 / 6 (BT.601), 7 (SMPTE ST 240) and 9 (BT.2020 NCL) are applied
//! directly; 0 (identity) routes the planes as G, B, R through the
//! planar GBR family; an unsignalled frame behaves exactly as before.
//!
//! # Odd dimensions
//!
//! Every subsampled planar / semi-planar layout accepts odd widths and
//! heights: the chroma planes hold `ceil(w / wsub)` × `ceil(h / hsub)`
//! samples (ITU-T T.81 A.1.1 component dimensions; the core
//! `PixelFormat::plane_dimensions` contract), the trailing odd luma
//! column / row decodes against the last chroma sample, and encoding /
//! chroma downsampling replicate the last column / row into a truncated
//! block. Even dimensions are unaffected. `Yuv411P` still needs a
//! multiple-of-4 width and packed `Yuyv422` / `Uyvy422` an even width.
//! See [`convert_with`] for the full statement.
//!
//! # Speed and threading
//!
//! The 8-bit planar YUV(A) ↔ `Rgb24` / `Rgba` rows share one row-band
//! engine: tight planes are borrowed rather than copied, RGBA is
//! interleaved in the same pass as the decode, and the picture is cut
//! into bands of whole chroma rows. [`convert`] runs serial (the
//! oxideav-core threading contract's default); [`convert_with`] with a
//! [`ConvertContext`] whose `execution` budget is above one converts the
//! bands on scoped threads. The output is byte-identical at every
//! budget, and identical to the scalar Q15 reference on every SIMD path
//! (AVX2 on x86_64, NEON on aarch64, both directions).
//!
//! # Feature coverage
//!
//! Not every pair in the Cartesian product of [`PixelFormat`] variants
//! is supported; the first-tier matrix is:
//!
//! - RGB family (Rgb24/Bgr24/Rgba/Bgra/Argb/Abgr) all-to-all.
//! - Yuv420P/422P/444P ↔ Rgb24 / Rgba under BT.601 / BT.709 / BT.2020
//! (limited range), plus the full-range YuvJ420P/422P/444P families
//! directly ↔ Rgb24 / Rgba (the format pins the matrix range; the
//! `ColorSpace` option picks the primaries).
//! - Gray8 ↔ every YUV family (luma extraction with range rescale /
//! neutral-chroma synthesis; no colour matrix), and Rgb24 / Rgba →
//! Gray8 full-range luminance projection.
//! - Yuv420P/422P/444P all-to-all direct (chroma resample only — luma
//! copied byte-for-byte, no RGB hop), plus the same six pairs on the
//! full-range `YuvJ*` family AND on the 16-bit `Yuv*P16Le` family
//! (full 16-bit precision chroma resample, identical rounding).
//! - Yuv411P ↔ Yuv420P/422P/444P (chroma resample only — luma copied),
//! plus Yuv411P ↔ Rgb24/Rgba under BT.601/709/2020. The 4:1:1 layout
//! has chroma horizontally subsampled by 4 (NTSC DV-25 / JPEG
//! `-sample 4x1`); RGB encode and decode stage through a 4:4:4
//! chroma intermediate so no new colour math is introduced.
//! - YuvJ420P/422P/444P ↔ Yuv* equivalents — plane copy with range
//! rescale.
//! - Nv12/Nv21 ↔ Yuv420P, plus direct ↔ Rgb24/Rgba via a fused path
//! that runs the planar 4:2:0 encoder/decoder under the hood.
//! - Yuyv422/Uyvy422 packed 4:2:2 ↔ Yuv422P, plus direct ↔ Rgb24/Rgba
//! under BT.601/709/2020 (limited or full range). Yuyv ↔ Uyvy is a
//! zero-math byte swap.
//! - Gray8 ↔ Rgb24/Rgba broadcast.
//! - Ya8 ↔ Gray8/Rgb24/Rgba (luma broadcast; alpha is carried through to
//! Rgba, dropped on the way to Rgb24, and synthesised as opaque 255 on
//! the return paths from Gray8/Rgb24).
//! - Ya16Le (packed 16-bit grey + alpha, full-scale LE16 words) ↔ Ya8
//! (exact ×257 widen / high-byte narrow on both words), Gray16Le
//! (luma word verbatim), Gray8, Rgba64Le (bit-exact broadcast +
//! alpha carry out; rounded-mean luma derivation back, so
//! grey-on-alpha content round-trips exactly) and Rgb24 / Rgba.
//! - Rgb48Le ↔ Rgb24, Rgba64Le ↔ Rgba (bit-shift), and
//! Rgb48Le ↔ Rgba64Le (colour words verbatim; opaque 65535 alpha
//! synthesised / dropped).
//! - The full ten-member planar GBR(A) ladder — `Gbrp8` / `Gbrap8`
//! (byte planes)
//! through the 10/12/14-bit members to the full-width `Gbrp16Le` /
//! `Gbrap16Le` — ↔ **both** deep packed formats (Rgb48Le / Rgba64Le,
//! alpha synthesised opaque full-scale or dropped when the shapes
//! differ) and the 8-bit packed ones (Rgb24 / Rgba), plus Gray8
//! interop: full-range luminance projection on the way out (same
//! kernel as the packed RGB → Gray8 rows, alpha dropped) and an
//! MSB-replicated `r = g = b` broadcast on the way in — gray content
//! round-trips exactly.
//! - The full bit-depth ladder: planar YUV 8 ↔ 10 ↔ 12 ↔ 16 bit (same
//! subsampling; MSB-replicated widen / truncating narrow, exact
//! round-trips), and Gray8 ↔ Gray10Le ↔ Gray12Le ↔ Gray16Le. On the
//! `Yuv*P16Le` trio every bit of the LE word is significant
//! (full-scale 65535), so the 8 → 16 widen is the exact ×257 mapping.
//! - Gray16Le ↔ Gray8.
//! - MonoBlack/MonoWhite ↔ Gray8.
//! - The full planar YUV + alpha family — Yuva420P/422P/444P plus the
//! deep 10/12/16-bit members at all three chroma sitings
//! (Yuva420P/422P/444P × 10/12/16Le): promote/drop vs
//! the alpha-less siblings, alpha-preserving chroma resample and
//! depth moves inside the family (luma + alpha bit-exact at same
//! depth, MSB-widened / truncated across depths), Rgb24/Rgba interop
//! (alpha carried / dropped / synthesised opaque), and Gray8 luma
//! extraction. Every ordered pair inside the uniform planar family
//! ({Yuv,Yuva} × {420,422,444} × {8,10,12,16}) is a **direct**
//! single-step conversion generated by a computed dispatch tier that
//! fuses the depth move, the chroma resample (performed at the deeper
//! of the two depths) and the alpha handling.
//! - Pal8 → Rgb24/Rgba uses the frame's attached palette side-channel
//! ([`oxideav_core::VideoFrame::palette`]) when present, falling back
//! to `opts.palette`; with neither, `Error::Invalid`.
//! - Rgb24/Rgba → Pal8 requires `opts.palette`; dithering per
//! `opts.dither`. The output frame carries the table it was quantised
//! against as its palette side-channel, so it is self-describing.
//! - Cmyk and CmykInverted ↔ Rgb24/Rgba — uncalibrated device-CMYK
//! approximation in both ink conventions (see [`cmyk`] for the
//! formulas and the ICC caveats), plus Cmyk ↔ CmykInverted as the
//! exact per-byte complement.
//! - **4:4:0 planar** (`Yuv440P` / `Yuv440P10Le` / `Yuv440P12Le` /
//! `Yuv440P16Le`, full-width half-height chroma): full members of
//! the computed planar family (direct to every planar YUV(A)
//! member at every siting and depth, `Rgb24` / `Rgba` / `Gray8`),
//! explicit `Yuv411P` rows, and `Yuv440P16Le` ↔ `Rgb48Le` on the
//! deep matrix. The only new arithmetic is a vertical pair-average
//! (4:4:4 → 4:4:0) and a row broadcast (4:4:0 → 4:4:4); every other
//! siting move composes those with the existing kernels through a
//! 4:4:4 intermediate, bit-identical to a fused kernel. Odd widths
//! and heights are legal (see the odd-dimension rule below).
//! - **Scene-referred float** (`GrayF32Le` / `RgbF32Le` / `RgbaF32Le`
//! / `GbrpF32Le` / `GbrapF32Le`): every ordered pair inside the
//! family is direct and never clamps (gray broadcast, linear-light
//! luminance projection under the selected primaries, alpha carry /
//! drop / synthesise `1.0`, packed ↔ planar reorder); direct hops to
//! and from eighteen integer shapes (`Gray8/10/12/16Le`, `Rgb24` /
//! `Rgba` / `Rgb48Le` / `Rgba64Le`, the ten-member GBR(A) ladder)
//! by pure normalisation in and saturating round-to-nearest
//! quantisation out; and direct hops to and from the 16-bit planar
//! YUV(A) tier through the deep matrix. See [`float`] for the exact
//! value rules.
//! - **Full-precision deep matrix**: the 16-bit planar tier
//! ({Yuv,Yuva} × {420,422,444,440} `P16Le`) ↔ Rgb48Le / Rgba64Le runs
//! the k-coefficient construction in Q30 over 16-bit samples — no
//! 8-bit narrowing anywhere on the path; chroma is resampled at
//! 16-bit precision and the Yuva alpha plane rides verbatim in the
//! packed alpha word. The 10/12-bit family reaches these rows
//! losslessly through the exact widen to the 16-bit tier.
//!
//! Pairs without a direct entry (table row or computed planar-family
//! op) are resolved automatically through a
//! **single-pivot staged conversion** (one intermediate format, chosen
//! in a fidelity-aware order: YUV pivots for YUV → YUV moves so no
//! colour matrix enters the path — with the 16-bit `Yuv(a)*P16Le` tier
//! preferred whenever a deep endpoint is YUV carriage, so chroma is
//! resampled and the matrix applied at full precision — and
//! alpha-capable / deep RGB pivots
//! preferred where the endpoints call for them). [`supports`] /
//! [`supports_direct`] report per-pair availability. The matrix is
//! fully closed: **every ordered pair** of the 70 `PixelFormat`
//! variants resolves — directly (1820 pairs) or through one staged
//! pivot (all 4830).
//!
//! # Bit-depth precision policy
//!
//! Every depth change in the crate (8 ↔ 10 ↔ 12 ↔ 16 on planar YUV,
//! the deep grayscale ladder, and the GBR(A) widen/narrow steps) uses
//! one deterministic rule and **no dithering**:
//!
//! - **Widening** places the value in the top bits and replicates its
//! MSBs into the freed low bits. Zero maps to zero, full-scale maps
//! to full-scale, the mapping is strictly monotonic, and it tracks
//! the ideal `v · (2^dst − 1) / (2^src − 1)` rescale within one
//! output code (exactly, for 8 → 16, where the ratio is the integer
//! 257).
//! - **Narrowing** truncates the low bits (keeps the top ones) — the
//! exact inverse of the replication fill, so widen → narrow and
//! narrow-of-widen round-trips are lossless.
//!
//! Dithered quantisation is deliberately excluded from depth moves:
//! codec pipelines feeding this crate (lossless intermediates,
//! reference comparisons, bit-exact round-trip tests) need
//! deterministic, invertible mappings. `Dither` applies only to the
//! palette quantisation path (`Rgb24`/`Rgba` → `Pal8`).
//!
//! # Integer ↔ float
//!
//! The integer formats state no transfer characteristic in the core
//! format definitions, so the hop to and from the float family is a
//! **pure normalisation**: `f = code / (2^bits − 1)` in, `code =
//! round(clamp(f, 0, 1) · (2^bits − 1))` out (NaN → 0). No OETF / EOTF
//! is applied in either direction — apply one from [`transfer`] on the
//! float samples when the pipeline calls for it. Values outside
//! `[0, 1]` are saturated on the way to an integer format (the only
//! lossy step); float → float moves preserve them.
//!
//! # Per-plane significant bits
//!
//! Source frames may carry the per-plane **significant-bits
//! side-channel** defined by [`oxideav_core::VideoFrame`] — one
//! LSB-anchored byte per image plane naming that plane's true depth,
//! e.g. `[12, 10, 10]` for a wavelet codec's 12-bit luma + 10-bit
//! chroma on a `Yuv444P12Le` / `Yuv444P16Le` surface. [`convert`]
//! honours the record (marked planes convert at their recorded depth),
//! rejects invalid records (`0` or above the surface's nominal depth)
//! with `Error::Invalid`, ignores records on `Pal8` (indices are not
//! magnitudes) and on the float surfaces (binary32 samples are
//! magnitudes already), and always emits nominal-depth output with no record
//! attached — see the policy section on [`convert`] for the details.
pub use ;
pub use ;
pub use ;
pub use ;