//! Contrast, on a device, at the two ends of the tonal range. //! //! The descriptor tests check that the fragment says the right words; these //! check what those words do to a pixel. Both failures here passed every //! descriptor test for months, because each is a property of the arithmetic //! at the extremes rather than of the shape of the code. use dr_gpu::{AdjustPass, DemosaicedImage, GpuContext}; use dr_pipeline::descriptor::ParamId; use dr_pipeline::operation::compose; use dr_pipeline::ops; const SIZE: u32 = 8; fn ctx() -> Option { pollster::block_on(GpuContext::new_headless()).ok() } /// A flat frame of one sRGB colour, contrast set to `amount`, rendered and /// read back as the colour of one pixel. fn render(ctx: &GpuContext, rgb: [u8; 3], amount: f32) -> [u8; 3] { let data: Vec = (0..SIZE * SIZE) .flat_map(|_| [rgb[0], rgb[1], rgb[2], 255]) .collect(); let source = DemosaicedImage::from_rgba8(ctx, &data, SIZE, SIZE).expect("upload"); let mut chain = ops::chain(); let op = chain .iter_mut() .find(|o| o.descriptor().id.0 == "contrast") .expect("contrast is in the chain"); op.set_param(ParamId("contrast"), amount); let shader = compose(&chain); let mut adjust = AdjustPass::new(ctx); adjust.render(&source, &shader, SIZE, SIZE).expect("render"); let pixels = adjust.export_pixels().expect("readback").0; [pixels[0], pixels[1], pixels[2]] } /// **The pink-blacks bug.** A near-black pixel whose red and blue sit a count /// above its green — what white-balanced sensor noise in a night shadow looks /// like — must come out grey when contrast is reduced, not magenta. /// /// The ratio form lifted it by a gain of well over a hundred, and a hundred /// times a one-count cast is a saturated colour. #[test] fn reducing_contrast_lifts_a_black_to_grey_not_to_magenta() { let Some(ctx) = ctx() else { eprintln!("no GPU adapter; skipping"); return; }; let [r, g, b] = render(&ctx, [4, 1, 4], -50.0); let spread = r.max(g).max(b) - r.min(g).min(b); assert!( g > 40, "a black at half contrast should be lifted toward grey, got ({r}, {g}, {b})" ); assert!( spread <= 6, "the lift must be neutral: ({r}, {g}, {b}) has a cast of {spread}" ); } /// **The pinned highlights.** A light tone, above twice middle grey, must not /// be pulled down to the top of the curve by the smallest positive contrast. #[test] fn a_little_contrast_leaves_a_highlight_where_it_was() { let Some(ctx) = ctx() else { eprintln!("no GPU adapter; skipping"); return; }; let before = render(&ctx, [230, 230, 230], 0.0)[1]; let after = render(&ctx, [230, 230, 230], 10.0)[1]; assert!( after >= before.saturating_sub(2), "contrast +10 took a highlight from {before} to {after}" ); }