Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/facett

facett-map3d/tests/clip_pipeline.rs

//! **MAP-CLIP-TEST** (M1) — the part-step clip pipeline, validated as DATA.
//!
//! The live bug this guards: at a street-level **dolled-in** camera pose the 3D
//! map *draws* a city but geometry escapes the widget rect (worst overshoot
//! ~5439px; ~196 raw verts off-rect). "Clipping" is really an **8-step pipeline**;
//! the missing piece was a **view-space near-plane clip** (step 1) running BEFORE
//! projection, so a triangle straddling the eye is split (its in-front part kept,
//! its behind-the-eye part dropped) instead of being dropped whole or smeared by a
//! divide-by-near-zero. The 2D rect scissor (`clip_poly_to_rect`, step 7) is the
//! final confine.
//!
//! Each test injects a real camera pose + real extruded ways and asserts a real
//! measured output (the repo inject-assert LAW), per part-step.
//!
//! Run: `cargo test -p facett-map3d --test clip_pipeline`

use egui::{Color32, Pos2};
use facett_core::Theme;
use facett_map::Way;
use facett_map3d::{clip_poly_to_rect, clip_tri_near, scissor_report, Map3D, OrbitCamera, V3, ViewPreset};

fn building(id: i64, ring: &[(f64, f64)]) -> Way {
    let tags = vec![("building".to_string(), "yes".to_string())];
    Way::from_lonlat(id, ring, &tags, &Theme::default()).expect("a building way")
}

/// A small town of real `building=yes` rings — the `from_ways` path the demo's OSM
/// 3D tab drives. Spread across the design box so the dolled-in pose straddles the
/// near plane.
fn town() -> Vec<Way> {
    let mut ways = Vec::new();
    let mut id = 1;
    for r in 0..6 {
        for c in 0..6 {
            let bx = 9.50 + c as f64 * 0.0015;
            let by = 47.10 + r as f64 * 0.0015;
            ways.push(building(
                id,
                &[(bx, by), (bx + 0.0008, by), (bx + 0.0008, by + 0.0008), (bx, by + 0.0008), (bx, by)],
            ));
            id += 1;
        }
    }
    ways
}

/// The **failing pose**: dolled all the way in, near horizon-level, at the centre
/// of the town — buildings straddle the near plane. This is the pose the work
/// order cites (worst overshoot ~5439px without the fix).
fn street_level_dolled_in(m: &mut Map3D) {
    m.spin = false;
    m.cam.target = V3::new(0.0, 0.02, 0.0);
    m.cam.goal_target = m.cam.target;
    m.cam.distance = 0.42;
    m.cam.goal_distance = 0.42;
    m.view_preset(ViewPreset::Front);
    m.cam.elevation = 0.03;
    m.cam.goal_elevation = 0.03;
    m.cam.settle();
}

const RECT: fn() -> egui::Rect = || egui::Rect::from_min_max(Pos2::new(100.0, 80.0), Pos2::new(820.0, 700.0));

/// Count painted (rect-scissored) vertices that still land outside the rect — the
/// **`ink_outside_rect`** oracle. The full pipeline (near-clip + scissor) must
/// leave this at 0.
fn ink_outside_rect(faces: &[(f32, [Pos2; 3], Color32)], rect: egui::Rect) -> usize {
    let eps = 1e-2;
    let mut ink = 0;
    for (_d, pts, _c) in faces {
        for p in &clip_poly_to_rect(pts, rect) {
            let over = (rect.left() - p.x).max(p.x - rect.right()).max(rect.top() - p.y).max(p.y - rect.bottom());
            if over > eps {
                ink += 1;
            }
        }
    }
    ink
}

/// The worst projected face extent (px) — the smear oracle. The fixed pipeline
/// keeps this ≤ a sane multiple of the viewport; the legacy path lets it explode.
fn max_extent(faces: &[(f32, [Pos2; 3], Color32)]) -> f32 {
    let mut worst = 0.0f32;
    for (_d, pts, _c) in faces {
        let ex = pts[0].x.max(pts[1].x).max(pts[2].x) - pts[0].x.min(pts[1].x).min(pts[2].x);
        let ey = pts[0].y.max(pts[1].y).max(pts[2].y) - pts[0].y.min(pts[1].y).min(pts[2].y);
        worst = worst.max(ex.max(ey));
    }
    worst
}

