diff --git a/crates/trace/src/lib.rs b/crates/trace/src/lib.rs index 185da7558..0cf283f6d 100644 --- a/crates/trace/src/lib.rs +++ b/crates/trace/src/lib.rs @@ -642,7 +642,12 @@ fn trace_paths(img: &Raster, params: &TraceParams, max_anchors: usize) -> Result let largest = job.boxes.iter().flatten().map(|b| (b.2 - b.0) * (b.3 - b.1)).max().unwrap_or(1).max(1); let at_a_time = ((512 << 20) / (largest * 10)).max(1); for (paths, &(_, ci)) in parallel_map_at_most(at_a_time, &layers, |&(r, ci)| job.layer(r, ci)).into_iter().zip(&layers) { - out.paths.extend(paths.ok_or(TraceError::TooComplex { max: max_anchors })?); + let paths = paths.ok_or(TraceError::TooComplex { max: max_anchors })?; + // A layer with no paths adds no color to the palette. + if paths.is_empty() { + continue; + } + out.paths.extend(paths); let color = q.palette[ci]; if !out.palette.contains(&color) { out.palette.push(color); diff --git a/crates/trace/src/tests.rs b/crates/trace/src/tests.rs index fd727d266..82e178b6f 100644 --- a/crates/trace/src/tests.rs +++ b/crates/trace/src/tests.rs @@ -1355,6 +1355,46 @@ fn a_traced_pixel_that_straightens_to_no_area_is_left_out_of_fills_and_kept_as_a } } +#[test] +fn a_color_whose_every_path_straightens_to_no_area_is_left_out_of_the_palette() { + // A light rectangle with a black block on it and a one-pixel row of a pale blue along its lower + // edge, on a transparent background. Traced with a library of the light, the black and a + // blue, the row is the only part in the blue. + const BLUE: [u8; 3] = [40, 120, 240]; + let img = Raster::from_fn(200, 120, |x, y| { + if (60..140).contains(&x) && (70..90).contains(&y) { + BLACK + } else if (20..180).contains(&x) && (20..100).contains(&y) { + [240, 240, 220, 255] + } else if y == 100 && (30..170).contains(&x) { + [140, 180, 230, 255] + } else { + [0, 0, 0, 0] + } + }); + let params = TraceParams { + mode: Mode::Color, + palette: Palette::DocumentLibrary, + swatches: vec![[240, 240, 220], [0, 0, 0], BLUE], + colors: 3, + paths: 90.0, + corners: 60.0, + noise: 25, + method: Method::Abutting, + ..TraceParams::default() + }; + let used = |res: &TraceResult| res.palette.iter().all(|c| res.paths.iter().any(|p| p.color == *c)); + // Left alone, the row is a path in the blue, and the blue is in the palette. + let res = trace(&img, ¶ms); + assert!(res.paths.iter().any(|p| p.color == BLUE), "{:?}", res.paths.iter().map(|p| (p.color, p.path.bounds())).collect::>()); + assert!(res.palette.contains(&BLUE) && used(&res), "{:?}", res.palette); + // Snapped, both sides of the row straighten onto one line and its path is left out: no path is + // in the blue, and the palette leaves it out too. + let res = trace(&img, &TraceParams { snap_curves_to_lines: true, ..params }); + assert!(res.paths.iter().all(|p| p.color != BLUE), "{:?}", res.paths.iter().map(|p| (p.color, p.path.bounds())).collect::>()); + assert!(!res.palette.contains(&BLUE) && used(&res), "{:?}", res.palette); +} + #[test] fn a_straight_anti_aliased_line_is_two_anchors_in_its_middle_as_wide_as_its_ink() { // 2.6 px wide, from (20.3, 30.7) to (170.6, 52.2): a little off the pixel rows.