mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 20:36:02 +00:00
Merge pull request #7469 from dotCooCoo/fix/trace-flat-16-levels
Image Trace: clean flat art of 10 to 16 evenly spaced levels traces as flat color
This commit is contained in:
@@ -283,11 +283,11 @@ pub(super) fn settled_wide(mut q: Quantized, img: &Raster, own: &[bool], gray: b
|
||||
/// channels within 8 levels of each other) is therefore also continuous tone when eight colors
|
||||
/// stay more than 0.065 as far as a single color does. So is a sepia, single-hue or duotone
|
||||
/// picture whose tones follow one curve through the color cube (19 in 20 of its opaque pixels on
|
||||
/// it, see [`on_one_curve`]). Neither is when sixteen colors stay at most a fifth as far as eight
|
||||
/// do. That exception holds for flat art of up to sixteen clean levels, gray or tinted, where
|
||||
/// sixteen colors come within about a level of every pixel. On gray and tinted photos sixteen
|
||||
/// colors stay 0.18 to 0.57 as far as eight do, and on most anti-aliased or noisy gray flat art
|
||||
/// 0.24 to 0.5 as far, so the exception holds for neither.
|
||||
/// it, see [`on_one_curve`]). Neither is, however far eight colors stay, when sixteen colors stay
|
||||
/// at most a fifth as far as eight do. That exception holds for flat art of up to sixteen clean
|
||||
/// levels, gray or tinted, where sixteen colors come within about a level of every pixel. On gray
|
||||
/// and tinted photos sixteen colors stay 0.18 to 0.57 as far as eight do, and on most anti-aliased
|
||||
/// or noisy gray flat art 0.24 to 0.5 as far, so the exception holds for neither.
|
||||
pub(crate) fn continuous_tone(img: &Raster) -> bool {
|
||||
let hk = HistKey::new(img);
|
||||
let mut bins = vec![Bin::default(); 1 << 15];
|
||||
@@ -318,10 +318,14 @@ pub(crate) fn continuous_tone(img: &Raster) -> bool {
|
||||
return false;
|
||||
}
|
||||
let one = unsaid(1);
|
||||
if eight > 0.1 * one {
|
||||
return true;
|
||||
if eight <= 0.065 * one {
|
||||
return false;
|
||||
}
|
||||
eight > 0.065 * one && unsaid(16) > 0.2 * eight && (20 * neutral >= 19 * opaque || 20 * on_one_curve(img) >= 19 * opaque)
|
||||
let one_line = || 20 * neutral >= 19 * opaque || 20 * on_one_curve(img) >= 19 * opaque;
|
||||
if eight > 0.1 * one {
|
||||
return !(unsaid(16) <= 0.2 * eight && one_line());
|
||||
}
|
||||
unsaid(16) > 0.2 * eight && one_line()
|
||||
}
|
||||
|
||||
/// How far (levels) a pixel may lie from the tone curve of its picture and still count as on it,
|
||||
|
||||
@@ -2206,6 +2206,59 @@ fn tinted_flat_art_stays_flat_art() {
|
||||
}
|
||||
}
|
||||
|
||||
/// `levels` clean gray bands 20 px wide, evenly spaced from black to white.
|
||||
fn even_gray_bands(levels: u32) -> Raster {
|
||||
Raster::from_fn(levels * 20, 60, |x, _| {
|
||||
let v = (x / 20 * 255 / (levels - 1)) as u8;
|
||||
[v, v, v, 255]
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn flat_art_of_up_to_sixteen_clean_levels_stays_flat_art() {
|
||||
// Equal bands: eight colors leave more than a tenth of what a single color leaves.
|
||||
let tints: [(&str, &[[f64; 3]]); 4] = [("gray", &GRAY_RAMP), ("sepia", &SEPIA), ("blue", &BLUE), ("duotone", &DUOTONE)];
|
||||
let mut smoothed = vec![];
|
||||
for (name, stops) in tints {
|
||||
for levels in [10, 12, 14, 16] {
|
||||
let bands = tinted(&even_gray_bands(levels), stops, 0.0);
|
||||
if quantize::continuous_tone(&bands) {
|
||||
smoothed.push(format!("{levels} {name} bands"));
|
||||
}
|
||||
if quantize::continuous_tone(&beside_transparent_colors(&bands)) {
|
||||
smoothed.push(format!("{levels} {name} bands beside transparent colors"));
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(smoothed.is_empty(), "continuous tone: {smoothed:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn thin_lines_on_flat_art_of_up_to_sixteen_clean_levels_are_traced() {
|
||||
for levels in [12, 16] {
|
||||
for (name, stops) in [("gray", &GRAY_RAMP[..]), ("sepia", &SEPIA[..])] {
|
||||
let bands = even_gray_bands(levels);
|
||||
// A 1 px black line across the bands at y = 30, and a 1 px white line down the second
|
||||
// band at x = 25.
|
||||
let lined = Raster::from_fn(bands.width, bands.height, |x, y| {
|
||||
if y == 30 {
|
||||
[0, 0, 0, 255]
|
||||
} else if x == 25 {
|
||||
[255, 255, 255, 255]
|
||||
} else {
|
||||
let i = ((y * bands.width + x) * 4) as usize;
|
||||
[bands.rgba[i], bands.rgba[i + 1], bands.rgba[i + 2], 255]
|
||||
}
|
||||
});
|
||||
let res = trace(&tinted(&lined, stops, 0.0), &sixteen_colors());
|
||||
assert_eq!(res.palette.len(), levels as usize, "{levels} {name} bands: {:?}", res.palette);
|
||||
// The black band with the black line; the white band above and below the line; the
|
||||
// white line above and below it; the second band in four; each other band in two.
|
||||
assert_eq!(res.paths.len(), 1 + 2 + 2 + 4 + 2 * (levels as usize - 3), "{levels} {name} bands");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pictures_without_tones_are_not_continuous_tone() {
|
||||
let photo = tinted(&photo_scene(6.0, true), &SEPIA, 2.0);
|
||||
|
||||
@@ -1425,6 +1425,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- **Image Trace, curve fitting tolerance:** the outline fitter keeps cutting a run while a point stays farther than the tolerance from its curve, so a run of fewer than six points or one cut 14 times no longer keeps a piece over the tolerance. Pieces already within the tolerance do not change, and noisy art can get more anchors. Tests: `logo::bezier::tests`, `logo::outline::tests`. Scores and hours unchanged.
|
||||
- **Image Trace, gray photos:** Color and Grayscale smooth a gray photo within its tones before choosing its colors, as they do a color photo. A picture with at least 95% of its opaque pixels within 8 levels across their channels counts as continuous tone when 8 colors leave an error of more than 6 levels and more than 0.065 of the error of one color, instead of 0.1, unless 16 colors leave at most a fifth of the error of 8 (VectorCraft's own values). That exception keeps gray flat art of up to 16 clean levels on its old route; anti-aliased or noisy gray flat art can now count as continuous tone too. Most gray photos the change reaches, and very noisy gray scans of flat art, trace with fewer specks and fewer staircase edges. Some 16 Colors and High Fidelity Photo palettes of gray photos grow toward the requested count, with more anchors and regions. Some gray illustrations and sketches get more anchors and staircase edges at 16 Colors and High Fidelity Photo, and some 6 Colors traces get more staircase edges. Tests: `tests::a_gray_photo_is_continuous_tone_as_its_color_original_is`, `tests::a_gray_photo_is_smoothed_before_it_is_traced`. Scores and hours unchanged.
|
||||
- **Image Trace, tinted photos:** Color and Grayscale smooth a sepia, single-hue or duotone photo within its tones before choosing its colors, as they do a gray photo. A picture with at least 95% of its opaque pixels within 6 levels of its tone curve counts as continuous tone under the same 0.065 rule as a neutral picture. The tone curve runs through the median color of each 8-level stretch along the direction in which the picture's colors spread the most, and a pixel on it must also lie within 6 levels of the plane the colors spread along most, so art in several unrelated colors is not counted (VectorCraft's own rule and values). The neutral rule's exception for 16 colors leaving at most a fifth of the error of 8 applies to it too. Clean tinted bands of 2, 3 and 9 levels and tinted scans of flat art with up to 8 levels of noise stay on the flat-art route. On the synthetic tinted photos the 16 Colors trace keeps 12 to 15 colors instead of 6 to 13, with 525 to 708 anchors instead of up to 1,402. A duotone with strong noise on each channel can still miss the curve and stay unsmoothed. Tests: `tests::a_tinted_photo_is_continuous_tone_as_a_gray_photo_is`, `tests::small_accents_of_other_colors_leave_a_tinted_photo_continuous_tone`, `tests::tinted_flat_art_stays_flat_art`, `tests::pictures_without_tones_are_not_continuous_tone`, `tests::a_tinted_photo_is_smoothed_before_it_is_traced`. Scores and hours unchanged.
|
||||
- **Image Trace, flat art of 10 to 16 levels:** the exception for 16 colors leaving at most a fifth of the error of 8 also applies to a neutral or tinted picture whose 8-color error is more than 0.1 of the error of one color (VectorCraft's own values), so Color and Grayscale trace clean gray, sepia, single-hue and duotone art of 10 to 16 evenly spaced levels as flat art, without smoothing it first. At 16 Colors, 16 gray or sepia levels crossed by two 1 px lines trace as the 35 regions the lines cut them into instead of 33, and Automatic traces 16 clean sepia levels in 9 colors instead of 4. Tests: `tests::flat_art_of_up_to_sixteen_clean_levels_stays_flat_art`, `tests::thin_lines_on_flat_art_of_up_to_sixteen_clean_levels_are_traced`. Scores and hours unchanged.
|
||||
- **Image Trace, JPEG blocks:** Color, Grayscale and Black and White traces smooth the edges of a JPEG's 8 × 8 blocks before tracing, so a dark ground whose tone changes slowly is no longer traced as staircases along the block edges. The block grid is found in the image itself: the mean step between neighboring pixels must peak at one position in eight, across and down. A cropped JPEG and a JPEG saved in another format are smoothed too. A picture of flat 8 × 8 cells is left as it is, and art made of 8 × 8 cells with slight texture inside each cell is smoothed as a JPEG copy would be. A small step at a block edge, with no edge in the eight pixels around it, is spread over those eight pixels, and larger steps are kept, as the deblocking filters of ITU-T H.263 Annex J and MPEG-4 Part 2 Annex F.3.1 do. Black and White smooths only the smallest steps, and traces of more than 16 colors move every row of a stretch of eight by the same amount (VectorCraft's own rule and values). On a 4000 × 3000 photo saved as JPEG, the straight outline segments that lie on the block grid fall from 52% and 51% (across and down) to 24% and 29% at 16 Colors, and from 54% and 51% to 43% and 42% at High Fidelity Photo; staircase runs fall from 83 to 67 (16 Colors), 11 to 1 (6 Colors), 30 to 13 (Black and White Logo) and 428 to 403 (High Fidelity Photo). A 600 px photo saved as a quality-40 JPEG has 4 staircase runs instead of 2 at 16 Colors. Images without a block grid trace as before. Tests: `tests::a_jpegs_blocks_are_not_traced_as_steps`, `deblock::tests`. Scores and hours unchanged.
|
||||
- **Image Trace, Document Library color groups:** with Document Swatches, the panel's Colors menu and `"colorGroup"` trace with one color group's colors instead of every swatch, as the public panel documentation describes Document Library; code-drawn tests in `crates/engine` and the panel.
|
||||
- **Image Trace, Document Library colors:** with Document Swatches, traced fills and strokes use the displayed colors of process, global, spot and tint swatches without links, so editing a swatch leaves the trace unchanged; swatch-library colors are also unlinked; code-drawn tests in `crates/engine`.
|
||||
|
||||
Reference in New Issue
Block a user