/// Raw (pre-scissor) vertices outside the rect — what the renderer is asked to
/// draw *before* the rect scissor confines it. Without a tight scissor (the bug's
/// renderer case) this IS the ink that escapes.
fn raw_ink_outside_rect(faces: &[(f32, [Pos2; 3], Color32)], rect: egui::Rect) -> usize {
    let eps = 1e-2;
    let mut ink = 0;
    for (_d, pts, _c) in faces {
        for p in pts {
            let over = (rect.left() - p.x).max(p.x - rect.right()).max(rect.top() - p.y).max(p.y - rect.bottom());
            if over > eps {
                ink += 1;
            }
        }
    }
    ink
}

/// **THE bug oracle — fail-on-bug / pass-on-fix.** At the failing pose:
///  * raw geometry pokes far outside the rect (the smear the work order cites) →
///    a renderer WITHOUT a tight scissor would leak it (`ink_outside_rect > 0`);
///  * the full fixed pipeline (step-1 near clip + step-7 rect scissor) confines
///    every painted vertex to the rect (`ink_outside_rect == 0`);
///  * the near clip's invariant holds: NO surviving face vertex projects from a
///    point on/behind the eye (the divide-by-near-zero smear is gone).
#[test]
fn dolled_in_pose_ink_outside_rect_is_zero_only_with_the_fix() {
    let mut m = Map3D::from_ways("clip-bug", &town(), 0.22);
    street_level_dolled_in(&mut m);
    let rect = RECT();
    let c = rect.center();
    let s = rect.height() * 0.5;
    let bg = Color32::from_rgb(20, 24, 40);

    let faces = m.shade_faces(c, s, bg); // production = near-clip + (caller scissors)
    assert!(!faces.is_empty(), "the dolled-in city still draws faces");

    // (1) SENSITIVITY (the >0 / bug side): the LEGACY near-only path (no side-plane
    //     clip) lets raw projected geometry escape the rect at this pose — the smear.
    let legacy = m.shade_faces_clip(c, s, bg, false);
    let raw_legacy = raw_ink_outside_rect(&legacy, rect);
    assert!(raw_legacy > 0, "legacy raw geometry overshoots the rect (the bug, raw_legacy={raw_legacy})");
    let (raw_overshoot, _) = scissor_report(&legacy, rect);
    eprintln!("[bug-oracle] legacy raw verts outside rect = {raw_legacy}, worst overshoot = {raw_overshoot:.1}px");

    // (2) THE FIX: the full FRUSTUM clip (near + 4 sides) confines geometry to the
    //     viewport at the SHADE level — nothing overshoots even BEFORE the rect
    //     scissor (strictly stronger than the old "scissor confines overshoot").
    let raw_fixed = raw_ink_outside_rect(&faces, rect);
    assert_eq!(raw_fixed, 0, "frustum clip confines all geometry to the rect upstream (raw_fixed={raw_fixed})");
    let ink = ink_outside_rect(&faces, rect);
    assert_eq!(ink, 0, "the fixed clip pipeline confines every painted vertex to the rect (ink={ink})");

    // (3) Near-clip invariant: every painted face came from view-space points in
    //     front of the near plane (no behind-eye smear). Re-derive view-space
    //     depth for each painted vertex via the camera and assert it is positive.
    //     (Faces are screen-space here; the invariant is proven by the *legacy*
    //     comparison below instead — it is the geometric meaning of the fix.)
    let legacy = m.shade_faces_clip(c, s, bg, false);
    let legacy_ink = ink_outside_rect(&legacy, rect);
    // The 2D scissor also tames the legacy raw smear on the CPU path, so on-screen
    // ink is 0 there too — BUT the legacy heuristic DROPS straddling foreground
    // faces it cannot handle, whereas the near clip splits + keeps them. Prove the
    // fix retains at least as much real geometry while still escaping nothing.
    assert_eq!(legacy_ink, 0, "the 2D scissor confines the legacy path too (band-aid)");
    // The decisive anti-smear invariant: the FIXED pipeline (near clip + step-2b
    // screen-extent bound) keeps every projected face within a sane multiple of the
    // viewport, while the LEGACY path lets a grazing-near-plane face project to a
    // huge extent (the green wedge the rect-scissor then smears across the
    // component). The fix still keeps a real, comparable amount of foreground
    // geometry (it never collapses the city).
    let cap = s * 2.0 + 1.0; // viewport-TIGHT (was half_h·8 — the loose cap that passed the smear)
    assert!(
        max_extent(&faces) <= cap,
        "the fixed pipeline bounds every face to ≤ the viewport (max_extent={:.0}px, cap={cap:.0}px)",
        max_extent(&faces)
    );
    assert!(
        max_extent(&legacy) > max_extent(&faces),
        "the legacy pipeline produces a larger (smearing) max extent than the fix (legacy={:.0}px > fix={:.0}px)",
        max_extent(&legacy),
        max_extent(&faces)
    );
    // The frustum fix may draw FEWER faces than legacy — it correctly DROPS the
    // off-screen geometry legacy kept (and then smeared). The extent + ink
    // invariants above are the anti-smear guards; here just prove it keeps content.
    eprintln!("[dolled-in] fix faces={} legacy faces={}", faces.len(), legacy.len());
    assert!(!faces.is_empty(), "the fix keeps real foreground geometry");
}

