diff --git a/core/dr-decode/src/base_curve.rs b/core/dr-decode/src/base_curve.rs index 898db24..5cc19fb 100644 --- a/core/dr-decode/src/base_curve.rs +++ b/core/dr-decode/src/base_curve.rs @@ -487,7 +487,8 @@ mod tests { // And the last span is shallower than the one before it, which is // what a shoulder *is*. Without one the curve clips highlights // harder than the linear rendering did. - let slope = |i: usize| (curve.ys[i + 1] - curve.ys[i]) / (curve.xs[i + 1] - curve.xs[i]); + let slope = + |i: usize| (curve.ys[i + 1] - curve.ys[i]) / (curve.xs[i + 1] - curve.xs[i]); assert!( slope(POINTS - 2) < slope(POINTS - 3), "{name} has no highlight shoulder" @@ -502,13 +503,7 @@ mod tests { // as a dark halo in a gradient, which reads as a rendering fault // rather than as a bad profile. assert_eq!( - BaseCurve::from_points(&[ - [0.0, 0.0], - [0.25, 0.4], - [0.5, 0.3], - [0.75, 0.8], - [1.0, 1.0] - ]), + BaseCurve::from_points(&[[0.0, 0.0], [0.25, 0.4], [0.5, 0.3], [0.75, 0.8], [1.0, 1.0]]), None ); } @@ -540,13 +535,7 @@ mod tests { // point above 1.0 would put the shoulder outside the range the curve // is defined over and silently flatten everything below it. assert_eq!( - BaseCurve::from_points(&[ - [0.0, 0.0], - [0.25, 0.3], - [0.5, 1.4], - [0.75, 1.5], - [1.0, 1.6] - ]), + BaseCurve::from_points(&[[0.0, 0.0], [0.25, 0.3], [0.5, 1.4], [0.75, 1.5], [1.0, 1.6]]), None ); } diff --git a/core/dr-decode/src/lib.rs b/core/dr-decode/src/lib.rs index a591fad..2833ed1 100644 --- a/core/dr-decode/src/lib.rs +++ b/core/dr-decode/src/lib.rs @@ -375,7 +375,10 @@ fn rawler_location(gps: &rawler::exif::ExifGPS) -> Option { (r.d != 0).then(|| r.n as f64 / r.d as f64) } - fn degrees(dms: &[rawler::formats::tiff::Rational; 3], reference: Option<&String>) -> Option { + fn degrees( + dms: &[rawler::formats::tiff::Rational; 3], + reference: Option<&String>, + ) -> Option { let d = ratio(&dms[0])? + ratio(&dms[1])? / 60.0 + ratio(&dms[2])? / 3600.0; // South and west are stored as positive magnitudes with a letter. let negative = matches!( @@ -534,7 +537,10 @@ pub fn decode(bytes: &[u8]) -> Result { // both callers ask the same object for it. let profile = profile::CameraProfile::extract(&image, &dng); let color_matrix = profile.as_ref().and_then(|p| p.cam_to_srgb()); - let wb_coeffs = sane_wb(image.wb_coeffs, profile.as_ref().map(|p| p.xyz_to_cam()).as_ref()); + let wb_coeffs = sane_wb( + image.wb_coeffs, + profile.as_ref().map(|p| p.xyz_to_cam()).as_ref(), + ); // The rendering half of the profile (FR-DEV-3e). rawler's cleaned strings // are preferred where it has them — they are what the shipped database is diff --git a/core/dr-decode/src/locate.rs b/core/dr-decode/src/locate.rs index 465cc22..94feeca 100644 --- a/core/dr-decode/src/locate.rs +++ b/core/dr-decode/src/locate.rs @@ -678,7 +678,8 @@ fn read_gps_entries(r: &TiffReader, entries: &[Entry]) -> Option Option { let e = find(tag)?; - let d = r.rational(e, 0)? + r.rational(e, 1).unwrap_or(0.0) / 60.0 + let d = r.rational(e, 0)? + + r.rational(e, 1).unwrap_or(0.0) / 60.0 + r.rational(e, 2).unwrap_or(0.0) / 3600.0; // The magnitude is unsigned; the hemisphere is a letter beside it. let south_or_west = find(ref_tag) @@ -1412,7 +1413,9 @@ pub fn defects(tiff_data: &[u8]) -> Defects { /// skip one this build does not implement rather than abandoning the list — /// and lists mixing a warp with a defect map are ordinary. fn read_opcode_list(bytes: &[u8], out: &mut Defects) { - let Some(count) = be_u32(bytes, 0) else { return }; + let Some(count) = be_u32(bytes, 0) else { + return; + }; // A sensor has a handful of opcodes, not thousands. A huge count is a // corrupt or hostile file. if count > 256 { diff --git a/core/dr-decode/src/profile.rs b/core/dr-decode/src/profile.rs index 115a211..e8fcb18 100644 --- a/core/dr-decode/src/profile.rs +++ b/core/dr-decode/src/profile.rs @@ -1048,7 +1048,11 @@ mod tests { // would render black, which is much harder to diagnose than // uncalibrated. assert!(!usable(&[[0.0; 3]; 3])); - assert!(!usable(&[[f32::NAN, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]])); + assert!(!usable(&[ + [f32::NAN, 0.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 0.0, 1.0] + ])); assert!(usable(&SIX_D_D65)); } diff --git a/core/dr-export/src/encode.rs b/core/dr-export/src/encode.rs index 9535a72..ac0a31a 100644 --- a/core/dr-export/src/encode.rs +++ b/core/dr-export/src/encode.rs @@ -77,9 +77,7 @@ pub fn encode( .map(|m| m.sanitised(settings.strip_location)); // JPEG and PNG take a finished block; TIFF writes the tags into its own // directory and needs the fields. - let block = carried - .as_ref() - .and_then(|m| exif::block(m, width, height)); + let block = carried.as_ref().and_then(|m| exif::block(m, width, height)); match settings.format { ExportFormat::Jpeg => jpeg( @@ -303,8 +301,11 @@ where W: std::io::Write + std::io::Seek, K: tiff::encoder::TiffKind, { - dir.write_tag(tiff::tags::Tag::Unknown(TAG_ICC_PROFILE), Undefined(profile)) - .map_err(|e| ExportError::Encode(e.to_string())) + dir.write_tag( + tiff::tags::Tag::Unknown(TAG_ICC_PROFILE), + Undefined(profile), + ) + .map_err(|e| ExportError::Encode(e.to_string())) } /// TRACES: FR-EXP-8 @@ -376,7 +377,10 @@ where )?; } if let Some(f) = md.aperture.filter(|f| *f > 0.0) { - dir.write_tag(Tag::Unknown(exif::tag::FNUMBER), Rationals(&[exif::tenths(f)]))?; + dir.write_tag( + Tag::Unknown(exif::tag::FNUMBER), + Rationals(&[exif::tenths(f)]), + )?; } if let Some(f) = md.focal_length.filter(|f| *f > 0.0) { dir.write_tag( @@ -935,11 +939,16 @@ mod tests { assert_eq!(md.aperture, Some(2.8), "{format:?}"); assert_eq!(md.focal_length, Some(85.0), "{format:?}"); - let loc = md.location.unwrap_or_else(|| panic!("{format:?} lost the fix")); + let loc = md + .location + .unwrap_or_else(|| panic!("{format:?} lost the fix")); // Within a metre of where it started, which is finer than any // consumer receiver and far finer than the tag's own rounding. assert!((loc.latitude - LATITUDE).abs() < 1e-5, "{format:?} {loc:?}"); - assert!((loc.longitude - LONGITUDE).abs() < 1e-5, "{format:?} {loc:?}"); + assert!( + (loc.longitude - LONGITUDE).abs() < 1e-5, + "{format:?} {loc:?}" + ); assert_eq!(loc.altitude, Some(35.0), "{format:?}"); } } diff --git a/core/dr-export/src/lib.rs b/core/dr-export/src/lib.rs index 4642064..3ff5c3b 100644 --- a/core/dr-export/src/lib.rs +++ b/core/dr-export/src/lib.rs @@ -250,7 +250,13 @@ mod tests { #[test] fn png_export_produces_a_png() { - let out = export(&frame(32, 32), &settings(ExportFormat::Png), "a.png".into(), None).unwrap(); + let out = export( + &frame(32, 32), + &settings(ExportFormat::Png), + "a.png".into(), + None, + ) + .unwrap(); assert_eq!(&out.bytes[..8], b"\x89PNG\r\n\x1a\n"); } @@ -270,8 +276,20 @@ mod tests { fn a_sixteen_bit_tiff_is_larger_than_an_eight_bit_one() { // Both are uncompressed RGB; the only difference is the sample width, // so this is what proves the 16-bit path is not quietly writing 8. - let eight = export(&frame(16, 16), &settings(ExportFormat::Tiff8), "a".into(), None).unwrap(); - let sixteen = export(&frame(16, 16), &settings(ExportFormat::Tiff16), "a".into(), None).unwrap(); + let eight = export( + &frame(16, 16), + &settings(ExportFormat::Tiff8), + "a".into(), + None, + ) + .unwrap(); + let sixteen = export( + &frame(16, 16), + &settings(ExportFormat::Tiff16), + "a".into(), + None, + ) + .unwrap(); assert!(sixteen.bytes.len() > eight.bytes.len()); } diff --git a/core/dr-gpu/examples/local.rs b/core/dr-gpu/examples/local.rs index e8f94b2..ff58bb6 100644 --- a/core/dr-gpu/examples/local.rs +++ b/core/dr-gpu/examples/local.rs @@ -26,9 +26,7 @@ //! mask pinned to the viewport, a slice read from the wrong layer — shows up //! here as an adjustment in the wrong place, and nowhere else. -use dr_gpu::{ - AdjustPass, DemosaicedImage, Demosaicer, GpuContext, MaskPass, SubjectMasks, -}; +use dr_gpu::{AdjustPass, DemosaicedImage, Demosaicer, GpuContext, MaskPass, SubjectMasks}; use dr_pipeline::descriptor::ParamId; use dr_pipeline::mask::{MaskLayer, MaskSource, MaskStack, Morphology}; use dr_pipeline::operation::compose_full; @@ -152,7 +150,18 @@ fn main() { drain.feather = 0.02; pop.push(drain); - render_stack(&ctx, &source, &mut masks, &mut adjust, &pop, &alpha, pw, ph, ow, oh); + render_stack( + &ctx, + &source, + &mut masks, + &mut adjust, + &pop, + &alpha, + pw, + ph, + ow, + oh, + ); write(&format!("{prefix}-colour-pop.ppm"), &adjust); // ---- 2. lift the subject out of its background ------------------------ @@ -170,7 +179,18 @@ fn main() { darker.feather = 0.03; lift.push(darker); - render_stack(&ctx, &source, &mut masks, &mut adjust, &lift, &alpha, pw, ph, ow, oh); + render_stack( + &ctx, + &source, + &mut masks, + &mut adjust, + &lift, + &alpha, + pw, + ph, + ow, + oh, + ); write(&format!("{prefix}-subject-lift.ppm"), &adjust); // ---- 3. the same edit, grown and shrunk ------------------------------- @@ -192,7 +212,18 @@ fn main() { layer.morph_radius = radius; stack.push(layer); - render_stack(&ctx, &source, &mut masks, &mut adjust, &stack, &alpha, pw, ph, ow, oh); + render_stack( + &ctx, + &source, + &mut masks, + &mut adjust, + &stack, + &alpha, + pw, + ph, + ow, + oh, + ); write(&format!("{prefix}-{name}.ppm"), &adjust); } @@ -292,12 +323,18 @@ fn render_stack( fn fit(w: u32, h: u32, longest: u32) -> (u32, u32) { let s = (longest as f32 / w.max(h) as f32).min(1.0); - (((w as f32 * s) as u32).max(1), ((h as f32 * s) as u32).max(1)) + ( + ((w as f32 * s) as u32).max(1), + ((h as f32 * s) as u32).max(1), + ) } fn write(path: &str, adjust: &AdjustPass) { let (rgba, w, h) = adjust.export_pixels().expect("readback"); - let rgb: Vec = rgba.chunks_exact(4).flat_map(|p| [p[0], p[1], p[2]]).collect(); + let rgb: Vec = rgba + .chunks_exact(4) + .flat_map(|p| [p[0], p[1], p[2]]) + .collect(); write_ppm(path, &rgb, w, h); } diff --git a/core/dr-gpu/examples/segment.rs b/core/dr-gpu/examples/segment.rs index 25db0ee..5ec6c06 100644 --- a/core/dr-gpu/examples/segment.rs +++ b/core/dr-gpu/examples/segment.rs @@ -12,8 +12,8 @@ //! PPM for the same reason `develop` uses it: no encoder dependency, and //! every viewer reads it. This is a diagnostic, not an export path. -use dr_segment::{MergeTree, RegionField}; use dr_gpu::{DemosaicedImage, Demosaicer, GpuContext, SegmentOptions, SegmentPass}; +use dr_segment::{MergeTree, RegionField}; /// The ladder the example dumps. Chosen to span "far too fine to be useful" /// through "one or two objects", because both ends are informative: if no rung diff --git a/core/dr-gpu/src/adjust.rs b/core/dr-gpu/src/adjust.rs index becc922..27c5241 100644 --- a/core/dr-gpu/src/adjust.rs +++ b/core/dr-gpu/src/adjust.rs @@ -589,14 +589,14 @@ impl AdjustPass { }); if !reuse { - let params_buf = self - .ctx - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("adjust-params"), - contents: bytemuck::cast_slice(&uniforms), - usage: wgpu::BufferUsages::UNIFORM, - }); + let params_buf = + self.ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("adjust-params"), + contents: bytemuck::cast_slice(&uniforms), + usage: wgpu::BufferUsages::UNIFORM, + }); let bind_group = self .ctx .device diff --git a/core/dr-gpu/src/detail.rs b/core/dr-gpu/src/detail.rs index dbd3b52..a9d7cab 100644 --- a/core/dr-gpu/src/detail.rs +++ b/core/dr-gpu/src/detail.rs @@ -123,8 +123,7 @@ impl Intermediates { // output these are never handed to a compositor, and no // COPY_SRC, because nothing reads them back — that is the // point (ARCH §6.1). - usage: wgpu::TextureUsages::STORAGE_BINDING - | wgpu::TextureUsages::TEXTURE_BINDING, + usage: wgpu::TextureUsages::STORAGE_BINDING | wgpu::TextureUsages::TEXTURE_BINDING, view_formats: &[], }); let view = texture.create_view(&Default::default()); diff --git a/core/dr-gpu/src/lib.rs b/core/dr-gpu/src/lib.rs index 6410553..cfb5290 100644 --- a/core/dr-gpu/src/lib.rs +++ b/core/dr-gpu/src/lib.rs @@ -30,8 +30,8 @@ pub use adjust::AdjustPass; // rather than an implementation detail: a detail pass is guaranteed linear, // unclipped, full internal precision (FR-DEV-2), and anyone reasoning about // VRAM at 24 MP needs to know what an intermediate costs. -pub use detail::INTERMEDIATE_FORMAT as DETAIL_INTERMEDIATE_FORMAT; pub use demosaic::{DemosaicedImage, Demosaicer}; +pub use detail::INTERMEDIATE_FORMAT as DETAIL_INTERMEDIATE_FORMAT; pub use error::GpuError; // Renamed on the way out: `BINS` says enough inside `histogram`, and nothing // at all at a crate root shared with demosaic and segmentation. diff --git a/core/dr-gpu/src/mask.rs b/core/dr-gpu/src/mask.rs index 4ff3718..3c4aab0 100644 --- a/core/dr-gpu/src/mask.rs +++ b/core/dr-gpu/src/mask.rs @@ -582,10 +582,7 @@ impl MaskPass { MaskSource::Subject { .. } => match subjects.filter(|s| slot < s.len()) { Some(s) => (s, slot), None => { - log::warn!( - "mask layer {} has no distance field; skipping", - layer.id - ); + log::warn!("mask layer {} has no distance field; skipping", layer.id); continue; } }, @@ -599,7 +596,14 @@ impl MaskPass { } _ => { let selected = self.selection_buffer(layer, field); - self.draw(&mut encoder, slot as u32, ¶ms, field, &selected, subject); + self.draw( + &mut encoder, + slot as u32, + ¶ms, + field, + &selected, + subject, + ); } } } @@ -730,14 +734,14 @@ impl MaskPass { // Still worth beginning the pass: the slice has to be cleared, or an // unpainted layer would show whatever the last edit left in it. let bind_group = (!batch.is_empty()).then(|| { - let params_buf = self - .ctx - .device - .create_buffer_init(&wgpu::util::BufferInitDescriptor { - label: Some("mask-brush-params"), - contents: bytemuck::bytes_of(params), - usage: wgpu::BufferUsages::UNIFORM, - }); + let params_buf = + self.ctx + .device + .create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("mask-brush-params"), + contents: bytemuck::bytes_of(params), + usage: wgpu::BufferUsages::UNIFORM, + }); // Rebuilt per rasterisation rather than kept and patched. This runs // when a mask's shape changes, not per frame, and a few kilobytes // of stroke geometry is cheaper to upload than a residency scheme @@ -901,12 +905,9 @@ impl MaskPass { wgpu::BindGroupEntry { binding: 3, resource: wgpu::BindingResource::TextureView( - subject - .0 - .view(subject.1) - .unwrap_or_else(|| { - self.empty_subject.view(0).expect("placeholder exists") - }), + subject.0.view(subject.1).unwrap_or_else(|| { + self.empty_subject.view(0).expect("placeholder exists") + }), ), }, ], @@ -949,7 +950,11 @@ impl MaskPass { } fn ensure_array(&mut self, width: u32, height: u32, layers: u32) -> Result<(), GpuError> { - if self.array.as_ref().is_some_and(|a| a.matches(width, height, layers)) { + if self + .array + .as_ref() + .is_some_and(|a| a.matches(width, height, layers)) + { return Ok(()); } @@ -1111,7 +1116,9 @@ mod tests { })); let mut pass = MaskPass::new(&ctx).expect("mask pass"); - let array = pass.render(&stack, Some(&field), None, w, h).expect("render"); + let array = pass + .render(&stack, Some(&field), None, w, h) + .expect("render"); assert_eq!(array.size(), (w, h)); assert_eq!(array.layers(), 1); } @@ -1201,7 +1208,11 @@ mod tests { assert_eq!(batch.headers.len(), 1); let h = &batch.headers[0]; - assert!(h.hi[0] - h.lo[0] > 0.2, "wide enough for the dab: {h:?}", h = (h.lo, h.hi)); + assert!( + h.hi[0] - h.lo[0] > 0.2, + "wide enough for the dab: {h:?}", + h = (h.lo, h.hi) + ); assert!( h.hi[1] - h.lo[1] > h.hi[0] - h.lo[0], "and taller than it is wide in normalised units, since the radius \ diff --git a/core/dr-gpu/tests/base_curve.rs b/core/dr-gpu/tests/base_curve.rs index 279e164..e0349e1 100644 --- a/core/dr-gpu/tests/base_curve.rs +++ b/core/dr-gpu/tests/base_curve.rs @@ -62,9 +62,7 @@ fn rendered_level(ctx: &GpuContext, level: u16, curve: BaseCurve) -> u8 { .expect("demosaic"); let shader = EditGraph::default_chain().compose(); let mut adjust = AdjustPass::new(ctx); - adjust - .render(&source, &shader, SIZE, SIZE) - .expect("render"); + adjust.render(&source, &shader, SIZE, SIZE).expect("render"); let (pixels, _, _) = adjust.export_pixels().expect("readback"); let centre = ((SIZE / 2) * SIZE + SIZE / 2) * 4; pixels[centre as usize] diff --git a/core/dr-gpu/tests/capture_sharpen.rs b/core/dr-gpu/tests/capture_sharpen.rs index 1b101ee..ec04b01 100644 --- a/core/dr-gpu/tests/capture_sharpen.rs +++ b/core/dr-gpu/tests/capture_sharpen.rs @@ -84,12 +84,7 @@ fn sharpened(amount: f32, radius: f32, threshold: f32) -> EditGraph { /// The whole calling convention a frontend adopts, in five lines: compose both /// halves from one graph at one output space, ask the graph for the scale, and /// pass the invalidation key through. -fn render( - pass: &mut AdjustPass, - graph: &EditGraph, - source: &DemosaicedImage, - out: u32, -) -> Vec { +fn render(pass: &mut AdjustPass, graph: &EditGraph, source: &DemosaicedImage, out: u32) -> Vec { let shader = graph.compose_for(ColourSpace::Srgb); let scale = graph.render_scale(source.size(), (out, out)); let detail = graph.compose_detail_for(scale, ColourSpace::Srgb); @@ -281,7 +276,10 @@ fn a_proxy_and_an_export_sharpen_the_same_photograph() { proxy_width > 0.05 && export_width > 0.05, "{proxy_width:.3} / {export_width:.3}" ); - assert!(proxy_area > 1.0 && export_area > 1.0, "{proxy_area} / {export_area}"); + assert!( + proxy_area > 1.0 && export_area > 1.0, + "{proxy_area} / {export_area}" + ); } #[test] diff --git a/core/dr-gpu/tests/detail_stage.rs b/core/dr-gpu/tests/detail_stage.rs index 1a4efdb..36e67e3 100644 --- a/core/dr-gpu/tests/detail_stage.rs +++ b/core/dr-gpu/tests/detail_stage.rs @@ -80,9 +80,7 @@ fn expected_profile(size: u32, r: i32) -> Vec { let edge = (size / 2) as i32; (0..size as i32) .map(|x| { - let white = (-r..=r) - .filter(|i| (x + i).clamp(0, last) >= edge) - .count(); + let white = (-r..=r).filter(|i| (x + i).clamp(0, last) >= edge).count(); srgb_encode(white as f32 / (2 * r + 1) as f32) }) .collect() @@ -232,7 +230,10 @@ fn an_inactive_detail_operation_costs_exactly_nothing() { let mut without = AdjustPass::new(&ctx); let b = render(&ctx, &mut without, &plain, &source, SIZE); - assert_eq!(a, b, "an operation at its defaults must not touch the image"); + assert_eq!( + a, b, + "an operation at its defaults must not touch the image" + ); } #[test] @@ -252,7 +253,11 @@ fn moving_a_detail_parameter_does_not_re_run_the_colour_pass() { graph.set_param(PROBE, RADIUS, 0.0625); render(&ctx, &mut pass, &graph, &source, SIZE); assert_eq!(pass.colour_dispatches(), 1); - assert_eq!(pass.detail_dispatches(), 2, "a separable blur is two passes"); + assert_eq!( + pass.detail_dispatches(), + 2, + "a separable blur is two passes" + ); // Drag the sharpening slider. The colour chain is untouched, so the linear // intermediate it wrote is still exactly right. diff --git a/core/dr-gpu/tests/local_adjustments.rs b/core/dr-gpu/tests/local_adjustments.rs index b577fb3..2f06301 100644 --- a/core/dr-gpu/tests/local_adjustments.rs +++ b/core/dr-gpu/tests/local_adjustments.rs @@ -65,12 +65,7 @@ fn render_at( h: u32, ) -> Vec { let source = grey_at(ctx, w, h); - let shader = compose_full( - &ops::chain(), - &Framing::new(), - ColourSpace::Srgb, - stack, - ); + let shader = compose_full(&ops::chain(), &Framing::new(), ColourSpace::Srgb, stack); let mut masks = MaskPass::new(ctx).expect("mask pass"); let array = masks.render(stack, field, None, w, h).expect("rasterise"); @@ -245,11 +240,14 @@ fn stacked_layers_use_their_own_masks() { ids: vec![0], })); // Right half down. - let mut darken = MaskLayer::new("m2", MaskSource::Regions { - signature: 1, - level: 2, - ids: vec![1], - }); + let mut darken = MaskLayer::new( + "m2", + MaskSource::Regions { + signature: 1, + level: 2, + ids: vec![1], + }, + ); darken.set_param("exposure", ParamId("exposure"), -2.0); stack.push(darken); @@ -412,12 +410,7 @@ fn a_stroke_that_doubles_back_does_not_build_up() { return; }; - let once = stack_of(painted(&[( - false, - 0.15, - 0.5, - vec![(0.1, 0.5), (0.9, 0.5)], - )])); + let once = stack_of(painted(&[(false, 0.15, 0.5, vec![(0.1, 0.5), (0.9, 0.5)])])); let twice = stack_of(painted(&[( false, 0.15, @@ -473,10 +466,17 @@ fn a_dab_is_round_on_a_frame_that_is_not_square() { let pixels = render_at(&ctx, &stack, None, W, H); let lit = |v: u8| v > 160; - let across = (0..W).filter(|&x| lit(luma_in(&pixels, W, x, H / 2))).count(); - let down = (0..H).filter(|&y| lit(luma_in(&pixels, W, W / 2, y))).count(); + let across = (0..W) + .filter(|&x| lit(luma_in(&pixels, W, x, H / 2))) + .count(); + let down = (0..H) + .filter(|&y| lit(luma_in(&pixels, W, W / 2, y))) + .count(); - assert!(across > 4 && down > 4, "the dab should exist: {across}x{down}"); + assert!( + across > 4 && down > 4, + "the dab should exist: {across}x{down}" + ); assert!( across.abs_diff(down) <= 2, "a dab must be as wide as it is tall, got {across} across and {down} down" diff --git a/core/dr-gpu/tests/local_contrast.rs b/core/dr-gpu/tests/local_contrast.rs index 5b36bee..df940a1 100644 --- a/core/dr-gpu/tests/local_contrast.rs +++ b/core/dr-gpu/tests/local_contrast.rs @@ -158,7 +158,12 @@ fn clarity_lifts_local_contrast_and_leaves_the_flat_regions_alone() { // Far outside the kernel's reach the base equals the pixel, the detail // signal is zero, and the output must be the input to the last code value. - for x in [0, reach / 2, SIZE as usize - 1 - reach / 2, SIZE as usize - 1] { + for x in [ + 0, + reach / 2, + SIZE as usize - 1 - reach / 2, + SIZE as usize - 1, + ] { assert!( edited[x].abs_diff(plain[x]) <= 1, "column {x} moved by {} away from any edge", @@ -246,7 +251,10 @@ fn the_soft_limit_bounds_the_halo_at_a_hard_edge() { an unlimited unsharp mask would give" ); // But it is still a real effect, not a control that does nothing. - assert!(worst > 0.1, "clarity moved almost nothing: {worst:.3} stops"); + assert!( + worst > 0.1, + "clarity moved almost nothing: {worst:.3} stops" + ); } #[test] @@ -476,7 +484,11 @@ fn dragging_the_slider_re_runs_the_detail_stage_and_nothing_else() { let mut graph = graph_with(CLARITY, 40.0); render(&mut pass, &graph, &source, 256); assert_eq!(pass.colour_dispatches(), 1); - assert_eq!(pass.detail_dispatches(), 2, "a separable mask is two passes"); + assert_eq!( + pass.detail_dispatches(), + 2, + "a separable mask is two passes" + ); let pipelines = pass.cached_detail_pipelines(); for amount in [50.0, 60.0, 70.0] { diff --git a/core/dr-gpu/tests/masked_outputs.rs b/core/dr-gpu/tests/masked_outputs.rs index 1dad59c..53c0b0f 100644 --- a/core/dr-gpu/tests/masked_outputs.rs +++ b/core/dr-gpu/tests/masked_outputs.rs @@ -25,7 +25,9 @@ fn ctx() -> Option { } fn grey(ctx: &GpuContext, size: u32) -> DemosaicedImage { - let data: Vec = (0..size * size).flat_map(|_| [128, 128, 128, 255]).collect(); + let data: Vec = (0..size * size) + .flat_map(|_| [128, 128, 128, 255]) + .collect(); DemosaicedImage::from_rgba8(ctx, &data, size, size).expect("upload") } diff --git a/core/dr-gpu/tests/noise_reduction.rs b/core/dr-gpu/tests/noise_reduction.rs index 8ebf2f3..5740344 100644 --- a/core/dr-gpu/tests/noise_reduction.rs +++ b/core/dr-gpu/tests/noise_reduction.rs @@ -112,11 +112,7 @@ fn split(pixels: &[u8], width: u32, x: u32, y: u32) -> (f32, [f32; 3]) { } /// Upload an image built from a per-pixel closure. -fn upload( - ctx: &GpuContext, - size: u32, - f: impl Fn(u32, u32) -> [u8; 3], -) -> DemosaicedImage { +fn upload(ctx: &GpuContext, size: u32, f: impl Fn(u32, u32) -> [u8; 3]) -> DemosaicedImage { let data: Vec = (0..size * size) .flat_map(|i| { let (x, y) = (i % size, i / size); @@ -248,7 +244,11 @@ fn modulation( let mut count = 0.0; for x in margin..(width - margin) { let value = sample(linear(pixels, width, x, row)); - let sign = if (x / half_period) % 2 == 0 { 1.0 } else { -1.0 }; + let sign = if (x / half_period) % 2 == 0 { + 1.0 + } else { + -1.0 + }; total += value * sign; count += 1.0; } @@ -305,7 +305,13 @@ fn chroma_noise_reduction_never_moves_lightness() { let mut off = AdjustPass::new(&ctx); let plain = render(&ctx, &mut off, &graph_with(0.0, 0.0), &source, (SIZE, SIZE)); let mut on = AdjustPass::new(&ctx); - let chroma = render(&ctx, &mut on, &graph_with(0.0, 100.0), &source, (SIZE, SIZE)); + let chroma = render( + &ctx, + &mut on, + &graph_with(0.0, 100.0), + &source, + (SIZE, SIZE), + ); let colour_before = modulation(&plain, SIZE, 4, chroma_r); let colour_after = modulation(&chroma, SIZE, 4, chroma_r); @@ -338,7 +344,13 @@ fn luminance_noise_reduction_never_moves_colour() { let mut off = AdjustPass::new(&ctx); let plain = render(&ctx, &mut off, &graph_with(0.0, 0.0), &source, (SIZE, SIZE)); let mut on = AdjustPass::new(&ctx); - let luma = render(&ctx, &mut on, &graph_with(100.0, 0.0), &source, (SIZE, SIZE)); + let luma = render( + &ctx, + &mut on, + &graph_with(100.0, 0.0), + &source, + (SIZE, SIZE), + ); // The colour difference — not the raw channel, which follows lightness. let row = SIZE / 2; @@ -413,10 +425,10 @@ fn the_same_edit_denoises_the_same_at_two_resolutions() { let Some(ctx) = ctx() else { return }; const SOURCE: u32 = 256; const HALF_PERIOD: u32 = 8; // in source pixels - // Six code values of swing. Small on purpose: the colour difference has to - // land near the filter's threshold, because a larger one is a colour - // boundary and the whole point of a bilateral is that it refuses to cross - // those. There would be nothing to measure at either resolution. + // Six code values of swing. Small on purpose: the colour difference has to + // land near the filter's threshold, because a larger one is a colour + // boundary and the whole point of a bilateral is that it refuses to cross + // those. There would be nothing to measure at either resolution. let source = chroma_pattern(&ctx, SOURCE, HALF_PERIOD, 6); // Sixty percent is an eight-source-pixel radius, which halves to exactly @@ -430,14 +442,26 @@ fn the_same_edit_denoises_the_same_at_two_resolutions() { let proxy_size = SOURCE / 2; let mut proxy_pass = AdjustPass::new(&ctx); - let proxy = render(&ctx, &mut proxy_pass, &graph, &source, (proxy_size, proxy_size)); + let proxy = render( + &ctx, + &mut proxy_pass, + &graph, + &source, + (proxy_size, proxy_size), + ); let proxy_amp = modulation(&proxy, proxy_size, HALF_PERIOD / 2, chroma_r); // Both must be doing something: two flat images would agree perfectly and // prove nothing. About 0.0098 of 0.0188, by the derivation above. let untouched = { let mut pass = AdjustPass::new(&ctx); - let plain = render(&ctx, &mut pass, &graph_with(0.0, 0.0), &source, (SOURCE, SOURCE)); + let plain = render( + &ctx, + &mut pass, + &graph_with(0.0, 0.0), + &source, + (SOURCE, SOURCE), + ); modulation(&plain, SOURCE, HALF_PERIOD, chroma_r) }; assert!( @@ -486,7 +510,13 @@ fn each_amount_costs_only_the_dispatches_it_needs() { for (luminance, chroma, expected) in [(60.0, 0.0, 1), (0.0, 60.0, 2), (60.0, 60.0, 3)] { let mut pass = AdjustPass::new(&ctx); - render(&ctx, &mut pass, &graph_with(luminance, chroma), &source, (SIZE, SIZE)); + render( + &ctx, + &mut pass, + &graph_with(luminance, chroma), + &source, + (SIZE, SIZE), + ); assert_eq!( pass.detail_dispatches(), expected, @@ -496,7 +526,13 @@ fn each_amount_costs_only_the_dispatches_it_needs() { } let mut neutral = AdjustPass::new(&ctx); - let a = render(&ctx, &mut neutral, &graph_with(0.0, 0.0), &source, (SIZE, SIZE)); + let a = render( + &ctx, + &mut neutral, + &graph_with(0.0, 0.0), + &source, + (SIZE, SIZE), + ); assert_eq!(neutral.detail_dispatches(), 0); assert_eq!(neutral.detail_allocations(), 0, "nothing was allocated"); @@ -504,7 +540,13 @@ fn each_amount_costs_only_the_dispatches_it_needs() { // touch the image, and a stage that ran and wrote back the same values // would still have quantised twice. let mut absent = AdjustPass::new(&ctx); - let b = render(&ctx, &mut absent, &EditGraph::default_chain(), &source, (SIZE, SIZE)); + let b = render( + &ctx, + &mut absent, + &EditGraph::default_chain(), + &source, + (SIZE, SIZE), + ); assert_eq!(a, b, "a neutral denoiser changed the picture"); } diff --git a/core/dr-gpu/tests/tone_curve.rs b/core/dr-gpu/tests/tone_curve.rs index b12eea0..85fc4fd 100644 --- a/core/dr-gpu/tests/tone_curve.rs +++ b/core/dr-gpu/tests/tone_curve.rs @@ -24,7 +24,9 @@ fn ctx() -> Option { /// The centre pixel's red, green and blue, after `graph` has run over flat /// mid-grey. fn rendered(ctx: &GpuContext, graph: &EditGraph) -> (u8, u8, u8) { - let data: Vec = (0..SIZE * SIZE).flat_map(|_| [128, 128, 128, 255]).collect(); + let data: Vec = (0..SIZE * SIZE) + .flat_map(|_| [128, 128, 128, 255]) + .collect(); let source = DemosaicedImage::from_rgba8(ctx, &data, SIZE, SIZE).expect("upload"); // Composed the way the display path composes it. A curve that generates @@ -44,11 +46,7 @@ fn rendered(ctx: &GpuContext, graph: &EditGraph) -> (u8, u8, u8) { /// an edit. fn lifted(channel: Channel) -> EditGraph { let mut graph = EditGraph::default_chain(); - graph.set_param( - curve::ID, - curve::coordinate(channel, 2, Axis::Y), - 0.75, - ); + graph.set_param(curve::ID, curve::coordinate(channel, 2, Axis::Y), 0.75); graph } @@ -62,7 +60,10 @@ fn the_master_curve_lifts_every_component_together() { let (r0, g0, b0) = rendered(&ctx, &EditGraph::default_chain()); let (r, g, b) = rendered(&ctx, &lifted(Channel::Master)); - assert!(r > r0, "the master curve did not lift the image: {r} vs {r0}"); + assert!( + r > r0, + "the master curve did not lift the image: {r} vs {r0}" + ); // Grey in, grey out: the master curve is applied as a ratio over // luminance, so it changes tone and not hue. A tolerance of one code // value, because the components travel through the ratio separately and diff --git a/core/dr-pipeline/build.rs b/core/dr-pipeline/build.rs index 5abf89e..e0e1a51 100644 --- a/core/dr-pipeline/build.rs +++ b/core/dr-pipeline/build.rs @@ -1532,7 +1532,10 @@ fn emit_node(out: &mut String, node: &Node) -> Result<(), String> { .iter() .map(|a| { let mut c = a.chars(); - let head = c.next().expect("attribute names are non-empty").to_uppercase(); + let head = c + .next() + .expect("attribute names are non-empty") + .to_uppercase(); format!("Attribute::{head}{}", c.as_str()) }) .collect(); diff --git a/core/dr-pipeline/src/detail.rs b/core/dr-pipeline/src/detail.rs index 003b191..24c3be2 100644 --- a/core/dr-pipeline/src/detail.rs +++ b/core/dr-pipeline/src/detail.rs @@ -499,11 +499,7 @@ pub fn compose_detail( // case where a photographer has a kernel switched on at a scale that // cannot draw it — against the alternative of the preview failing outright // or `compose_full` growing a resolution argument it has no other use for. - if planned.is_empty() - && ops - .iter() - .any(|o| o.is_active() && o.detail().is_some()) - { + if planned.is_empty() && ops.iter().any(|o| o.is_active() && o.detail().is_some()) { return ComposedDetail { passes: vec![compose_one( RESOLVE_ID, diff --git a/core/dr-pipeline/src/graph.rs b/core/dr-pipeline/src/graph.rs index 91d5eb7..32f7cac 100644 --- a/core/dr-pipeline/src/graph.rs +++ b/core/dr-pipeline/src/graph.rs @@ -8,8 +8,7 @@ //! so reordering the pipeline needs no code change. use crate::descriptor::{ - Attribute, - Facet, LocalizedKey, OpDescriptor, OpId, ParamId, ParamKind, Presentation, + Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamId, ParamKind, Presentation, }; use crate::framing::{CropRect, Framing}; use crate::mask::MaskStack; @@ -378,7 +377,11 @@ impl EditGraph { /// Only the detail stage needs this. Every point operation is scale-free /// — a multiply is a multiply at any resolution — which is why nothing in /// the pipeline had to know its own size until a kernel arrived. - pub fn render_scale(&self, source: (u32, u32), render: (u32, u32)) -> crate::detail::RenderScale { + pub fn render_scale( + &self, + source: (u32, u32), + render: (u32, u32), + ) -> crate::detail::RenderScale { let (fw, fh) = self.framing.output_size(source.0, source.1); let view = self.framing.view(); // The *viewed* part of the framed image, at source resolution. Zoom @@ -397,7 +400,10 @@ impl EditGraph { /// Empty for every edit with no active neighbourhood operation, which is /// almost all of them — and in that case [`Self::compose`] emits the /// single encoded dispatch it always has. - pub fn compose_detail(&self, scale: crate::detail::RenderScale) -> crate::detail::ComposedDetail { + pub fn compose_detail( + &self, + scale: crate::detail::RenderScale, + ) -> crate::detail::ComposedDetail { self.compose_detail_for(scale, dr_types::ColourSpace::Srgb) } diff --git a/core/dr-pipeline/src/lib.rs b/core/dr-pipeline/src/lib.rs index 2c23fb1..35006d1 100644 --- a/core/dr-pipeline/src/lib.rs +++ b/core/dr-pipeline/src/lib.rs @@ -44,8 +44,8 @@ pub mod preset; pub mod sidecar; pub use descriptor::{ - Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, ParamKind, Presentation, - Scale, Unit, WidgetDemand, WidgetKind, + Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, ParamKind, + Presentation, Scale, Unit, WidgetDemand, WidgetKind, }; pub use detail::{ compose_detail, ComposedDetail, ComposedDetailPass, DetailPass, DetailStage, RenderScale, diff --git a/core/dr-pipeline/src/mask.rs b/core/dr-pipeline/src/mask.rs index e77e963..47e0067 100644 --- a/core/dr-pipeline/src/mask.rs +++ b/core/dr-pipeline/src/mask.rs @@ -856,7 +856,11 @@ impl MaskLayer { pub fn begin_stroke(&mut self, erase: bool, radius: f32, hardness: f32, flow: f32) -> bool { let full = self.stroke_points() >= MAX_LAYER_POINTS; let MaskSource::Brush { strokes } = &mut self.source else { - log::warn!("layer {} is a {} mask, not a brush", self.id, self.source.kind()); + log::warn!( + "layer {} is a {} mask, not a brush", + self.id, + self.source.kind() + ); return false; }; if full { @@ -1121,7 +1125,10 @@ impl MaskStack { /// Generate a fresh layer id that does not collide with an existing one. pub fn next_id(&self) -> String { - (1..).map(|n| format!("m{n}")).find(|id| self.get(id).is_none()).expect("infinite range") + (1..) + .map(|n| format!("m{n}")) + .find(|id| self.get(id).is_none()) + .expect("infinite range") } } @@ -1178,10 +1185,7 @@ pub(crate) fn compose_layers(stack: &MaskStack) -> LayerShader { // place. A second copy here would be a second thing to keep in step // with `Framing::wgsl_prologue`, and the failure would be a mask that // is subtly wrong only when straightened. - let _ = writeln!( - out.body, - " var m = sample_mask(uv_src, {slot});" - ); + let _ = writeln!(out.body, " var m = sample_mask(uv_src, {slot});"); let _ = writeln!( out.body, " m = select(m, 1.0 - m, u.{prefix}_invert > 0.5);" @@ -1403,7 +1407,11 @@ mod tests { assert!(shader.uniform_fields.contains("mask1_exposure_")); assert_eq!( shader.uniform_values.len(), - shader.uniform_fields.lines().filter(|l| l.trim_start().starts_with("mask")).count(), + shader + .uniform_fields + .lines() + .filter(|l| l.trim_start().starts_with("mask")) + .count(), "one value per emitted field" ); } @@ -1448,7 +1456,11 @@ mod tests { // A gradient has no region ids, so nothing can go stale about it. let grad = MaskLayer::new( "m2", - MaskSource::Linear { centre: (0.5, 0.5), angle: 0.0, width: 0.2 }, + MaskSource::Linear { + centre: (0.5, 0.5), + angle: 0.0, + width: 0.2, + }, ); assert!(!grad.is_stale(999)); } @@ -1458,8 +1470,16 @@ mod tests { let base = segmentation_signature(1600, 1067, 6730, 2); assert_eq!(base, segmentation_signature(1600, 1067, 6730, 2)); assert_ne!(base, segmentation_signature(1600, 1067, 6730, 5), "tuning"); - assert_ne!(base, segmentation_signature(800, 1067, 6730, 2), "proxy size"); - assert_ne!(base, segmentation_signature(1600, 1067, 42, 2), "region count"); + assert_ne!( + base, + segmentation_signature(800, 1067, 6730, 2), + "proxy size" + ); + assert_ne!( + base, + segmentation_signature(1600, 1067, 42, 2), + "region count" + ); } #[test] diff --git a/core/dr-pipeline/src/operation.rs b/core/dr-pipeline/src/operation.rs index 5e29404..e9be1d7 100644 --- a/core/dr-pipeline/src/operation.rs +++ b/core/dr-pipeline/src/operation.rs @@ -472,10 +472,7 @@ pub fn compose_full( // than from a flag the caller sets, because a caller that got the flag // wrong would produce a shader whose storage format does not match the // texture bound to it. - let output_mode = if ops - .iter() - .any(|o| o.is_active() && o.detail().is_some()) - { + let output_mode = if ops.iter().any(|o| o.is_active() && o.detail().is_some()) { OutputMode::LinearWorking } else { OutputMode::Encoded @@ -1389,7 +1386,10 @@ mod tests { // how a shoulder rolls off. let mut curve = crate::ops::ToneCurve::new(); curve.set_param(crate::ops::curve::P2_Y, 0.7); - assert!(curve.is_active(), "the fixture must actually reach the shader"); + assert!( + curve.is_active(), + "the fixture must actually reach the shader" + ); let source = compose(&[Box::new(curve)]).source; assert_eq!( diff --git a/core/dr-pipeline/src/ops/aberration.rs b/core/dr-pipeline/src/ops/aberration.rs index bc0195e..1cf7bdb 100644 --- a/core/dr-pipeline/src/ops/aberration.rs +++ b/core/dr-pipeline/src/ops/aberration.rs @@ -32,7 +32,9 @@ //! least to perceived sharpness. Scaling all three about a virtual reference //! would soften the image even when the correction is right. -use crate::descriptor::{Attribute, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, Scale, Unit}; +use crate::descriptor::{ + Attribute, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, Scale, Unit, +}; use crate::lens::Warp; use crate::operation::{Helper, Uniform}; diff --git a/core/dr-pipeline/src/ops/capture_sharpen.rs b/core/dr-pipeline/src/ops/capture_sharpen.rs index d727a19..4f01908 100644 --- a/core/dr-pipeline/src/ops/capture_sharpen.rs +++ b/core/dr-pipeline/src/ops/capture_sharpen.rs @@ -764,9 +764,11 @@ mod tests { }; assert_eq!(gate_of(0.0), 0.0); assert!((gate_of(1.0) - 0.25).abs() < 1e-6); - assert!(chain_at(&sharpening(50.0, 1.0), RenderScale::full((512, 512))).passes[0] - .source - .contains("gate > 0.0")); + assert!( + chain_at(&sharpening(50.0, 1.0), RenderScale::full((512, 512))).passes[0] + .source + .contains("gate > 0.0") + ); } #[test] @@ -783,7 +785,11 @@ mod tests { for scale in scales { for pass in chain_at(&sharpening(75.0, 1.0), scale).passes { assert_eq!(pass.uniforms.len() % 4, 0, "{}", pass.label); - assert!(pass.uniforms.iter().all(|v| v.is_finite()), "{}", pass.label); + assert!( + pass.uniforms.iter().all(|v| v.is_finite()), + "{}", + pass.label + ); } } } @@ -805,10 +811,38 @@ mod tests { // word be safe in one stage and not in the other, which is the sort of // difference nobody discovers until a shader fails to compile. const RESERVED: &[&str] = &[ - "target", "sample", "filter", "texture", "buffer", "binding", "const", "enum", "mat", - "vec", "ptr", "ref", "shared", "static", "typedef", "union", "unless", "handle", - "layout", "packed", "premerge", "regardless", "active", "do", "input", "output", - "private", "resource", "restrict", "self", "std", "where", + "target", + "sample", + "filter", + "texture", + "buffer", + "binding", + "const", + "enum", + "mat", + "vec", + "ptr", + "ref", + "shared", + "static", + "typedef", + "union", + "unless", + "handle", + "layout", + "packed", + "premerge", + "regardless", + "active", + "do", + "input", + "output", + "private", + "resource", + "restrict", + "self", + "std", + "where", ]; for pass in chain_at(&sharpening(100.0, 2.0), RenderScale::full((512, 512))).passes { diff --git a/core/dr-pipeline/src/ops/colour_mixer.rs b/core/dr-pipeline/src/ops/colour_mixer.rs index f965e02..60abe72 100644 --- a/core/dr-pipeline/src/ops/colour_mixer.rs +++ b/core/dr-pipeline/src/ops/colour_mixer.rs @@ -30,7 +30,9 @@ //! adjusted, each one's share depends on what the other is set to, so turning //! up one colour's saturation quietly weakened its neighbour's hue shift. -use crate::descriptor::{Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId}; +use crate::descriptor::{ + Attribute, Facet, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, +}; use crate::operation::{Helper, Operation, Uniform}; use crate::ops::helpers; diff --git a/core/dr-pipeline/src/ops/curve.rs b/core/dr-pipeline/src/ops/curve.rs index c2c6782..ce845bd 100644 --- a/core/dr-pipeline/src/ops/curve.rs +++ b/core/dr-pipeline/src/ops/curve.rs @@ -121,12 +121,7 @@ impl Channel { /// Master first because it runs first — see the module documentation for /// why that is the composition order — and because a list showing the /// grade before the tone would be describing a different operation. - pub const ALL: [Channel; 4] = [ - Channel::Master, - Channel::Red, - Channel::Green, - Channel::Blue, - ]; + pub const ALL: [Channel; 4] = [Channel::Master, Channel::Red, Channel::Green, Channel::Blue]; /// Position in [`Self::ALL`], and so in every array keyed by channel. pub const fn index(self) -> usize { @@ -771,7 +766,10 @@ impl Operation for ToneCurve { // declared from, so a call and its uniform block cannot disagree // about a name. let args = UNIFORM_NAMES[channel.index()].join(", "); - let _ = writeln!(body, "c.{component} = channel_curve(c.{component}, {args});"); + let _ = writeln!( + body, + "c.{component} = channel_curve(c.{component}, {args});" + ); } // Whichever curves ran, the result has to be a colour: the spline's @@ -1345,7 +1343,9 @@ mod tests { let body = c.wgsl_body(); let master = body.find("apply_tone_gain").expect("the master curve runs"); - let red = body.find("c.r = channel_curve").expect("the red curve runs"); + let red = body + .find("c.r = channel_curve") + .expect("the red curve runs"); assert!(master < red, "the master curve must run first:\n{body}"); } diff --git a/core/dr-pipeline/src/ops/distortion.rs b/core/dr-pipeline/src/ops/distortion.rs index 345087b..d5fe6b7 100644 --- a/core/dr-pipeline/src/ops/distortion.rs +++ b/core/dr-pipeline/src/ops/distortion.rs @@ -26,7 +26,9 @@ //! profile is a "make the horizon straight" task, which one term does well. //! The full triple is reachable by loading a profile. -use crate::descriptor::{Attribute, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, Scale, Unit}; +use crate::descriptor::{ + Attribute, LocalizedKey, OpDescriptor, OpId, ParamDescriptor, ParamId, Scale, Unit, +}; use crate::lens::Warp; use crate::operation::{Helper, Uniform}; diff --git a/core/dr-pipeline/src/ops/local_contrast.rs b/core/dr-pipeline/src/ops/local_contrast.rs index 29de880..63cbd31 100644 --- a/core/dr-pipeline/src/ops/local_contrast.rs +++ b/core/dr-pipeline/src/ops/local_contrast.rs @@ -619,7 +619,10 @@ mod tests { let scale = RenderScale::full((2000, 1500)); let only_clarity = composed(50.0, 0.0, scale); assert_eq!(only_clarity.len(), 2, "one operation, two passes"); - assert!(only_clarity.passes.iter().all(|p| p.label.starts_with("clarity/"))); + assert!(only_clarity + .passes + .iter() + .all(|p| p.label.starts_with("clarity/"))); let both = composed(50.0, 50.0, scale); assert_eq!(both.len(), 4); @@ -701,7 +704,10 @@ mod tests { // acutance family's unit. let proxy = RenderScale::new((2000, 1500), (6000, 4500)); let clarity = Clarity::with_amount(60.0); - assert_eq!(clarity.kernel(proxy), clarity.kernel(RenderScale::full((2000, 1500)))); + assert_eq!( + clarity.kernel(proxy), + clarity.kernel(RenderScale::full((2000, 1500))) + ); assert!( proxy.source_pixels(96.0) < 40.0, "the same length in the other unit would have collapsed" @@ -786,7 +792,9 @@ mod tests { // slider can produce is `gain * threshold` stops however violent the // edge — a bound that holds by construction rather than by tuning. let combine = &Clarity::with_amount(100.0).passes(RenderScale::full((2000, 1500)))[1]; - assert!(combine.wgsl.contains("threshold * tanh(detail / threshold)")); + assert!(combine + .wgsl + .contains("threshold * tanh(detail / threshold)")); let bound = |u: &[Uniform]| { let get = |n| u.iter().find(|x| x.name == n).unwrap().value; @@ -819,7 +827,10 @@ mod tests { // And texture does not carry the helpers it would need for one, so its // shader says what it does rather than merely not calling it. - assert!(Texture::new().helpers().iter().any(|h| h.name == "log_luma")); + assert!(Texture::new() + .helpers() + .iter() + .any(|h| h.name == "log_luma")); assert!(!Texture::new() .helpers() .iter() diff --git a/core/dr-pipeline/src/ops/noise_reduction.rs b/core/dr-pipeline/src/ops/noise_reduction.rs index c26c4ca..3006587 100644 --- a/core/dr-pipeline/src/ops/noise_reduction.rs +++ b/core/dr-pipeline/src/ops/noise_reduction.rs @@ -820,7 +820,13 @@ mod tests { // Everything else about the two must match, or one axis is filtered // harder than the other and a round blotch comes out oval. assert_eq!(passes[0].radius, passes[1].radius); - for name in ["radius", "inv_spatial", "guide_k", "chroma_k", "noise_floor"] { + for name in [ + "radius", + "inv_spatial", + "guide_k", + "chroma_k", + "noise_floor", + ] { let of = |p: &DetailPass| { p.uniforms .iter() diff --git a/core/dr-pipeline/src/sidecar.rs b/core/dr-pipeline/src/sidecar.rs index a7b8d8f..db1ad5b 100644 --- a/core/dr-pipeline/src/sidecar.rs +++ b/core/dr-pipeline/src/sidecar.rs @@ -67,9 +67,7 @@ use std::fmt; use std::fmt::Write as _; use crate::graph::EditGraph; -use crate::mask::{ - Falloff, MaskLayer, MaskSource, MaskStack, Morphology, Stroke, DEFAULT_FEATHER, -}; +use crate::mask::{Falloff, MaskLayer, MaskSource, MaskStack, Morphology, Stroke, DEFAULT_FEATHER}; use crate::preset::{resolve, Preset}; /// Format version of the document itself. @@ -662,7 +660,6 @@ impl Sidecar { } } - /// Write one mask layer as its own block. /// /// The version uuid is repeated in the header rather than relying on the @@ -1292,7 +1289,11 @@ mod tests { // else. assert_eq!(g.param(curve::ID, curve::P1_Y), Some(0.15)); assert_eq!(g.param(curve::ID, curve::P3_Y), Some(0.85)); - for channel in [curve::Channel::Red, curve::Channel::Green, curve::Channel::Blue] { + for channel in [ + curve::Channel::Red, + curve::Channel::Green, + curve::Channel::Blue, + ] { for point in 0..curve::POINTS { for axis in [curve::Axis::X, curve::Axis::Y] { let id = curve::coordinate(channel, point, axis); diff --git a/core/dr-pipeline/tests/attributes.rs b/core/dr-pipeline/tests/attributes.rs index bf62a23..05e1072 100644 --- a/core/dr-pipeline/tests/attributes.rs +++ b/core/dr-pipeline/tests/attributes.rs @@ -40,7 +40,10 @@ fn the_groups_are_derivable_from_the_chain() { present.sort(); present.dedup(); - assert!(present.contains(&Attribute::Tone), "the chain has tonal work"); + assert!( + present.contains(&Attribute::Tone), + "the chain has tonal work" + ); assert!(present.contains(&Attribute::Colour)); assert!( present.contains(&Attribute::Geometry), diff --git a/core/dr-pipeline/tests/mask_sidecar.rs b/core/dr-pipeline/tests/mask_sidecar.rs index 53e66fd..962755b 100644 --- a/core/dr-pipeline/tests/mask_sidecar.rs +++ b/core/dr-pipeline/tests/mask_sidecar.rs @@ -55,7 +55,14 @@ fn a_region_mask_survives_a_round_trip() { assert_eq!(layer.id, "m1"); assert_eq!(layer.name, "Subject"); assert_eq!(layer.source, regions(&[3, 7, 12])); - assert_eq!(layer.ops.iter().find(|o| o.descriptor().id.0 == "exposure").map(|o| o.param(ParamId("exposure"))), Some(0.75)); + assert_eq!( + layer + .ops + .iter() + .find(|o| o.descriptor().id.0 == "exposure") + .map(|o| o.param(ParamId("exposure"))), + Some(0.75) + ); } #[test] @@ -120,7 +127,10 @@ fn flags_and_opacity_survive() { let layer = &restored.masks().layers()[0]; assert!(layer.invert); assert!((layer.opacity - 0.35).abs() < 1e-6); - assert!(!layer.enabled, "a disabled layer must stay disabled, not vanish"); + assert!( + !layer.enabled, + "a disabled layer must stay disabled, not vanish" + ); } /// A selection's identity is the *set*. Two files naming the same regions in @@ -272,7 +282,10 @@ fn unknown_top_level_keys_still_round_trip_alongside_masks() { exposure.exposure = 1\n"; let out = Sidecar::parse(text).expect("parse").to_text(); - assert!(out.contains("future.thing"), "unknown keys are still preserved"); + assert!( + out.contains("future.thing"), + "unknown keys are still preserved" + ); assert!(out.contains("[mask default m1]")); } @@ -361,7 +374,13 @@ fn a_brush_layer_writes_one_line_per_stroke() { .lines() .filter(|l| l.starts_with("stroke = ")) .collect(); - assert_eq!(lines, ["stroke = add 0.05 0.5 1 0.2,0.5 0.8,0.5", "stroke = erase 0.05 0.5 1 0.5,0.5"]); + assert_eq!( + lines, + [ + "stroke = add 0.05 0.5 1 0.2,0.5 0.8,0.5", + "stroke = erase 0.05 0.5 1 0.5,0.5" + ] + ); } #[test] diff --git a/core/dr-pipeline/tests/tone_curve.rs b/core/dr-pipeline/tests/tone_curve.rs index d54abb4..db58cde 100644 --- a/core/dr-pipeline/tests/tone_curve.rs +++ b/core/dr-pipeline/tests/tone_curve.rs @@ -12,7 +12,8 @@ use dr_pipeline::{EditGraph, Sidecar}; /// A version block carrying `params`, in the on-disk spelling. fn sidecar_with(params: &[(&str, f32)]) -> String { - let mut text = String::from("drsc 1\n\n[version u1]\nname = Default\nrevision = 2\nmodified = 0\n"); + let mut text = + String::from("drsc 1\n\n[version u1]\nname = Default\nrevision = 2\nmodified = 0\n"); for (key, value) in params { text.push_str(&format!("{key} = {value}\n")); } @@ -199,4 +200,3 @@ fn every_point_of_every_curve_round_trips_through_a_sidecar() { } } } - diff --git a/core/dr-segment/examples/detect.rs b/core/dr-segment/examples/detect.rs index e9077f6..fdfe9bc 100644 --- a/core/dr-segment/examples/detect.rs +++ b/core/dr-segment/examples/detect.rs @@ -46,7 +46,11 @@ fn main() { }; println!( "tiling {}", - if tiled { "grid, 25% overlap" } else { "whole frame" } + if tiled { + "grid, 25% overlap" + } else { + "whole frame" + } ); let t0 = std::time::Instant::now(); @@ -57,10 +61,7 @@ fn main() { let instances = model .detect(&rgb, width, height, &options) .expect("inference"); - println!( - "detect {:.0} ms", - t1.elapsed().as_secs_f32() * 1000.0 - ); + println!("detect {:.0} ms", t1.elapsed().as_secs_f32() * 1000.0); println!("found {} instances", instances.len()); for (i, inst) in instances.iter().enumerate() { @@ -131,10 +132,7 @@ fn load_jpeg(path: &str) -> (Vec, usize, usize) { // where *not* linearising is the correct thing to do. let rgb = match pixels.len() / (w * h) { 3 => pixels.iter().map(|&v| v as f32 / 255.0).collect(), - 1 => pixels - .iter() - .flat_map(|&v| [v as f32 / 255.0; 3]) - .collect(), + 1 => pixels.iter().flat_map(|&v| [v as f32 / 255.0; 3]).collect(), n => panic!("unexpected {n} components per pixel"), }; diff --git a/core/dr-segment/src/distance.rs b/core/dr-segment/src/distance.rs index 0c10d22..c4ae55c 100644 --- a/core/dr-segment/src/distance.rs +++ b/core/dr-segment/src/distance.rs @@ -346,8 +346,8 @@ impl Shaped { radius_px: f32, ) -> Self { let distance = signed_distance(coverage, width, height, threshold); - let distance = apply_morphology(&distance, width, height, morphology, radius_px) - .unwrap_or(distance); + let distance = + apply_morphology(&distance, width, height, morphology, radius_px).unwrap_or(distance); Self { distance, width, @@ -403,7 +403,11 @@ mod tests { // Distance from the boundary, so the half-pixel comes back off to // compare against the centre-to-centre figures. let at = |x: usize, y: usize| -d[y * 5 + x] + 0.5; - assert!((at(1, 1) - std::f32::consts::SQRT_2).abs() < 1e-4, "{}", at(1, 1)); + assert!( + (at(1, 1) - std::f32::consts::SQRT_2).abs() < 1e-4, + "{}", + at(1, 1) + ); assert!((at(0, 0) - (8.0f32).sqrt()).abs() < 1e-4, "{}", at(0, 0)); assert_eq!(at(2, 0), 2.0, "straight up is exactly two"); } @@ -489,7 +493,10 @@ mod tests { m[10 * w + 10] = 0; let before = signed_distance(&m, w, h, 128); - assert!(before[10 * w + 10] < 0.0, "the hole starts outside the mask"); + assert!( + before[10 * w + 10] < 0.0, + "the hole starts outside the mask" + ); let after = apply_morphology(&before, w, h, Morphology::Close, 2.0).expect("recomputed"); assert!(after[10 * w + 10] >= 0.0, "closing should have filled it"); diff --git a/core/dr-segment/src/prior.rs b/core/dr-segment/src/prior.rs index f6545ef..3f75f1c 100644 --- a/core/dr-segment/src/prior.rs +++ b/core/dr-segment/src/prior.rs @@ -142,7 +142,11 @@ impl Membership { /// The instance a region belongs to, if any. pub fn instance_of(&self, region: u32) -> Option { - self.of.get(region as usize).copied().flatten().map(|(i, _)| i) + self.of + .get(region as usize) + .copied() + .flatten() + .map(|(i, _)| i) } /// How two regions relate semantically, in `-1.0..=1.0`. @@ -233,11 +237,7 @@ pub fn apply_semantic_prior( /// /// The returned ids are sorted, so the same click always produces the same /// mask — which is what lets it be a cache key. -pub fn regions_for_instance( - field: &RegionField, - mask: &[f32], - options: &PriorOptions, -) -> Vec { +pub fn regions_for_instance(field: &RegionField, mask: &[f32], options: &PriorOptions) -> Vec { let mut coverage = vec![0.0f32; field.region_count]; let mut sizes = vec![0u32; field.region_count]; @@ -278,10 +278,26 @@ mod tests { labels: vec![0, 0, 2, 2, 1, 1, 3, 3], region_count: 4, adjacency: vec![ - Edge { a: 0, b: 1, saddle: 1.0 }, - Edge { a: 0, b: 2, saddle: 1.0 }, - Edge { a: 1, b: 3, saddle: 1.0 }, - Edge { a: 2, b: 3, saddle: 1.0 }, + Edge { + a: 0, + b: 1, + saddle: 1.0, + }, + Edge { + a: 0, + b: 2, + saddle: 1.0, + }, + Edge { + a: 1, + b: 3, + saddle: 1.0, + }, + Edge { + a: 2, + b: 3, + saddle: 1.0, + }, ], } } @@ -361,7 +377,10 @@ mod tests { #[test] fn zero_strength_is_arm_a_exactly() { let f = field(); - let opts = PriorOptions { strength: 0.0, ..PriorOptions::default() }; + let opts = PriorOptions { + strength: 0.0, + ..PriorOptions::default() + }; let m = Membership::compute(&f, &[&left_half()], &opts).unwrap(); assert_eq!(apply_semantic_prior(&f, &m, &opts).adjacency, f.adjacency); } @@ -394,7 +413,11 @@ mod tests { let err = Membership::compute(&f, &[&vec![0.0; 3]], &PriorOptions::default()).unwrap_err(); assert_eq!( err, - MembershipError::MaskSize { instance: 0, expected: 8, got: 3 } + MembershipError::MaskSize { + instance: 0, + expected: 8, + got: 3 + } ); } @@ -403,12 +426,8 @@ mod tests { fn different_instances_repel() { let f = field(); let right_half = vec![0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0]; - let m = Membership::compute( - &f, - &[&left_half(), &right_half], - &PriorOptions::default(), - ) - .unwrap(); + let m = Membership::compute(&f, &[&left_half(), &right_half], &PriorOptions::default()) + .unwrap(); assert_eq!(m.instance_of(0), Some(0)); assert_eq!(m.instance_of(2), Some(1)); diff --git a/core/dr-segment/src/semantic.rs b/core/dr-segment/src/semantic.rs index 6bb7983..885b0b0 100644 --- a/core/dr-segment/src/semantic.rs +++ b/core/dr-segment/src/semantic.rs @@ -357,7 +357,6 @@ impl SemanticModel { options, )) } - } /// Turn one forward pass into instances in source-image space. @@ -543,7 +542,11 @@ fn assemble_mask( let (gx1, gy1) = ((gx0 + 1).min(pw - 1), (gy0 + 1).min(ph - 1)); let mut acc = 0.0; - for (k, &c) in coeffs.iter().enumerate().take(PROTOTYPES.min(protos.shape()[0])) { + for (k, &c) in coeffs + .iter() + .enumerate() + .take(PROTOTYPES.min(protos.shape()[0])) + { if c == 0.0 { continue; } @@ -643,7 +646,12 @@ mod tests { #[test] fn letterbox_round_trips_a_landscape_window() { - let window = Window { x: 0.0, y: 0.0, w: 1600.0, h: 1067.0 }; + let window = Window { + x: 0.0, + y: 0.0, + w: 1600.0, + h: 1067.0, + }; let lb = Letterbox::fit(window.w, window.h); // A source point maps into the input and back to where it started. diff --git a/ui/dr-ui/src/collections_ui.rs b/ui/dr-ui/src/collections_ui.rs index d1036e7..c63fa0d 100644 --- a/ui/dr-ui/src/collections_ui.rs +++ b/ui/dr-ui/src/collections_ui.rs @@ -548,8 +548,10 @@ pub fn cancel_press(window: &AppWindow, ctl: &Rc, ids: &[ let Some(undo) = ctl.press_undo.borrow_mut().take() else { return; }; - let (previous_anchor, cursor) = - undo.restore(&mut ctl.selection.borrow_mut(), &mut ctl.anchor.borrow_mut()); + let (previous_anchor, cursor) = undo.restore( + &mut ctl.selection.borrow_mut(), + &mut ctl.anchor.borrow_mut(), + ); ctl.previous_anchor.set(previous_anchor); ctl.set_cursor(cursor); sync_selection(window, ctl, ids); diff --git a/ui/dr-ui/src/develop.rs b/ui/dr-ui/src/develop.rs index 95851c6..8c84511 100644 --- a/ui/dr-ui/src/develop.rs +++ b/ui/dr-ui/src/develop.rs @@ -206,7 +206,13 @@ impl SegmentationJob { // few places in this codebase where not linearising is correct. let rgb = rgba .chunks_exact(4) - .flat_map(|p| [p[0] as f32 / 255.0, p[1] as f32 / 255.0, p[2] as f32 / 255.0]) + .flat_map(|p| { + [ + p[0] as f32 / 255.0, + p[1] as f32 / 255.0, + p[2] as f32 / 255.0, + ] + }) .collect(); Ok((rgb, pw as usize, ph as usize)) } @@ -1458,10 +1464,7 @@ impl DevelopSession { /// What kind of mask a layer is — "regions", "linear", "radial". pub fn mask_kind(&self, id: &str) -> &'static str { - self.graph - .masks() - .get(id) - .map_or("", |l| l.source.kind()) + self.graph.masks().get(id).map_or("", |l| l.source.kind()) } pub fn mask_inverted(&self, id: &str) -> bool { @@ -1625,7 +1628,11 @@ impl DevelopSession { width: 0.3, } }; - if !self.graph.masks_mut().push(MaskLayer::new(id.clone(), source)) { + if !self + .graph + .masks_mut() + .push(MaskLayer::new(id.clone(), source)) + { return None; } self.active_mask = Some(id.clone()); @@ -1660,7 +1667,8 @@ impl DevelopSession { pub fn set_mask_feather(&mut self, id: &str, feather: f32) { if let Some(layer) = self.graph.masks_mut().get_mut(id) { layer.feather = feather.clamp(0.0, 1.0); - self.history.record(&self.graph, Edit::Op(OpId("mask-feather"))); + self.history + .record(&self.graph, Edit::Op(OpId("mask-feather"))); } } @@ -1694,7 +1702,8 @@ impl DevelopSession { pub fn set_mask_morph_radius(&mut self, id: &str, radius: f32) { if let Some(layer) = self.graph.masks_mut().get_mut(id) { layer.morph_radius = radius.clamp(0.0, 1.0); - self.history.record(&self.graph, Edit::Op(OpId("mask-morph"))); + self.history + .record(&self.graph, Edit::Op(OpId("mask-morph"))); } } @@ -1702,7 +1711,10 @@ impl DevelopSession { pub fn mask_falloff(&self, id: &str) -> usize { use dr_pipeline::mask::Falloff; self.graph.masks().get(id).map_or(0, |l| { - Falloff::ALL.iter().position(|&f| f == l.falloff).unwrap_or(0) + Falloff::ALL + .iter() + .position(|&f| f == l.falloff) + .unwrap_or(0) }) } @@ -1732,7 +1744,10 @@ impl DevelopSession { pub fn mask_is_shapeable(&self, id: &str) -> bool { use dr_pipeline::mask::MaskSource; self.graph.masks().get(id).is_some_and(|l| { - matches!(l.source, MaskSource::Subject { .. } | MaskSource::Regions { .. }) + matches!( + l.source, + MaskSource::Subject { .. } | MaskSource::Regions { .. } + ) }) } @@ -1742,7 +1757,8 @@ impl DevelopSession { // `Op` rather than `Discrete`: opacity is dragged, and a drag is // one decision however many values it passes through. `Discrete` // would put every intermediate position on the undo stack. - self.history.record(&self.graph, Edit::Op(OpId("mask-opacity"))); + self.history + .record(&self.graph, Edit::Op(OpId("mask-opacity"))); } } @@ -2510,9 +2526,8 @@ mod tests { fn a_job_carries_the_session_it_was_taken_from() { let Some(ctx) = headless() else { return }; let rgba: Vec = (0..16 * 16).flat_map(|_| [128, 128, 128, 255]).collect(); - let session = - DevelopSession::open_rgb(&ctx, &rgba, 16, 16, dr_types::Orientation::NORMAL) - .expect("session"); + let session = DevelopSession::open_rgb(&ctx, &rgba, 16, 16, dr_types::Orientation::NORMAL) + .expect("session"); assert_eq!(session.segmentation_job().session(), session.id()); } @@ -2523,9 +2538,8 @@ mod tests { fn an_abandoned_job_does_no_work() { let Some(ctx) = headless() else { return }; let rgba: Vec = (0..16 * 16).flat_map(|_| [128, 128, 128, 255]).collect(); - let session = - DevelopSession::open_rgb(&ctx, &rgba, 16, 16, dr_types::Orientation::NORMAL) - .expect("session"); + let session = DevelopSession::open_rgb(&ctx, &rgba, 16, 16, dr_types::Orientation::NORMAL) + .expect("session"); let job = session.segmentation_job(); job.abandon().now(); @@ -3993,7 +4007,10 @@ mod tests { let narrowed = session.rows().len(); assert!(narrowed > 0, "a tab must show something"); - assert!(narrowed < all, "and less than everything: {narrowed} of {all}"); + assert!( + narrowed < all, + "and less than everything: {narrowed} of {all}" + ); } /// The trap: `op_index` counts over *every* capability, so a row that @@ -4054,7 +4071,6 @@ mod tests { session.set_active_tab(99); assert_eq!(session.rows().len(), all); } - } /// `dr_pipeline`'s morphology, as `dr_segment` names it. diff --git a/ui/dr-ui/src/masks_ui.rs b/ui/dr-ui/src/masks_ui.rs index f83679e..789c011 100644 --- a/ui/dr-ui/src/masks_ui.rs +++ b/ui/dr-ui/src/masks_ui.rs @@ -844,7 +844,6 @@ mod tests { ); } - /// The ordinary case: the answer comes back to the photograph that asked. #[test] fn a_result_for_the_open_photograph_is_applied() { diff --git a/ui/dr-ui/src/presets.rs b/ui/dr-ui/src/presets.rs index 4cc1469..618562d 100644 --- a/ui/dr-ui/src/presets.rs +++ b/ui/dr-ui/src/presets.rs @@ -318,8 +318,8 @@ fn refresh_thumbnail_for( session: &Rc>>, library: &Rc, ) { - let worth_it = window.get_can_undo() - || session.borrow().as_ref().is_some_and(|s| !s.is_neutral()); + let worth_it = + window.get_can_undo() || session.borrow().as_ref().is_some_and(|s| !s.is_neutral()); if !worth_it { return; }