diff --git a/crates/render/src/dots.rs b/crates/render/src/dots.rs index f3effd1f1..5d4ae1fc1 100644 --- a/crates/render/src/dots.rs +++ b/crates/render/src/dots.rs @@ -92,13 +92,14 @@ pub(crate) fn draw(pix: &mut Pixmap, view: &View, d: &Dots) { } } -/// The device pixel nearest `p` pixels from the view's top-left. A mark halfway between two pixels -/// goes to the right (or lower) one on either side of the origin, and `p` is first rounded to -/// 1/65536 px: a view a whole number of pixels off (a pan, a band, a patch) reaches it with another -/// rounding error, and would otherwise put the marks that fall halfway on the other pixel. +/// The first device pixel of a mark whose edge is `p` pixels from the view's top-left: the one +/// whose left (or top) edge is nearest, and the right (or lower) one when two are as near. `p` is +/// first rounded to 1/65536 px, halves up as well: a view a whole number of pixels off (a pan, a +/// band, a patch) reaches it with another rounding error, and, rounded either way, would put the +/// marks that fall halfway on the other pixel. fn nearest_px(p: f64) -> f64 { const STEPS: f64 = 65_536.0; - ((p * STEPS).round() / STEPS + 0.5).floor() + ((p * STEPS + 0.5).floor() / STEPS + 0.5).floor() } /// Which line covers each device pixel along an axis: 0 none, 1 a faded one, 2 a strong one. diff --git a/crates/render/src/dots_tests.rs b/crates/render/src/dots_tests.rs index a5ff2edc9..18d96345f 100644 --- a/crates/render/src/dots_tests.rs +++ b/crates/render/src/dots_tests.rs @@ -158,3 +158,25 @@ fn dots_halfway_between_pixels_stay_put() { } } } + +/// A mark that rounds to a half step of 1/65536 px goes the same way on both sides of a view's +/// origin too: at a step of 25.5 − 2⁻¹⁷ px, the first mark is where 1/65536 px rounds up. +#[test] +fn dots_on_the_rounding_step_stay_put() { + let (d, page) = page_with_square(); + let step = 25.5 - 2f64.powi(-17); + for lines in [false, true] { + let opts = Options { dots: Some(Dots { step, lines, ..dots() }), ..Default::default() }; + let full = View { origin: (0.0, 0.0), scale: 1.0, width: 300, height: 200 }; + let a = render_view_single(&d, page, &full, &opts).unwrap(); + for pan in 1..60u32 { + let v = View { origin: (f64::from(pan), f64::from(pan)), scale: 1.0, width: 200, height: 120 }; + let b = render_view_single(&d, page, &v, &opts).unwrap(); + for y in 0..120 { + for x in 0..200 { + assert_eq!(rgba(&b, x, y), rgba(&a, x + pan, y + pan), "lines {lines}, a pan by {pan}, at {x}, {y}"); + } + } + } + } +}