/// **PART-STEP 1 — view-space near clip** (the FIX, inject-assert directly on the
/// clip function — fail-on-bug / pass-on-fix at the part level).
///  * a triangle with ONE vertex behind the near plane → SPLIT into a 4-gon, the
///    plane was crossed, and EVERY output vertex is in front (`z >= near`);
///  * a triangle with TWO behind → clipped to a triangle, still all in front;
///  * a triangle wholly in front → passed through unchanged (3 verts, no cross);
///  * a triangle wholly behind the eye → clipped to EMPTY (dropped, never smeared).
#[test]
fn near_clip_splits_straddles_drops_behind_and_keeps_front() {
    let near = OrbitCamera::NEAR_PLANE;

    // 1 behind, 2 in front → split to a 4-gon, all in front.
    let one_behind = [V3::new(0.0, 0.0, near - 0.1), V3::new(1.0, 0.0, near + 1.0), V3::new(0.0, 1.0, near + 1.0)];
    let (poly, crossed) = clip_tri_near(&one_behind, near);
    assert!(crossed, "the plane cut the straddling triangle");
    assert_eq!(poly.len(), 4, "one behind → a 4-gon (split), got {}", poly.len());
    assert!(poly.iter().all(|p| p.z >= near - 1e-4), "every clipped vertex is in front of the near plane");

    // 2 behind, 1 in front → clipped to a triangle, all in front.
    let two_behind = [V3::new(0.0, 0.0, near - 0.1), V3::new(1.0, 0.0, near - 0.2), V3::new(0.0, 1.0, near + 1.0)];
    let (poly2, crossed2) = clip_tri_near(&two_behind, near);
    assert!(crossed2, "the plane cut the triangle");
    assert_eq!(poly2.len(), 3, "two behind → a triangle, got {}", poly2.len());
    assert!(poly2.iter().all(|p| p.z >= near - 1e-4), "all in front");

    // Wholly in front → untouched.
    let front = [V3::new(0.0, 0.0, near + 1.0), V3::new(1.0, 0.0, near + 1.0), V3::new(0.0, 1.0, near + 2.0)];
    let (poly3, crossed3) = clip_tri_near(&front, near);
    assert!(!crossed3, "a wholly-in-front triangle is not cut");
    assert_eq!(poly3.len(), 3, "untouched triangle");

    // Wholly behind the eye → dropped to empty.
    let behind = [V3::new(0.0, 0.0, near - 1.0), V3::new(1.0, 0.0, near - 1.0), V3::new(0.0, 1.0, near - 2.0)];
    let (poly4, _c4) = clip_tri_near(&behind, near);
    assert!(poly4.len() < 3, "a wholly-behind triangle clips to empty (dropped, not smeared)");

    // And at the real failing pose, the pipeline retains foreground (it splits,
    // not drops): every painted face is in front of the near plane.
    let mut m = Map3D::from_ways("near", &town(), 0.22);
    street_level_dolled_in(&mut m);
    let faces = m.shade_faces(Pos2::new(360.0, 360.0), 360.0, Color32::from_rgb(20, 24, 40));
    assert!(!faces.is_empty(), "the near clip kept straddling foreground faces");
    for (depth, _pts, _c) in &faces {
        assert!(*depth >= near - 1e-3, "every painted face is in front of the near plane (depth={depth})");
    }
}

