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