Skip to main content

zenpixels_convert/
load_bearing.rs

1//! Descriptor-aware load-bearing analysis: "what parts of this buffer's
2//! declared descriptor are actually carrying information?"
3//!
4//! Each predicate in `crate::scan` answers a single byte-level question
5//! ("is this alpha lane all 0xFF?"); this module assembles those answers
6//! into a [`LoadBearingReport`] keyed off the buffer's [`PixelDescriptor`]
7//! and provides a one-call extension method on [`PixelSlice`] that runs
8//! the right predicates for the descriptor and folds the results into a
9//! narrower target descriptor.
10//!
11//! The entry points:
12//!   * [`PixelSliceLoadBearingExt::determine_load_bearing`] -- analysis,
13//!     no buffer modification
14//!   * [`PixelSliceLoadBearingExt::try_reduce_to_load_bearing_format`]
15//!     -- analysis + buffer rewrite into a fresh allocation, `None` when
16//!     no narrowing is possible
17//!   * [`PixelBufferLoadBearingExt::reduce_to_load_bearing_format_in_place`]
18//!     -- analysis + buffer rewrite into the buffer's own bytes (no
19//!     allocation), re-describing the buffer in the same call
20//!
21//! Every reduction this module reports is **bit-exact invertible**: drop
22//! an all-max alpha lane and a decoder resynthesizes it; collapse
23//! `R==G==B` to gray and the expansion is exact; narrow bit-replicated
24//! U16 to U8 and `u8 * 0x0101` reconstructs every sample. Primaries /
25//! gamut narrowing is deliberately **not** part of this analysis: it is
26//! a re-encoding (EOTF decode → 3×3 matrix in linear light →
27//! re-quantize) that rewrites stored pixel values, so it belongs to an
28//! explicit opt-in conversion API that pairs the descriptor re-tag with
29//! the buffer rewrite -- never to a descriptor-level "reduction" where
30//! a re-tag without the rewrite would silently misinterpret pixels.
31
32#[cfg(test)]
33use alloc::vec::Vec;
34
35use alloc::sync::Arc;
36
37use zenpixels::{
38    AlphaMode, ChannelLayout, ChannelType, ColorContext, InPlacePixels, PixelBuffer,
39    PixelDescriptor, PixelFormat, PixelSlice, PixelSliceMut,
40};
41
42use crate::scan::{self, FusedRequest};
43
44/// What a buffer's content actually exercises about its declared
45/// descriptor. Each field is `Option<T>` so it self-reports whether
46/// the predicate was actually measured against this buffer:
47///
48/// - `Some(value)` -- the predicate ran; `value` reflects measured truth
49/// - `None` -- the predicate didn't run (channel type unsupported, or the
50///   field doesn't apply to this layout). Codecs should treat `None` as
51///   "I don't know -- keep the conservative interpretation".
52///
53/// For boolean fields, the interesting signal for codecs is `Some(false)`:
54/// "this dimension isn't load-bearing, it's safe to narrow". `Some(true)`
55/// or `None` both mean "leave it alone".
56///
57/// `Default::default()` produces an all-`None` report -- the safe starting
58/// state when no analysis has run.
59#[derive(Clone, Copy, Debug, Default)]
60#[non_exhaustive]
61pub struct LoadBearingReport {
62    /// `Some(true)` → at least one alpha sample is not channel-max
63    /// (alpha is load-bearing). `Some(false)` → alpha can be dropped
64    /// (every sample is channel-max, OR the layout has no alpha
65    /// channel -- codec drops alpha either way). `None` → predicate
66    /// didn't run (unsupported channel type).
67    pub uses_alpha: Option<bool>,
68
69    /// `Some(true)` → at least one pixel has differing chroma channels
70    /// (R != G or G != B). `Some(false)` → no chroma variation (either
71    /// R==G==B everywhere or the layout is already grayscale). `None`
72    /// → predicate didn't run.
73    pub uses_chroma: Option<bool>,
74
75    /// `Some(true)` → at least one U16 sample has its low byte differ
76    /// from its high byte. `Some(false)` → no information lost in
77    /// U16 → U8 narrowing (either bit-replicated samples or the
78    /// buffer is already at U8). `None` → predicate didn't run (F32,
79    /// F16, etc.).
80    pub uses_low_bits: Option<bool>,
81}
82
83impl LoadBearingReport {
84    /// True if the analysis returned at least one non-`None` field --
85    /// i.e. some predicate ran (or answered structurally). Codecs that
86    /// need a quick "is there anything actionable here" check before
87    /// consulting individual fields; `false` means the buffer's
88    /// layout × channel-type combination isn't wired and the report
89    /// carries no information.
90    #[inline]
91    pub const fn any_analyzed(&self) -> bool {
92        self.uses_alpha.is_some() || self.uses_chroma.is_some() || self.uses_low_bits.is_some()
93    }
94
95    /// Produce the narrowest descriptor justified by this report.
96    ///
97    /// Order of reduction (each step's outcome feeds the next):
98    ///   1. Channel-type narrowing (U16 → U8 when `uses_low_bits` is
99    ///      false)
100    ///   2. Alpha drop (when `uses_alpha` is false and the layout has
101    ///      alpha)
102    ///   3. Chroma drop (when `uses_chroma` is false and the layout
103    ///      has chroma)
104    ///
105    /// Color signaling (primaries, transfer, signal range) carries over
106    /// from `src` untouched -- a load-bearing reduction never re-tags
107    /// color, because every reduction here keeps stored values exact.
108    ///
109    /// Alpha drop from `Bgra` narrows to `Rgb` -- there is no `Bgr`
110    /// layout, so the buffer rewrite in
111    /// [`PixelSliceLoadBearingExt::try_reduce_to_load_bearing_format`]
112    /// reorders channels (B,G,R,A → R,G,B). Callers applying this
113    /// descriptor with their own rewrite must do the same reorder.
114    ///
115    /// If a step would yield an unrepresentable `(channel_type, layout,
116    /// alpha)` triple, the source format is kept.
117    #[must_use]
118    pub fn apply_to(&self, src: &PixelDescriptor) -> PixelDescriptor {
119        let mut channel_type = src.channel_type();
120        let mut layout = src.layout();
121        let mut alpha = src.alpha;
122
123        // Each step triggers ONLY on Some(false) -- the explicit
124        // "not load-bearing" signal. Some(true) and None both mean
125        // "leave this dimension alone".
126
127        // 1. Channel-type narrowing.
128        if matches!(self.uses_low_bits, Some(false)) && channel_type == ChannelType::U16 {
129            channel_type = ChannelType::U8;
130        }
131
132        // 2. Alpha drop. Bgra → Rgb implies the B,G,R,A → R,G,B
133        // channel reorder in the buffer rewrite.
134        if matches!(self.uses_alpha, Some(false)) {
135            layout = match layout {
136                ChannelLayout::Rgba | ChannelLayout::Bgra => ChannelLayout::Rgb,
137                ChannelLayout::GrayAlpha => ChannelLayout::Gray,
138                other => other,
139            };
140            if layout != src.layout() {
141                alpha = None;
142            }
143        }
144
145        // 3. Chroma drop.
146        if matches!(self.uses_chroma, Some(false)) {
147            layout = match layout {
148                ChannelLayout::Rgb => ChannelLayout::Gray,
149                ChannelLayout::Rgba | ChannelLayout::Bgra => ChannelLayout::GrayAlpha,
150                other => other,
151            };
152        }
153
154        // Assemble the new format. PixelFormat::from_parts returns None
155        // for unrepresentable triples; in that case keep the source.
156        let format = PixelFormat::from_parts(channel_type, layout, alpha).unwrap_or(src.format);
157
158        PixelDescriptor::from_pixel_format(format)
159            .with_transfer(src.transfer)
160            .with_primaries(src.primaries)
161            .with_alpha(alpha)
162            .with_signal_range(src.signal_range)
163    }
164}
165
166// ── Extension trait on PixelSlice ──────────────────────────────────────
167
168mod sealed {
169    /// Seals [`super::PixelSliceLoadBearingExt`] to the slice types —
170    /// the analysis is keyed off their descriptor + row iteration
171    /// contract, so external impls have nothing valid to implement.
172    pub trait Sealed {}
173    impl<P> Sealed for zenpixels::PixelSlice<'_, P> {}
174    impl<P> Sealed for zenpixels::PixelSliceMut<'_, P> {}
175}
176
177/// Run all relevant load-bearing predicates against a [`PixelSlice`] and
178/// (optionally) produce a narrower buffer.
179///
180/// Sealed: implemented for [`PixelSlice`] only.
181pub trait PixelSliceLoadBearingExt: sealed::Sealed {
182    /// Run all relevant predicates and return the report. Pure analysis
183    /// -- no buffer rewrite, no descriptor changes.
184    ///
185    /// Use [`LoadBearingReport::apply_to`] on the slice's descriptor to
186    /// see what the buffer could become; use
187    /// [`Self::try_reduce_to_load_bearing_format`] to actually build it.
188    fn determine_load_bearing(&self) -> LoadBearingReport;
189
190    /// Run analysis and return the rewritten buffer if any reduction is
191    /// available; `None` if the buffer is already at its load-bearing
192    /// minimum, the predicates couldn't run, or allocation failed.
193    ///
194    /// The returned [`PixelBuffer`] carries the narrowed descriptor and
195    /// the buffer's standard SIMD-aligned row stride (it is not
196    /// byte-tightly packed; use the buffer's own accessors or
197    /// [`PixelBuffer::as_slice`] downstream).
198    fn try_reduce_to_load_bearing_format(&self) -> Option<PixelBuffer>;
199}
200
201impl<P> PixelSliceLoadBearingExt for PixelSlice<'_, P> {
202    fn determine_load_bearing(&self) -> LoadBearingReport {
203        let descriptor = self.descriptor();
204        let layout = descriptor.layout();
205        let channel_type = descriptor.channel_type();
206
207        // ── Descriptor-level alpha answers ───────────────────────
208        // Two `AlphaMode`s answer the alpha question without touching
209        // a single pixel:
210        //   * `Undefined` (RGBX/BGRX padding): the lane bytes are
211        //     meaningless -- scanning them would derive answers from
212        //     garbage. Structurally droppable.
213        //   * `Opaque`: the descriptor *contracts* every sample is
214        //     channel-max. Trust it -- same answer a scan of a
215        //     genuinely all-opaque buffer produces.
216        // `Straight` and `Premultiplied` scan normally. (All
217        // reductions here stay valid under premultiplication: alpha
218        // only drops when uniformly max, where premultiplied ==
219        // straight; `R==G==B` and bit-replication are value-exact
220        // tests unaffected by what the values encode.)
221        let alpha_structural: Option<Option<bool>> = if layout.has_alpha() {
222            match descriptor.alpha {
223                Some(AlphaMode::Undefined) | Some(AlphaMode::Opaque) => Some(Some(false)),
224                _ => None,
225            }
226        } else {
227            None
228        };
229        let scan_alpha = alpha_structural.is_none();
230
231        // ── Per-pixel byte-level predicates ──────────────────────
232        // Each branch returns `Some(value)` when the predicate ran
233        // (or the answer is structurally trivial -- e.g. `uses_alpha
234        // == Some(false)` for a layout with no alpha channel) and
235        // `None` when the predicate isn't wired for this channel
236        // type. Codecs treat `Some(false)` as the actionable
237        // "drop this" signal.
238        let (mut uses_alpha, uses_chroma) = match (layout, channel_type) {
239            (ChannelLayout::Rgba | ChannelLayout::Bgra, ChannelType::U8) => {
240                let fused = fused_rgba8_over_rows(
241                    self,
242                    FusedRequest {
243                        check_opaque: scan_alpha,
244                        check_grayscale: true,
245                    },
246                );
247                (Some(!fused.is_opaque), Some(!fused.is_grayscale))
248            }
249            (ChannelLayout::Rgba, ChannelType::U16) => (
250                Some(scan_alpha && !rows_all(self, cast_u16, scan::is_opaque_rgba16)),
251                Some(!rows_all(self, cast_u16, scan::is_grayscale_rgba16)),
252            ),
253            (ChannelLayout::Rgb, ChannelType::U8) => (
254                Some(false), // no alpha channel -- structurally not load-bearing
255                Some(!rows_all(self, cast_u8, scan::is_grayscale_rgb8)),
256            ),
257            (ChannelLayout::Rgb, ChannelType::U16) => (
258                Some(false),
259                Some(!rows_all(self, cast_u16, scan::is_grayscale_rgb16)),
260            ),
261            (ChannelLayout::GrayAlpha, ChannelType::U8) => (
262                Some(scan_alpha && !rows_all(self, cast_u8, scan::is_opaque_ga8)),
263                Some(false), // already grayscale -- no chroma to be load-bearing
264            ),
265            (ChannelLayout::GrayAlpha, ChannelType::U16) => (
266                Some(scan_alpha && !rows_all(self, cast_u16, scan::is_opaque_ga16)),
267                Some(false),
268            ),
269
270            // Gray-anything: structurally no alpha and no chroma to
271            // test. Both fields are `Some(false)` regardless of the
272            // channel-type-specific predicate availability.
273            (ChannelLayout::Gray, _) => (Some(false), Some(false)),
274
275            // F32 RGB(A) / GrayAlpha -- predicates wired.
276            (ChannelLayout::Rgba, ChannelType::F32) => (
277                Some(scan_alpha && !rows_all(self, cast_f32, scan::is_opaque_rgba_f32)),
278                Some(!rows_all(self, cast_f32, scan::is_grayscale_rgba_f32)),
279            ),
280            (ChannelLayout::Rgb, ChannelType::F32) => (
281                Some(false),
282                Some(!rows_all(self, cast_f32, scan::is_grayscale_rgb_f32)),
283            ),
284            (ChannelLayout::GrayAlpha, ChannelType::F32) => (
285                Some(scan_alpha && !rows_all(self, cast_f32, scan::is_opaque_ga_f32)),
286                Some(false),
287            ),
288
289            // F16 / Oklab / CMYK with non-Gray layout -- predicates
290            // not yet wired. All fields stay `None`.
291            _ => (None, None),
292        };
293
294        // Overlay the structural alpha answer (the scan, when one ran
295        // at all, was told not to compute it). `uses_alpha.is_some()`
296        // limits the overlay to layout × channel-type combos whose
297        // predicates are wired -- unanalyzed combos stay all-`None`.
298        if let Some(structural_uses) = alpha_structural
299            && uses_alpha.is_some()
300        {
301            uses_alpha = structural_uses;
302        }
303
304        // ── Low bits (U16 → U8) ──────────────────────────────────
305        let uses_low_bits = match channel_type {
306            ChannelType::U16 => Some(!rows_all(
307                self,
308                cast_u16,
309                scan::bit_replication_lossless_u16,
310            )),
311            // U8 is already at minimum integer depth -- structurally
312            // not load-bearing in the U16-narrowing sense.
313            ChannelType::U8 => Some(false),
314            // F32 / F16 -- no defined narrowing without lossy
315            // quantization. `None` = predicate doesn't apply.
316            _ => None,
317        };
318
319        LoadBearingReport {
320            uses_alpha,
321            uses_chroma,
322            uses_low_bits,
323        }
324    }
325
326    fn try_reduce_to_load_bearing_format(&self) -> Option<PixelBuffer> {
327        let src = self.descriptor();
328        let mut report = self.determine_load_bearing();
329        // Color-signaling plan: collapsing RGB(A) to gray would pair an
330        // attached RGB-class ICC profile with a Gray layout -- invalid
331        // signaling. Either a GRAY-class variant stands in (Swap), the
332        // context is already gray-valid (Carry), or the chroma signal is
333        // masked for the *rewrite* (Suppress); see
334        // [`plan_chroma_collapse_signaling`].
335        let plan = plan_chroma_collapse_signaling(self.color_context());
336        if matches!(plan, GraySignalPlan::Suppress) {
337            report.uses_chroma = None;
338        }
339        let target = report.apply_to(&src);
340        if target == src {
341            return None;
342        }
343        // One fallible zeroed allocation (calloc path) at the target's
344        // standard aligned stride; rows are then written in place --
345        // no per-pixel Vec growth anywhere in the rewrite.
346        let mut out = PixelBuffer::try_new(self.width(), self.rows(), target).ok()?;
347        transform_into(self, &src, &target, &mut out)?;
348        // Every reduction is value-exact, so color metadata travels with
349        // the reduced buffer: swapped to the GRAY-class context when the
350        // rewrite collapsed chroma under a Swap plan, carried verbatim
351        // otherwise (class-preserving reductions keep it valid).
352        let ctx = match (chroma_collapsed(src.layout(), target.layout()), plan) {
353            (true, GraySignalPlan::Swap(swapped)) => swapped,
354            _ => self.color_context().cloned(),
355        };
356        Some(match ctx {
357            Some(ctx) => out.with_color_context(ctx),
358            None => out,
359        })
360    }
361}
362
363/// How the chroma-collapse rewrite (RGB(A)/BGRA -> Gray/GrayAlpha) must
364/// treat the buffer's color signaling. Produced by
365/// [`plan_chroma_collapse_signaling`]; consumed by both reduce variants.
366enum GraySignalPlan {
367    /// Collapse allowed; the existing context (or none) stays valid for
368    /// gray and carries over as-is.
369    Carry,
370    /// Collapse allowed, but only with this replacement context: a
371    /// GRAY-class ICC swapped in for the RGB-class one (or the ICC
372    /// dropped in favor of CICP/descriptor signaling when the color is
373    /// the assumed sRGB default). Applied only if the rewrite actually
374    /// collapses chroma.
375    Swap(Option<Arc<ColorContext>>),
376    /// No valid gray signaling can be derived -- the rewrite keeps the
377    /// RGB form (alpha-drop and U16 -> U8 stay available).
378    Suppress,
379}
380
381/// Decide whether the chroma collapse may rewrite this buffer, and what
382/// the reduced buffer's [`ColorContext`] must become.
383///
384/// An ICC profile's header declares a device color space class
385/// (`'RGB '`, `'GRAY'`, ...); pairing a Gray-layout image with an
386/// RGB-class profile is invalid signaling (libpng, among others,
387/// rejects it). So when ICC bytes are attached, the collapse is allowed
388/// only if a **GRAY-class variant** can stand in: derive the CICP
389/// description of the attached profile -- the context's explicit `cicp`
390/// field, then an embedded `cICP` tag
391/// ([`zenpixels::icc::extract_cicp`]), then the normalized-hash
392/// identification of well-known profiles
393/// ([`zenpixels::icc::identify_common`]; worst accepted TRC deviation
394/// ±56/65535, sub-step at 8-bit) -- and feed it to
395/// [`crate::icc_profiles::synthesize_gray_icc_for_cicp`]. The swapped
396/// context keeps the source's `cicp` field alongside the new gray ICC.
397/// Unidentifiable profiles (and profiles whose color has no CICP code
398/// points, e.g. Adobe RGB) suppress the collapse.
399///
400/// CICP-only contexts have no class to violate: H.273 primaries (the
401/// white point) and transfer characteristics remain meaningful for
402/// single-channel data, and matrix coefficients describe a YCbCr<->RGB
403/// mapping that gray consumers ignore -- they carry over unchanged.
404///
405/// Note this gates only the buffer-rewriting APIs. The
406/// [`LoadBearingReport`] still reports measured chroma truth; encoders
407/// with their own color-emit pipelines can act on it and synthesize /
408/// re-resolve signaling themselves.
409fn plan_chroma_collapse_signaling(ctx: Option<&Arc<ColorContext>>) -> GraySignalPlan {
410    use crate::icc_profiles::{SynthesizedIcc, synthesize_gray_icc_for_cicp};
411
412    let Some(ctx) = ctx else {
413        return GraySignalPlan::Carry;
414    };
415    let Some(icc) = ctx.icc.as_deref() else {
416        return GraySignalPlan::Carry;
417    };
418
419    let cicp = ctx
420        .cicp
421        .or_else(|| zenpixels::icc::extract_cicp(icc))
422        .or_else(|| zenpixels::icc::identify_common(icc).and_then(|id| id.to_cicp()));
423    let Some(cicp) = cicp else {
424        return GraySignalPlan::Suppress;
425    };
426
427    match synthesize_gray_icc_for_cicp(cicp) {
428        SynthesizedIcc::Profile(bytes) => {
429            let mut swapped = ColorContext::from_icc(bytes.into_owned());
430            swapped.cicp = ctx.cicp;
431            GraySignalPlan::Swap(Some(Arc::new(swapped)))
432        }
433        // Assumed sRGB default: gray output needs no ICC (descriptor /
434        // container-level signaling suffices); keep the CICP if the
435        // source context carried one.
436        SynthesizedIcc::NotNeeded => {
437            GraySignalPlan::Swap(ctx.cicp.map(|c| Arc::new(ColorContext::from_cicp(c))))
438        }
439        // Off-grid code points / future variants: nothing derivable.
440        _ => GraySignalPlan::Suppress,
441    }
442}
443
444/// Whether the layout transition `src -> dst` collapses chroma (the
445/// transition [`plan_chroma_collapse_signaling`] gates).
446fn chroma_collapsed(src: ChannelLayout, dst: ChannelLayout) -> bool {
447    matches!(
448        src,
449        ChannelLayout::Rgb | ChannelLayout::Rgba | ChannelLayout::Bgra
450    ) && matches!(dst, ChannelLayout::Gray | ChannelLayout::GrayAlpha)
451}
452
453// ── In-place reduction on PixelBuffer ──────────────────────────────────
454
455/// In-place load-bearing reduction: rewrite a [`PixelBuffer`]'s own
456/// bytes to the narrowest justified format -- no allocation, and the
457/// buffer's descriptor/geometry/color are updated **atomically** via
458/// [`PixelBuffer::transform_in_place`] (a stale-descriptor state is
459/// unrepresentable; this is deliberately the only in-place entry point).
460///
461/// Sealed: implemented for [`PixelBuffer`] only.
462pub trait PixelBufferLoadBearingExt: sealed::Sealed {
463    /// Run the load-bearing analysis and rewrite this buffer in place to
464    /// the narrowest justified format, adopting the narrowed descriptor
465    /// and tight row stride (`width * bytes_per_pixel`) in the same call.
466    /// When no reduction applies the buffer is unchanged -- compare
467    /// [`PixelBuffer::descriptor`] before/after to detect it.
468    ///
469    /// Every rewrite is the same bit-exact byte selection
470    /// [`PixelSliceLoadBearingExt::try_reduce_to_load_bearing_format`]
471    /// performs; only the destination is the buffer itself. Rows are
472    /// compacted front-to-back (narrowing means every write lands at or
473    /// before the bytes it just consumed), so strided input works and
474    /// the result is always tightly packed.
475    ///
476    /// `force_alpha_restructuring` controls the one reduction that has a
477    /// tag-only alternative:
478    ///
479    /// * `false` -- a provably non-load-bearing alpha lane is **not**
480    ///   compacted away. A scanned-opaque `Straight`/`Premultiplied`
481    ///   buffer is re-tagged [`AlphaMode::Opaque`] instead (zero data
482    ///   movement -- encoders that re-layout internally only need the
483    ///   contract); RGBX/BGRX padding keeps its
484    ///   [`AlphaMode::Undefined`] tag. Channel-type narrowing and chroma
485    ///   collapse still rewrite, keeping the alpha lane in the layout
486    ///   (RGBA16 -> GrayA8, not Gray8).
487    /// * `true` -- the alpha/padding lane is physically removed
488    ///   (RGBA/BGRA -> RGB, GrayAlpha -> Gray), for consumers that need
489    ///   the packed narrow form (TIFF/JXL-style writers).
490    ///
491    /// Color metadata ([`ColorContext`]) follows the same rules as the
492    /// allocating variant: it stays on the buffer. When the rewrite
493    /// collapses chroma and ICC bytes are attached (an RGB-class profile
494    /// cannot describe a Gray layout), a GRAY-class variant is swapped in
495    /// if the profile's CICP description is derivable (explicit `cicp`
496    /// field, embedded `cICP` tag, or well-known-profile identification);
497    /// otherwise the collapse is suppressed. CICP-only contexts stay
498    /// valid for gray and carry over unchanged.
499    fn reduce_to_load_bearing_format_in_place(&mut self, force_alpha_restructuring: bool);
500}
501
502impl sealed::Sealed for PixelBuffer {}
503
504impl PixelBufferLoadBearingExt for PixelBuffer {
505    fn reduce_to_load_bearing_format_in_place(&mut self, force_alpha_restructuring: bool) {
506        self.transform_in_place(|px| reduce_in_place_impl(px, force_alpha_restructuring));
507    }
508}
509
510/// The transform body behind
511/// [`PixelBufferLoadBearingExt::reduce_to_load_bearing_format_in_place`]:
512/// analyze, plan, compact, and return the re-described view for
513/// [`PixelBuffer::transform_in_place`] to adopt. Returns the input
514/// re-wrapped unchanged when no reduction applies.
515fn reduce_in_place_impl(
516    px: InPlacePixels<'_>,
517    force_alpha_restructuring: bool,
518) -> PixelSliceMut<'_> {
519    let InPlacePixels {
520        bytes,
521        width,
522        rows,
523        stride: in_stride,
524        descriptor: src,
525        color: original_ctx,
526        ..
527    } = px;
528    fn rewrap<'b>(
529        bytes: &'b mut [u8],
530        width: u32,
531        rows: u32,
532        stride: usize,
533        desc: PixelDescriptor,
534        ctx: Option<Arc<ColorContext>>,
535    ) -> PixelSliceMut<'b> {
536        let out = PixelSliceMut::new(bytes, width, rows, stride, desc)
537            .expect("in-place reduction geometry is always valid");
538        match ctx {
539            Some(c) => out.with_color_context(c),
540            None => out,
541        }
542    }
543    if width == 0 || rows == 0 {
544        return rewrap(bytes, width, rows, in_stride, src, original_ctx);
545    }
546
547    let mut report = {
548        let view = PixelSlice::new(&bytes[..], width, rows, in_stride, src)
549            .expect("buffer-backed view is always valid");
550        view.determine_load_bearing()
551    };
552
553    // Same color-signaling plan as the allocating variant.
554    let plan = plan_chroma_collapse_signaling(original_ctx.as_ref());
555    if matches!(plan, GraySignalPlan::Suppress) {
556        report.uses_chroma = None;
557    }
558
559    // Alpha plan: physical drop only on request. Otherwise the lane
560    // stays in the layout and a scanned-opaque Straight/Premultiplied
561    // tag upgrades to the contract the scan just measured.
562    let alpha_droppable = matches!(report.uses_alpha, Some(false)) && src.layout().has_alpha();
563    if !force_alpha_restructuring {
564        report.uses_alpha = None;
565    }
566
567    let mut target = report.apply_to(&src);
568    if !force_alpha_restructuring
569        && alpha_droppable
570        && target.layout().has_alpha()
571        && !matches!(
572            src.alpha,
573            Some(AlphaMode::Undefined) | Some(AlphaMode::Opaque)
574        )
575    {
576        target = target.with_alpha(Some(AlphaMode::Opaque));
577    }
578
579    if target == src {
580        return rewrap(bytes, width, rows, in_stride, src, original_ctx);
581    }
582
583    // Equal-bpp target: no physical narrowing happened.
584    // `apply_to`'s `from_parts` can re-spell a same-shape format
585    // (Rgba8 + Undefined alpha normalizes to Rgbx8); a reduction
586    // API must narrow, not re-spell, so keep the source format and
587    // apply only the alpha re-tag if one was earned above. No
588    // bytes move and the original stride is kept.
589    if target.bytes_per_pixel() == src.bytes_per_pixel() {
590        let retagged = src.with_alpha(target.alpha);
591        return rewrap(bytes, width, rows, in_stride, retagged, original_ctx);
592    }
593
594    // Physical rewrite. `apply_to` only produces transitions
595    // `selection_map` knows, so the fallback is unreachable -- but
596    // stay total and hand the view back rather than panic.
597    let narrow16 =
598        src.channel_type() == ChannelType::U16 && target.channel_type() == ChannelType::U8;
599    let Some(map) = selection_map(src.layout(), target.layout()) else {
600        return rewrap(bytes, width, rows, in_stride, src, original_ctx);
601    };
602
603    // Same context rule as the allocating variant: swap to the
604    // GRAY-class context when this rewrite collapses chroma under a
605    // Swap plan, carry the original otherwise.
606    let ctx = match (chroma_collapsed(src.layout(), target.layout()), plan) {
607        (true, GraySignalPlan::Swap(swapped)) => swapped,
608        _ => original_ctx,
609    };
610
611    let in_bpp = src.bytes_per_pixel();
612    let out_bpp = target.bytes_per_pixel();
613    debug_assert!(out_bpp < in_bpp, "reduction always shrinks bpp");
614    let out_stride = width as usize * out_bpp;
615
616    compact_rows_in_place(
617        bytes,
618        width as usize,
619        rows as usize,
620        in_stride,
621        in_bpp,
622        out_bpp,
623        src.layout(),
624        target.layout(),
625        map,
626        narrow16,
627    );
628
629    rewrap(bytes, width, rows, out_stride, target, ctx)
630}
631
632/// Rewrite rows front-to-back in place, narrowing `in_bpp` -> `out_bpp`
633/// per pixel (channel selection via `map`, optional U16 -> U8
634/// narrowing). Output rows are tightly packed at `width * out_bpp`.
635///
636/// Overlap safety (plain index math, no `unsafe`): for pixel `(y, x)`,
637/// `dst_end = y*out_stride + (x+1)*out_bpp <= y*in_stride +
638/// (x+1)*in_bpp`, the start of the next unread source pixel -- every
639/// write lands at or before the bytes already consumed. Rows whose
640/// destination span is disjoint from their source span (all but the
641/// first `~out_bpp / (in_bpp - out_bpp)` rows on tight input) borrow
642/// both spans via `split_at_mut` and reuse the allocating path's
643/// SIMD/shuffle row kernels; the overlapping prefix rows stage each
644/// pixel through a fixed temp so the within-pixel read stays ahead of
645/// the write (dst == src only at the very first pixel).
646#[allow(clippy::too_many_arguments)]
647fn compact_rows_in_place(
648    data: &mut [u8],
649    width: usize,
650    rows: usize,
651    in_stride: usize,
652    in_bpp: usize,
653    out_bpp: usize,
654    src_layout: ChannelLayout,
655    dst_layout: ChannelLayout,
656    map: &[usize],
657    narrow16: bool,
658) {
659    let out_stride = width * out_bpp;
660    let in_ch = src_layout.channels();
661    let elem = if narrow16 { 2 } else { in_bpp / in_ch };
662    let row_in_len = width * in_bpp;
663    for y in 0..rows {
664        let src_start = y * in_stride;
665        let dst_start = y * out_stride;
666        let dst_end = dst_start + out_stride;
667        if dst_end <= src_start {
668            // Disjoint spans: same row kernels as the allocating rewrite.
669            let (head, tail) = data.split_at_mut(src_start);
670            let row_in = &tail[..row_in_len];
671            let row_out = &mut head[dst_start..dst_end];
672            if narrow16 {
673                select_row_u16_to_u8(row_in, row_out, in_ch, map);
674            } else {
675                match (elem, src_layout, dst_layout) {
676                    (1, ChannelLayout::Rgba, ChannelLayout::Rgb) => {
677                        // Length mismatch is impossible here; the scalar
678                        // fallback keeps the loop total instead of
679                        // propagating an unreachable error.
680                        if garb::bytes::rgba_to_rgb(row_in, row_out).is_err() {
681                            select_row::<1>(row_in, row_out, in_ch, map);
682                        }
683                    }
684                    (1, ChannelLayout::Bgra, ChannelLayout::Rgb) => {
685                        if garb::bytes::bgra_to_rgb(row_in, row_out).is_err() {
686                            select_row::<1>(row_in, row_out, in_ch, map);
687                        }
688                    }
689                    (1, ..) => select_row::<1>(row_in, row_out, in_ch, map),
690                    (2, ..) => select_row::<2>(row_in, row_out, in_ch, map),
691                    _ => select_row::<4>(row_in, row_out, in_ch, map),
692                }
693            }
694        } else {
695            // Overlapping prefix rows: per-pixel staging through a
696            // fixed temp (max in_bpp = RgbaF32 = 16 bytes).
697            for x in 0..width {
698                let s = src_start + x * in_bpp;
699                let mut tmp = [0u8; 16];
700                tmp[..in_bpp].copy_from_slice(&data[s..s + in_bpp]);
701                let d = dst_start + x * out_bpp;
702                if narrow16 {
703                    for (k, &c) in map.iter().enumerate() {
704                        data[d + k] = tmp[c * 2];
705                    }
706                } else {
707                    for (k, &c) in map.iter().enumerate() {
708                        data[d + k * elem..d + (k + 1) * elem]
709                            .copy_from_slice(&tmp[c * elem..(c + 1) * elem]);
710                    }
711                }
712            }
713        }
714    }
715}
716
717// ── Strided row iteration helpers ──────────────────────────────────────
718//
719// "Every function that operates on rows of pixels MUST natively support
720// strided rows, at no additional runtime cost on the tightly-packed
721// path." (Per global CLAUDE.md.) These helpers implement that contract:
722// when the slice is contiguous, ONE call to the inner predicate; when
723// strided, one call per row. Output of the predicate AND-reduces across
724// rows with early-exit on first false.
725
726/// AND-reduce a slice-level predicate across rows: one call on the
727/// contiguous fast path, one call per row when strided, early-exit on
728/// the first `false`. `cast` reinterprets each row's bytes as the
729/// predicate's element type (`cast_u8` / `cast_u16` / `cast_f32`).
730#[inline]
731fn rows_all<P, T, F>(slice: &PixelSlice<'_, P>, cast: fn(&[u8]) -> &[T], predicate: F) -> bool
732where
733    T: 'static,
734    F: Fn(&[T]) -> bool,
735{
736    if let Some(bytes) = slice.as_contiguous_bytes() {
737        predicate(cast(bytes))
738    } else {
739        for y in 0..slice.rows() {
740            if !predicate(cast(slice.row(y))) {
741                return false;
742            }
743        }
744        true
745    }
746}
747
748/// Row-aware fused predicate for RGBA8/Bgra8. Drops finished checks
749/// from the next row's request so per-row work shrinks as flags flip.
750/// Single fused call on contiguous buffers. Unrequested checks come
751/// back `false` ("not computed"), mirroring `FusedResult` semantics.
752fn fused_rgba8_over_rows<P>(slice: &PixelSlice<'_, P>, request: FusedRequest) -> scan::FusedResult {
753    if let Some(bytes) = slice.as_contiguous_bytes() {
754        return scan::fused_predicates_rgba8_cg(bytes, request);
755    }
756    let mut req = request;
757    let mut total = scan::FusedResult {
758        is_opaque: req.check_opaque,
759        is_grayscale: req.check_grayscale,
760    };
761    for y in 0..slice.rows() {
762        if !req.check_opaque && !req.check_grayscale {
763            break;
764        }
765        let row = slice.row(y);
766        let r = scan::fused_predicates_rgba8_cg(row, req);
767        if req.check_opaque && !r.is_opaque {
768            total.is_opaque = false;
769            req.check_opaque = false;
770        }
771        if req.check_grayscale && !r.is_grayscale {
772            total.is_grayscale = false;
773            req.check_grayscale = false;
774        }
775    }
776    total
777}
778
779// ── Helpers ────────────────────────────────────────────────────────────
780
781fn cast_u8(bytes: &[u8]) -> &[u8] {
782    bytes
783}
784
785fn cast_u16(bytes: &[u8]) -> &[u16] {
786    bytemuck::cast_slice(bytes)
787}
788
789fn cast_f32(bytes: &[u8]) -> &[f32] {
790    bytemuck::cast_slice(bytes)
791}
792
793/// Fill `out` (pre-zeroed, target descriptor, aligned stride) from
794/// `slice`, row by row. Strided input costs nothing extra -- the loop
795/// is per-row either way. Returns `None` for descriptor pairs this
796/// module doesn't know how to rewrite.
797///
798/// Every transition is a pure byte selection -- no sample value
799/// changes. The two RGBA-family alpha drops delegate to `garb`'s SIMD
800/// swizzles; the remaining selections are fixed-stride copy loops that
801/// LLVM turns into shuffles (and they only run when the corresponding
802/// scan proved the dropped bytes redundant).
803fn transform_into<P>(
804    slice: &PixelSlice<'_, P>,
805    src: &PixelDescriptor,
806    dst: &PixelDescriptor,
807    out: &mut PixelBuffer,
808) -> Option<()> {
809    let src_ct = src.channel_type();
810    let dst_ct = dst.channel_type();
811    let src_layout = src.layout();
812    let dst_layout = dst.layout();
813
814    // U16 → U8 narrowing is the only channel-type transition. The
815    // bit-replication precondition (`uses_low_bits == Some(false)`)
816    // proves both bytes of every sample are equal, so byte 0 is the
817    // (replicated) high byte regardless of endianness.
818    let narrow16 = src_ct == ChannelType::U16 && dst_ct == ChannelType::U8;
819    if !narrow16 && src_ct != dst_ct {
820        return None;
821    }
822
823    // Source-channel selection map for the layout transition.
824    let in_ch = src_layout.channels();
825    let map: &[usize] = selection_map(src_layout, dst_layout)?;
826
827    let mut out_rows = out.as_slice_mut();
828    for y in 0..slice.rows() {
829        let row_in = slice.row(y);
830        let row_out = out_rows.row_mut(y);
831        if narrow16 {
832            select_row_u16_to_u8(row_in, row_out, in_ch, map);
833        } else {
834            match (dst_ct.byte_size(), src_layout, dst_layout) {
835                (1, ChannelLayout::Rgba, ChannelLayout::Rgb) => {
836                    garb::bytes::rgba_to_rgb(row_in, row_out).ok()?;
837                }
838                (1, ChannelLayout::Bgra, ChannelLayout::Rgb) => {
839                    garb::bytes::bgra_to_rgb(row_in, row_out).ok()?;
840                }
841                (1, ..) => select_row::<1>(row_in, row_out, in_ch, map),
842                (2, ..) => select_row::<2>(row_in, row_out, in_ch, map),
843                (4, ..) => select_row::<4>(row_in, row_out, in_ch, map),
844                _ => return None,
845            }
846        }
847    }
848    Some(())
849}
850
851/// Source-channel selection map for a layout transition, in element
852/// units. Identity when the layout doesn't change (channel-type-only
853/// narrowing); `None` for pairs the reducer never produces.
854fn selection_map(src_layout: ChannelLayout, dst_layout: ChannelLayout) -> Option<&'static [usize]> {
855    static IDENTITY: [usize; 4] = [0, 1, 2, 3];
856    Some(match (src_layout, dst_layout) {
857        _ if src_layout == dst_layout => &IDENTITY[..src_layout.channels()],
858        (ChannelLayout::Rgba, ChannelLayout::Rgb) => &[0, 1, 2],
859        // Bgra stores B,G,R,A -- dropping alpha into the Rgb layout
860        // requires the B↔R reorder, not a prefix copy.
861        (ChannelLayout::Bgra, ChannelLayout::Rgb) => &[2, 1, 0],
862        // Channel 0 is R for Rgba and B for Bgra; either is the gray
863        // value because these transitions only fire when R == G == B
864        // held for every pixel.
865        (ChannelLayout::Rgba | ChannelLayout::Bgra, ChannelLayout::GrayAlpha) => &[0, 3],
866        (ChannelLayout::Rgba | ChannelLayout::Bgra, ChannelLayout::Gray) => &[0],
867        (ChannelLayout::Rgb, ChannelLayout::Gray) => &[0],
868        (ChannelLayout::GrayAlpha, ChannelLayout::Gray) => &[0],
869        _ => return None,
870    })
871}
872
873/// Copy the `map`-selected channels (element size `E` bytes) of each
874/// pixel in `row_in` into `row_out`. Fixed `E` + `chunks_exact` keeps
875/// the loop bounds-check-free and auto-vectorizable.
876#[inline]
877fn select_row<const E: usize>(row_in: &[u8], row_out: &mut [u8], in_ch: usize, map: &[usize]) {
878    let out_px = map.len() * E;
879    let in_px = in_ch * E;
880    for (dst, src) in row_out
881        .chunks_exact_mut(out_px)
882        .zip(row_in.chunks_exact(in_px))
883    {
884        for (k, &c) in map.iter().enumerate() {
885            dst[k * E..(k + 1) * E].copy_from_slice(&src[c * E..c * E + E]);
886        }
887    }
888}
889
890/// Like [`select_row`] but narrows each selected u16 sample to u8 by
891/// taking byte 0 (valid because bit-replication was proven first).
892#[inline]
893fn select_row_u16_to_u8(row_in: &[u8], row_out: &mut [u8], in_ch: usize, map: &[usize]) {
894    let in_px = in_ch * 2;
895    for (dst, src) in row_out
896        .chunks_exact_mut(map.len())
897        .zip(row_in.chunks_exact(in_px))
898    {
899        for (k, &c) in map.iter().enumerate() {
900            dst[k] = src[c * 2];
901        }
902    }
903}
904
905#[cfg(test)]
906mod tests {
907    use super::*;
908    use zenpixels::{Cicp, ColorPrimaries, PixelSlice, TransferFunction};
909
910    fn make_slice<'a>(
911        bytes: &'a [u8],
912        width: u32,
913        height: u32,
914        format: PixelFormat,
915    ) -> PixelSlice<'a> {
916        let descriptor =
917            PixelDescriptor::from_pixel_format(format).with_transfer(TransferFunction::Srgb);
918        let stride = width as usize * format.bytes_per_pixel();
919        PixelSlice::new(bytes, width, height, stride, descriptor).unwrap()
920    }
921
922    fn make_slice_with_primaries<'a>(
923        bytes: &'a [u8],
924        width: u32,
925        height: u32,
926        format: PixelFormat,
927        primaries: ColorPrimaries,
928    ) -> PixelSlice<'a> {
929        let descriptor = PixelDescriptor::from_pixel_format(format)
930            .with_transfer(TransferFunction::Srgb)
931            .with_primaries(primaries);
932        let stride = width as usize * format.bytes_per_pixel();
933        PixelSlice::new(bytes, width, height, stride, descriptor).unwrap()
934    }
935
936    /// Analysis + combiner: the descriptor this buffer would reduce to.
937    fn reduced(slice: &PixelSlice<'_>) -> PixelDescriptor {
938        slice.determine_load_bearing().apply_to(&slice.descriptor())
939    }
940
941    // ── Reductions on common channel types ────────────────────────
942
943    #[test]
944    fn rgba8_all_opaque_gray_reduces_to_gray8() {
945        let bytes: Vec<u8> = (0..4)
946            .flat_map(|i| {
947                let g = (i * 30) as u8;
948                [g, g, g, 255]
949            })
950            .collect();
951        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
952        let r = slice.determine_load_bearing();
953        // analyzed bool removed
954        assert_eq!(r.uses_alpha, Some(false));
955        assert_eq!(r.uses_chroma, Some(false));
956
957        let target = r.apply_to(&slice.descriptor());
958        assert_eq!(target.format, PixelFormat::Gray8);
959    }
960
961    #[test]
962    fn rgba8_with_real_color_keeps_rgba_drops_alpha() {
963        let bytes: Vec<u8> = (0..4)
964            .flat_map(|i| {
965                [
966                    (i * 60 + 10) as u8,
967                    (i * 30 + 50) as u8,
968                    (i * 90 + 20) as u8,
969                    255,
970                ]
971            })
972            .collect();
973        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
974        let r = slice.determine_load_bearing();
975        // analyzed bool removed
976        assert_eq!(r.uses_alpha, Some(false));
977        assert_eq!(r.uses_chroma, Some(true));
978
979        let target = r.apply_to(&slice.descriptor());
980        assert_eq!(target.format, PixelFormat::Rgb8);
981    }
982
983    #[test]
984    fn rgba8_alpha_mix_0_and_255_reports_binary() {
985        let bytes: Vec<u8> = (0..4)
986            .flat_map(|i| {
987                let a = if i & 1 == 0 { 0 } else { 255 };
988                [50, 50, 50, a]
989            })
990            .collect();
991        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
992        let r = slice.determine_load_bearing();
993        assert_eq!(r.uses_alpha, Some(true), "alpha varies → load-bearing");
994        assert_eq!(r.uses_chroma, Some(false));
995    }
996
997    #[test]
998    fn rgba16_bit_replicated_reduces_to_rgba8() {
999        let bytes: Vec<u8> = (0..4)
1000            .flat_map(|i| {
1001                let r = (i * 60) as u8;
1002                let g = (i * 30 + 10) as u8;
1003                let b = (i * 80 + 5) as u8;
1004                let a = 0xFF;
1005                [r, r, g, g, b, b, a, a]
1006            })
1007            .collect();
1008        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba16);
1009        let r = slice.determine_load_bearing();
1010        // analyzed bool removed
1011        assert_eq!(r.uses_low_bits, Some(false));
1012        assert_eq!(r.uses_alpha, Some(false));
1013        let target = r.apply_to(&slice.descriptor());
1014        assert_eq!(target.format, PixelFormat::Rgb8);
1015    }
1016
1017    #[test]
1018    fn rgba16_actual_high_precision_keeps_u16() {
1019        let bytes: Vec<u8> = (0..4)
1020            .flat_map(|i| {
1021                let r_lo = (i * 17 + 1) as u8;
1022                let r_hi = (i * 60) as u8;
1023                [r_hi, r_lo, 0, 0, 0, 0, 0xFF, 0xFF]
1024            })
1025            .collect();
1026        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba16);
1027        let r = slice.determine_load_bearing();
1028        assert_eq!(r.uses_low_bits, Some(true));
1029    }
1030
1031    // ── Sub-byte gray detection ──────────────────────────────────
1032
1033    // ── try_reduce ─────────────────────────────────────────────
1034
1035    #[test]
1036    fn try_reduce_returns_some_when_reduction_available() {
1037        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
1038        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
1039        let result = slice.try_reduce_to_load_bearing_format();
1040        let out = result.expect("should reduce");
1041        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
1042        assert_eq!(out.as_slice().row(0), &[0u8, 30, 60, 90]);
1043    }
1044
1045    #[test]
1046    fn try_reduce_returns_none_when_already_minimal() {
1047        let bytes: Vec<u8> = (0..4)
1048            .flat_map(|i| [i * 60, 100, 200, i * 40 + 1])
1049            .collect();
1050        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
1051        assert!(slice.try_reduce_to_load_bearing_format().is_none());
1052    }
1053
1054    // ── analyzed flag ─────────────────────────────────────────────
1055
1056    #[test]
1057    fn default_report_is_fully_unanalyzed() {
1058        // With per-field Option semantics, the default report is
1059        // "nothing was checked" — every field is None. apply_to on a
1060        // None-only report is identity (no Some(false) signals).
1061        let r = LoadBearingReport::default();
1062        assert_eq!(r.uses_alpha, None);
1063        assert_eq!(r.uses_chroma, None);
1064        assert_eq!(r.uses_low_bits, None);
1065        assert!(!r.any_analyzed());
1066    }
1067
1068    #[test]
1069    fn any_analyzed_fires_when_at_least_one_field_set() {
1070        let mut r = LoadBearingReport::default();
1071        assert!(!r.any_analyzed());
1072        r.uses_alpha = Some(true);
1073        assert!(r.any_analyzed(), "any_analyzed fires for any Some");
1074        r.uses_alpha = None;
1075        r.uses_low_bits = Some(false);
1076        assert!(r.any_analyzed(), "any_analyzed fires on low-bits too");
1077    }
1078
1079    // ── Color signaling is never re-tagged ───────────────────────
1080
1081    #[test]
1082    fn wide_primaries_tag_is_preserved_and_ignored_by_analysis() {
1083        // A P3-tagged buffer analyzes exactly like an sRGB-tagged one
1084        // (the analysis is value-exact and color-space-blind), and the
1085        // reduced descriptor keeps the P3 tag — load-bearing reduction
1086        // never re-tags primaries, because a re-tag without a pixel
1087        // rewrite would reinterpret the buffer in the wrong space.
1088        let bytes: Vec<u8> = (0..4)
1089            .flat_map(|i| {
1090                let g = (i * 30) as u8;
1091                [g, g, g, 255]
1092            })
1093            .collect();
1094        let p3 =
1095            make_slice_with_primaries(&bytes, 4, 1, PixelFormat::Rgba8, ColorPrimaries::DisplayP3);
1096        let srgb =
1097            make_slice_with_primaries(&bytes, 4, 1, PixelFormat::Rgba8, ColorPrimaries::Bt709);
1098
1099        let r_p3 = p3.determine_load_bearing();
1100        let r_srgb = srgb.determine_load_bearing();
1101        assert_eq!(r_p3.uses_alpha, r_srgb.uses_alpha);
1102        assert_eq!(r_p3.uses_chroma, r_srgb.uses_chroma);
1103
1104        let out = p3
1105            .try_reduce_to_load_bearing_format()
1106            .expect("gray+opaque should reduce");
1107        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
1108        assert_eq!(
1109            out.descriptor().primaries,
1110            ColorPrimaries::DisplayP3,
1111            "primaries tag must carry over untouched"
1112        );
1113        // Bit-exact: the gray bytes are the original channel values.
1114        assert_eq!(out.as_slice().row(0), &[0u8, 30, 60, 90]);
1115    }
1116
1117    // ── Apply combiner ──────────────────────────────────────────
1118
1119    #[test]
1120    fn apply_to_no_op_on_fully_load_bearing() {
1121        let src = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8);
1122        let r = LoadBearingReport::default();
1123        assert_eq!(r.apply_to(&src), src);
1124    }
1125
1126    #[test]
1127    fn ga8_opaque_reduces_to_gray8() {
1128        let bytes = [10u8, 255, 50, 255, 100, 255];
1129        let slice = make_slice(&bytes, 3, 1, PixelFormat::GrayA8);
1130        assert_eq!(reduced(&slice).format, PixelFormat::Gray8);
1131    }
1132
1133    #[test]
1134    fn rgba16_grayscale_alpha_replicated_reduces_to_gray8() {
1135        let bytes: Vec<u8> = (0..4)
1136            .flat_map(|i| {
1137                let g = (i * 60) as u8;
1138                [g, g, g, g, g, g, 0xFF, 0xFF]
1139            })
1140            .collect();
1141        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba16);
1142        assert_eq!(reduced(&slice).format, PixelFormat::Gray8);
1143    }
1144
1145    // ── AlphaMode-driven structural answers ─────────────────────
1146
1147    #[test]
1148    fn undefined_alpha_padding_is_structurally_droppable() {
1149        // RGBX-style buffer: lane 3 is garbage padding (0x7B), NOT an
1150        // alpha channel. The analysis must not scan it — uses_alpha
1151        // answers from the descriptor and the padding never poisons
1152        // the result; alpha_is_binary doesn't apply.
1153        let bytes = [10u8, 20, 30, 0x7B, 40, 50, 60, 0x01];
1154        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1155            .with_transfer(TransferFunction::Srgb)
1156            .with_alpha(Some(AlphaMode::Undefined));
1157        let slice = PixelSlice::new(&bytes, 2, 1, 8, descriptor).unwrap();
1158        let r = slice.determine_load_bearing();
1159        assert_eq!(
1160            r.uses_alpha,
1161            Some(false),
1162            "padding lane is never load-bearing"
1163        );
1164        assert_eq!(r.uses_chroma, Some(true), "chroma still measured");
1165        // try_reduce drops the padding lane.
1166        let out = slice
1167            .try_reduce_to_load_bearing_format()
1168            .expect("padding drop is a reduction");
1169        assert_eq!(out.descriptor().format, PixelFormat::Rgb8);
1170        assert_eq!(out.as_slice().row(0), &[10u8, 20, 30, 40, 50, 60]);
1171    }
1172
1173    #[test]
1174    fn declared_opaque_alpha_is_trusted_without_scanning() {
1175        // AlphaMode::Opaque is a descriptor-level contract: every alpha
1176        // sample is channel-max. The analysis trusts it (mirroring what
1177        // a scan of a genuinely all-opaque buffer reports) instead of
1178        // re-verifying per pixel.
1179        let bytes = [10u8, 10, 10, 255, 20, 20, 20, 255];
1180        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1181            .with_transfer(TransferFunction::Srgb)
1182            .with_alpha(Some(AlphaMode::Opaque));
1183        let slice = PixelSlice::new(&bytes, 2, 1, 8, descriptor).unwrap();
1184        let r = slice.determine_load_bearing();
1185        assert_eq!(r.uses_alpha, Some(false));
1186        assert_eq!(r.uses_chroma, Some(false), "chroma still measured");
1187    }
1188
1189    #[test]
1190    fn premultiplied_alpha_scans_like_straight() {
1191        // Premultiplied buffers run the same value-exact predicates:
1192        // alpha only drops when uniformly max (premul == straight
1193        // there), and varying premultiplied alpha stays load-bearing.
1194        let bytes = [10u8, 10, 10, 128, 20, 20, 20, 64];
1195        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1196            .with_transfer(TransferFunction::Srgb)
1197            .with_alpha(Some(AlphaMode::Premultiplied));
1198        let slice = PixelSlice::new(&bytes, 2, 1, 8, descriptor).unwrap();
1199        let r = slice.determine_load_bearing();
1200        assert_eq!(
1201            r.uses_alpha,
1202            Some(true),
1203            "varying premul alpha is load-bearing"
1204        );
1205    }
1206
1207    // ── Strided-row tests ──────────────────────────────────────
1208    //
1209    // These build a buffer with stride > width × bpp (i.e. padding
1210    // between rows) and verify that:
1211    //   1. `determine_load_bearing` runs the predicates per-row and
1212    //      reaches the same answer as the equivalent contiguous buffer
1213    //   2. `try_reduce_to_load_bearing_format` produces the same
1214    //      tightly-packed output regardless of input stride
1215    //   3. The padding bytes (which contain garbage that would poison
1216    //      a contiguous-only predicate) don't affect the result
1217
1218    /// Build a strided RGBA8 buffer: each row's `width × 4` pixel bytes
1219    /// are followed by `padding_bytes` of garbage. Returns the byte
1220    /// buffer and the stride in bytes.
1221    fn build_strided_rgba8(
1222        width: u32,
1223        height: u32,
1224        padding_bytes: usize,
1225        mut pixel_at: impl FnMut(u32, u32) -> [u8; 4],
1226    ) -> (Vec<u8>, usize) {
1227        let row_pixels = width as usize * 4;
1228        let stride = row_pixels + padding_bytes;
1229        let mut buf = vec![0xAAu8; stride * height as usize]; // 0xAA garbage
1230        for y in 0..height {
1231            for x in 0..width {
1232                let p = pixel_at(x, y);
1233                let off = y as usize * stride + x as usize * 4;
1234                buf[off..off + 4].copy_from_slice(&p);
1235            }
1236            // Stamp obvious garbage in the padding to catch leaks.
1237            for k in row_pixels..stride {
1238                buf[y as usize * stride + k] = 0xCD;
1239            }
1240        }
1241        (buf, stride)
1242    }
1243
1244    fn slice_from_strided<'a>(
1245        bytes: &'a [u8],
1246        width: u32,
1247        height: u32,
1248        stride: usize,
1249        format: PixelFormat,
1250    ) -> PixelSlice<'a> {
1251        let descriptor =
1252            PixelDescriptor::from_pixel_format(format).with_transfer(TransferFunction::Srgb);
1253        PixelSlice::new(bytes, width, height, stride, descriptor).unwrap()
1254    }
1255
1256    #[test]
1257    fn strided_rgba8_all_opaque_gray_reduces_correctly() {
1258        // 4 rows × 4 pixels, 32 bytes of garbage per row of padding.
1259        let (buf, stride) = build_strided_rgba8(4, 4, 32, |x, y| {
1260            let g = ((x + y) * 30) as u8;
1261            [g, g, g, 255]
1262        });
1263        let slice = slice_from_strided(&buf, 4, 4, stride, PixelFormat::Rgba8);
1264        assert!(!slice.is_contiguous(), "test fixture must be strided");
1265        let r = slice.determine_load_bearing();
1266        // analyzed bool removed
1267        assert_eq!(r.uses_alpha, Some(false));
1268        assert_eq!(r.uses_chroma, Some(false));
1269        let target = r.apply_to(&slice.descriptor());
1270        assert_eq!(target.format, PixelFormat::Gray8);
1271    }
1272
1273    #[test]
1274    fn strided_rgba8_garbage_padding_doesnt_poison_predicates() {
1275        // Pixel content makes the buffer NOT all-opaque (alpha=128).
1276        // The padding bytes (0xCD) would falsely look like "alpha != 255"
1277        // if the predicate accidentally read them. Verify the trait
1278        // dispatch reads only pixel bytes, not stride.
1279        let (buf, stride) = build_strided_rgba8(8, 3, 16, |_x, _y| [50, 50, 50, 255]);
1280        let slice = slice_from_strided(&buf, 8, 3, stride, PixelFormat::Rgba8);
1281        let r = slice.determine_load_bearing();
1282        assert_eq!(
1283            r.uses_alpha,
1284            Some(false),
1285            "alpha is uniformly 255 -- must not be confused by 0xCD padding"
1286        );
1287        // Same buffer but with one real non-opaque pixel -- predicate should fire.
1288        let (buf, stride) = build_strided_rgba8(8, 3, 16, |x, y| {
1289            if x == 2 && y == 1 {
1290                [10, 10, 10, 0]
1291            } else {
1292                [50, 50, 50, 255]
1293            }
1294        });
1295        let slice = slice_from_strided(&buf, 8, 3, stride, PixelFormat::Rgba8);
1296        let r = slice.determine_load_bearing();
1297        assert_eq!(
1298            r.uses_alpha,
1299            Some(true),
1300            "real transparent pixel must be detected"
1301        );
1302    }
1303
1304    #[test]
1305    fn strided_rgba8_try_reduce_produces_tight_output() {
1306        // 4 rows × 4 pixels grayscale opaque → reduces to Gray8 tight.
1307        let (buf, stride) = build_strided_rgba8(4, 4, 16, |x, y| {
1308            let g = ((x + y) * 20) as u8;
1309            [g, g, g, 255]
1310        });
1311        let slice = slice_from_strided(&buf, 4, 4, stride, PixelFormat::Rgba8);
1312        let out = slice
1313            .try_reduce_to_load_bearing_format()
1314            .expect("strided buffer should reduce");
1315        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
1316        // Logical content survives independent of the output stride.
1317        let view = out.as_slice();
1318        for y in 0..4u32 {
1319            let row = view.row(y);
1320            for (x, &g) in row.iter().enumerate() {
1321                let expected = ((x as u32 + y) * 20) as u8;
1322                assert_eq!(g, expected, "gray byte at ({x},{y}) wrong");
1323            }
1324        }
1325    }
1326
1327    #[test]
1328    fn strided_rgba8_matches_contiguous_result() {
1329        // Build the same logical content as a contiguous and a strided
1330        // slice; verify the report is identical.
1331        fn fill(x: u32, y: u32) -> [u8; 4] {
1332            [(x * 30) as u8, (y * 50) as u8, ((x + y) * 11) as u8, 255]
1333        }
1334        let width = 6;
1335        let height = 5;
1336
1337        // Contiguous version
1338        let mut contig = Vec::with_capacity(width as usize * height as usize * 4);
1339        for y in 0..height {
1340            for x in 0..width {
1341                contig.extend_from_slice(&fill(x, y));
1342            }
1343        }
1344        let contig_slice = make_slice(&contig, width, height, PixelFormat::Rgba8);
1345
1346        // Strided version (with garbage padding)
1347        let (strided, stride) = build_strided_rgba8(width, height, 24, fill);
1348        let strided_slice = slice_from_strided(&strided, width, height, stride, PixelFormat::Rgba8);
1349
1350        let r_contig = contig_slice.determine_load_bearing();
1351        let r_strided = strided_slice.determine_load_bearing();
1352
1353        // Compare every analytical field.
1354        assert_eq!(r_contig.any_analyzed(), r_strided.any_analyzed());
1355        assert_eq!(r_contig.uses_alpha, r_strided.uses_alpha);
1356        assert_eq!(r_contig.uses_chroma, r_strided.uses_chroma);
1357        assert_eq!(r_contig.uses_low_bits, r_strided.uses_low_bits);
1358    }
1359
1360    // ── F32 load_bearing tests ────────────────────────────────
1361
1362    fn make_f32_slice<'a>(
1363        bytes: &'a [u8],
1364        width: u32,
1365        height: u32,
1366        format: PixelFormat,
1367        transfer: TransferFunction,
1368    ) -> PixelSlice<'a> {
1369        let descriptor = PixelDescriptor::from_pixel_format(format).with_transfer(transfer);
1370        let stride = width as usize * format.bytes_per_pixel();
1371        PixelSlice::new(bytes, width, height, stride, descriptor).unwrap()
1372    }
1373
1374    #[test]
1375    fn rgba_f32_all_opaque_gray_reduces_to_gray_f32() {
1376        // 4 RGBA f32 pixels: gray + opaque.
1377        let pixels: [f32; 16] = [
1378            0.1, 0.1, 0.1, 1.0, //
1379            0.5, 0.5, 0.5, 1.0, //
1380            0.9, 0.9, 0.9, 1.0, //
1381            0.0, 0.0, 0.0, 1.0,
1382        ];
1383        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1384        let slice = make_f32_slice(bytes, 4, 1, PixelFormat::RgbaF32, TransferFunction::Linear);
1385        let r = slice.determine_load_bearing();
1386        // analyzed bool removed
1387        assert_eq!(r.uses_alpha, Some(false));
1388        assert_eq!(r.uses_chroma, Some(false));
1389
1390        let target = r.apply_to(&slice.descriptor());
1391        assert_eq!(target.format, PixelFormat::GrayF32);
1392    }
1393
1394    #[test]
1395    fn rgba_f32_with_real_color_reduces_to_rgb_f32() {
1396        let pixels: [f32; 16] = [
1397            0.1, 0.2, 0.3, 1.0, 0.4, 0.5, 0.6, 1.0, 0.7, 0.8, 0.9, 1.0, 0.0, 0.5, 1.0, 1.0,
1398        ];
1399        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1400        let slice = make_f32_slice(bytes, 4, 1, PixelFormat::RgbaF32, TransferFunction::Linear);
1401        let r = slice.determine_load_bearing();
1402        assert_eq!(r.uses_alpha, Some(false));
1403        assert_eq!(r.uses_chroma, Some(true));
1404
1405        let target = r.apply_to(&slice.descriptor());
1406        assert_eq!(target.format, PixelFormat::RgbF32);
1407    }
1408
1409    #[test]
1410    fn rgba_f32_with_intermediate_alpha_keeps_alpha() {
1411        let pixels: [f32; 12] = [0.5, 0.5, 0.5, 0.25, 0.7, 0.7, 0.7, 0.5, 0.3, 0.3, 0.3, 0.75];
1412        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1413        let slice = make_f32_slice(bytes, 3, 1, PixelFormat::RgbaF32, TransferFunction::Linear);
1414        let r = slice.determine_load_bearing();
1415        assert_eq!(r.uses_alpha, Some(true));
1416        assert_eq!(r.uses_chroma, Some(false));
1417
1418        let target = r.apply_to(&slice.descriptor());
1419        assert_eq!(target.format, PixelFormat::GrayAF32);
1420    }
1421
1422    #[test]
1423    fn try_reduce_rgba_f32_to_gray_f32() {
1424        let pixels: [f32; 16] = [
1425            0.1, 0.1, 0.1, 1.0, //
1426            0.5, 0.5, 0.5, 1.0, //
1427            0.9, 0.9, 0.9, 1.0, //
1428            0.4, 0.4, 0.4, 1.0,
1429        ];
1430        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1431        let slice = make_f32_slice(bytes, 4, 1, PixelFormat::RgbaF32, TransferFunction::Linear);
1432        let out = slice
1433            .try_reduce_to_load_bearing_format()
1434            .expect("should reduce");
1435        assert_eq!(out.descriptor().format, PixelFormat::GrayF32);
1436        let view = out.as_slice();
1437        let gray: &[f32] = bytemuck::cast_slice(view.row(0));
1438        assert_eq!(gray, &[0.1, 0.5, 0.9, 0.4]);
1439    }
1440
1441    #[test]
1442    fn linear_f32_wide_primaries_reduce_keeps_tag_and_values() {
1443        // P3-tagged linear f32 gray+opaque: reduces structurally
1444        // (alpha drop + chroma collapse) with values untouched and the
1445        // P3 tag carried over -- no primaries re-tag, no matrix.
1446        let pixels: [f32; 16] = [
1447            0.5, 0.5, 0.5, 1.0, 0.25, 0.25, 0.25, 1.0, 0.75, 0.75, 0.75, 1.0, 0.1, 0.1, 0.1, 1.0,
1448        ];
1449        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1450        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::RgbaF32)
1451            .with_transfer(TransferFunction::Linear)
1452            .with_primaries(ColorPrimaries::DisplayP3);
1453        let slice = PixelSlice::new(bytes, 4, 1, 4 * 16, descriptor).unwrap();
1454        let out = slice
1455            .try_reduce_to_load_bearing_format()
1456            .expect("should reduce");
1457        assert_eq!(out.descriptor().format, PixelFormat::GrayF32);
1458        assert_eq!(out.descriptor().primaries, ColorPrimaries::DisplayP3);
1459        let view = out.as_slice();
1460        let gray: &[f32] = bytemuck::cast_slice(view.row(0));
1461        assert_eq!(gray, &[0.5_f32, 0.25, 0.75, 0.1], "values bit-exact");
1462    }
1463
1464    #[test]
1465    fn ga_f32_opaque_reduces_to_gray_f32() {
1466        let pixels: [f32; 6] = [0.1, 1.0, 0.5, 1.0, 0.9, 1.0];
1467        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1468        let slice = make_f32_slice(bytes, 3, 1, PixelFormat::GrayAF32, TransferFunction::Linear);
1469        assert_eq!(reduced(&slice).format, PixelFormat::GrayF32);
1470    }
1471
1472    #[test]
1473    fn rgb_f32_grayscale_reduces_to_gray_f32() {
1474        let pixels: [f32; 9] = [0.1, 0.1, 0.1, 0.5, 0.5, 0.5, 0.9, 0.9, 0.9];
1475        let bytes: &[u8] = bytemuck::cast_slice(&pixels);
1476        let slice = make_f32_slice(bytes, 3, 1, PixelFormat::RgbF32, TransferFunction::Linear);
1477        assert_eq!(reduced(&slice).format, PixelFormat::GrayF32);
1478    }
1479
1480    // ── Edge cases: idempotency ───────────────────────────────
1481    //
1482    // apply_to a report twice should be idempotent -- running the
1483    // narrower descriptor through the same report shouldn't narrow
1484    // further (it's already at the report's target). This catches
1485    // bugs where apply_to has hidden state or order-dependent loops.
1486
1487    #[test]
1488    fn apply_to_is_idempotent() {
1489        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
1490        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8);
1491        let r = slice.determine_load_bearing();
1492        let target_a = r.apply_to(&slice.descriptor());
1493        let target_b = r.apply_to(&target_a);
1494        assert_eq!(
1495            target_a, target_b,
1496            "apply_to twice must equal apply_to once"
1497        );
1498    }
1499
1500    #[test]
1501    fn apply_to_no_op_on_already_minimal_gray8() {
1502        // Gray8 has nothing to reduce -- report says everything is
1503        // false / None, apply_to should return the source unchanged.
1504        let bytes = [50u8, 100, 150, 200];
1505        let slice = make_slice(&bytes, 4, 1, PixelFormat::Gray8);
1506        let r = slice.determine_load_bearing();
1507        assert_eq!(r.uses_alpha, Some(false));
1508        assert_eq!(r.uses_chroma, Some(false));
1509        assert_eq!(r.uses_low_bits, Some(false));
1510        let target = r.apply_to(&slice.descriptor());
1511        assert_eq!(target, slice.descriptor());
1512    }
1513
1514    // ── Edge cases: trait method consistency ──────────────────
1515    //
1516    // try_reduce_to_load_bearing_format's returned descriptor should
1517    // match report.apply_to(descriptor). Running them independently
1518    // must produce the same target.
1519
1520    #[test]
1521    fn try_reduce_descriptor_matches_determine_reduced() {
1522        let bytes: Vec<u8> = (0..8).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
1523        let slice = make_slice(&bytes, 8, 1, PixelFormat::Rgba8);
1524        let determined = reduced(&slice);
1525        let out = slice.try_reduce_to_load_bearing_format().unwrap();
1526        assert_eq!(determined, out.descriptor());
1527    }
1528
1529    #[test]
1530    fn try_reduce_returns_none_when_descriptor_unchanged() {
1531        // Gray8 with 8-bit-needing values -- nothing to reduce.
1532        let bytes = [50u8, 100, 150, 200];
1533        let slice = make_slice(&bytes, 4, 1, PixelFormat::Gray8);
1534        assert!(slice.try_reduce_to_load_bearing_format().is_none());
1535        // The combiner agrees: same descriptor back.
1536        assert_eq!(reduced(&slice), slice.descriptor());
1537    }
1538
1539    // ── Edge cases: 1×1 / single-row / single-column inputs ───
1540
1541    #[test]
1542    fn single_pixel_inputs_for_each_layout() {
1543        // 1×1 Rgba8: opaque + gray → reduces to Gray8.
1544        let s = make_slice(&[100u8, 100, 100, 255], 1, 1, PixelFormat::Rgba8);
1545        assert_eq!(reduced(&s).format, PixelFormat::Gray8);
1546
1547        // 1×1 Rgb8 with R=G=B → reduces to Gray8.
1548        let s = make_slice(&[42u8, 42, 42], 1, 1, PixelFormat::Rgb8);
1549        assert_eq!(reduced(&s).format, PixelFormat::Gray8);
1550
1551        // 1×1 GrayA8 opaque → Gray8.
1552        let s = make_slice(&[42u8, 255], 1, 1, PixelFormat::GrayA8);
1553        assert_eq!(reduced(&s).format, PixelFormat::Gray8);
1554
1555        // 1×1 Gray8 -- no reduction available.
1556        let s = make_slice(&[42u8], 1, 1, PixelFormat::Gray8);
1557        assert_eq!(reduced(&s), s.descriptor());
1558    }
1559
1560    #[test]
1561    fn single_row_tall_buffer() {
1562        // 1 row, many cols -- exercises the per-row loop with one pass.
1563        let bytes: Vec<u8> = (0..32).flat_map(|i| [i * 7, i * 7, i * 7, 255]).collect();
1564        let s = make_slice(&bytes, 32, 1, PixelFormat::Rgba8);
1565        assert_eq!(reduced(&s).format, PixelFormat::Gray8);
1566    }
1567
1568    #[test]
1569    fn single_col_tall_buffer() {
1570        // 1 col, many rows -- heavily strided territory.
1571        let height = 16u32;
1572        let width = 1u32;
1573        let stride = 32; // 1 byte content + 31 bytes padding per row
1574        let mut buf = vec![0xAAu8; stride * height as usize];
1575        for y in 0..height {
1576            buf[y as usize * stride] = (y * 7) as u8;
1577        }
1578        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Gray8)
1579            .with_transfer(TransferFunction::Srgb);
1580        let s = PixelSlice::new(&buf, width, height, stride, descriptor).unwrap();
1581        assert!(!s.is_contiguous());
1582        // Should run without panicking; at least the structural
1583        // bools (alpha, chroma) populate for any U8 layout.
1584        assert!(s.determine_load_bearing().any_analyzed());
1585    }
1586
1587    // ── Edge cases: full PixelFormat matrix ──────────────────
1588    //
1589    // Every PixelFormat is either analyzed=true (predicates run) or
1590    // analyzed=false (explicit unsupported). No format should panic.
1591
1592    fn dummy_bytes_for(format: PixelFormat) -> Vec<u8> {
1593        // 1×1 buffer of the right byte size, all zeros.
1594        vec![0u8; format.bytes_per_pixel()]
1595    }
1596
1597    #[test]
1598    fn analyzed_status_for_every_pixel_format() {
1599        // U8 layouts: should analyze (all have SIMD predicate paths).
1600        for fmt in [
1601            PixelFormat::Rgb8,
1602            PixelFormat::Rgba8,
1603            PixelFormat::Bgra8,
1604            PixelFormat::Gray8,
1605            PixelFormat::GrayA8,
1606        ] {
1607            let bytes = dummy_bytes_for(fmt);
1608            let s = make_slice(&bytes, 1, 1, fmt);
1609            assert!(
1610                s.determine_load_bearing().any_analyzed(),
1611                "{fmt:?} should produce at least one Some field"
1612            );
1613        }
1614        // U16 layouts: should analyze.
1615        for fmt in [
1616            PixelFormat::Rgb16,
1617            PixelFormat::Rgba16,
1618            PixelFormat::Gray16,
1619            PixelFormat::GrayA16,
1620        ] {
1621            let bytes = dummy_bytes_for(fmt);
1622            let s = make_slice(&bytes, 1, 1, fmt);
1623            assert!(
1624                s.determine_load_bearing().any_analyzed(),
1625                "{fmt:?} should produce at least one Some field"
1626            );
1627        }
1628        // F32 RGB(A) / GA -- should analyze.
1629        for fmt in [
1630            PixelFormat::RgbF32,
1631            PixelFormat::RgbaF32,
1632            PixelFormat::GrayAF32,
1633        ] {
1634            let bytes = dummy_bytes_for(fmt);
1635            let s = make_slice(&bytes, 1, 1, fmt);
1636            assert!(
1637                s.determine_load_bearing().any_analyzed(),
1638                "{fmt:?} should produce at least one Some field"
1639            );
1640        }
1641        // Gray-layout formats analyze trivially regardless of channel
1642        // type -- there's no chroma or alpha to test (those fields are
1643        // structurally absent), so the report's bools are valid even
1644        // for channel types whose byte-level predicates aren't wired.
1645        for fmt in [PixelFormat::GrayF32, PixelFormat::GrayF16] {
1646            let bytes = dummy_bytes_for(fmt);
1647            let s = make_slice(&bytes, 1, 1, fmt);
1648            // Gray-layout formats produce Some(false) for both
1649            // alpha and chroma regardless of channel type -- the
1650            // structural answer is valid even when channel-type
1651            // predicates aren't wired.
1652            let r = s.determine_load_bearing();
1653            assert_eq!(r.uses_alpha, Some(false), "{fmt:?} alpha");
1654            assert_eq!(r.uses_chroma, Some(false), "{fmt:?} chroma");
1655        }
1656
1657        // F16 / Oklab / CMYK with non-trivial layouts -- unanalyzed for
1658        // v0 because their byte-level predicates aren't wired yet.
1659        for fmt in [
1660            PixelFormat::RgbF16,
1661            PixelFormat::RgbaF16,
1662            PixelFormat::GrayAF16,
1663            PixelFormat::OklabF32,
1664            PixelFormat::OklabaF32,
1665            PixelFormat::Cmyk8,
1666        ] {
1667            let bytes = dummy_bytes_for(fmt);
1668            let s = make_slice(&bytes, 1, 1, fmt);
1669            let r = s.determine_load_bearing();
1670            // No predicate ran for this layout × channel-type combo --
1671            // every field stays None.
1672            assert_eq!(r.uses_alpha, None, "{fmt:?} alpha should be None");
1673            assert_eq!(r.uses_chroma, None, "{fmt:?} chroma should be None");
1674        }
1675    }
1676
1677    // ── Edge cases: Bgra alpha-drop reorder ───────────────────
1678
1679    #[test]
1680    fn bgra8_opaque_color_reduces_to_rgb8_with_reorder() {
1681        // Bgra stores B,G,R,A. Alpha-drop narrows to Rgb -- and the
1682        // buffer rewrite must reorder channels, not prefix-copy.
1683        // Pixel 0: B=50, G=100, R=150; pixel 1: B=60, G=110, R=160.
1684        let bytes = [50u8, 100, 150, 255, 60, 110, 160, 255];
1685        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Bgra8)
1686            .with_transfer(TransferFunction::Srgb);
1687        let s = PixelSlice::new(&bytes, 2, 1, 8, descriptor).unwrap();
1688        let r = s.determine_load_bearing();
1689        assert_eq!(r.uses_alpha, Some(false));
1690        assert_eq!(r.uses_chroma, Some(true));
1691        let target = r.apply_to(&s.descriptor());
1692        assert_eq!(target.format, PixelFormat::Rgb8);
1693
1694        let out = s
1695            .try_reduce_to_load_bearing_format()
1696            .expect("opaque Bgra8 should reduce");
1697        assert_eq!(out.descriptor().format, PixelFormat::Rgb8);
1698        assert_eq!(
1699            out.as_slice().row(0),
1700            &[150u8, 100, 50, 160, 110, 60],
1701            "B,G,R,A → R,G,B requires the B↔R swap"
1702        );
1703    }
1704
1705    #[test]
1706    fn bgra8_grayscale_collapses_to_gray_alpha8() {
1707        // R==G==B, alpha varying -- should collapse to GrayA8 even
1708        // for Bgra8 source (chroma drop, alpha kept).
1709        let bytes = [42u8, 42, 42, 100, 99, 99, 99, 200];
1710        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Bgra8)
1711            .with_transfer(TransferFunction::Srgb);
1712        let s = PixelSlice::new(&bytes, 2, 1, 8, descriptor).unwrap();
1713        let r = s.determine_load_bearing();
1714        assert_eq!(reduced(&s).format, PixelFormat::GrayA8);
1715        assert_eq!(r.uses_chroma, Some(false));
1716        // Rewrite keeps gray + alpha pairs.
1717        let out = s.try_reduce_to_load_bearing_format().unwrap();
1718        assert_eq!(out.descriptor().format, PixelFormat::GrayA8);
1719        assert_eq!(out.as_slice().row(0), &[42u8, 100, 99, 200]);
1720    }
1721
1722    // ── Edge cases: report.fully_load_bearing as starting state ─
1723
1724    #[test]
1725    fn fully_load_bearing_apply_to_is_identity() {
1726        // Default report → no narrowing. apply_to produces input.
1727        let r = LoadBearingReport::default();
1728        for fmt in [
1729            PixelFormat::Rgb8,
1730            PixelFormat::Rgba8,
1731            PixelFormat::Rgba16,
1732            PixelFormat::GrayAF32,
1733        ] {
1734            let src = PixelDescriptor::from_pixel_format(fmt);
1735            assert_eq!(r.apply_to(&src), src, "{fmt:?} identity broke");
1736        }
1737    }
1738
1739    // ── Edge cases: zero-row buffers ─────────────────────────
1740    //
1741    // 0×0 / 0×N / N×0 buffers -- width or rows = 0 means no pixels.
1742    // Predicates should return vacuous-true; report should still run.
1743
1744    #[test]
1745    fn zero_pixel_buffer_analyzes_with_vacuous_truth() {
1746        // Empty bytes via a 0×0 image (no rows, no width).
1747        let bytes: [u8; 0] = [];
1748        // PixelSlice may not allow width=0 directly; build a 1-row
1749        // slice with 0 effective width via stride.
1750        // Use rows=1, width=0 if validate_slice allows.
1751        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1752            .with_transfer(TransferFunction::Srgb);
1753        if let Ok(s) = PixelSlice::new(&bytes, 0, 0, 0, descriptor) {
1754            let r = s.determine_load_bearing();
1755            // No pixels: every "uses" is vacuously false (nothing to
1756            // demand the channel).
1757            // analyzed bool removed
1758            assert_eq!(r.uses_alpha, Some(false));
1759            assert_eq!(r.uses_chroma, Some(false));
1760        }
1761        // Some validators reject 0-dimensional descriptors. If so,
1762        // we don't lose semantics -- codecs won't see this case in
1763        // practice. The test passes either way.
1764    }
1765
1766    // ── Sanity: every layout's reduced format round-trips ────
1767
1768    // ── In-place reduction (PixelBuffer entry + transform impl) ───
1769
1770    /// Tight-stride PixelBuffer fixture with an sRGB-tagged descriptor.
1771    fn lb_buf(bytes: &[u8], width: u32, height: u32, format: PixelFormat) -> PixelBuffer {
1772        let descriptor =
1773            PixelDescriptor::from_pixel_format(format).with_transfer(TransferFunction::Srgb);
1774        PixelBuffer::from_vec(bytes.to_vec(), width, height, descriptor).unwrap()
1775    }
1776
1777    #[test]
1778    fn in_place_rgba8_gray_opaque_force_true_compacts_to_gray8() {
1779        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
1780        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8);
1781        buf.reduce_to_load_bearing_format_in_place(true);
1782        assert_eq!(buf.descriptor().format, PixelFormat::Gray8);
1783        assert_eq!(buf.stride(), 4, "tight stride");
1784        assert_eq!(buf.as_slice().row(0), &[0u8, 30, 60, 90]);
1785    }
1786
1787    #[test]
1788    fn in_place_rgba8_gray_opaque_force_false_keeps_alpha_lane() {
1789        // Chroma collapse still rewrites, but the (non-load-bearing)
1790        // alpha lane stays in the layout and re-tags Opaque.
1791        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
1792        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8);
1793        buf.reduce_to_load_bearing_format_in_place(false);
1794        assert_eq!(buf.descriptor().format, PixelFormat::GrayA8);
1795        assert_eq!(buf.descriptor().alpha, Some(AlphaMode::Opaque));
1796        assert_eq!(
1797            buf.as_slice().row(0),
1798            &[0u8, 255, 30, 255, 60, 255, 90, 255]
1799        );
1800    }
1801
1802    #[test]
1803    fn in_place_colorful_opaque_force_false_is_retag_only() {
1804        let original: Vec<u8> = (0..4i32)
1805            .flat_map(|i| {
1806                [
1807                    (i * 60 + 10) as u8,
1808                    (i * 30 + 50) as u8,
1809                    (i * 90 + 20) as u8,
1810                    255,
1811                ]
1812            })
1813            .collect();
1814        let mut buf = lb_buf(&original, 4, 1, PixelFormat::Rgba8);
1815        let in_stride = buf.stride();
1816        buf.reduce_to_load_bearing_format_in_place(false);
1817        assert_eq!(buf.descriptor().format, PixelFormat::Rgba8, "layout kept");
1818        assert_eq!(
1819            buf.descriptor().alpha,
1820            Some(AlphaMode::Opaque),
1821            "scanned-opaque straight alpha upgrades to the Opaque contract"
1822        );
1823        assert_eq!(buf.stride(), in_stride, "no bytes moved");
1824        assert_eq!(buf.as_slice().row(0), &original[..], "no bytes changed");
1825    }
1826
1827    #[test]
1828    fn in_place_colorful_opaque_force_true_drops_alpha() {
1829        let bytes: Vec<u8> = (0..4i32)
1830            .flat_map(|i| {
1831                [
1832                    (i * 60 + 10) as u8,
1833                    (i * 30 + 50) as u8,
1834                    (i * 90 + 20) as u8,
1835                    255,
1836                ]
1837            })
1838            .collect();
1839        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8);
1840        buf.reduce_to_load_bearing_format_in_place(true);
1841        assert_eq!(buf.descriptor().format, PixelFormat::Rgb8);
1842        assert_eq!(
1843            buf.as_slice().row(0),
1844            &[10u8, 50, 20, 70, 80, 110, 130, 110, 200, 190, 140, 34]
1845        );
1846    }
1847
1848    #[test]
1849    fn in_place_bgra8_force_true_reorders_to_rgb8() {
1850        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Bgra8)
1851            .with_transfer(TransferFunction::Srgb);
1852        let mut buf = PixelBuffer::from_vec(
1853            vec![50u8, 100, 150, 255, 60, 110, 160, 255],
1854            2,
1855            1,
1856            descriptor,
1857        )
1858        .unwrap();
1859        buf.reduce_to_load_bearing_format_in_place(true);
1860        assert_eq!(buf.descriptor().format, PixelFormat::Rgb8);
1861        assert_eq!(buf.as_slice().row(0), &[150u8, 100, 50, 160, 110, 60]);
1862    }
1863
1864    #[test]
1865    fn in_place_rgba16_replicated_gray_opaque_both_force_modes() {
1866        let build = |i: u8| {
1867            let g = i * 60;
1868            [g, g, g, g, g, g, 0xFF, 0xFF]
1869        };
1870        let bytes: Vec<u8> = (0..4).flat_map(build).collect();
1871        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba16);
1872        buf.reduce_to_load_bearing_format_in_place(true);
1873        assert_eq!(buf.descriptor().format, PixelFormat::Gray8);
1874        assert_eq!(buf.as_slice().row(0), &[0u8, 60, 120, 180]);
1875
1876        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba16);
1877        buf.reduce_to_load_bearing_format_in_place(false);
1878        assert_eq!(buf.descriptor().format, PixelFormat::GrayA8);
1879        assert_eq!(buf.descriptor().alpha, Some(AlphaMode::Opaque));
1880        assert_eq!(
1881            buf.as_slice().row(0),
1882            &[0u8, 255, 60, 255, 120, 255, 180, 255]
1883        );
1884    }
1885
1886    #[test]
1887    fn in_place_gray16_replicated_single_row_overlap_path() {
1888        // Gray16 -> Gray8 on one row: dst and src spans share the same
1889        // start, so the entire row takes the overlapping per-pixel path.
1890        let bytes: Vec<u8> = (0..64u16).flat_map(|i| [(i * 4) as u8; 2]).collect();
1891        let mut buf = lb_buf(&bytes, 64, 1, PixelFormat::Gray16);
1892        buf.reduce_to_load_bearing_format_in_place(true);
1893        assert_eq!(buf.descriptor().format, PixelFormat::Gray8);
1894        let expected: Vec<u8> = (0..64u16).map(|i| (i * 4) as u8).collect();
1895        assert_eq!(buf.as_slice().row(0), &expected[..]);
1896    }
1897
1898    #[test]
1899    fn in_place_undefined_padding_retag_vs_restructure() {
1900        // RGBX padding: force=false leaves the buffer alone entirely
1901        // (the Undefined tag already says "ignore the lane" -- it must
1902        // NOT be upgraded to Opaque, the bytes are garbage).
1903        let original = [10u8, 20, 30, 0x7B, 40, 50, 60, 0x01];
1904        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1905            .with_transfer(TransferFunction::Srgb)
1906            .with_alpha(Some(AlphaMode::Undefined));
1907        let mut buf = PixelBuffer::from_vec(original.to_vec(), 2, 1, descriptor).unwrap();
1908        buf.reduce_to_load_bearing_format_in_place(false);
1909        assert_eq!(buf.descriptor(), descriptor, "fully unchanged");
1910        assert_eq!(buf.as_slice().row(0), &original[..]);
1911
1912        let mut buf = PixelBuffer::from_vec(original.to_vec(), 2, 1, descriptor).unwrap();
1913        buf.reduce_to_load_bearing_format_in_place(true);
1914        assert_eq!(buf.descriptor().format, PixelFormat::Rgb8);
1915        assert_eq!(buf.as_slice().row(0), &[10u8, 20, 30, 40, 50, 60]);
1916    }
1917
1918    #[test]
1919    fn in_place_load_bearing_alpha_is_untouched() {
1920        // Varying premultiplied alpha + real chroma: nothing reduces,
1921        // both force modes leave the buffer unchanged.
1922        let original = [10u8, 20, 30, 128, 40, 50, 60, 64];
1923        for force in [false, true] {
1924            let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1925                .with_transfer(TransferFunction::Srgb)
1926                .with_alpha(Some(AlphaMode::Premultiplied));
1927            let mut buf = PixelBuffer::from_vec(original.to_vec(), 2, 1, descriptor).unwrap();
1928            buf.reduce_to_load_bearing_format_in_place(force);
1929            assert_eq!(buf.descriptor(), descriptor, "force={force}");
1930            assert_eq!(buf.as_slice().row(0), &original[..]);
1931        }
1932    }
1933
1934    #[test]
1935    fn reduce_impl_strided_input_compacts_like_allocating() {
1936        // Arbitrary stride padding isn't constructible through the
1937        // public buffer constructors, so the strided path is pinned at
1938        // the transform level via a hand-built InPlacePixels.
1939        let (buf, stride) = build_strided_rgba8(5, 4, 24, |x, y| {
1940            let g = ((x + y) * 19) as u8;
1941            [g, g, g, 255]
1942        });
1943        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::Rgba8)
1944            .with_transfer(TransferFunction::Srgb);
1945        let reference = PixelSlice::new(&buf, 5, 4, stride, descriptor)
1946            .unwrap()
1947            .try_reduce_to_load_bearing_format()
1948            .expect("reduces");
1949
1950        let mut mut_buf = buf.clone();
1951        let out = reduce_in_place_impl(
1952            InPlacePixels::new(&mut mut_buf, 5, 4, stride, descriptor, None),
1953            true,
1954        );
1955        assert_eq!(out.descriptor(), reference.descriptor());
1956        for y in 0..4 {
1957            assert_eq!(out.row(y), reference.as_slice().row(y), "row {y}");
1958        }
1959    }
1960
1961    #[test]
1962    fn in_place_matches_allocating_across_geometries() {
1963        // Differential: every (width, rows) drives the overlap-prefix /
1964        // disjoint-row split differently; the buffer entry point must be
1965        // byte-identical to the allocating rewrite for every content
1966        // class that triggers a distinct transition.
1967        #[derive(Clone, Copy)]
1968        enum Content {
1969            GrayOpaque,    // Rgba8 -> Gray8 (4 -> 1)
1970            ColorOpaque,   // Rgba8 -> Rgb8  (4 -> 3, garb path)
1971            GrayVaryAlpha, // Rgba8 -> GrayA8 (4 -> 2)
1972            Replicated16,  // Rgba16 -> Gray8 (8 -> 1)
1973        }
1974        let mut lcg: u32 = 0x2F6E_2B1D;
1975        let mut next = move || {
1976            lcg = lcg.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
1977            (lcg >> 24) as u8
1978        };
1979        for content in [
1980            Content::GrayOpaque,
1981            Content::ColorOpaque,
1982            Content::GrayVaryAlpha,
1983            Content::Replicated16,
1984        ] {
1985            for (width, rows) in [
1986                (1u32, 1u32),
1987                (1, 7),
1988                (2, 3),
1989                (3, 2),
1990                (5, 5),
1991                (17, 3),
1992                (64, 4),
1993                (65, 2),
1994            ] {
1995                let (format, bytes): (PixelFormat, Vec<u8>) = match content {
1996                    Content::GrayOpaque => (
1997                        PixelFormat::Rgba8,
1998                        (0..width * rows)
1999                            .flat_map(|_| {
2000                                let g = next();
2001                                [g, g, g, 255]
2002                            })
2003                            .collect(),
2004                    ),
2005                    Content::ColorOpaque => (
2006                        PixelFormat::Rgba8,
2007                        (0..width * rows)
2008                            .flat_map(|_| [next(), next(), next(), 255])
2009                            .collect(),
2010                    ),
2011                    Content::GrayVaryAlpha => (
2012                        PixelFormat::Rgba8,
2013                        (0..width * rows)
2014                            .flat_map(|_| {
2015                                let g = next();
2016                                [g, g, g, next()]
2017                            })
2018                            .collect(),
2019                    ),
2020                    Content::Replicated16 => (
2021                        PixelFormat::Rgba16,
2022                        (0..width * rows)
2023                            .flat_map(|_| {
2024                                let g = next();
2025                                [g, g, g, g, g, g, 0xFF, 0xFF]
2026                            })
2027                            .collect(),
2028                    ),
2029                };
2030                let reference =
2031                    make_slice(&bytes, width, rows, format).try_reduce_to_load_bearing_format();
2032                let mut buf = lb_buf(&bytes, width, rows, format);
2033                buf.reduce_to_load_bearing_format_in_place(true);
2034                match reference {
2035                    Some(reference) => {
2036                        assert_eq!(
2037                            buf.descriptor(),
2038                            reference.descriptor(),
2039                            "{width}x{rows} descriptor"
2040                        );
2041                        for y in 0..rows {
2042                            assert_eq!(
2043                                buf.as_slice().row(y),
2044                                reference.as_slice().row(y),
2045                                "{width}x{rows} row {y}"
2046                            );
2047                        }
2048                    }
2049                    None => {
2050                        // Every content class above guarantees at least
2051                        // one reduction (gray collapse or alpha drop).
2052                        panic!("{width}x{rows} expected a reduction");
2053                    }
2054                }
2055            }
2056        }
2057    }
2058
2059    // ── ColorContext propagation ───────────────────────────────
2060
2061    #[test]
2062    fn color_context_carries_through_class_preserving_reductions() {
2063        // Alpha drop keeps the RGB class -- ICC must travel, unchanged.
2064        let ctx = Arc::new(ColorContext::from_icc(alloc::vec![0u8; 8]));
2065        let bytes: Vec<u8> = (0..4i32)
2066            .flat_map(|i| {
2067                [
2068                    (i * 60 + 10) as u8,
2069                    (i * 30 + 50) as u8,
2070                    (i * 90 + 20) as u8,
2071                    255,
2072                ]
2073            })
2074            .collect();
2075        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2076        let out = slice
2077            .try_reduce_to_load_bearing_format()
2078            .expect("alpha drop available");
2079        assert_eq!(out.descriptor().format, PixelFormat::Rgb8);
2080        assert!(
2081            out.as_slice()
2082                .color_context()
2083                .is_some_and(|c| Arc::ptr_eq(c, &ctx)),
2084            "ICC context must carry over for class-preserving reductions"
2085        );
2086
2087        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2088        buf.reduce_to_load_bearing_format_in_place(true);
2089        assert_eq!(buf.descriptor().format, PixelFormat::Rgb8);
2090        assert!(
2091            buf.as_slice()
2092                .color_context()
2093                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2094        );
2095    }
2096
2097    #[test]
2098    fn icc_context_suppresses_gray_collapse_but_not_other_reductions() {
2099        // Gray + opaque RGBA with *unidentifiable* ICC bytes attached:
2100        // no CICP description is derivable, so no GRAY-class variant can
2101        // stand in, and the rewrite stops at the class-preserving alpha
2102        // drop. The report itself still measures chroma truthfully.
2103        let ctx = Arc::new(ColorContext::from_icc(alloc::vec![0u8; 8]));
2104        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2105        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2106        assert_eq!(
2107            slice.determine_load_bearing().uses_chroma,
2108            Some(false),
2109            "analysis stays truthful"
2110        );
2111        let out = slice
2112            .try_reduce_to_load_bearing_format()
2113            .expect("alpha drop still available");
2114        assert_eq!(
2115            out.descriptor().format,
2116            PixelFormat::Rgb8,
2117            "gray collapse suppressed, alpha drop kept"
2118        );
2119        assert!(
2120            out.as_slice()
2121                .color_context()
2122                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2123        );
2124
2125        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2126        buf.reduce_to_load_bearing_format_in_place(true);
2127        assert_eq!(buf.descriptor().format, PixelFormat::Rgb8);
2128        assert!(
2129            buf.as_slice()
2130                .color_context()
2131                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2132        );
2133    }
2134
2135    #[test]
2136    fn cicp_only_context_carries_through_gray_collapse() {
2137        // CICP has no device-class to violate: primaries/transfer stay
2138        // meaningful for gray and matrix coefficients don't apply to
2139        // single-channel data -- the collapse proceeds and the context
2140        // travels.
2141        let ctx = Arc::new(ColorContext::from_cicp(Cicp::DISPLAY_P3));
2142        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2143        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2144        let out = slice
2145            .try_reduce_to_load_bearing_format()
2146            .expect("gray collapse available");
2147        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
2148        assert!(
2149            out.as_slice()
2150                .color_context()
2151                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2152        );
2153
2154        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2155        buf.reduce_to_load_bearing_format_in_place(true);
2156        assert_eq!(buf.descriptor().format, PixelFormat::Gray8);
2157        assert!(
2158            buf.as_slice()
2159                .color_context()
2160                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2161        );
2162    }
2163
2164    // ── Gray-class ICC swap on collapse ────────────────────────────
2165
2166    /// Assert the context carries a GRAY-class ICC (header bytes 16..20).
2167    fn assert_gray_class_icc(ctx: Option<&Arc<ColorContext>>) -> Arc<[u8]> {
2168        let icc = ctx
2169            .expect("reduced buffer must carry a context")
2170            .icc
2171            .clone()
2172            .expect("swapped context must hold ICC bytes");
2173        assert_eq!(&icc[16..20], b"GRAY", "swapped profile must be GRAY-class");
2174        assert_eq!(&icc[36..40], b"acsp", "swapped profile must be a valid ICC");
2175        icc
2176    }
2177
2178    #[cfg(feature = "icc-db")]
2179    #[test]
2180    fn icc_with_cicp_swaps_to_gray_class_profile_on_collapse() {
2181        // Both fields populated: the cicp field describes the attached
2182        // RGB profile, so the collapse swaps in the GRAY-class synthesis
2183        // for that CICP and keeps the cicp alongside.
2184        let mut both = ColorContext::from_icc(alloc::vec![0u8; 8]);
2185        both.cicp = Some(Cicp::DISPLAY_P3);
2186        let ctx = Arc::new(both);
2187        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2188
2189        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2190        let out = slice
2191            .try_reduce_to_load_bearing_format()
2192            .expect("gray collapse available via gray-ICC swap");
2193        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
2194        let swapped = assert_gray_class_icc(out.as_slice().color_context());
2195        assert_eq!(
2196            out.as_slice().color_context().unwrap().cicp,
2197            Some(Cicp::DISPLAY_P3),
2198            "source cicp must ride along"
2199        );
2200
2201        // The buffer entry point produces the same signaling.
2202        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2203        buf.reduce_to_load_bearing_format_in_place(true);
2204        assert_eq!(buf.descriptor().format, PixelFormat::Gray8);
2205        let view = buf.as_slice();
2206        let swapped_in_place = assert_gray_class_icc(view.color_context());
2207        assert_eq!(swapped_in_place.as_ref(), swapped.as_ref());
2208    }
2209
2210    #[cfg(feature = "icc-db")]
2211    #[test]
2212    fn recognized_rgb_profile_swaps_to_gray_class_on_collapse() {
2213        // ICC-only context holding the bundled Display-P3 profile: the
2214        // normalized-hash identification recognizes it as (P3-D65, sRGB)
2215        // and the collapse swaps in the matching GRAY-class profile.
2216        let ctx = Arc::new(ColorContext::from_icc(
2217            crate::icc_profiles::DISPLAY_P3_V4.to_vec(),
2218        ));
2219        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2220        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2221        let out = slice
2222            .try_reduce_to_load_bearing_format()
2223            .expect("gray collapse available via identification");
2224        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
2225        assert_gray_class_icc(out.as_slice().color_context());
2226        assert_eq!(
2227            out.as_slice().color_context().unwrap().cicp,
2228            None,
2229            "no cicp on the source context, none invented"
2230        );
2231    }
2232
2233    #[test]
2234    fn srgb_described_icc_drops_to_cicp_only_on_collapse() {
2235        // ICC + cicp where the description is the assumed sRGB default:
2236        // gray output needs no ICC at all -- the swap drops to a
2237        // CICP-only context.
2238        let mut both = ColorContext::from_icc(alloc::vec![0u8; 8]);
2239        both.cicp = Some(Cicp::SRGB);
2240        let ctx = Arc::new(both);
2241        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2242        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2243        let out = slice
2244            .try_reduce_to_load_bearing_format()
2245            .expect("gray collapse available");
2246        assert_eq!(out.descriptor().format, PixelFormat::Gray8);
2247        let new_ctx = out
2248            .as_slice()
2249            .color_context()
2250            .cloned()
2251            .expect("cicp-only context expected");
2252        assert!(new_ctx.icc.is_none(), "sRGB-default gray needs no ICC");
2253        assert_eq!(new_ctx.cicp, Some(Cicp::SRGB));
2254    }
2255
2256    #[test]
2257    fn non_cicp_recognized_profile_still_suppresses_collapse() {
2258        // Adobe RGB has no CICP code points: even if the profile is
2259        // recognized, no gray variant is derivable from the CICP grid --
2260        // the collapse stays suppressed and the original context carries.
2261        let ctx = Arc::new(ColorContext::from_icc(
2262            crate::icc_profiles::ADOBE_RGB.to_vec(),
2263        ));
2264        let bytes: Vec<u8> = (0..4).flat_map(|i| [i * 30, i * 30, i * 30, 255]).collect();
2265        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2266        let out = slice
2267            .try_reduce_to_load_bearing_format()
2268            .expect("alpha drop still available");
2269        assert_eq!(
2270            out.descriptor().format,
2271            PixelFormat::Rgb8,
2272            "collapse suppressed without a CICP-expressible color"
2273        );
2274        assert!(
2275            out.as_slice()
2276                .color_context()
2277                .is_some_and(|c| Arc::ptr_eq(c, &ctx))
2278        );
2279    }
2280
2281    #[test]
2282    fn colorful_content_keeps_original_context_despite_swap_plan() {
2283        // A swappable context on a buffer whose chroma IS load-bearing:
2284        // no collapse happens, so no swap applies -- the original
2285        // context carries through the alpha drop untouched.
2286        let mut both = ColorContext::from_icc(alloc::vec![0u8; 8]);
2287        both.cicp = Some(Cicp::DISPLAY_P3);
2288        let ctx = Arc::new(both);
2289        let bytes: Vec<u8> = (0..4i32)
2290            .flat_map(|i| {
2291                [
2292                    (i * 60 + 10) as u8,
2293                    (i * 30 + 50) as u8,
2294                    (i * 90 + 20) as u8,
2295                    255,
2296                ]
2297            })
2298            .collect();
2299        let slice = make_slice(&bytes, 4, 1, PixelFormat::Rgba8).with_color_context(ctx.clone());
2300        let out = slice
2301            .try_reduce_to_load_bearing_format()
2302            .expect("alpha drop available");
2303        assert_eq!(out.descriptor().format, PixelFormat::Rgb8);
2304        assert!(
2305            out.as_slice()
2306                .color_context()
2307                .is_some_and(|c| Arc::ptr_eq(c, &ctx)),
2308            "no collapse -> original context, not the swap"
2309        );
2310    }
2311
2312    #[test]
2313    fn in_place_rgba_f32_gray_opaque_force_true() {
2314        let pixels: [f32; 16] = [
2315            0.1, 0.1, 0.1, 1.0, //
2316            0.5, 0.5, 0.5, 1.0, //
2317            0.9, 0.9, 0.9, 1.0, //
2318            0.4, 0.4, 0.4, 1.0,
2319        ];
2320        let descriptor = PixelDescriptor::from_pixel_format(PixelFormat::RgbaF32)
2321            .with_transfer(TransferFunction::Linear);
2322        let mut buf =
2323            PixelBuffer::from_vec(bytemuck::cast_slice(&pixels).to_vec(), 4, 1, descriptor)
2324                .unwrap();
2325        buf.reduce_to_load_bearing_format_in_place(true);
2326        assert_eq!(buf.descriptor().format, PixelFormat::GrayF32);
2327        let view = buf.as_slice();
2328        let gray: &[f32] = bytemuck::cast_slice(view.row(0));
2329        assert_eq!(gray, &[0.1_f32, 0.5, 0.9, 0.4], "values bit-exact");
2330    }
2331
2332    #[test]
2333    fn in_place_true_u16_keeps_channel_type() {
2334        // Genuine 16-bit data (lo != hi): no narrowing, and with
2335        // colorful opaque pixels + force=false only the alpha re-tag
2336        // applies.
2337        let bytes: Vec<u8> = (0..4u16)
2338            .flat_map(|i| {
2339                let r = 0x1234 + i * 0x0101;
2340                let g = 0x4567;
2341                let b = 0x89AB;
2342                [r, g, b, 0xFFFF]
2343            })
2344            .flat_map(u16::to_ne_bytes)
2345            .collect();
2346        let mut buf = lb_buf(&bytes, 4, 1, PixelFormat::Rgba16);
2347        buf.reduce_to_load_bearing_format_in_place(false);
2348        assert_eq!(buf.descriptor().format, PixelFormat::Rgba16);
2349        assert_eq!(buf.descriptor().alpha, Some(AlphaMode::Opaque));
2350    }
2351
2352    #[test]
2353    fn every_reduction_target_is_constructable() {
2354        // For each non-trivial reduction, build a buffer that triggers
2355        // it and verify try_reduce produces a Vec<u8> of the right size.
2356        struct Case {
2357            src: PixelFormat,
2358            bytes: Vec<u8>,
2359            width: u32,
2360            height: u32,
2361            expect_format: PixelFormat,
2362            expect_size: usize,
2363        }
2364        let cases = vec![
2365            Case {
2366                src: PixelFormat::Rgba8,
2367                bytes: vec![10, 10, 10, 255, 20, 20, 20, 255],
2368                width: 2,
2369                height: 1,
2370                expect_format: PixelFormat::Gray8,
2371                expect_size: 2,
2372            },
2373            Case {
2374                src: PixelFormat::Rgba8,
2375                bytes: vec![10, 20, 30, 255, 40, 50, 60, 255],
2376                width: 2,
2377                height: 1,
2378                expect_format: PixelFormat::Rgb8,
2379                expect_size: 6,
2380            },
2381            Case {
2382                src: PixelFormat::GrayA8,
2383                bytes: vec![10, 255, 50, 255],
2384                width: 2,
2385                height: 1,
2386                expect_format: PixelFormat::Gray8,
2387                expect_size: 2,
2388            },
2389            Case {
2390                src: PixelFormat::Rgba16,
2391                bytes: vec![
2392                    10, 10, 10, 10, 10, 10, 0xFF, 0xFF, // gray opaque (bit-rep)
2393                    20, 20, 20, 20, 20, 20, 0xFF, 0xFF,
2394                ],
2395                width: 2,
2396                height: 1,
2397                expect_format: PixelFormat::Gray8,
2398                expect_size: 2,
2399            },
2400        ];
2401        for c in cases {
2402            let s = make_slice(&c.bytes, c.width, c.height, c.src);
2403            let out = s
2404                .try_reduce_to_load_bearing_format()
2405                .unwrap_or_else(|| panic!("{:?} should reduce", c.src));
2406            assert_eq!(
2407                out.descriptor().format,
2408                c.expect_format,
2409                "format from {:?}",
2410                c.src
2411            );
2412            assert_eq!(
2413                out.as_slice().row(0).len(),
2414                c.expect_size,
2415                "row size from {:?}",
2416                c.src
2417            );
2418        }
2419    }
2420}