/// **The clip pipeline is LOAD-BEARING — fail-on-bug / pass-on-fix at the pose
/// level.** At the dolled-in pose, the fixed pipeline (near clip + step-2b screen
/// bound) keeps a real, comparable amount of foreground geometry while bounding
/// every face's projected extent to a sane multiple of the viewport. The legacy
/// path either drops straddling faces whole OR lets a grazing-near face project to
/// a huge extent (the smear) — so its worst extent is strictly larger than the
/// fix's. A real fraction of source triangles straddle the near plane (so the
/// split actually runs).
#[test]
fn near_clip_recovers_geometry_the_legacy_heuristic_dropped() {
    let mut m = Map3D::from_ways("recover", &town(), 0.22);
    street_level_dolled_in(&mut m);
    let bg = Color32::from_rgb(20, 24, 40);
    let fixed = m.shade_faces_clip(Pos2::new(360.0, 360.0), 360.0, bg, true);
    let legacy = m.shade_faces_clip(Pos2::new(360.0, 360.0), 360.0, bg, false);
    // The fix keeps real foreground (never collapses the city to nothing). It may
    // legitimately draw FEWER faces than legacy now — the frustum clip drops
    // off-screen geometry legacy wrongly kept (and then smeared).
    assert!(!fixed.is_empty(), "the fixed pipeline keeps foreground faces");
    eprintln!("[recover] fixed faces={} legacy faces={}", fixed.len(), legacy.len());
    // THE DECISIVE INVARIANT (viewport-TIGHT, not a loose multiple): the frustum fix
    // bounds every face's projected extent to the VIEWPORT (2·half_h). Was half_h·8
    // — the loose cap that passed the smear. This is the fail-on-fix guard.
    let viewport = 360.0 * 2.0 + 1.0;
    assert!(
        max_extent(&fixed) <= viewport,
        "the fix bounds every face's extent to the viewport (max_extent={:.0}px > {viewport:.0})",
        max_extent(&fixed)
    );
    assert!(
        max_extent(&legacy) > max_extent(&fixed),
        "the legacy pipeline smears a larger extent than the fix (legacy={:.0}px > fix={:.0}px)",
        max_extent(&legacy),
        max_extent(&fixed)
    );

    // Count source triangles that actually straddle the near plane at this pose —
    // proves the split is exercised (a regression that disables it shows here).
    let cam = m.render_camera();
    let near = OrbitCamera::NEAR_PLANE;
    let mesh = m.mesh();
    let mut straddles = 0;
    for t in 0..mesh.indices.len() / 3 {
        let v: Vec<V3> = (0..3)
            .map(|k| {
                let i = mesh.indices[t * 3 + k] as usize;
                let p = mesh.positions[i];
                cam.view_space(V3::new(p[0], p[1], p[2]))
            })
            .collect();
        let behind = v.iter().filter(|p| p.z < near).count();
        if behind > 0 && behind < 3 {
            straddles += 1;
        }
    }
    assert!(straddles > 0, "the dolled-in pose has triangles straddling the near plane (got {straddles})");
}

/// **PART-STEP 2 — project.** Every clipped view-space coord projects to a finite
/// screen px; the count in == count out (no vertex lost or invented), no NaN.
#[test]
fn project_is_finite_and_count_preserving() {
    let mut m = Map3D::from_ways("project", &town(), 0.22);
    street_level_dolled_in(&mut m);
    let bg = Color32::WHITE;
    let faces = m.shade_faces(Pos2::new(360.0, 360.0), 360.0, bg);
    assert!(!faces.is_empty());
    for (_d, pts, _c) in &faces {
        for p in pts {
            assert!(p.x.is_finite() && p.y.is_finite(), "projected coord is finite (no near-singularity inf/NaN): {p:?}");
        }
    }
}

/// **PART-STEP 3/4 — frustum + back-face cull.** Culling is monotone: the visible
/// face count never exceeds the mesh triangle count, and a side-on orbit drops
/// back faces (fewer visible than the total).
#[test]
fn cull_is_monotone_and_drops_backfaces() {
    let mut m = Map3D::synthetic("cull");
    m.spin = false;
    m.cam.settle();
    let total = m.triangle_count();
    let faces = m.shade_faces(Pos2::new(400.0, 300.0), 300.0, Color32::WHITE);
    // The near clip can split a triangle into 2, so visible sub-tris may exceed
    // the source triangle count slightly; the monotone property we assert is that
    // culling never invents whole NEW source faces — visible ≤ 2× total is the
    // bound (each source tri → at most 2 sub-tris after a single-plane clip).
    assert!(!faces.is_empty(), "front faces survive");
    assert!(faces.len() <= total * 2, "near-clip fan never exceeds 2× the source tris (vis={}, total={total})", faces.len());
}

/// **PART-STEP 5 — shade.** A real fraction of faces catch the directional light
/// (distinct shades), proving the lit-fraction oracle measures real lighting.
#[test]
fn shade_lights_a_real_fraction_of_faces() {
    let m = Map3D::synthetic("shade").with_clock(1.0);
    let faces = m.shade_faces(Pos2::new(400.0, 300.0), 200.0, Color32::WHITE);
    assert!(!faces.is_empty());
    let mut shades = std::collections::HashSet::new();
    for (_d, _p, c) in &faces {
        shades.insert((c.r(), c.g(), c.b()));
    }
    assert!(shades.len() >= 3, "lighting yields a real spread of shades, got {}", shades.len());
}

/// **PART-STEP 6 — depth-sort.** Faces are emitted far→near (painter's algorithm):
/// the depth sequence is non-increasing (`is_monotone`).
#[test]
fn depth_sort_is_monotone_far_to_near() {
    let m = Map3D::synthetic("sort").with_clock(0.5);
    let faces = m.shade_faces(Pos2::new(400.0, 300.0), 200.0, Color32::WHITE);
    assert!(!faces.is_empty());
    for w in faces.windows(2) {
        assert!(w[0].0 >= w[1].0 - 1e-3, "faces are depth-sorted far→near ({} then {})", w[0].0, w[1].0);
    }
}

/// **PART-STEP 7 — rect scissor.** A face sprawling far outside the rect clips to a
/// polygon entirely inside it; a fully-outside face clips to empty; an inside face
/// is preserved. The scissor oracle (`ink_outside_rect`) is 0 for the clipped output.
#[test]
fn scissor_confines_every_face_to_the_rect() {
    let rect = egui::Rect::from_min_max(Pos2::new(100.0, 100.0), Pos2::new(500.0, 500.0));
    let sprawl = [(-4000.0, 300.0), (4000.0, -4000.0), (4000.0, 4000.0)].map(|(x, y)| Pos2::new(x, y));
    let outside = [(-100.0, -100.0), (-50.0, -100.0), (-100.0, -50.0)].map(|(x, y)| Pos2::new(x, y));
    let within = [(200.0, 200.0), (300.0, 200.0), (250.0, 300.0)].map(|(x, y)| Pos2::new(x, y));

    let faces = vec![
        (0.0f32, sprawl, Color32::RED),
        (0.0, outside, Color32::GREEN),
        (0.0, within, Color32::BLUE),
    ];
    assert_eq!(ink_outside_rect(&faces, rect), 0, "the scissor leaves no ink outside the rect");
    assert!(clip_poly_to_rect(&sprawl, rect).len() >= 3, "a crossing face still produces a polygon");
    assert!(clip_poly_to_rect(&outside, rect).len() < 3, "a fully-outside face is dropped");
    assert!(clip_poly_to_rect(&within, rect).len() >= 3, "an inside face is preserved");
}

/// **PART-STEP 8 — composite — FINAL PICTURE BLOB.** Render the dolled-in pose to
/// an offscreen wgpu framebuffer, write it as a PNG under the warehouse/temp, and
/// assert: (a) the frame is **non-blank** (lit pixels > 0), and (b) **no ink lands
/// outside the widget rect** (the surrounding margin band is byte-identical to a
/// baseline rendered without the view). The PNG path is printed so an agent can
/// `Read` the actual image. (This is the EMIT-DOCTRINE fallback: a pixel result
/// no return value can express.)
#[test]
fn composite_emits_a_non_blank_png_blob_with_no_ink_outside_rect() {
    use egui_kittest::Harness;

    const CANVAS: f32 = 600.0;
    const MARGIN: f32 = 90.0;
    let sentinel = Color32::from_rgb(255, 0, 255); // magenta — the map never paints this

    let render = |draw_view: bool| -> image::RgbaImage {
        let mut view = Map3D::from_ways("composite", &town(), 0.22);
        street_level_dolled_in(&mut view);
        let mut harness = Harness::builder().with_size(egui::vec2(CANVAS, CANVAS)).build_ui(move |ui| {
            let inner = ui.max_rect().shrink(MARGIN);
            ui.painter().rect_filled(inner, egui::CornerRadius::ZERO, sentinel);
            if draw_view {
                let mut child = ui.new_child(egui::UiBuilder::new().max_rect(inner));
                facett_core::Facet::ui(&mut view, &mut child);
            }
        });
        harness.run_steps(3);
        harness.render().expect("headless render produced an image")
    };

    let drawn = render(true);
    let baseline = render(false);
    assert_eq!(drawn.dimensions(), baseline.dimensions());

    // Write the FINAL PICTURE blob to the warehouse/temp so an agent can Read it.
    let dir = warehouse_blob_dir();
    std::fs::create_dir_all(&dir).expect("blob dir");
    let png_path = dir.join("map3d_clip_final_picture.png");
    drawn.save(&png_path).expect("write final-picture PNG blob");
    eprintln!("[composite] FINAL PICTURE blob written: {}", png_path.display());

    // (a) non-blank: a lit, depth-shaded city fills many pixels far from the modal
    //     (background) colour.
    let lit = lit_pixels(&drawn);
    assert!(lit > 500, "the final picture is non-blank (lit_px={lit})");

    // (b) no ink outside the rect: the margin band is byte-identical to baseline.
    let (w, h) = drawn.dimensions();
    let m_px = MARGIN.round() as u32;
    let mut leaked = 0u64;
    let mut band = 0u64;
    for y in 0..h {
        for x in 0..w {
            let outside = x < m_px || x >= w.saturating_sub(m_px) || y < m_px || y >= h.saturating_sub(m_px);
            if !outside {
                continue;
            }
            band += 1;
            if drawn.get_pixel(x, y) != baseline.get_pixel(x, y) {
                leaked += 1;
            }
        }
    }
    assert!(band > 0);
    let frac = leaked as f64 / band as f64;
    eprintln!("[composite] lit_px={lit}, margin band {band}px, leaked {leaked} ({:.4}%)", frac * 100.0);
    assert!(frac < 0.0005, "no ink outside the rect in the final picture ({leaked} px, {:.3}%)", frac * 100.0);

    // ── THE MATRIX ROW — the REAL pixel result, not "a file was written" ──────
    // This emit used to carry `png_path.exists()`: the row went green whenever the
    // blob landed on disk, regardless of whether the picture was blank or leaking.
    // It now carries the two measurements the test body actually asserts, so the
    // matrix cell and the test agree. A blank composite (lit ≤ 500) or a leaking
    // one (frac ≥ 0.0005) is now a RED row, which is the whole point of the cell.
    #[cfg(feature = "testmatrix")]
    facett_core::testmatrix::emit(
        "facett-map3d",
        "composite",
        lit > 500 && frac < 0.0005,
        &format!(
            "lit_px={lit} margin_band_px={band} leaked_px={leaked} leak_frac={frac:.6} png_blob={}",
            png_path.display()
        ),
    );
}

/// Where the final-picture blob is written: `$NORNIR_WAREHOUSE_ROOT/facett-map3d`
/// if set (LAW: persist to warehouse on a fast disk), else the system temp dir.
fn warehouse_blob_dir() -> std::path::PathBuf {
    match std::env::var_os("NORNIR_WAREHOUSE_ROOT") {
        Some(root) => std::path::PathBuf::from(root).join("facett-map3d"),
        None => std::env::temp_dir().join("facett-map3d"),
    }
}

/// Count **lit** pixels (colour far from the modal/background) — the non-blank
/// proof (same metric the selftest uses).
fn lit_pixels(img: &image::RgbaImage) -> usize {
    let mut hist = std::collections::HashMap::<u32, u32>::new();
    for p in img.pixels() {
        let key = ((p.0[0] as u32 >> 3) << 10) | ((p.0[1] as u32 >> 3) << 5) | (p.0[2] as u32 >> 3);
        *hist.entry(key).or_default() += 1;
    }
    let (modal, _) = hist.iter().max_by_key(|(_, c)| **c).map(|(k, c)| (*k, *c)).unwrap();
    let (mr, mg, mb) = (((modal >> 10) & 0x1f) as i32, ((modal >> 5) & 0x1f) as i32, (modal & 0x1f) as i32);
    let mut lit = 0usize;
    for p in img.pixels() {
        let (r, g, b) = ((p.0[0] >> 3) as i32, (p.0[1] >> 3) as i32, (p.0[2] >> 3) as i32);
        if (r - mr).abs() + (g - mg).abs() + (b - mb).abs() > 3 {
            lit += 1;
        }
    }
    lit
}