Compare commits
18
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
761cc2371a | ||
|
|
3752b3ec31 | ||
|
|
c2ed31dc4c | ||
|
|
d9e44350d2 | ||
|
|
0f4de5b98e | ||
|
|
a971b5b70e | ||
|
|
6beaa89be2 | ||
|
|
40b02f479b | ||
|
|
afda1cd846 | ||
|
|
a7c6644691 | ||
|
|
3a88ad0855 | ||
|
|
61cc3f6040 | ||
|
|
3239e8b97e | ||
|
|
48d5bd1aee | ||
|
|
5203f4f047 | ||
|
|
a072e29016 | ||
|
|
ba68fcfbfc | ||
|
|
c9fac95760 |
No files matched your search
@@ -120,7 +120,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Make",
|
||||
["Object", "Image Trace"],
|
||||
None,
|
||||
"{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite, snapCurvesToLines, fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors}",
|
||||
"{id?: image or Image Trace group (default: selection), preset?: name (imageTrace.presets), params?: {mode: \"blackAndWhite\"|\"grayscale\"|\"color\"|\"logo\", threshold: 0-255, palette: \"limited\"|\"fullTone\"|\"automatic\"|\"documentLibrary\" (color mode; limited: the `colors` best colours, fullTone: as many as the tones need, more with colorDetail, automatic: flat art with its own colours, else fullTone, documentLibrary: at most `colors` of the library's colours, as they are), colors: 2-256, colorDetail: 0-100 (fullTone, automatic), library: \"document\" (the document's swatches) or a swatch library id or name (swatch.library.list; documentLibrary), paths: 0-100, corners: 0-100, noise: px, method: \"abutting\"|\"overlapping\", ignoreWhite (leave out white areas), ignoreColor?: \"#rrggbb\" (with ignoreWhite: the colour left out instead of white), snapCurvesToLines, fills: true, strokes: false (Create: lines up to strokeWidth wide become stroked centre lines; without fills, wider areas become stroked outlines), strokeWidth: px (10), logoColors?: [\"#rrggbb\"…] (mode logo: flat-colour logos only; colours found automatically or named here, white is always its own colour, at most ten; noise = areas under noise/10 px ignored; edges to a fraction of a pixel, sharp corners restored, lines and circles snapped; enclosed transparent areas are painted white unless ignoreWhite makes them holes; fills/strokes are ignored; gradients and photos are refused)}, view?: as imageTrace.setView (default: the traced object's own, else tracingResult), ids?: [id…] (instead of id)} → {id, paths, anchors, colors}",
|
||||
has_selection_or_ids,
|
||||
|s, p| trace_make(s, p, false)
|
||||
),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use kurbo::{ParamCurve, ParamCurveNearest};
|
||||
use kurbo::{CubicBez, ParamCurve, ParamCurveNearest};
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Color, Paint};
|
||||
use vectorcraft_doc::{Appearance, Document, Node, NodeId, NodeKind, PressureProfile, Selection};
|
||||
@@ -1048,17 +1048,46 @@ fn smooth_subpath(sp: &SubPath, near: &dyn Fn(Point) -> bool, tol: f64) -> Optio
|
||||
open.anchors.push(open.anchors[0]);
|
||||
m.push(false);
|
||||
open.closed = false;
|
||||
let mut res = smooth_open(&open.anchors, &m, tol);
|
||||
let mut res = smooth_open(&open.anchors, &m, near, tol);
|
||||
let l = res.pop()?;
|
||||
if let Some(r0) = res.first_mut() {
|
||||
r0.h_in = l.h_in;
|
||||
}
|
||||
return Some(SubPath::new(res, true));
|
||||
}
|
||||
Some(SubPath::new(smooth_open(&sp.anchors, &marked, tol), false))
|
||||
Some(SubPath::new(smooth_open(&sp.anchors, &marked, near, tol), false))
|
||||
}
|
||||
|
||||
fn smooth_open(anchors: &[Anchor], marked: &[bool], tol: f64) -> Vec<Anchor> {
|
||||
/// The segment `c`, which ends in the drag's reach (`near`) and starts outside it, split where it
|
||||
/// enters the reach: the part outside, then the part in reach. `None` when no more than a sliver
|
||||
/// is outside.
|
||||
fn split_at_reach(c: CubicBez, near: &dyn Fn(Point) -> bool) -> Option<(CubicBez, CubicBez)> {
|
||||
const STEPS: u32 = 32;
|
||||
let step = 1.0 / f64::from(STEPS);
|
||||
// Back from the end in reach to the first sample outside, then halving to the crossing.
|
||||
let outside = (1..=STEPS).map(|i| 1.0 - f64::from(i) * step).find(|t| !near(c.eval(*t)))?;
|
||||
let (mut lo, mut hi) = (outside, outside + step);
|
||||
for _ in 0..20 {
|
||||
let mid = (lo + hi) / 2.0;
|
||||
if near(c.eval(mid)) {
|
||||
hi = mid;
|
||||
} else {
|
||||
lo = mid;
|
||||
}
|
||||
}
|
||||
// A straight segment's parts stay straight, their handles on their ends.
|
||||
let straight = c.p1 == c.p0 && c.p2 == c.p3;
|
||||
let part = |range| {
|
||||
let s = c.subsegment(range);
|
||||
if straight { CubicBez::new(s.p0, s.p0, s.p3, s.p3) } else { s }
|
||||
};
|
||||
(hi > 0.02).then(|| (part(0.0..hi), part(hi..1.0)))
|
||||
}
|
||||
|
||||
/// `anchors` with each run of `marked` ones refitted, together with the stretch of the segments
|
||||
/// leading into the run that is in the drag's reach (`near`): a short drag smooths what is under
|
||||
/// it, and the rest of a long segment next to it stays as it was (#1512).
|
||||
fn smooth_open(anchors: &[Anchor], marked: &[bool], near: &dyn Fn(Point) -> bool, tol: f64) -> Vec<Anchor> {
|
||||
let n = anchors.len();
|
||||
let mut runs = vec![];
|
||||
let mut i = 0;
|
||||
@@ -1079,8 +1108,27 @@ fn smooth_open(anchors: &[Anchor], marked: &[bool], tol: f64) -> Vec<Anchor> {
|
||||
if hi - lo < 2 {
|
||||
continue;
|
||||
}
|
||||
let new = refit_run(&out[lo..=hi], tol);
|
||||
out.splice(lo..=hi, new);
|
||||
let mut piece = out[lo..=hi].to_vec();
|
||||
// The anchors outside the reach, kept with the part of their segment that is outside.
|
||||
let (mut head, mut tail) = (None, None);
|
||||
if hi > b
|
||||
&& let [.., inner, outer] = piece.as_mut_slice()
|
||||
&& let Some((far, reach)) = split_at_reach(CubicBez::new(outer.p, outer.h_in, inner.h_out, inner.p), near)
|
||||
{
|
||||
inner.h_out = reach.p2;
|
||||
tail = Some(Anchor { h_in: far.p1, ..*outer });
|
||||
*outer = Anchor::with_handles(far.p3, reach.p1, far.p2);
|
||||
}
|
||||
if lo < a
|
||||
&& let [outer, inner, ..] = piece.as_mut_slice()
|
||||
&& let Some((far, reach)) = split_at_reach(CubicBez::new(outer.p, outer.h_out, inner.h_in, inner.p), near)
|
||||
{
|
||||
inner.h_in = reach.p2;
|
||||
head = Some(Anchor { h_out: far.p1, ..*outer });
|
||||
*outer = Anchor::with_handles(far.p3, far.p2, reach.p1);
|
||||
}
|
||||
let new = refit_run(&piece, tol);
|
||||
out.splice(lo..=hi, head.into_iter().chain(new).chain(tail));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -795,7 +795,8 @@ fn image_trace_presets_query_and_errors() {
|
||||
let mut s = session();
|
||||
let r = s.execute("imageTrace.presets", &json!({})).unwrap();
|
||||
let names: Vec<&str> = r["presets"].as_array().unwrap().iter().map(|p| p["name"].as_str().unwrap()).collect();
|
||||
assert_eq!(names.len(), 13);
|
||||
assert_eq!(names.len(), 15);
|
||||
assert!(names.contains(&"Auto Color") && names.contains(&"Outline"));
|
||||
assert!(names.contains(&"Technical Drawing") && names.contains(&"Shades of Gray") && names.contains(&"Flat Logo"));
|
||||
let a = rect(&mut s, 0.0, 0.0, 10.0, 10.0);
|
||||
assert!(s.execute("imageTrace.make", &json!({"id": a.0})).is_err(), "not an image");
|
||||
|
||||
@@ -412,6 +412,29 @@ fn smooth_tool_reduces_jagged_path() {
|
||||
assert_eq!(p.subpaths[0].anchors[0].p, Point::new(100.0, 200.0));
|
||||
}
|
||||
|
||||
/// #1512: a short drag over a small bump smooths the stretch of path under it. The long straight
|
||||
/// sides next to the bump stay straight lines from their far ends, which keep their place.
|
||||
#[test]
|
||||
fn a_short_smooth_drag_leaves_the_rest_of_long_segments_alone() {
|
||||
let mut s = session();
|
||||
let anchors: Vec<_> =
|
||||
[(50.0, 200.0), (190.0, 200.0), (200.0, 192.0), (210.0, 200.0), (350.0, 200.0)].map(|(x, y)| json!({"x": x, "y": y})).to_vec();
|
||||
let r = s.execute("path.create", &json!({"anchors": anchors})).unwrap();
|
||||
let id = NodeId(r["id"].as_u64().unwrap());
|
||||
let before = path(&s, id);
|
||||
drag(&mut s, "smooth", &[(195.0, 194.0), (205.0, 194.0)], Mods::default());
|
||||
let p = path(&s, id);
|
||||
assert_ne!(p, before, "the bump is smoothed");
|
||||
let a = &p.subpaths[0].anchors;
|
||||
let (first, second, last) = (a[0], a[1], a[a.len() - 1]);
|
||||
assert!(first.p == Point::new(50.0, 200.0) && !first.has_out() && !second.has_in(), "the left side is a line still: {first:?} {second:?}");
|
||||
assert!(last.p == Point::new(350.0, 200.0) && !last.has_in() && !a[a.len() - 2].has_out(), "and the right one: {last:?}");
|
||||
// Every other anchor is within the tool's reach of the drag (12 px either way).
|
||||
for x in &a[1..a.len() - 1] {
|
||||
assert!((182.0..=218.0).contains(&x.p.x) && (190.0..=201.0).contains(&x.p.y), "{x:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn join_tool_joins_two_lines() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -158,8 +158,11 @@ impl GradientDef {
|
||||
if self.gradient.stops.is_empty() {
|
||||
return None;
|
||||
}
|
||||
// Stops come last first: reversed, stops sharing a ramp point keep their order.
|
||||
self.gradient.stops.reverse();
|
||||
// Stops are listed along the ramp from either end: listed downwards they're reversed, so
|
||||
// stops sharing a ramp point keep their order (and each its midpoint, to the next stop).
|
||||
if self.gradient.stops.first().zip(self.gradient.stops.last()).is_some_and(|(first, last)| first.offset > last.offset) {
|
||||
self.gradient.stops.reverse();
|
||||
}
|
||||
self.gradient.stops.sort_by(|a, b| a.offset.total_cmp(&b.offset));
|
||||
Some((self.name, self.gradient))
|
||||
}
|
||||
|
||||
@@ -410,6 +410,19 @@ fn stops_sharing_a_ramp_point_keep_their_order() {
|
||||
assert_eq!(colors, [Color::rgb(0.0, 0.0, 1.0), Color::rgb(0.0, 1.0, 0.0), Color::rgb(1.0, 0.0, 0.0)]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stops_listed_upwards_keep_the_midpoint_of_the_one_that_starts_the_ramp() {
|
||||
// GUARDS: reversing a list that runs upwards swaps two stops at 0, and the ramp to 100 takes the
|
||||
// midpoint of the zero-length part between them.
|
||||
let defs = "%AI5_BeginGradient: (U)\n(U) 0 3 Bd\n[\n<00FF>\n0\n%_0 0 0 0 1 1 1 2 1 6 50 0 Bs\n\
|
||||
%_0 0 0 0 1 1 1 2 1 6 87 0 Bs\n%_0 0 0 0 0 0 0 2 1 6 50 100 Bs\nBD\n%AI5_EndGradient\n";
|
||||
let art_ = format!("{}Bb\n1 (U) 0 0 0 1 1 0 0 1 0 0 1 Bg\n10 0 0 10 100 50 Xm\nf\n0 BB\n", rect_path(0.0, 0.0, 200.0, 100.0));
|
||||
let doc = read_ok(&format!("{defs}{}", layer(&art_))).doc;
|
||||
let Paint::Gradient(g) = fill(&art(&doc)[0]) else { panic!() };
|
||||
let midpoints: Vec<_> = g.gradient.stops.iter().map(|s| s.midpoint).collect();
|
||||
assert_eq!(midpoints, [0.5, 0.87, 0.5]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn per_anchor_data_reads() {
|
||||
let art_ = format!("{RED}{}f\n(00800080) Xt 0 XD\n%_(00004000) Xt 1 XD\n", rect_path(0.0, 0.0, 10.0, 10.0));
|
||||
|
||||
@@ -155,8 +155,8 @@ impl Corner {
|
||||
let (h_out, h_in) = match kind {
|
||||
// An arc tangent to both sides, turning by 180° less the angle between them there.
|
||||
CornerKind::Round => {
|
||||
let h = radius * arc_handle(-cos);
|
||||
(start - du * h, end - dv * h)
|
||||
let (hu, hv) = self.round_handles(radius, (start, du), (end, dv));
|
||||
(start - du * hu, end - dv * hv)
|
||||
}
|
||||
// An arc centred on the corner through both ends, spanning the angle.
|
||||
CornerKind::InvertedRound => {
|
||||
@@ -169,6 +169,33 @@ impl Corner {
|
||||
}
|
||||
}
|
||||
|
||||
impl Corner {
|
||||
/// The handle lengths of a round cut of `radius` between `start` and `end` (each a point and
|
||||
/// the direction its side runs on in there). Between straight sides they are the arc's. A
|
||||
/// curved side has turned by the time the cut meets it, so the arc's handles would reach past
|
||||
/// where the tangents at the cut's ends cross and the cut would stick out of the corner
|
||||
/// (#1512): there each handle is the share an arc takes of its tangent, up to that crossing
|
||||
/// (a third of the way to the other end where the tangents don't cross between them).
|
||||
fn round_handles(&self, radius: f64, (start, du): (Point, Vec2), (end, dv): (Point, Vec2)) -> (f64, f64) {
|
||||
let cos = du.dot(dv);
|
||||
let arc = arc_handle(-cos);
|
||||
if self.side_u.straight() && self.side_v.straight() {
|
||||
return (radius * arc, radius * arc);
|
||||
}
|
||||
// start − du·a = end − dv·b is where the tangents cross.
|
||||
let (cross, w) = (du.cross(dv), start - end);
|
||||
let (a, b) = (w.cross(dv) / cross, w.cross(du) / cross);
|
||||
if cross.abs() > MIN_SINE && a > EPS && b > EPS && a.is_finite() && b.is_finite() {
|
||||
// An arc's handle over its tangent's length, radius · tan(turn / 2).
|
||||
let share = arc * (1.0 - cos) / cross.abs();
|
||||
(a * share, b * share)
|
||||
} else {
|
||||
let third = start.distance(end) / 3.0;
|
||||
(third, third)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A corner cut: the parameters where it meets its sides (from the corner), its two new anchors
|
||||
/// and the handles between them.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
@@ -401,6 +428,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
/// A round cut next to a curve stays inside its corner, however large (#1512): its handles
|
||||
/// stop short of where the tangents at its ends cross, so it doesn't stick out past the sides.
|
||||
#[test]
|
||||
fn a_round_cut_next_to_a_curve_stays_inside_its_corner() {
|
||||
// A square whose top side arches up, meeting the upright sides at an angle.
|
||||
let mut sp =
|
||||
SubPath::polyline(&[Point::new(100.0, 150.0), Point::new(300.0, 150.0), Point::new(300.0, 300.0), Point::new(100.0, 300.0)], true);
|
||||
sp.anchors[0].h_out = Point::new(120.0, 20.0);
|
||||
sp.anchors[1].h_in = Point::new(280.0, 20.0);
|
||||
let path = PathData::single(sp);
|
||||
let bounds = path.bounds().unwrap().inflate(1e-6, 1e-6);
|
||||
for radius in [10.0, 60.0, 200.0, 1e6] {
|
||||
let (cut, _) = cut_corners(&path, &[radius, radius], &[]);
|
||||
let b = cut.bounds().unwrap();
|
||||
assert!(bounds.union(b) == bounds, "radius {radius}: {b:?} is outside {bounds:?}");
|
||||
// Each cut's handles end before its tangents cross: the cut turns one way only.
|
||||
let a = &cut.subpaths[0].anchors;
|
||||
for (from, to) in [(a[a.len() - 1], a[0]), (a[1], a[2])] {
|
||||
let (t0, t1, chord) = (from.h_out - from.p, to.p - to.h_in, to.p - from.p);
|
||||
assert!(t0.cross(chord) * chord.cross(t1) > 0.0 && t0.cross(t1) * t0.cross(chord) > 0.0, "radius {radius}: {from:?} {to:?}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Inverted round and chamfer cuts end where the round one does, at any angle.
|
||||
#[test]
|
||||
fn kinds_share_their_ends() {
|
||||
|
||||
@@ -197,6 +197,8 @@ pub fn import_with_report(bytes: &[u8], opts: &ImportOptions) -> Result<ImportRe
|
||||
b.colors.uncalibrated_rgb = opts.uncalibrated_rgb && picked.iter().filter_map(|&n| pages.get(n)).any(crate::pages::has_private_data);
|
||||
let cache = InterpreterCache::new();
|
||||
let mut x = 0.0;
|
||||
// Where the next page's art may begin (a `.ai` page's art reaches past its artboard).
|
||||
let mut clear = f64::NEG_INFINITY;
|
||||
// Every page only text over private data: a file saved without its PDF part shows a notice
|
||||
// there, not its art. A page that states where its art is isn't that notice.
|
||||
let (mut only_text, mut art_boxes) = (true, false);
|
||||
@@ -220,6 +222,8 @@ pub fn import_with_report(bytes: &[u8], opts: &ImportOptions) -> Result<ImportRe
|
||||
let xf = Affine::translate((x - frame.x0, -frame.y0)) * init;
|
||||
let mut ctx = Context::new(xf, ab, &cache, pdf.xref(), settings.clone());
|
||||
b.page = ab;
|
||||
// The first id of this page's art.
|
||||
let first_id = b.next;
|
||||
let scan = scan_page(page, &mut ocgs, all_on, &mut b.fonts);
|
||||
if route {
|
||||
let mut seen = HashSet::new();
|
||||
@@ -267,6 +271,33 @@ pub fn import_with_report(bytes: &[u8], opts: &ImportOptions) -> Result<ImportRe
|
||||
if clipped {
|
||||
b.warn(PAST_PAGE_NOTE);
|
||||
}
|
||||
// A `.ai` page keeps its art past the artboard edge, unclipped (#646), so a page whose art
|
||||
// reaches far to the left would lay it over the page before it in the row: there it would
|
||||
// be taken for that page's art (by the check against the editing data, which then finds
|
||||
// art its layers don't have there, and by `place_pages`). Such a page moves right until its
|
||||
// art clears the art before it.
|
||||
let mut art_right = f64::NEG_INFINITY;
|
||||
if ai && let Some(art) = parts.iter().flat_map(|(_, v)| v.iter()).filter_map(|n| n.visual_bounds()).reduce(|a, b| a.union(b)) {
|
||||
// Art at a coordinate that isn't a number (a damaged file) moves nothing.
|
||||
let dx = Some((clear - art.x0).max(0.0)).filter(|dx| dx.is_finite()).unwrap_or(0.0);
|
||||
if dx > 0.0 {
|
||||
let shift = Affine::translate((dx, 0.0));
|
||||
for (_, v) in &mut parts {
|
||||
for n in v.iter_mut() {
|
||||
Arc::make_mut(n).transform(shift, false);
|
||||
}
|
||||
}
|
||||
// What the page's lines of type were is told where their glyphs are (the outlines
|
||||
// stroked on them are looked for there), so that moves with them.
|
||||
for facts in b.lines.iter_mut().filter(|(id, _)| id.0 >= first_id).map(|(_, facts)| facts) {
|
||||
for ink in &mut facts.ink {
|
||||
*ink = *ink + kurbo::Vec2::new(dx, 0.0);
|
||||
}
|
||||
}
|
||||
ab = Rect::new(ab.x0 + dx, ab.y0, ab.x1 + dx, ab.y1);
|
||||
}
|
||||
art_right = art.x1 + dx;
|
||||
}
|
||||
let children: Vec<Arc<Node>> = parts.iter().flat_map(|(_, v)| v.iter().cloned()).collect();
|
||||
only_text &= ai && is_only_text(&children);
|
||||
art_boxes |= crate::pages::states_art(page);
|
||||
@@ -278,6 +309,7 @@ pub fn import_with_report(bytes: &[u8], opts: &ImportOptions) -> Result<ImportRe
|
||||
ab = art;
|
||||
}
|
||||
x = right + opts.artboard_gap;
|
||||
clear = right.max(art_right) + opts.artboard_gap;
|
||||
doc.artboards.push(Artboard {
|
||||
id: i as u32 + 1,
|
||||
name: format!("Artboard {}", i + 1),
|
||||
|
||||
@@ -131,6 +131,28 @@ fn a_stroke_written_as_stroked_glyph_outlines_is_the_types_stroke() {
|
||||
assert_eq!(d.layers[0].children().unwrap().len(), 2);
|
||||
}
|
||||
|
||||
/// A `.ai` page moved right in the row, so its art past the artboard clears the page before it
|
||||
/// (#1528), still has the stroke drawn on its type's glyphs read as the type's stroke.
|
||||
#[test]
|
||||
fn a_stroke_on_the_type_of_a_moved_ai_page_is_still_the_types_stroke() {
|
||||
let ai = "/PieceInfo << /Illustrator << /Private << /AIPrivateData1 7 /NumBlock 1 >> >> >>";
|
||||
let content = format!(
|
||||
"0 g -70 10 80 20 re f BT 1 0 0 rg /F1 24 Tf 10 50 Td (Hi) Tj ET 0 0 1 RG 0.5 w {}{}",
|
||||
outline('H', 10.0),
|
||||
outline('i', 10.0 + 0.722 * 24.0)
|
||||
);
|
||||
let pages = [("0 g 10 10 20 20 re f", String::new()), (content.as_str(), HELVETICA.to_string())].map(|(c, resources)| PdfPage {
|
||||
entries: ai.into(),
|
||||
resources,
|
||||
..PdfPage::new(100.0, 100.0, c)
|
||||
});
|
||||
let d = import(&vectorcraft_testkit::pdf::pdf(&pages, None)).unwrap();
|
||||
assert!(d.artboards[1].rect.x0 > d.artboards[0].rect.x1 + 36.0 + 1.0, "page 2 moved: {:?}", d.artboards);
|
||||
let art = d.layers[1].children().unwrap();
|
||||
assert_eq!(art.len(), 2, "the note and the type: {art:?}");
|
||||
assert!(matches!(art[1].appearance.items.as_slice(), [AppearanceItem::Stroke(_)]), "{:?}", art[1].appearance);
|
||||
}
|
||||
|
||||
/// A 300 × 200 pt page with `lines` of (x, y from the top, size, text) in Source Sans 3.
|
||||
fn source_sans(lines: &[(f32, f32, f32, &str)]) -> Vec<u8> {
|
||||
crate::tests_live_text::text_pdf(lines, false)
|
||||
|
||||
@@ -158,6 +158,27 @@ fn art_of_neighbouring_artboards_off_a_page_is_left_to_its_own_page() {
|
||||
assert_eq!(d.layers[0].children().map_or(0, |c| c.len()), 2);
|
||||
}
|
||||
|
||||
/// A `.ai` keeps its art past the artboard edge (#646): a note mostly left of artboard 2 (as notes
|
||||
/// for a design are put beside it) doesn't lie over artboard 1 in the row, where it would be taken
|
||||
/// for that page's art: page 2 moves right until its art clears page 1's.
|
||||
#[test]
|
||||
fn art_of_an_ai_page_reaching_left_does_not_lie_over_the_page_before() {
|
||||
let ai = "/PieceInfo << /Illustrator << /Private << /AIPrivateData1 7 /NumBlock 1 >> >> >>";
|
||||
let page = |content: &str| PdfPage { entries: ai.into(), ..PdfPage::new(100.0, 100.0, content) };
|
||||
// Page 2's note runs from 70 points left of its box to 10 points into it.
|
||||
let pages = [page("0 g 10 10 20 20 re f"), page("1 1 1 rg 1 0 0 RG -70 10 80 60 re B")];
|
||||
let d = open(&pdf(&pages, None), ImportOptions::default());
|
||||
let (a, b) = (d.artboards[0].rect, d.artboards[1].rect);
|
||||
let note = d.layers[1].children().unwrap()[0].visual_bounds().unwrap();
|
||||
assert!(note.x0 >= a.x1 + 36.0, "the note clears artboard 1: {note:?} {a:?}");
|
||||
assert!((b.x0 - note.x0 - 70.0).abs() < 1.0, "and keeps its place by artboard 2: {note:?} {b:?}");
|
||||
assert_eq!((b.width(), b.height()), (100.0, 100.0));
|
||||
// A plain PDF clips its art to the page: its pages stay 36 points apart.
|
||||
let plain = [PdfPage::new(100.0, 100.0, "0 g 10 10 20 20 re f"), PdfPage::new(100.0, 100.0, "1 0 0 RG -70 10 80 60 re S")];
|
||||
let d = open(&pdf(&plain, None), ImportOptions::default());
|
||||
assert_eq!(d.artboards[1].rect.x0, d.artboards[0].rect.x1 + 36.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn an_encrypted_pdf_opens_with_its_password_only() {
|
||||
let bytes = pdf(&[PdfPage::new(200.0, 100.0, "1 0 0 rg 10 10 50 50 re f")], Some("secret"));
|
||||
|
||||
@@ -468,10 +468,11 @@ impl Ctx {
|
||||
None => {
|
||||
let id = href(tp)?.trim().strip_prefix('#')?;
|
||||
let el = xml.descendants().find(|n| n.is_element() && n.attribute("id") == Some(id))?;
|
||||
if el.tag_name().name() != "path" {
|
||||
return None;
|
||||
}
|
||||
let mut bp = BezPath::from_svg(el.attribute("d")?).ok()?;
|
||||
let d = match el.tag_name().name() {
|
||||
"path" => el.attribute("d")?.to_string(),
|
||||
_ => self.shape_path(el)?,
|
||||
};
|
||||
let mut bp = BezPath::from_svg(&d).ok()?;
|
||||
bp.apply_affine(parse_transform(el.attribute("transform")));
|
||||
bp
|
||||
}
|
||||
@@ -483,6 +484,82 @@ impl Ctx {
|
||||
usable.then_some(bp)
|
||||
}
|
||||
|
||||
/// The equivalent path of a basic shape (SVG 2 › Basic Shapes), as path data: what a
|
||||
/// `<textPath>` that points at it follows. Circles and ellipses start at 3 o'clock and rounded
|
||||
/// rects at the end of the top-left corner, all running clockwise; lines and polylines follow
|
||||
/// their points. `None` for other elements and for shapes that render nothing.
|
||||
fn shape_path(&self, el: XNode) -> Option<String> {
|
||||
let (vw, vh) = self.viewport;
|
||||
let len = |a: &str, pct: f64| el.attribute(a).and_then(|v| self.length(v, DEFAULT_FONT_SIZE, pct));
|
||||
let x = |a: &str| len(a, vw).unwrap_or(0.0);
|
||||
let y = |a: &str| len(a, vh).unwrap_or(0.0);
|
||||
let points = || -> Vec<f64> { el.attribute("points").map(|v| list(v).map_while(|t| t.parse().ok()).collect()).unwrap_or_default() };
|
||||
let ellipse = |cx: f64, cy: f64, rx: f64, ry: f64| {
|
||||
(rx > 0.0 && ry > 0.0).then(|| {
|
||||
format!(
|
||||
"M{} {} A{rx} {ry} 0 0 1 {cx} {} A{rx} {ry} 0 0 1 {} {cy} A{rx} {ry} 0 0 1 {cx} {} A{rx} {ry} 0 0 1 {} {cy}Z",
|
||||
cx + rx,
|
||||
cy,
|
||||
cy + ry,
|
||||
cx - rx,
|
||||
cy - ry,
|
||||
cx + rx
|
||||
)
|
||||
})
|
||||
};
|
||||
match el.tag_name().name() {
|
||||
"circle" => {
|
||||
let r = len("r", ((vw * vw + vh * vh) / 2.0).sqrt())?;
|
||||
ellipse(x("cx"), y("cy"), r, r)
|
||||
}
|
||||
"ellipse" => {
|
||||
let (rx, ry) = (len("rx", vw), len("ry", vh));
|
||||
let (rx, ry) = (rx.or(ry)?, ry.or(rx)?);
|
||||
ellipse(x("cx"), y("cy"), rx, ry)
|
||||
}
|
||||
"rect" => {
|
||||
let (x0, y0, w, h) = (x("x"), y("y"), len("width", vw)?, len("height", vh)?);
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
return None;
|
||||
}
|
||||
let (rx, ry) = (len("rx", vw).filter(|v| *v > 0.0), len("ry", vh).filter(|v| *v > 0.0));
|
||||
let (rx, ry) = (rx.or(ry).unwrap_or(0.0).min(w / 2.0), ry.or(rx).unwrap_or(0.0).min(h / 2.0));
|
||||
if rx <= 0.0 || ry <= 0.0 {
|
||||
return Some(format!("M{x0} {y0} H{} V{} H{x0}Z", x0 + w, y0 + h));
|
||||
}
|
||||
let (x1, y1) = (x0 + w, y0 + h);
|
||||
Some(format!(
|
||||
"M{} {y0} H{} A{rx} {ry} 0 0 1 {x1} {} V{} A{rx} {ry} 0 0 1 {} {y1} H{} A{rx} {ry} 0 0 1 {x0} {} V{} A{rx} {ry} 0 0 1 {} {y0}Z",
|
||||
x0 + rx,
|
||||
x1 - rx,
|
||||
y0 + ry,
|
||||
y1 - ry,
|
||||
x1 - rx,
|
||||
x0 + rx,
|
||||
y1 - ry,
|
||||
y0 + ry,
|
||||
x0 + rx
|
||||
))
|
||||
}
|
||||
"line" => Some(format!("M{} {} L{} {}", x("x1"), y("y1"), x("x2"), y("y2"))),
|
||||
tag @ ("polyline" | "polygon") => {
|
||||
let pts = points();
|
||||
let mut d = String::new();
|
||||
for (i, [x, y]) in pts.as_chunks::<2>().0.iter().enumerate() {
|
||||
let _ = write!(d, "{}{x} {y} ", if i == 0 { 'M' } else { 'L' });
|
||||
}
|
||||
if d.is_empty() {
|
||||
return None;
|
||||
}
|
||||
if tag == "polygon" {
|
||||
d.push('Z');
|
||||
}
|
||||
Some(d)
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Read one `<text>`: the pending text and its `url(#…)` paints.
|
||||
fn build(&self, xml: &roxmltree::Document, t: XNode, label: &str, warnings: &mut Vec<String>) -> Option<(PendingText, Vec<String>)> {
|
||||
let preserve = t.ancestors().filter(|a| a.is_element()).find_map(|a| a.attribute((roxmltree::NS_XML_URI, "space"))) == Some("preserve");
|
||||
|
||||
@@ -518,3 +518,68 @@ fn gradients_on_scaled_text_stay_in_place() {
|
||||
let (start, end) = (t.xf * geom.start, t.xf * geom.end);
|
||||
assert!(start.distance(Point::new(40.0, 0.0)) < 1e-6 && end.distance(Point::new(240.0, 0.0)) < 1e-6, "{start:?} {end:?}");
|
||||
}
|
||||
|
||||
/// The start of a type-on-a-path object's path and a point a little way along it, in document
|
||||
/// space.
|
||||
fn path_start(t: &TextObject) -> (Point, Point) {
|
||||
use vectorcraft_geom::kurbo::{ParamCurve, PathEl};
|
||||
let TextKind::OnPath { path, .. } = &t.kind else { panic!("{:?}", t.kind) };
|
||||
let mut bp = path.to_bezpath();
|
||||
bp.apply_affine(t.xf);
|
||||
let PathEl::MoveTo(p0) = bp.elements()[0] else { panic!("{bp:?}") };
|
||||
let next = bp.segments().next().unwrap().eval(0.05);
|
||||
(p0, next)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_path_follows_basic_shapes_as_their_equivalent_paths() {
|
||||
let (d, w) = import_with_report(&svg(r##"<circle id="c" cx="150" cy="150" r="100"/>
|
||||
<ellipse id="e" cx="150" cy="150" rx="80" ry="40" transform="translate(10 0)"/>
|
||||
<rect id="r" x="20" y="30" width="100" height="60" rx="10"/>
|
||||
<rect id="s" x="20" y="30" width="100" height="60"/>
|
||||
<line id="l" x1="10" y1="200" x2="210" y2="200"/>
|
||||
<polyline id="pl" points="10,10 110,10 110,60"/>
|
||||
<polygon id="pg" points="10 10, 60 10, 35 50"/>
|
||||
<text font-size="10"><textPath xlink:href="#c">TEXT ON A CIRCLE</textPath></text>
|
||||
<text font-size="10"><textPath href="#e">Ellipse</textPath></text>
|
||||
<text font-size="10"><textPath href="#r">Rounded</textPath></text>
|
||||
<text font-size="10"><textPath href="#s">Square</textPath></text>
|
||||
<text font-size="10"><textPath href="#l">Line</textPath></text>
|
||||
<text font-size="10"><textPath href="#pl">Polyline</textPath></text>
|
||||
<text font-size="10"><textPath href="#pg">Polygon</textPath></text>"##))
|
||||
.unwrap();
|
||||
assert!(!w.iter().any(|w| w.contains("textPath")), "{w:?}");
|
||||
let t = texts(&d);
|
||||
assert_eq!(t.len(), 7);
|
||||
assert!(t.iter().all(|t| matches!(t.kind, TextKind::OnPath { .. })), "{:?}", t.iter().map(|t| &t.kind).collect::<Vec<_>>());
|
||||
let near = |a: Point, b: Point| (a - b).hypot() < 1e-6;
|
||||
// Circle: starts at 3 o'clock and runs clockwise (down first, y pointing down), around the circle.
|
||||
let (p0, next) = path_start(t[0]);
|
||||
assert!(near(p0, Point::new(250.0, 150.0)) && next.y > p0.y && next.x < p0.x, "{p0:?} {next:?}");
|
||||
let TextKind::OnPath { path, .. } = &t[0].kind else { panic!("not on a path") };
|
||||
let b = t[0].xf.transform_rect_bbox(path.bounds().unwrap());
|
||||
assert!(close(b.x0, 50.0, 1e-6) && close(b.y0, 50.0, 1e-6) && close(b.x1, 250.0, 1e-6) && close(b.y1, 250.0, 1e-6), "{b:?}");
|
||||
assert!(path.is_closed());
|
||||
// Ellipse, with the shape's own transform.
|
||||
let (p0, next) = path_start(t[1]);
|
||||
assert!(near(p0, Point::new(240.0, 150.0)) && next.y > p0.y, "{p0:?} {next:?}");
|
||||
// Rounded rect: after the top-left corner, along the top edge to the right.
|
||||
let (p0, next) = path_start(t[2]);
|
||||
assert!(near(p0, Point::new(30.0, 30.0)) && next.x > p0.x && close(next.y, 30.0, 1e-9), "{p0:?} {next:?}");
|
||||
let (p0, next) = path_start(t[3]);
|
||||
assert!(near(p0, Point::new(20.0, 30.0)) && next.x > p0.x, "{p0:?} {next:?}");
|
||||
// Line, polyline and polygon follow their points.
|
||||
assert!(near(path_start(t[4]).0, Point::new(10.0, 200.0)));
|
||||
assert!(near(path_start(t[5]).0, Point::new(10.0, 10.0)));
|
||||
let TextKind::OnPath { path, .. } = &t[6].kind else { panic!("not on a path") };
|
||||
assert!(path.is_closed() && near(path_start(t[6]).0, Point::new(10.0, 10.0)));
|
||||
assert_eq!(t[0].plain_text(), "TEXT ON A CIRCLE");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_path_to_an_empty_shape_still_falls_back_to_point_type() {
|
||||
let (d, w) =
|
||||
import_with_report(&svg(r##"<circle id="c" cx="5" cy="5" r="0"/><text x="5" y="10"><textPath href="#c">Zero</textPath></text>"##)).unwrap();
|
||||
assert!(matches!(only_text(&d).kind, TextKind::Point));
|
||||
assert!(w.iter().any(|w| w.contains("textPath")), "{w:?}");
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
//! The font folders fontconfig is set up with (Linux and the other Unix desktops): the `<dir>`
|
||||
//! entries of its configuration, `/etc/fonts/fonts.conf` and what that includes (`conf.d`, the
|
||||
//! user's own files). Distributions and packages add folders outside the usual ones there: TeX
|
||||
//! fonts under `/usr/share/texmf/fonts`, say, which `fc-list` and other apps then show (#1529).
|
||||
//!
|
||||
//! The format is fontconfig's documented one
|
||||
//! (<https://www.freedesktop.org/software/fontconfig/fontconfig-user.html>, "Configuration file
|
||||
//! format"): `<dir prefix="…">` and `<include prefix="…">` are all that is read here. The files are
|
||||
//! read as they are: fontconfig itself (a library, or `fc-list`) isn't asked.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The most configuration files read, and the largest one (they are a few kilobytes each).
|
||||
const MAX_FILES: usize = 512;
|
||||
const MAX_FILE_BYTES: u64 = 1 << 20;
|
||||
/// The most folders taken, and how deep files may include files.
|
||||
const MAX_DIRS: usize = 256;
|
||||
const MAX_DEPTH: usize = 8;
|
||||
|
||||
/// A `<dir>` (a font folder) or an `<include>` (another configuration file, or a folder of them)
|
||||
/// with its `prefix` attribute and its text.
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Entry {
|
||||
include: bool,
|
||||
prefix: String,
|
||||
path: String,
|
||||
}
|
||||
|
||||
/// The `<dir>` and `<include>` elements of a configuration file, in order. Anything that isn't
|
||||
/// one of them, comments included, is skipped; a file that isn't well formed gives what was read
|
||||
/// before the damage.
|
||||
fn entries(xml: &str) -> Vec<Entry> {
|
||||
let mut out = vec![];
|
||||
let mut rest = xml;
|
||||
while let Some(at) = rest.find('<') {
|
||||
rest = rest.get(at + 1..).unwrap_or_default();
|
||||
if let Some(comment) = rest.strip_prefix("!--") {
|
||||
rest = comment.split_once("-->").map_or("", |(_, after)| after);
|
||||
continue;
|
||||
}
|
||||
let Some((tag, after)) = rest.split_once('>') else { break };
|
||||
let name = tag.split_whitespace().next().unwrap_or_default();
|
||||
if !matches!(name, "dir" | "include") || tag.ends_with('/') {
|
||||
continue;
|
||||
}
|
||||
let Some((text, after)) = after.split_once("</") else { break };
|
||||
rest = after;
|
||||
// No element is inside these: text with a tag in it isn't a path.
|
||||
if text.contains('<') {
|
||||
continue;
|
||||
}
|
||||
let path = text.trim().replace("&", "&").replace("<", "<").replace(">", ">").replace(""", "\"").replace("'", "'");
|
||||
if !path.is_empty() {
|
||||
out.push(Entry { include: name == "include", prefix: attribute(tag, "prefix").unwrap_or_default().to_string(), path });
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// The value of attribute `name` in a start tag's text.
|
||||
fn attribute<'a>(tag: &'a str, name: &str) -> Option<&'a str> {
|
||||
let mut rest = tag;
|
||||
while let Some(at) = rest.find(name) {
|
||||
let before_ok = rest.get(..at).is_some_and(|b| b.ends_with(char::is_whitespace));
|
||||
rest = rest.get(at + name.len()..)?;
|
||||
let Some(value) = rest.trim_start().strip_prefix('=') else { continue };
|
||||
let value = value.trim_start();
|
||||
let quote = value.chars().next().filter(|q| matches!(q, '"' | '\''))?;
|
||||
let (value, _) = value.get(1..)?.split_once(quote)?;
|
||||
if before_ok {
|
||||
return Some(value);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Where the user's files are: the home folder and the XDG data and configuration folders.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub(crate) struct Places {
|
||||
pub home: Option<PathBuf>,
|
||||
pub data_home: Option<PathBuf>,
|
||||
pub config_home: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl Places {
|
||||
/// The path an entry of the file `from` names: `~` is the home folder, `prefix="xdg"` the XDG
|
||||
/// data folder (a `<dir>`) or configuration folder (an `<include>`), `prefix="relative"` the
|
||||
/// file's own folder, as is an `<include>` that isn't a full path. A `<dir>` relative to the
|
||||
/// folder the app was started in is none.
|
||||
fn resolve(&self, e: &Entry, from: &Path) -> Option<PathBuf> {
|
||||
// `~` alone or before a `/`: another user's home (`~name`) isn't looked up.
|
||||
if let Some(rest) = e.path.strip_prefix('~').filter(|rest| rest.is_empty() || rest.starts_with('/')) {
|
||||
return Some(self.home.as_ref()?.join(rest.trim_start_matches('/')));
|
||||
}
|
||||
let path = Path::new(&e.path);
|
||||
let base = match e.prefix.as_str() {
|
||||
"xdg" if e.include => self.config_home.as_deref(),
|
||||
"xdg" => self.data_home.as_deref(),
|
||||
_ if path.is_absolute() => return Some(path.to_path_buf()),
|
||||
"relative" => from.parent(),
|
||||
_ if e.include => from.parent(),
|
||||
_ => None,
|
||||
};
|
||||
Some(base?.join(path)).filter(|p| p.is_absolute())
|
||||
}
|
||||
}
|
||||
|
||||
/// The font folders the configuration file `root` and the files it includes list, in order, each
|
||||
/// once. Files that are missing or can't be read list none.
|
||||
pub(crate) fn font_dirs(root: &Path, places: &Places) -> Vec<PathBuf> {
|
||||
let mut read = Reader { places, files: HashSet::new(), dirs: vec![] };
|
||||
read.file(root, 0);
|
||||
read.dirs
|
||||
}
|
||||
|
||||
struct Reader<'a> {
|
||||
places: &'a Places,
|
||||
/// The files read so far (a file including itself is read once).
|
||||
files: HashSet<PathBuf>,
|
||||
dirs: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl Reader<'_> {
|
||||
fn file(&mut self, path: &Path, depth: usize) {
|
||||
if depth > MAX_DEPTH || self.files.len() >= MAX_FILES || !self.files.insert(path.to_path_buf()) {
|
||||
return;
|
||||
}
|
||||
// A folder of configuration files: those named as fontconfig reads them (a digit first,
|
||||
// `.conf` last), in name order.
|
||||
if let Ok(list) = std::fs::read_dir(path) {
|
||||
let mut files: Vec<PathBuf> = list
|
||||
.flatten()
|
||||
.take(MAX_FILES)
|
||||
.filter(|e| {
|
||||
let name = e.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
name.starts_with(|c: char| c.is_ascii_digit()) && name.ends_with(".conf")
|
||||
})
|
||||
.map(|e| e.path())
|
||||
.collect();
|
||||
files.sort();
|
||||
for f in files {
|
||||
self.file(&f, depth + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// A file that is missing, too large or not text lists nothing.
|
||||
if !std::fs::metadata(path).is_ok_and(|m| m.is_file() && m.len() <= MAX_FILE_BYTES) {
|
||||
return;
|
||||
}
|
||||
let Ok(text) = std::fs::read_to_string(path) else { return };
|
||||
for e in entries(&text) {
|
||||
let Some(to) = self.places.resolve(&e, path) else { continue };
|
||||
if e.include {
|
||||
self.file(&to, depth + 1);
|
||||
} else if self.dirs.len() < MAX_DIRS && !self.dirs.contains(&to) {
|
||||
self.dirs.push(to);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The configuration file fontconfig starts from: `FONTCONFIG_FILE`, else `fonts.conf` in
|
||||
/// `FONTCONFIG_PATH`, else `/etc/fonts/fonts.conf`.
|
||||
pub(crate) fn root() -> PathBuf {
|
||||
let var = |name| std::env::var_os(name).map(PathBuf::from).filter(|p| p.is_absolute());
|
||||
var("FONTCONFIG_FILE").or_else(|| var("FONTCONFIG_PATH").map(|d| d.join("fonts.conf"))).unwrap_or_else(|| "/etc/fonts/fonts.conf".into())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(include: bool, prefix: &str, path: &str) -> Entry {
|
||||
Entry { include, prefix: prefix.into(), path: path.into() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dirs_and_includes_are_read_and_the_rest_is_skipped() {
|
||||
let xml = r#"<?xml version="1.0"?>
|
||||
<!DOCTYPE fontconfig SYSTEM "urn:fontconfig:fonts.dtd">
|
||||
<fontconfig>
|
||||
<description>Default configuration file</description>
|
||||
<!-- <dir>/commented/out</dir> -->
|
||||
<dir>/usr/share/fonts</dir>
|
||||
<dir prefix="xdg">fonts</dir>
|
||||
<dir prefix = 'relative' salt="x"> more & more </dir>
|
||||
<dir>~/.fonts</dir>
|
||||
<dir></dir>
|
||||
<cachedir>/var/cache/fontconfig</cachedir>
|
||||
<match target="pattern"><test qual="any" name="family"><string>mono</string></test></match>
|
||||
<include ignore_missing="yes">conf.d</include>
|
||||
<include ignore_missing="yes" prefix="xdg">fontconfig/fonts.conf</include>
|
||||
<dir>/cut/off"#;
|
||||
assert_eq!(
|
||||
entries(xml),
|
||||
[
|
||||
entry(false, "", "/usr/share/fonts"),
|
||||
entry(false, "xdg", "fonts"),
|
||||
entry(false, "relative", "more & more"),
|
||||
entry(false, "", "~/.fonts"),
|
||||
entry(true, "", "conf.d"),
|
||||
entry(true, "xdg", "fontconfig/fonts.conf"),
|
||||
]
|
||||
);
|
||||
// Damaged files and files that aren't XML read as nothing, without a panic.
|
||||
for junk in
|
||||
["", "<", "<dir", "<dir>", "<dir><dir></dir>", "</dir>", "<!--", "<dir prefix=>x</dir", "\u{0}<dir prefix=\"", "<dir prefix='é>ü</dir>"]
|
||||
{
|
||||
let _ = entries(junk);
|
||||
}
|
||||
assert_eq!(attribute("dir notprefix=\"a\" prefix=\"b\"", "prefix"), Some("b"));
|
||||
}
|
||||
|
||||
/// A configuration as distributions lay it out: the main file lists the usual folders and
|
||||
/// includes `conf.d`, where a package's file adds its own folder (TeX fonts, #1529) and
|
||||
/// another includes the user's files.
|
||||
#[test]
|
||||
fn folders_of_included_files_are_listed_once_in_order() {
|
||||
let tmp = std::env::temp_dir().join(format!("vc-fontconfig-{}", std::process::id()));
|
||||
let _ = std::fs::remove_dir_all(&tmp);
|
||||
let (etc, home) = (tmp.join("etc/fonts"), tmp.join("home"));
|
||||
let write = |path: PathBuf, body: &str| {
|
||||
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||
std::fs::write(path, format!("<fontconfig>{body}</fontconfig>")).unwrap();
|
||||
};
|
||||
let texmf = tmp.join("usr/share/texmf/fonts/opentype");
|
||||
write(
|
||||
etc.join("fonts.conf"),
|
||||
&format!(
|
||||
"<dir>{}</dir><dir prefix=\"xdg\">fonts</dir><dir>~/.fonts</dir><dir>fonts-of-the-working-folder</dir>\
|
||||
<include ignore_missing=\"yes\">conf.d</include><include>{}</include><include>fonts.conf</include>",
|
||||
tmp.join("usr/share/fonts").display(),
|
||||
etc.join("missing.conf").display()
|
||||
),
|
||||
);
|
||||
write(etc.join("conf.d/09-texlive.conf"), &format!("<dir>{}</dir>", texmf.display()));
|
||||
write(etc.join("conf.d/50-user.conf"), "<include ignore_missing=\"yes\" prefix=\"xdg\">fontconfig/fonts.conf</include>");
|
||||
write(etc.join("conf.d/README"), "<dir>/not/a/conf/file</dir>");
|
||||
write(etc.join("conf.d/user.conf"), "<dir>/no/digit/first</dir>");
|
||||
write(home.join(".config/fontconfig/fonts.conf"), &format!("<dir prefix=\"relative\">mine</dir><dir>{}</dir>", texmf.display()));
|
||||
let places = Places { home: Some(home.clone()), data_home: Some(home.join(".local/share")), config_home: Some(home.join(".config")) };
|
||||
assert_eq!(
|
||||
font_dirs(&etc.join("fonts.conf"), &places),
|
||||
[tmp.join("usr/share/fonts"), home.join(".local/share/fonts"), home.join(".fonts"), texmf, home.join(".config/fontconfig/mine")]
|
||||
);
|
||||
// Without a home folder, what needs one is left out; a missing file lists nothing.
|
||||
assert_eq!(font_dirs(&etc.join("fonts.conf"), &Places::default()).len(), 2);
|
||||
assert!(font_dirs(&tmp.join("none.conf"), &places).is_empty());
|
||||
let _ = std::fs::remove_dir_all(&tmp);
|
||||
}
|
||||
}
|
||||
@@ -1269,12 +1269,21 @@ pub fn system_font_dirs() -> Vec<PathBuf> {
|
||||
// list their own data folders), relative paths ignored as the XDG spec says.
|
||||
let xdg = |var| std::env::var_os(var).map(|v| std::env::split_paths(&v).filter(|p| p.is_absolute()).collect::<Vec<_>>()).unwrap_or_default();
|
||||
let data_home = xdg("XDG_DATA_HOME").into_iter().next().or_else(|| home.as_ref().map(|h| h.join(".local/share")));
|
||||
dirs.extend(data_home.into_iter().chain(xdg("XDG_DATA_DIRS")).map(|d| d.join("fonts")));
|
||||
dirs.extend(data_home.iter().cloned().chain(xdg("XDG_DATA_DIRS")).map(|d| d.join("fonts")));
|
||||
if let Some(h) = &home {
|
||||
dirs.push(h.join(".fonts"));
|
||||
}
|
||||
// In a Flatpak sandbox, the host's fonts (system, local and the user's).
|
||||
dirs.extend(["/run/host/fonts", "/run/host/local-fonts", "/run/host/user-fonts"].map(Into::into));
|
||||
// The folders fontconfig is set up with outside those: packages add their own (TeX fonts
|
||||
// under `/usr/share/texmf/fonts`, #1529), and `fc-list` and other apps show their fonts.
|
||||
let config_home = xdg("XDG_CONFIG_HOME").into_iter().next().or_else(|| home.as_ref().map(|h| h.join(".config")));
|
||||
let places = crate::fontconfig::Places { home: home.clone(), data_home, config_home };
|
||||
for dir in crate::fontconfig::font_dirs(&crate::fontconfig::root(), &places) {
|
||||
if !dirs.iter().any(|known| dir.starts_with(known)) {
|
||||
dirs.push(dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
dirs.extend(app_font_dir().map(Path::to_path_buf));
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
|
||||
@@ -14,6 +14,7 @@ pub mod craft_fonts;
|
||||
pub mod edit;
|
||||
pub mod embed;
|
||||
mod features;
|
||||
mod fontconfig;
|
||||
mod fontdb;
|
||||
pub mod hyphen;
|
||||
mod layout;
|
||||
|
||||
@@ -51,7 +51,8 @@ fn step(d: u8) -> (i32, i32) {
|
||||
fn label(mask: &[bool], w: usize, h: usize) -> (Vec<u32>, Vec<usize>) {
|
||||
let mut comp = vec![u32::MAX; w * h];
|
||||
let mut sizes = Vec::new();
|
||||
let mut stack = Vec::new();
|
||||
// (Indices as `u32`: a mask has at most 64 million pixels, and this stack can hold them all.)
|
||||
let mut stack: Vec<u32> = Vec::new();
|
||||
for s in 0..w * h {
|
||||
if !mask[s] || comp[s] != u32::MAX {
|
||||
continue;
|
||||
@@ -59,14 +60,15 @@ fn label(mask: &[bool], w: usize, h: usize) -> (Vec<u32>, Vec<usize>) {
|
||||
let id = sizes.len() as u32;
|
||||
let mut n = 0usize;
|
||||
comp[s] = id;
|
||||
stack.push(s);
|
||||
stack.push(s as u32);
|
||||
while let Some(i) = stack.pop() {
|
||||
let i = i as usize;
|
||||
n += 1;
|
||||
let (x, y) = (i % w, i / w);
|
||||
let mut go = |j: usize| {
|
||||
if mask[j] && comp[j] == u32::MAX {
|
||||
comp[j] = id;
|
||||
stack.push(j);
|
||||
stack.push(j as u32);
|
||||
}
|
||||
};
|
||||
if x > 0 {
|
||||
|
||||
+119
-33
@@ -269,8 +269,12 @@ pub struct TraceParams {
|
||||
/// `noise / 10` pixels of the source image.)
|
||||
pub noise: u32,
|
||||
pub method: Method,
|
||||
/// Drop white areas (no white background shape).
|
||||
/// Ignore Color: drop the areas of one colour (no background shape): white, unless
|
||||
/// `ignore_color` names another.
|
||||
pub ignore_white: bool,
|
||||
/// The colour Ignore Color leaves out (`#rrggbb`): the palette colours within about 30 levels
|
||||
/// of it. `None` is white. (Flat Logo mode leaves out white only.)
|
||||
pub ignore_color: Option<String>,
|
||||
/// Replace nearly-straight curves with straight lines. In Logo mode this also snaps tight arcs
|
||||
/// to true circles.
|
||||
pub snap_curves_to_lines: bool,
|
||||
@@ -302,6 +306,7 @@ impl Default for TraceParams {
|
||||
noise: 25,
|
||||
method: Method::Abutting,
|
||||
ignore_white: false,
|
||||
ignore_color: None,
|
||||
snap_curves_to_lines: false,
|
||||
fills: true,
|
||||
strokes: false,
|
||||
@@ -353,6 +358,7 @@ impl TraceParams {
|
||||
/// Built-in preset names in panel order (our own parameter sets).
|
||||
pub const PRESET_NAMES: &[&str] = &[
|
||||
"Default",
|
||||
"Auto Color",
|
||||
"High Fidelity Photo",
|
||||
"Low Fidelity Photo",
|
||||
"3 Colors",
|
||||
@@ -364,6 +370,7 @@ pub const PRESET_NAMES: &[&str] = &[
|
||||
"Silhouettes",
|
||||
"Line Art",
|
||||
"Technical Drawing",
|
||||
"Outline",
|
||||
"Flat Logo",
|
||||
];
|
||||
|
||||
@@ -392,6 +399,10 @@ pub fn preset(name: &str) -> Option<TraceParams> {
|
||||
};
|
||||
Some(match key.as_str() {
|
||||
"default" => d,
|
||||
// A posterised image of a photo or of artwork: its own colours where it is flat art, as
|
||||
// many as its tones need where it is not.
|
||||
// (Color detail 85: a colour on a hundredth of the picture is still one of its colours.)
|
||||
"auto color" => TraceParams { palette: Palette::Automatic, color_detail: 85.0, ..color(6, 65.0, 60.0, 16, Method::Abutting) },
|
||||
"high fidelity photo" => color(64, 90.0, 25.0, 4, Method::Overlapping),
|
||||
"low fidelity photo" => color(20, 60.0, 50.0, 12, Method::Overlapping),
|
||||
"3 colors" => color(3, 60.0, 60.0, 20, Method::Abutting),
|
||||
@@ -403,6 +414,8 @@ pub fn preset(name: &str) -> Option<TraceParams> {
|
||||
"silhouettes" => bw(200, 40.0, 60.0, 30, false),
|
||||
"line art" => bw(128, 80.0, 70.0, 10, false),
|
||||
"technical drawing" => bw(128, 95.0, 90.0, 4, true),
|
||||
// Black outlines: lines as stroked centre lines, wider areas as stroked outlines.
|
||||
"outline" => TraceParams { fills: false, strokes: true, ..bw(128, 80.0, 70.0, 10, false) },
|
||||
// Flat-colour logos: colours found automatically, edges placed to a fraction of a pixel.
|
||||
"flat logo" => TraceParams { mode: Mode::Logo, colors: 8, paths: 60.0, corners: 50.0, noise: 6, snap_curves_to_lines: true, ..d },
|
||||
_ => return None,
|
||||
@@ -446,6 +459,14 @@ fn is_white(c: [u8; 3]) -> bool {
|
||||
c.iter().all(|&v| v >= 245)
|
||||
}
|
||||
|
||||
/// Whether Ignore Color leaves palette colour `c` out: `target` is the colour picked (`None`: white).
|
||||
fn is_ignored(c: [u8; 3], target: Option<[u8; 3]>) -> bool {
|
||||
match target {
|
||||
None => is_white(c),
|
||||
Some(t) => (0..3).map(|k| (f64::from(c[k]) - f64::from(t[k])).powi(2)).sum::<f64>() <= 30.0 * 30.0 || (is_white(t) && is_white(c)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Trace `img` with `params`. Failures (an image Logo mode refuses, say) give an empty result: use
|
||||
/// [`trace_within`] to see why.
|
||||
pub fn trace(img: &Raster, params: &TraceParams) -> TraceResult {
|
||||
@@ -464,16 +485,24 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
|
||||
if params.mode == Mode::Logo {
|
||||
return logo::trace_logo(img, params, max_anchors);
|
||||
}
|
||||
// The colour Ignore Color leaves out, if not white.
|
||||
let ignore = match ¶ms.ignore_color {
|
||||
Some(name) if params.ignore_white => Some(logo::parse_hex(name).ok_or_else(|| TraceError::BadPalette(name.clone()))?),
|
||||
_ => None,
|
||||
};
|
||||
// A photo is traced from a copy with its noise smoothed away (see `smooth`).
|
||||
let smoothed = (params.mode != Mode::BlackAndWhite && quantize::continuous_tone(img)).then(|| smooth::smoothed(img));
|
||||
let img = smoothed.as_ref().unwrap_or(img);
|
||||
let mut q = quantize(img, params);
|
||||
denoise(&mut q.labels, w, h, params.noise as usize);
|
||||
// Pixel count per palette entry.
|
||||
// Pixel count per palette entry, and the box its pixels lie in.
|
||||
let mut counts = vec![0usize; q.palette.len()];
|
||||
for &l in &q.labels {
|
||||
if let Some(c) = counts.get_mut(l as usize) {
|
||||
let mut boxes: Vec<Option<(usize, usize, usize, usize)>> = vec![None; q.palette.len()];
|
||||
for (i, &l) in q.labels.iter().enumerate() {
|
||||
if let (Some(c), Some(b)) = (counts.get_mut(l as usize), boxes.get_mut(l as usize)) {
|
||||
*c += 1;
|
||||
let (x, y) = (i % w, i / w);
|
||||
*b = Some(b.map_or((x, y, x + 1, y + 1), |b| (b.0.min(x), b.1.min(y), b.2.max(x + 1), b.3.max(y + 1))));
|
||||
}
|
||||
}
|
||||
// Layers bottom → top: largest area first.
|
||||
@@ -497,12 +526,14 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
|
||||
rank[i] = r;
|
||||
}
|
||||
let layers: Vec<(usize, usize)> =
|
||||
order.iter().copied().enumerate().filter(|&(_, ci)| !(params.ignore_white && is_white(q.palette[ci]))).collect();
|
||||
order.iter().copied().enumerate().filter(|&(_, ci)| !(params.ignore_white && is_ignored(q.palette[ci], ignore))).collect();
|
||||
let job = Layers {
|
||||
q: &q,
|
||||
rank: &rank,
|
||||
params,
|
||||
img,
|
||||
ignore,
|
||||
boxes,
|
||||
bw_tones: bw_tones(img, &q),
|
||||
style: params.outline_style(),
|
||||
opts: fit::FitOptions {
|
||||
@@ -517,7 +548,12 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
|
||||
max_anchors,
|
||||
};
|
||||
let mut out = TraceResult::default();
|
||||
for (paths, &(_, ci)) in parallel_map(&layers, |&(r, ci)| job.layer(r, ci)).into_iter().zip(&layers) {
|
||||
// A layer takes about ten bytes a pixel of its box while it is traced (its mask, the labels
|
||||
// of its components, the stack that fills them, its edges): as many layers at a time as fit
|
||||
// in half a gigabyte, so that a large image is not traced at the cost of gigabytes.
|
||||
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 color = q.palette[ci];
|
||||
if !out.palette.contains(&color) {
|
||||
@@ -527,17 +563,32 @@ pub fn trace_within(img: &Raster, params: &TraceParams, max_anchors: usize) -> R
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// The mean luminance of the pixels labelled 0 (white) and 1 (black) of a Black and White
|
||||
/// quantisation: pure white and black where a side has no pixel.
|
||||
/// The luminance of white and of black in a Black and White quantisation: what its lightest
|
||||
/// white pixels and its darkest black ones have (the 98th and 2nd percentiles; pure white and
|
||||
/// black where a side has no pixel). Not the means: where the black is thin lines, blurred or
|
||||
/// scanned, most black pixels are part paper, their mean is a gray, and read against it the middle
|
||||
/// of every line would count as more than all ink, now more and now less as the line crosses the
|
||||
/// pixel rows, which put a ripple on its edges.
|
||||
fn bw_tones(img: &Raster, q: &Quantized) -> [f64; 2] {
|
||||
let (mut sum, mut n) = ([0.0f64; 2], [0.0f64; 2]);
|
||||
let mut hist = [[0u64; 256]; 2];
|
||||
for (p, &l) in img.rgba.as_chunks::<4>().0.iter().zip(&q.labels) {
|
||||
if let (Some(s), Some(n)) = (sum.get_mut(l as usize), n.get_mut(l as usize)) {
|
||||
*s += (f64::from(p[0]) * 299.0 + f64::from(p[1]) * 587.0 + f64::from(p[2]) * 114.0) / 1000.0;
|
||||
*n += 1.0;
|
||||
if let Some(h) = hist.get_mut(l as usize) {
|
||||
h[((u32::from(p[0]) * 299 + u32::from(p[1]) * 587 + u32::from(p[2]) * 114 + 500) / 1000).min(255) as usize] += 1;
|
||||
}
|
||||
}
|
||||
[if n[0] > 0.0 { sum[0] / n[0] } else { 255.0 }, if n[1] > 0.0 { sum[1] / n[1] } else { 0.0 }]
|
||||
// The luminance a share `at` of the side's pixels are darker than.
|
||||
let percentile = |h: &[u64; 256], at: f64, none: f64| {
|
||||
let total: u64 = h.iter().sum();
|
||||
let mut seen = 0;
|
||||
for (v, n) in h.iter().enumerate() {
|
||||
seen += n;
|
||||
if total > 0 && seen as f64 >= at * total as f64 {
|
||||
return v as f64;
|
||||
}
|
||||
}
|
||||
none
|
||||
};
|
||||
[percentile(&hist[0], 0.98, 255.0), percentile(&hist[1], 0.02, 0.0)]
|
||||
}
|
||||
|
||||
/// What tracing one colour layer needs.
|
||||
@@ -547,7 +598,11 @@ struct Layers<'a> {
|
||||
rank: &'a [usize],
|
||||
params: &'a TraceParams,
|
||||
img: &'a Raster,
|
||||
/// Black and White: the mean luminance of the white pixels and of the black ones.
|
||||
/// The colour Ignore Color leaves out, if not white.
|
||||
ignore: Option<[u8; 3]>,
|
||||
/// The box each palette entry's pixels lie in: `(x0, y0, x1, y1)`, the far sides excluded.
|
||||
boxes: Vec<Option<(usize, usize, usize, usize)>>,
|
||||
/// Black and White: the luminance of white and of black (see `bw_tones`).
|
||||
bw_tones: [f64; 2],
|
||||
/// How outlines are fitted.
|
||||
style: logo::Style,
|
||||
@@ -564,7 +619,7 @@ impl Layers<'_> {
|
||||
/// Ignore White: white is no part of any layer (stacked layers reach under the layers above
|
||||
/// them, and must not reach under white that is left out).
|
||||
fn ignored(&self, label: u16) -> bool {
|
||||
self.params.ignore_white && self.q.palette.get(label as usize).is_some_and(|c| is_white(*c))
|
||||
self.params.ignore_white && self.q.palette.get(label as usize).is_some_and(|c| is_ignored(*c, self.ignore))
|
||||
}
|
||||
/// The region across a crack, for cutting outlines at their junctions: the outside pixel's
|
||||
/// colour. Stacked layers end on many colours at once and share no edge: one neighbour.
|
||||
@@ -609,7 +664,7 @@ impl Layers<'_> {
|
||||
/// How far the edge lies from a crack between two pixels, towards the outside one (−0.5 to 0.5).
|
||||
///
|
||||
/// Each pixel of the column across the crack is read as a blend of the tones on its two sides
|
||||
/// (the two pixels' palette colours; in Black and White the mean luminance of the black and of
|
||||
/// (the two pixels' palette colours; in Black and White the luminance of the image's black and of
|
||||
/// the white pixels). The edge is where the blends, summed, put it: as far out as there is
|
||||
/// inside tone in the outside pixels, less the outside tone in the inside ones (two pixels each
|
||||
/// way, while they are on the same side and no more blended than the pixel before: more would be
|
||||
@@ -630,8 +685,20 @@ impl Layers<'_> {
|
||||
return 0.0;
|
||||
}
|
||||
let bw = self.params.mode == Mode::BlackAndWhite;
|
||||
// Black and White: black is the image's black; white is the lightest pixel within a few
|
||||
// pixels of the crack, the paper as it is here (a scan's paper is darker in one corner than
|
||||
// in another, and read against the image's lightest white it would count as part ink).
|
||||
let black = self.bw_tones[1];
|
||||
let white = if bw {
|
||||
let light = (-3..=4)
|
||||
.filter_map(|k| c.at(k).and_then(|i| self.img.rgba.get(i * 4..i * 4 + 3)))
|
||||
.map(|p| (f64::from(p[0]) * 299.0 + f64::from(p[1]) * 587.0 + f64::from(p[2]) * 114.0) / 1000.0);
|
||||
light.fold(f64::NEG_INFINITY, f64::max).clamp((black + 32.0).min(self.bw_tones[0]), self.bw_tones[0])
|
||||
} else {
|
||||
self.bw_tones[0]
|
||||
};
|
||||
let (ca, cb) = if bw {
|
||||
let tone = |l: u16| [self.bw_tones[usize::from(l == 1)]; 3];
|
||||
let tone = |l: u16| [if l == 1 { black } else { white }; 3];
|
||||
(tone(la), tone(lb))
|
||||
} else {
|
||||
let (Some(ca), Some(cb)) = (self.q.palette.get(la as usize), self.q.palette.get(lb as usize)) else { return 0.0 };
|
||||
@@ -689,7 +756,6 @@ impl Layers<'_> {
|
||||
};
|
||||
if bw {
|
||||
// The share of black at which a pixel turns black.
|
||||
let (white, black) = (self.bw_tones[0], self.bw_tones[1]);
|
||||
let turn = ((white - f64::from(self.params.threshold)) / (white - black).max(1.0)).clamp(0.02, 0.98);
|
||||
shift += if la == 1 { 1.0 } else { -1.0 } * (0.5 - turn) / steep;
|
||||
}
|
||||
@@ -709,17 +775,23 @@ impl Layers<'_> {
|
||||
} else {
|
||||
(vec![], vec![])
|
||||
};
|
||||
let mask: Vec<bool> = self
|
||||
.q
|
||||
.labels
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, &l)| {
|
||||
l != TRANSPARENT
|
||||
&& !thin.get(i).copied().unwrap_or(false)
|
||||
&& if overlapping { self.rank.get(l as usize).is_some_and(|&lr| lr >= r) && !self.ignored(l) } else { l as usize == ci }
|
||||
})
|
||||
.collect();
|
||||
// The layer is traced within the box its pixels lie in, not the whole image.
|
||||
let part = |l: u16| if overlapping { self.rank.get(l as usize).is_some_and(|&lr| lr >= r) && !self.ignored(l) } else { l as usize == ci };
|
||||
let (mut x0, mut y0, mut x1, mut y1) = (w, h, 0, 0);
|
||||
for (l, b) in self.boxes.iter().enumerate() {
|
||||
if let Some(b) = b.filter(|_| part(l as u16)) {
|
||||
(x0, y0, x1, y1) = (x0.min(b.0), y0.min(b.1), x1.max(b.2), y1.max(b.3));
|
||||
}
|
||||
}
|
||||
if x0 >= x1 || y0 >= y1 {
|
||||
return Some(vec![]);
|
||||
}
|
||||
let (bw, bh) = (x1 - x0, y1 - y0);
|
||||
let mut mask = Vec::with_capacity(bw * bh);
|
||||
for y in y0..y1 {
|
||||
let row = self.q.labels.get(y * w + x0..y * w + x1)?;
|
||||
mask.extend(row.iter().enumerate().map(|(k, &l)| l != TRANSPARENT && !thin.get(y * w + x0 + k).copied().unwrap_or(false) && part(l)));
|
||||
}
|
||||
// Without Fills, areas are outlined with a 1 px stroke.
|
||||
let area_stroke = (params.strokes && !params.fills).then_some(1.0);
|
||||
let min_hole = params.noise.max(1) as i64;
|
||||
@@ -732,7 +804,16 @@ impl Layers<'_> {
|
||||
paths.push(TracedPath { path, color, pixels, stroke });
|
||||
true
|
||||
};
|
||||
for comp in trace_mask(&mask, w, h) {
|
||||
let mut comps = trace_mask(&mask, bw, bh);
|
||||
drop(mask);
|
||||
for comp in &mut comps {
|
||||
for l in std::iter::once(&mut comp.outer).chain(&mut comp.holes) {
|
||||
for p in &mut l.pts {
|
||||
*p = (p.0 + x0 as i32, p.1 + y0 as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
for comp in comps {
|
||||
let fit = |l: &Loop| fit::fit_loop(l, w, h, &|c| (self.shift(c), self.neighbour(c)), &|x, y| self.junction(x, y), &self.style);
|
||||
let Some(outer) = fit(&comp.outer) else { continue };
|
||||
let mut subs = vec![outer];
|
||||
@@ -756,9 +837,14 @@ impl Layers<'_> {
|
||||
}
|
||||
|
||||
/// `f` of each of `items`, in order, spread over the available cores.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn parallel_map<T: Sync, R: Send>(items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec<R> {
|
||||
let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(items.len());
|
||||
parallel_map_at_most(usize::MAX, items, f)
|
||||
}
|
||||
|
||||
/// `f` of each of `items`, in order, spread over the available cores, at most `at_a_time` at once.
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn parallel_map_at_most<T: Sync, R: Send>(at_a_time: usize, items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec<R> {
|
||||
let threads = std::thread::available_parallelism().map_or(1, |n| n.get()).min(items.len()).min(at_a_time);
|
||||
if threads <= 1 {
|
||||
return items.iter().map(f).collect();
|
||||
}
|
||||
@@ -787,7 +873,7 @@ fn parallel_map<T: Sync, R: Send>(items: &[T], f: impl Fn(&T) -> R + Sync) -> Ve
|
||||
|
||||
/// `f` of each of `items`, in order (no threads on the web).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn parallel_map<T: Sync, R: Send>(items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec<R> {
|
||||
fn parallel_map_at_most<T: Sync, R: Send>(_: usize, items: &[T], f: impl Fn(&T) -> R + Sync) -> Vec<R> {
|
||||
items.iter().map(f).collect()
|
||||
}
|
||||
|
||||
|
||||
@@ -26,5 +26,6 @@ mod unmix;
|
||||
|
||||
pub(crate) use geom::{Cubic, all_within, resample_closed, resample_open, sample_cubics};
|
||||
pub(crate) use outline::{Stats, Style, fit_open, fit_outline};
|
||||
pub(crate) use palette::parse_hex;
|
||||
pub(crate) use pipeline::trace_logo;
|
||||
pub(crate) use restore::RestoreParams;
|
||||
@@ -255,7 +255,7 @@ fn snap_curves_to_lines_straightens_near_lines() {
|
||||
|
||||
#[test]
|
||||
fn presets_resolve() {
|
||||
assert_eq!(presets().len(), 13);
|
||||
assert_eq!(presets().len(), 15);
|
||||
for n in PRESET_NAMES {
|
||||
assert!(preset(n).is_some(), "{n}");
|
||||
}
|
||||
@@ -1037,3 +1037,59 @@ fn abutting_regions_share_one_curve_along_their_edge() {
|
||||
}
|
||||
assert!(shared >= 12, "{shared} shared curves");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn layers_traced_a_few_at_a_time_come_out_the_same() {
|
||||
let items: Vec<usize> = (0..40).collect();
|
||||
for at_a_time in [1, 3, usize::MAX] {
|
||||
assert_eq!(parallel_map_at_most(at_a_time, &items, |i| i * i), items.iter().map(|i| i * i).collect::<Vec<_>>());
|
||||
}
|
||||
// A layer is traced within the box of its pixels: a shape far from the image's origin stays there.
|
||||
let img = Raster::from_fn(90, 70, |x, y| if (60..80).contains(&x) && (40..60).contains(&y) { BLACK } else { WHITE });
|
||||
let b = trace(&img, &bw()).paths[0].path.bounds().unwrap();
|
||||
assert!([(b.x0, 60.0), (b.y0, 40.0), (b.x1, 80.0), (b.y1, 60.0)].iter().all(|(got, want)| (got - want).abs() < 0.01), "{b:?}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_blurred_thin_line_on_unevenly_lit_paper_is_a_few_anchors() {
|
||||
// A line 2 px wide a little off the pixel rows, blurred over three pixels, on paper that is a
|
||||
// quarter darker at one end than at the other: nowhere is its ink pure black, or the paper white.
|
||||
let img = Raster::from_fn(160, 40, |x, y| {
|
||||
let (fx, fy) = (f64::from(x) + 0.5, f64::from(y) + 0.5);
|
||||
let paper = 235.0 - 55.0 * fx / 160.0;
|
||||
let centre = 17.3 + 0.06 * fx;
|
||||
// The share of ink in the pixel, the line's edges blurred linearly over 1.5 px either way.
|
||||
let ink = if (10.0..150.0).contains(&fx) { ((1.0 + 1.5 - (fy - centre).abs()) / 3.0).clamp(0.0, 1.0) } else { 0.0 };
|
||||
let v = (paper * (1.0 - ink) + 40.0 * ink).round() as u8;
|
||||
[v, v, v, 255]
|
||||
});
|
||||
let res = trace(&img, &preset("Line Art").unwrap());
|
||||
assert_eq!(res.paths.len(), 1);
|
||||
let path = &res.paths[0].path;
|
||||
// Its two long edges are smooth: a ripple would take an anchor every few pixels.
|
||||
assert!(path.anchor_count() <= 10, "{} anchors", path.anchor_count());
|
||||
// And it lies along the line's middle, about as wide as its ink.
|
||||
let off = along(path).iter().filter(|p| p.x > 14.0 && p.x < 146.0).map(|p| ((p.y - (17.3 + 0.06 * p.x)).abs() - 1.0).abs()).fold(0.0, f64::max);
|
||||
assert!(off < 0.5, "edges {off} px from where the ink's width puts them");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ignore_color_leaves_out_the_colour_picked() {
|
||||
// Navy field, yellow disc, green bar.
|
||||
let img = three_colour_art(0.0);
|
||||
let params = TraceParams { colors: 3, ignore_white: true, ..sixteen_colors() };
|
||||
// White by default: nothing here is white.
|
||||
assert_eq!(trace(&img, ¶ms).paths.len(), 3);
|
||||
// The navy picked a little off: the field goes, the disc and the bar stay.
|
||||
let res = trace(&img, &TraceParams { ignore_color: Some("#18263c".replace("3c", "5e")), ..params.clone() });
|
||||
assert_eq!(res.paths.len(), 2);
|
||||
assert!(res.paths.iter().all(|p| p.color != [20, 40, 90]));
|
||||
// Not ticked, the colour is not left out; and a colour that is none is refused.
|
||||
assert_eq!(trace(&img, &TraceParams { ignore_white: false, ignore_color: Some("#14285a".into()), ..params.clone() }).paths.len(), 3);
|
||||
let bad = trace_within(&img, &TraceParams { ignore_color: Some("navy".into()), ..params.clone() }, usize::MAX);
|
||||
assert!(matches!(bad, Err(TraceError::BadPalette(_))));
|
||||
// Old documents have no such setting: white.
|
||||
let old: TraceParams = serde_json::from_str(r#"{"mode":"color","ignoreWhite":true}"#).unwrap();
|
||||
assert_eq!(old.ignore_color, None);
|
||||
assert_eq!(serde_json::to_value(¶ms).unwrap()["ignoreColor"], serde_json::Value::Null);
|
||||
}
|
||||
@@ -68,10 +68,10 @@ fn target(preset: &str, input: &Input) -> bool {
|
||||
"High Fidelity Photo" | "Low Fidelity Photo" | "Shades of Gray" => &[Class::Tone],
|
||||
// Three colours cannot show the badge's five.
|
||||
"3 Colors" => return input.name == "two touching halves",
|
||||
"6 Colors" | "16 Colors" | "Flat Logo" => &[Class::Flat],
|
||||
"6 Colors" | "16 Colors" | "Flat Logo" | "Auto Color" => &[Class::Flat],
|
||||
"Sketched Art" => &[Class::Shape, Class::Lines],
|
||||
"Silhouettes" => &[Class::Shape],
|
||||
"Line Art" => &[Class::Lines],
|
||||
"Line Art" | "Outline" => &[Class::Lines],
|
||||
"Technical Drawing" => &[Class::Lines, Class::Poly],
|
||||
_ => &[Class::Shape, Class::Poly],
|
||||
};
|
||||
|
||||
@@ -621,6 +621,7 @@
|
||||
VectorCraft on GitHub VectorCraft na GitHubu
|
||||
Search Commands… Hledat příkazy…
|
||||
VectorCraft Help… Nápověda VectorCraft…
|
||||
Auto Color Automatická barva
|
||||
Automatic Automaticky
|
||||
Isolate Selected Group Izolovat vybranou skupinu
|
||||
Exit Isolation Mode Ukončit izolovaný režim
|
||||
@@ -1424,6 +1425,7 @@ lighting Light Světlo
|
||||
Abutting Sousedící
|
||||
Black and White Černobílé
|
||||
Corners Rohy
|
||||
Ignore Color Ignorovat barvu
|
||||
Ignore White Ignorovat bílou
|
||||
Palette: Paleta:
|
||||
Limited Omezená
|
||||
@@ -3660,6 +3662,7 @@ effect Regular Pravidelný
|
||||
Stroke Pressure Tlak tahu
|
||||
|
||||
# Plurals: one|few|other
|
||||
imageTrace Outline Obrys
|
||||
@plural {n} item|{n} items {n} položka|{n} položky|{n} položek
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Tabulka barev ({n} barva)|Tabulka barev ({n} barvy)|Tabulka barev ({n} barev)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Odstranit {n} grafický styl?|Odstranit {n} grafické styly?|Odstranit {n} grafických stylů?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -229,6 +229,7 @@
|
||||
Author Title: Titel des Autors:
|
||||
Author: Autor:
|
||||
Auto Auto
|
||||
Auto Color Automatische Farbe
|
||||
Automatic Automatisch
|
||||
Auto Size New Area Type Größe von neuem Flächentext automatisch anpassen
|
||||
Auto Spell Check Automatische Rechtschreibprüfung
|
||||
@@ -1226,6 +1227,7 @@
|
||||
Identify Objects By Objekte identifizieren nach
|
||||
Ignore Ignorieren
|
||||
Ignore Artboards (all the art as one page) Zeichenflächen ignorieren (gesamte Grafik als eine Seite)
|
||||
Ignore Color Farbe ignorieren
|
||||
Ignore White Weiß ignorieren
|
||||
Palette: Palette:
|
||||
Limited Begrenzt
|
||||
@@ -3135,6 +3137,7 @@ effect Points Punkte
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: Klicken korrigiert auf die nächstliegende Farbe ({hex})
|
||||
“{file}” is protected by a password. „{file}“ ist durch ein Kennwort geschützt.
|
||||
● Recording ● Aufzeichnung
|
||||
imageTrace Outline Kontur
|
||||
@plural {n} item|{n} items {n} Element|{n} Elemente
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Farbtabelle ({n} Farbe)|Farbtabelle ({n} Farben)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Diesen {n} Grafikstil löschen?|Diese {n} Grafikstile löschen?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -247,6 +247,7 @@
|
||||
Author Title: Cargo del autor:
|
||||
Author: Autor:
|
||||
Auto Auto
|
||||
Auto Color Color automático
|
||||
Automatic Automático
|
||||
Auto Size New Area Type Ajustar automáticamente el tamaño del nuevo texto de área
|
||||
Auto Spell Check Revisión ortográfica automática
|
||||
@@ -1244,6 +1245,7 @@
|
||||
Identify Objects By Identificar objetos por
|
||||
Ignore Ignorar
|
||||
Ignore Artboards (all the art as one page) Ignorar mesas de trabajo (toda la ilustración como una página)
|
||||
Ignore Color Ignorar color
|
||||
Ignore White Ignorar blanco
|
||||
Palette: Paleta:
|
||||
Limited Limitada
|
||||
@@ -3236,6 +3238,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: clic para corregir al color más cercano ({hex})
|
||||
“{file}” is protected by a password. “{file}” está protegido con contraseña.
|
||||
● Recording ● Grabando
|
||||
imageTrace Outline Contorno
|
||||
@plural {n} item|{n} items {n} elemento|{n} elementos
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Tabla de colores ({n} color)|Tabla de colores ({n} colores)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? ¿Eliminar {n} estilo gráfico?|¿Eliminar estos {n} estilos gráficos?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -248,6 +248,7 @@
|
||||
Author Title: Fonction de l’auteur :
|
||||
Author: Auteur :
|
||||
Auto Auto
|
||||
Auto Color Couleur automatique
|
||||
Automatic Automatique
|
||||
Auto Size New Area Type Taille automatique du nouveau texte captif
|
||||
Auto Spell Check Vérification orthographique automatique
|
||||
@@ -1243,6 +1244,7 @@
|
||||
Identify Objects By Identifier les objets par
|
||||
Ignore Ignorer
|
||||
Ignore Artboards (all the art as one page) Ignorer les plans de travail (toute l’illustration sur une page)
|
||||
Ignore Color Ignorer la couleur
|
||||
Ignore White Ignorer le blanc
|
||||
Palette: Palette :
|
||||
Limited Limitée
|
||||
@@ -3233,6 +3235,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning} : cliquez pour corriger avec la couleur la plus proche ({hex})
|
||||
“{file}” is protected by a password. « {file} » est protégé par un mot de passe.
|
||||
● Recording ● Enregistrement
|
||||
imageTrace Outline Contour
|
||||
@plural {n} item|{n} items {n} élément|{n} éléments
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Table des couleurs ({n} couleur)|Table des couleurs ({n} couleurs)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Supprimer {n} style graphique ?|Supprimer ces {n} styles graphiques ?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -360,6 +360,7 @@
|
||||
Auto Spell Check Automatikus helyesírás-ellenőrzés
|
||||
Auto-Collapse Iconic Panels Ikonpanelek automatikus becsukása
|
||||
Auto-Rotate (turn the paper to each artboard) Automatikus elforgatás (a papír igazodik minden rajztáblához)
|
||||
Auto Color Automatikus szín
|
||||
Automatic Automatikus
|
||||
Automatic flatness Automatikus simaság
|
||||
Automatically Automatikusan
|
||||
@@ -1608,6 +1609,7 @@
|
||||
Identify Objects By Objektumok azonosítása
|
||||
Ignore Kihagy
|
||||
Ignore Artboards (all the art as one page) Rajztáblák figyelmen kívül hagyása (az összes grafika egy oldalon)
|
||||
Ignore Color Szín figyelmen kívül hagyása
|
||||
Ignore White Fehér figyelmen kívül hagyása
|
||||
Image Kép
|
||||
Image Info Kép adatai
|
||||
@@ -3935,6 +3937,7 @@ lighting Light Fény
|
||||
theme Appearance Kinézet
|
||||
|
||||
# Plurals: one|other
|
||||
imageTrace Outline Körvonal
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Színtábla ({n} szín)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? {n} grafikai stílus törlése?
|
||||
@plural {n} area highlighted|{n} areas highlighted Kiemelt terület: {n}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -247,6 +247,7 @@
|
||||
Author Title: Qualifica dell'autore:
|
||||
Author: Autore:
|
||||
Auto Auto
|
||||
Auto Color Colore automatico
|
||||
Automatic Automatico
|
||||
Auto Size New Area Type Dimensionamento automatico del nuovo testo in area
|
||||
Auto Spell Check Controllo ortografico automatico
|
||||
@@ -1243,6 +1244,7 @@
|
||||
Identify Objects By Identifica oggetti per
|
||||
Ignore Ignora
|
||||
Ignore Artboards (all the art as one page) Ignora tavole da disegno (tutta la grafica come una sola pagina)
|
||||
Ignore Color Ignora colore
|
||||
Ignore White Ignora bianco
|
||||
Palette: Palette:
|
||||
Limited Limitata
|
||||
@@ -3236,6 +3238,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: fai clic per correggere con il colore più vicino ({hex})
|
||||
“{file}” is protected by a password. “{file}” è protetto da password.
|
||||
● Recording ● Registrazione in corso
|
||||
imageTrace Outline Contorno
|
||||
@plural {n} item|{n} items {n} elemento|{n} elementi
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Tabella colori ({n} colore)|Tabella colori ({n} colori)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Eliminare {n} stile grafico?|Eliminare questi {n} stili grafici?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -666,6 +666,7 @@
|
||||
Hide Print Tiling プリント分割を隠す
|
||||
Unlock Grid グリッドをロック解除
|
||||
Edit Envelope エンベロープを編集
|
||||
Auto Color 自動カラー
|
||||
Automatic 自動
|
||||
Isolate Selected Group 選択したグループを分離
|
||||
Exit Isolation Mode 分離モードを終了
|
||||
@@ -1481,6 +1482,7 @@
|
||||
Identify Objects By オブジェクトの識別方法
|
||||
Ignore 無視
|
||||
Ignore Artboards (all the art as one page) アートボードを無視 (すべてのアートを 1 ページとして扱う)
|
||||
Ignore Color カラーを無視
|
||||
Ignore White 白を無視
|
||||
Palette: パレット:
|
||||
Limited 限定
|
||||
@@ -3225,6 +3227,7 @@
|
||||
Yellow イエロー
|
||||
[Normal Character Style] [標準文字スタイル]
|
||||
[Normal Paragraph Style] [標準段落スタイル]
|
||||
imageTrace Outline アウトライン
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) カラーテーブル ({n} 色)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? {n} 個のグラフィックスタイルを削除しますか?
|
||||
@plural {n} Layer|{n} Layers {n} レイヤー
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -664,6 +664,7 @@
|
||||
Hide Print Tiling 인쇄 타일링 숨기기
|
||||
Unlock Grid 격자 잠금 해제
|
||||
Edit Envelope 엔벨로프 편집
|
||||
Auto Color 자동 색상
|
||||
Automatic 자동
|
||||
Isolate Selected Group 선택한 그룹 격리
|
||||
Exit Isolation Mode 격리 모드 종료
|
||||
@@ -1476,6 +1477,7 @@
|
||||
Identify Objects By 개체 식별 기준
|
||||
Ignore 무시
|
||||
Ignore Artboards (all the art as one page) 아트보드 무시 (모든 아트웍을 한 페이지로)
|
||||
Ignore Color 색상 무시
|
||||
Ignore White 흰색 무시
|
||||
Palette: 팔레트:
|
||||
Limited 제한
|
||||
@@ -3218,6 +3220,7 @@
|
||||
Yellow 노랑
|
||||
[Normal Character Style] [기본 문자 스타일]
|
||||
[Normal Paragraph Style] [기본 단락 스타일]
|
||||
imageTrace Outline 윤곽선
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) 색상 표 ({n}색)|색상 표 ({n}색)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? 그래픽 스타일 {n}개를 삭제하시겠습니까?|그래픽 스타일 {n}개를 삭제하시겠습니까?
|
||||
@plural {n} Layer|{n} Layers 레이어 {n}개|레이어 {n}개
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -606,6 +606,7 @@
|
||||
VectorCraft on GitHub VectorCraft w serwisie GitHub
|
||||
Search Commands… Szukaj poleceń…
|
||||
VectorCraft Help… Pomoc VectorCraft…
|
||||
Auto Color Kolor automatyczny
|
||||
Automatic Automatycznie
|
||||
Isolate Selected Group Izoluj zaznaczoną grupę
|
||||
Exit Isolation Mode Wyjdź z trybu izolacji
|
||||
@@ -1374,6 +1375,7 @@ lighting Light Światło
|
||||
Abutting Stykające się
|
||||
Black and White Czarno-biały
|
||||
Corners Narożniki
|
||||
Ignore Color Ignoruj kolor
|
||||
Ignore White Ignoruj biel
|
||||
Palette: Paleta:
|
||||
Limited Ograniczona
|
||||
@@ -3528,6 +3530,7 @@ effect Regular Normalny
|
||||
Stroke Direction Kierunek pociągnięć
|
||||
Stroke Width Szerokość pociągnięć
|
||||
Stroke Pressure Nacisk pociągnięć
|
||||
imageTrace Outline Kontur
|
||||
@plural {n} item|{n} items {n} element|{n} elementy|{n} elementów
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Tablica kolorów ({n} kolor)|Tablica kolorów ({n} kolory)|Tablica kolorów ({n} kolorów)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Usunąć {n} styl graficzny?|Usunąć {n} style graficzne?|Usunąć {n} stylów graficznych?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1070,7 +1070,7 @@
|
||||
Identify Objects By Identificar Objetos Por
|
||||
Ignore Ignorar
|
||||
Ignore Artboards (all the art as one page) Ignorar Mesas de Trabalho (toda a arte como uma página)
|
||||
Ignore White Ignorar Branco
|
||||
Ignore Color Ignorar Cor
|
||||
Image Info Informações da Imagem
|
||||
Image Map: Mapa da Imagem:
|
||||
Image Size Tamanho da Imagem
|
||||
@@ -2661,6 +2661,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: clique para corrigir para a cor mais próxima ({hex})
|
||||
“{file}” is protected by a password. “{file}” é protegido por uma senha.
|
||||
● Recording ● Gravando
|
||||
imageTrace Outline Contorno
|
||||
@plural {n} item|{n} items {n} item|{n} itens
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Tabela de Cores ({n} cor)|Tabela de Cores ({n} cores)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Excluir este {n} estilo gráfico?|Excluir estes {n} estilos gráficos?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -245,6 +245,7 @@
|
||||
Author Title: Должность автора:
|
||||
Author: Автор:
|
||||
Auto Авто
|
||||
Auto Color Автоцвет
|
||||
Automatic Автоматически
|
||||
Auto Size New Area Type Автоматически подгонять размер нового текста в области
|
||||
Auto Spell Check Автоматическая проверка орфографии
|
||||
@@ -1241,6 +1242,7 @@
|
||||
Identify Objects By Определять объекты по
|
||||
Ignore Игнорировать
|
||||
Ignore Artboards (all the art as one page) Игнорировать монтажные области (весь рисунок как одна страница)
|
||||
Ignore Color Игнорировать цвет
|
||||
Ignore White Игнорировать белый
|
||||
Palette: Палитра:
|
||||
Limited Ограниченная
|
||||
@@ -3232,6 +3234,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: щёлкните для исправления на ближайший цвет ({hex})
|
||||
“{file}” is protected by a password. «{file}» защищён паролем.
|
||||
● Recording ● Запись
|
||||
imageTrace Outline Контур
|
||||
@plural {n} item|{n} items {n} элемент|{n} элемента|{n} элементов
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Цветовая таблица ({n} цвет)|Цветовая таблица ({n} цвета)|Цветовая таблица ({n} цветов)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Удалить этот графический стиль ({n})?|Удалить эти графические стили ({n})?|Удалить эти графические стили ({n})?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -241,6 +241,7 @@
|
||||
Author Title: Посада автора:
|
||||
Author: Автор:
|
||||
Auto Авто
|
||||
Auto Color Автоколір
|
||||
Automatic Автоматично
|
||||
Auto Size New Area Type Автоматично підганяти розмір нового тексту в області
|
||||
Auto Spell Check Автоматична перевірка орфографії
|
||||
@@ -1237,6 +1238,7 @@
|
||||
Identify Objects By Визначати об’єкти по
|
||||
Ignore Ігнорувати
|
||||
Ignore Artboards (all the art as one page) Ігнорувати монтажні області (весь малюнок як одна сторінка)
|
||||
Ignore Color Ігнорувати колір
|
||||
Ignore White Ігнорувати білий
|
||||
Palette: Палітра:
|
||||
Limited Обмежена
|
||||
@@ -3229,6 +3231,7 @@ effect Points Точки
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: натисніть , щоб виправити найближчий колір ({hex})
|
||||
“{file}” is protected by a password. "{file}" захищений паролем.
|
||||
● Recording ● Запис
|
||||
imageTrace Outline Контур
|
||||
@plural {n} item|{n} items {n} елемент|{n} елементи|{n} елементів
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Таблиця кольорів ({n} колір)|Таблиця кольорів ({n} кольори)|Таблиця кольорів ({n} кольорів)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Видалити цей графічний стиль ({n})?|Видалити ці графічні стилі ({n})?|Видалити ці графічні стилі ({n})?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 660 and column 40.
|
@@ -288,6 +288,7 @@
|
||||
Auto Spell Check Kiểm tra chính tả tự động
|
||||
Auto-Collapse Iconic Panels Tự động thu gọn bảng dạng biểu tượng
|
||||
Auto-Rotate (turn the paper to each artboard) Tự động xoay (xoay trang cho mỗi bảng vẽ)
|
||||
Auto Color Màu tự động
|
||||
Automatic Tự động
|
||||
Automatic flatness Độ phẳng tự động
|
||||
Automatically Tự động
|
||||
@@ -1437,6 +1438,7 @@
|
||||
Identify Objects By Nhận diện đối tượng theo
|
||||
Ignore Bỏ qua
|
||||
Ignore Artboards (all the art as one page) Bỏ qua bảng vẽ (toàn bộ bản vẽ thành một trang)
|
||||
Ignore Color Bỏ qua màu
|
||||
Ignore White Bỏ qua màu trắng
|
||||
Image Hình ảnh
|
||||
Image Info Thông tin hình ảnh
|
||||
@@ -3556,6 +3558,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}: bấm để chỉnh sang màu gần nhất ({hex})
|
||||
“{file}” is protected by a password. “{file}” được bảo vệ bằng mật khẩu.
|
||||
● Recording ● Đang ghi
|
||||
imageTrace Outline Đường viền
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) Bảng màu ({n} màu)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? Xóa {n} kiểu đồ họa này?
|
||||
@plural {n} area highlighted|{n} areas highlighted {n} vùng được tô sáng
|
||||
|
||||
|
@@ -205,6 +205,7 @@
|
||||
Author Title: 作者职务:
|
||||
Author: 作者:
|
||||
Auto 自动
|
||||
Auto Color 自动颜色
|
||||
Automatic 自动
|
||||
Auto Size New Area Type 自动调整新区域文字大小
|
||||
Auto Spell Check 自动拼写检查
|
||||
@@ -1189,6 +1190,7 @@
|
||||
Identify Objects By 对象识别方式
|
||||
Ignore 忽略
|
||||
Ignore Artboards (all the art as one page) 忽略画板 (所有图稿作为一页)
|
||||
Ignore Color 忽略颜色
|
||||
Ignore White 忽略白色
|
||||
Palette: 调板:
|
||||
Limited 有限
|
||||
@@ -3129,6 +3131,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}:单击以修正为最接近的颜色 ({hex})
|
||||
“{file}” is protected by a password. “{file}”受密码保护。
|
||||
● Recording ● 录制中
|
||||
imageTrace Outline 轮廓
|
||||
@plural {n} item|{n} items {n} 个项目
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) 颜色表 ({n} 色)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? 要删除这 {n} 个图形样式吗?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -216,6 +216,7 @@
|
||||
Author Title: 作者職稱:
|
||||
Author: 作者:
|
||||
Auto 自動
|
||||
Auto Color 自動顏色
|
||||
Automatic 自動
|
||||
Auto Size New Area Type 自動調整新區域文字大小
|
||||
Auto Spell Check 自動拼字檢查
|
||||
@@ -1219,6 +1220,7 @@
|
||||
Identify Objects By 識別物件依據
|
||||
Ignore 略過
|
||||
Ignore Artboards (all the art as one page) 忽略工作區域 (所有圖稿視為單一頁面)
|
||||
Ignore Color 忽略顏色
|
||||
Ignore White 忽略白色
|
||||
Palette: 調色盤:
|
||||
Limited 有限
|
||||
@@ -3222,6 +3224,7 @@
|
||||
{warning}: click to correct to the closest color ({hex}) {warning}:按一下以修正為最接近的顏色 ({hex})
|
||||
“{file}” is protected by a password. 「{file}」受密碼保護。
|
||||
● Recording ● 錄製中
|
||||
imageTrace Outline 輪廓
|
||||
@plural {n} item|{n} items {n} 個項目
|
||||
@plural Color Table ({n} color)|Color Table ({n} colors) 色彩表 ({n} 色)
|
||||
@plural Delete this {n} graphic style?|Delete these {n} graphic styles? 要刪除這 {n} 個繪圖樣式嗎?
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -19,6 +19,16 @@ const MODES: [(&str, &str); 4] = [("blackAndWhite", "Black and White"), ("graysc
|
||||
const PALETTES: [&str; 4] = ["limited", "fullTone", "automatic", "documentLibrary"];
|
||||
|
||||
/// Palette `id`, in the UI language.
|
||||
/// The colour Ignore Color leaves out in `params` (`ignoreColor`, `#rrggbb`): white when there is none.
|
||||
fn ignore_color(params: &Value) -> [u8; 3] {
|
||||
let hex = params["ignoreColor"].as_str().unwrap_or("").trim_start_matches('#');
|
||||
let channel = |i: usize| hex.get(i * 2..i * 2 + 2).and_then(|h| u8::from_str_radix(h, 16).ok());
|
||||
match (hex.len(), channel(0), channel(1), channel(2)) {
|
||||
(6, Some(r), Some(g), Some(b)) => [r, g, b],
|
||||
_ => [255; 3],
|
||||
}
|
||||
}
|
||||
|
||||
fn palette_label(id: &str) -> &'static str {
|
||||
match id {
|
||||
"fullTone" => tl!("Full Tone"),
|
||||
@@ -128,10 +138,16 @@ fn trace_with(app: &mut VectorcraftApp, preset: &str) {
|
||||
|
||||
/// The built-in presets to choose from (`current` highlighted) → the one chosen. Listed only
|
||||
/// while the menu is open.
|
||||
/// A preset's name as shown: translated as a preset's name where the catalog has it so (the
|
||||
/// Outline preset is not the Outline view), as any other string else.
|
||||
fn preset_label(name: &str) -> &str {
|
||||
crate::i18n::tr_ctx(crate::i18n::current(), "imageTrace", name)
|
||||
}
|
||||
|
||||
fn preset_list(app: &mut VectorcraftApp, ui: &mut Ui, current: &str) -> Option<String> {
|
||||
let mut chosen = None;
|
||||
for (name, _) in presets(app) {
|
||||
if ui.add(egui::Button::selectable(name == current, tl!(&name))).clicked() {
|
||||
if ui.add(egui::Button::selectable(name == current, preset_label(&name))).clicked() {
|
||||
chosen = Some(name);
|
||||
}
|
||||
}
|
||||
@@ -338,14 +354,29 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
retrace |= release;
|
||||
}
|
||||
for (key, label) in [("snapCurvesToLines", tl!("Snap Curves To Lines")), ("ignoreWhite", tl!("Ignore White"))] {
|
||||
let on = st.params[key].as_bool().unwrap_or(false);
|
||||
if widgets::check(ui, label, on, true) {
|
||||
st.params[key] = json!(!on);
|
||||
let snap = st.params["snapCurvesToLines"].as_bool().unwrap_or(false);
|
||||
if widgets::check(ui, tl!("Snap Curves To Lines"), snap, true) {
|
||||
st.params["snapCurvesToLines"] = json!(!snap);
|
||||
st.preset = "Custom".into();
|
||||
retrace = true;
|
||||
}
|
||||
// Ignore Color: white, unless another colour is picked.
|
||||
ui.horizontal(|ui| {
|
||||
let on = st.params["ignoreWhite"].as_bool().unwrap_or(false);
|
||||
if widgets::check(ui, tl!("Ignore Color"), on, true) {
|
||||
st.params["ignoreWhite"] = json!(!on);
|
||||
st.preset = "Custom".into();
|
||||
retrace = true;
|
||||
}
|
||||
}
|
||||
if on {
|
||||
let mut rgb = ignore_color(&st.params);
|
||||
if ui.color_edit_button_srgb(&mut rgb).changed() {
|
||||
st.params["ignoreColor"] = json!(format!("#{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
|
||||
st.preset = "Custom".into();
|
||||
retrace = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
widgets::divider(ui);
|
||||
if let Some((p, a, c)) = st.info {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# File format parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (grayscale TIFFs with an alpha channel or extra channels place and open, #1466 in part; a faded or masked fill keeps its stroke apart on PDF and `.ai` import, #1434; SVG knockout groups without masks, #1435; Affinity import: a text frame too short for its first line is made taller, #1423; `.ai` editing data: a compound path takes its last path's paint and keeps paths grouped inside it, #1410; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; `editingData: "always"` opens a `.ai` from its editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; SVG knockout groups of opaque art without masks; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` keeps its RGB numbers; PDF import keeps a stroke apart from a fill masked or faded by its group) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-10-13 · **Change:** minor (a `.ai` page's art past its artboard no longer lies over the page before it when read as its PDF part; grayscale TIFFs with an alpha channel or extra channels place and open, #1466 in part; a faded or masked fill keeps its stroke apart on PDF and `.ai` import, #1434; SVG knockout groups without masks, #1435; Affinity import: a text frame too short for its first line is made taller, #1423; `.ai` editing data: a compound path takes its last path's paint and keeps paths grouped inside it, #1410; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; `editingData: "always"` opens a `.ai` from its editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; `.ai` art styles, clipping fills and compound looks from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` gradient matrices, meshes and artboard names from the editing data; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; SVG knockout groups of opaque art without masks; a `.ai` whose PDF part is only text opens from it, #1387; PDF and `.ai` import keep overprint, #1388; `.ai` editing data: gradients with middle stops, #1390; type of objects with an appearance, hidden layers and later artboards, #1391; area type in a frame too short for the fallback font, #1392; `.ai` editing data: placed (linked) files read, #1264; gradient-filled shapes import as paths, #1304; white constant alpha soft masks, #1286 in part; DXF control-point SPLINE closure read, #1299 follow-up; `.ai` and PDF trim and bleed boxes as artboard and bleed, #1266; `.ai` keeps its RGB numbers; PDF import keeps a stroke apart from a fill masked or faded by its group) · **Target:** Adobe Illustrator 2026 (30.x)
|
||||
|
||||
Every format Illustrator opens, places, saves or exports, against VectorCraft's format table (`FORMATS`,
|
||||
`OPEN_EXTS`, `PLACE_EXTS` in `crates/engine/src/cmd/fileio/mod.rs`). Illustrator's list comes from its public
|
||||
@@ -50,7 +50,7 @@ private editing data. VectorCraft:
|
||||
|
||||
### Illustrator's private editing data
|
||||
|
||||
the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping, object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), opacity, blend modes, isolation and knockout, embedded images with their alpha channel, placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), guides, and every artboard where it is. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with an Outer Glow or Drop Shadow comes in as itself with them; one with more, other effects, a brush or dashes aligned to corners comes in as its drawn look (a group named after it). Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type, area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing; the page's drawing of it (often in pieces) is left out. Where the text document can't say (a frame of another kind, or a document that doesn't agree with the page), type that shows comes from the page into its text object's place (no group around it), and hidden type is left out with a note. Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). Symbols, pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5%, where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on itLine truncated
|
||||
the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPrivateData` streams, a PDF's `AIPDFPrivateData` streams when it was saved with the editing data (#868), Zstandard or zlib, the EPS's in ASCII85) is not documented as a whole. VectorCraft reads its structure: layers and sublayers (names, order, visibility, lock, printing, preview, dimming, colour), groups as they were nested, compound paths (painted by their last path, or the last painted one, with the paths of groups inside them kept as their paths: #1410), clipping groups and layer clipping, object names, hidden and locked objects, fills and strokes (grey, CMYK, RGB, named and spot colours, spot colours defined in Lab with their Lab values (#1032), global process colours as global swatches at their tint), linear and radial gradients, pattern fills with their patterns as pattern swatches (#1025), opacity, blend modes, isolation and knockout, embedded images with their alpha channel, placed (linked) files as linked images in their place, each showing the low-resolution preview the file keeps of it until its file is found or relinked in the Links panel (#1264), guides, and every artboard where it is. A gradient with stops in the middle runs its whole ramp across its shape, and a gradient object does not pass its fill to the next path (#1390). An object whose art style is one fill and one stroke at most (solid colours, its own gradient, a variable-width profile) with an Outer Glow or Drop Shadow comes in as itself with them; one with more, other effects, a brush or dashes aligned to corners comes in as its drawn look (a group named after it). Gradients take their matrix (`Xm`, radials and skewed ones too), focal point and the order of stops sharing a ramp point; gradient meshes, a clipping path's own fill and the artboards' names are read too. `editingData: "always"` (`convert --editing-data`) opens a `.ai` from its editing data even where it draws differently from its page, which can be older, with a note. Type is made from the file's text document, whole and editable, shown or not: point type, area type (a story in several frames as threaded type) and type on a path, with its characters, fonts, size, leading, tracking, scaling, baseline shift, fill and stroke (grey, RGB or CMYK), alignment, indents and paragraph spacing; the page's drawing of it (often in pieces) is left out. Where the text document can't say (a frame of another kind, or a document that doesn't agree with the page), type that shows comes from the page into its text object's place (no group around it), and hidden type is left out with a note. Kerning, underline and the other character and paragraph options aren't read yet. A `.ai` saved without PDF compatibility (a placeholder page) opens from its editing data alone, its point type, area type and type on a path made from the text document. A `.ai` whose PDF part shows only text isn't taken for that placeholder when its page states an art, trim or bleed box: it opens from its editing data alone where that reads, else from its PDF part (#1387). Text objects with an appearance keep their own text and frame, type of hidden layers stays hidden, and type on later artboards lands on its text objects (#1391); area type whose frame is too short for a line of the fallback font shows its first line (the frame is made taller, with a warning in the open result) (#1392). Symbols, pattern fills whose pattern it can't read, placed files the data keeps no preview of and anything else it doesn't read make the file come in as its page (on a layer that doesn't show, they are left out of that layer), with a warning saying what. The layers' art is also compared with the page (by lightness, so colours written as their CMYK equivalents compare alike, and by whether a pixel is inked at all); the page is used when they differ by more than 5%, where one draws an object the other doesn't (away from type, whose lines may be laid out afresh), and when an EPS whose page is its art's box has art printed outside it (#735). An EPS whose page is its art's box keeps that page when it comes in through its layers, not the editing data's artboard, and a text object counts as drawn by the page by its colours, not only its lightness, so a red stroke stacked over a dark red one stays (#1102). `textAs: "outlines"` opens a `.ai` whose type shows from its PDF part, which has the outlines; `editingData: false` opens any of them as its page or PDF part only, for print pipelines (#735). A file whose import left something out (hidden text, art or layers) is never written over by an export or Save As to its own path unless `acknowledgeLoss: true` (`convert --replace-lossy`), so the original keeps what the document lacks. Where a `.ai` can't be read that way (or its layers are turned off in the import options), we read its PDF-compatible part, so Illustrator-only live objects arrive as appearance, and plain groups arrive ungrouped: that part draws each object on itLine truncated
|
||||
|
||||
## Format by format
|
||||
|
||||
@@ -86,6 +86,7 @@ the app's own editing copy of a file (an EPS after its `%%EOF`, a `.ai`'s `AIPri
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-13 | minor | `.ai` read as its PDF part: a page whose art reaches past its artboard to the left (notes beside a design) moves right in the row until that art clears the page before it, so it is no longer taken for that page's art; a multi-artboard `.ai` with such notes opened as its PDF part ("the art on them differs from the page's"), without the art outside its artboards, and now opens from its editing data |
|
||||
| 2026-10-13 | minor | TIFF: grayscale files with an alpha channel or other extra channels place, relink and open, read as CMYK files with alpha already were (8 and 16 bits, either polarity; #1489, part of #1466); the rows for #1434 (strokes kept apart from a faded or masked fill, a `.ai`'s RGB numbers) and #1435 (SVG knockout groups without masks, `--decimals`) were written by those PRs; scores unchanged (estimated) |
|
||||
| 2026-10-12 | minor | Affinity import: a text frame too short for its first line (a 64 pt title in a 54 pt frame) is made taller from its top, with a warning in the open result, instead of showing nothing (#1423), as `.ai` area type is (#1392); the spacing Affinity itself fits the line with isn't read; scores unchanged |
|
||||
| 2026-10-12 | minor | `.ai` editing data: a compound path takes the paint, opacity, blend mode and fill rule of its last path (the last painted one when that has none), so gradient-filled compound paths no longer come in black, and paths grouped inside a compound path stay its paths (#1426, fixes #1410); hand-written editing data test; scores unchanged |
|
||||
|
||||
+184
-145
@@ -1,6 +1,6 @@
|
||||
# Image Trace quality
|
||||
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (shared edges: gaps and overlaps between abutting paths from 22 to 50 px down to under 1)
|
||||
> **Last reviewed:** 2026-10-13 · **Last updated:** 2026-10-13 · **Change:** minor (two documented presets, Auto Color and Outline, measured; Ignore Color)
|
||||
|
||||
What Image Trace (`crates/trace`) makes of images whose right answer is known, measured by a harness and
|
||||
compared with the numbers before each change. Nothing here is a judgement by eye, and nothing is compared
|
||||
@@ -72,25 +72,27 @@ Each preset on the inputs it is meant for, all sizes and variants.
|
||||
| Technical Drawing | polygons, star, letter A, lines | 2.66 | 13.45 | 0.201 | 1.37 | 105.76 | 4.8 | 32 | 2.23 | 100 | 85 | 5.9 | – | – | – | – | 0.1 | 0.0 |
|
||||
| Flat Logo | two touching halves, four-colour badge | 0.43 | 3.53 | 3.106 | 88.50 | 0.78 | 69.9 | 352 | 38.92 | 38 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 |
|
||||
|
||||
### Now (sub-pixel edges, the palette, photo smoothing, shared edges)
|
||||
### Now (sub-pixel edges, the palette, photo smoothing, shared edges, memory, thin lines, two more presets)
|
||||
|
||||
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|
||||
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.04 | 0.092 | 1.02 | 2.17 | 1.9 | 18 | 1.78 | 29 | 68 | 0.4 | – | – | 0.8 | 0.5 | 0.0 | 0.0 |
|
||||
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.02 | 0.090 | 1.02 | 2.22 | 1.9 | 17 | 1.74 | 29 | 69 | 0.4 | – | – | 0.5 | 0.6 | 0.0 | 0.0 |
|
||||
| Auto Color | two touching halves, four-colour badge | 0.55 | 4.98 | 0.140 | 1.33 | 1.21 | 4.0 | 53 | 3.28 | 38 | 86 | 5.1 | 0.0 | 0 | 1.4 | 1.1 | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | gradient, photo-like | 1.75 | 1.97 | – | – | – | 120.7 | 10001 | – | 38 | – | – | – | – | 0.0 | – | 10.3 | 0.0 |
|
||||
| Low Fidelity Photo | gradient, photo-like | 2.45 | 2.69 | – | – | – | 37.1 | 2118 | – | 41 | – | – | – | – | 0.0 | – | 0.0 | 0.0 |
|
||||
| 3 Colors | two touching halves | 0.42 | 3.98 | 0.104 | 0.91 | 0.91 | 2.0 | 11 | 1.81 | 39 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| 6 Colors | two touching halves, four-colour badge | 0.55 | 4.98 | 0.140 | 1.34 | 1.19 | 4.0 | 50 | 3.13 | 38 | 86 | 5.2 | 0.0 | 0 | 0.2 | 0.3 | 0.0 | 0.0 |
|
||||
| 16 Colors | two touching halves, four-colour badge | 0.55 | 4.90 | 0.141 | 1.31 | 1.19 | 4.2 | 62 | 3.79 | 39 | 86 | 6.4 | 0.0 | 0 | 0.2 | 0.2 | 0.0 | 0.0 |
|
||||
| Shades of Gray | gradient, photo-like | 9.26 | 9.06 | – | – | – | 16.5 | 695 | – | 51 | – | – | – | – | 0.0 | – | 0.5 | 0.0 |
|
||||
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.37 | 0.063 | 1.02 | 2.17 | 1.4 | 27 | 2.69 | 39 | 68 | 0.2 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.12 | 0.197 | 1.00 | 105.53 | 1.8 | 17 | 1.48 | 15 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.22 | 0.523 | 2.23 | 2.21 | 1.2 | 14 | 1.64 | 11 | 75 | 0.2 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | lines | 2.65 | 9.88 | 0.093 | 0.91 | 105.75 | 5.4 | 78 | 3.92 | 36 | – | – | – | – | – | – | 0.1 | 0.0 |
|
||||
| Technical Drawing | polygons, star, letter A, lines | 2.55 | 9.72 | 0.092 | 1.06 | 105.75 | 4.8 | 56 | 3.67 | 71 | 74 | 0.7 | – | – | – | – | 1.3 | 0.0 |
|
||||
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.33 | 0.059 | 1.02 | 2.22 | 1.4 | 29 | 2.88 | 39 | 69 | 0.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 11.98 | 0.186 | 1.00 | 105.61 | 1.8 | 17 | 1.47 | 16 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.28 | 0.524 | 2.19 | 2.18 | 1.2 | 14 | 1.65 | 11 | 79 | 0.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | lines | 2.62 | 9.44 | 0.055 | 0.96 | 105.82 | 5.4 | 46 | 2.29 | 41 | – | – | – | – | – | – | 0.1 | 0.0 |
|
||||
| Technical Drawing | polygons, star, letter A, lines | 2.54 | 9.65 | 0.084 | 1.02 | 105.82 | 4.8 | 46 | 3.23 | 71 | 73 | 0.8 | – | – | – | – | 1.2 | 0.0 |
|
||||
| Outline | lines | 2.93 | 15.91 | 0.310 | 1.84 | 106.00 | 5.0 | 81 | 4.03 | 15 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Flat Logo | two touching halves, four-colour badge | 0.43 | 3.53 | 3.106 | 88.50 | 0.78 | 69.9 | 352 | 38.92 | 38 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 |
|
||||
|
||||
The sections below are the numbers now (sub-pixel edges, the palette, photo smoothing, shared edges).
|
||||
The sections below are the numbers now (sub-pixel edges, the palette, photo smoothing, shared edges, memory, thin lines, two more presets).
|
||||
|
||||
## 1. Each preset on the inputs it is meant for
|
||||
|
||||
@@ -98,29 +100,36 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
|
||||
|
||||
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|
||||
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.04 | 0.092 | 1.02 | 2.17 | 1.9 | 18 | 1.78 | 29 | 68 | 0.4 | – | – | 0.8 | 0.5 | 0.0 | 0.0 |
|
||||
| Default | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.60 | 10.02 | 0.090 | 1.02 | 2.22 | 1.9 | 17 | 1.74 | 29 | 69 | 0.4 | – | – | 0.5 | 0.6 | 0.0 | 0.0 |
|
||||
| Auto Color | two touching halves, four-colour badge | 0.55 | 4.98 | 0.140 | 1.33 | 1.21 | 4.0 | 53 | 3.28 | 38 | 86 | 5.1 | 0.0 | 0 | 1.4 | 1.1 | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | gradient, photo-like | 1.75 | 1.97 | – | – | – | 120.7 | 10001 | – | 38 | – | – | – | – | 0.0 | – | 10.3 | 0.0 |
|
||||
| Low Fidelity Photo | gradient, photo-like | 2.45 | 2.69 | – | – | – | 37.1 | 2118 | – | 41 | – | – | – | – | 0.0 | – | 0.0 | 0.0 |
|
||||
| 3 Colors | two touching halves | 0.42 | 3.98 | 0.104 | 0.91 | 0.91 | 2.0 | 11 | 1.81 | 39 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| 6 Colors | two touching halves, four-colour badge | 0.55 | 4.98 | 0.140 | 1.34 | 1.19 | 4.0 | 50 | 3.13 | 38 | 86 | 5.2 | 0.0 | 0 | 0.2 | 0.3 | 0.0 | 0.0 |
|
||||
| 16 Colors | two touching halves, four-colour badge | 0.55 | 4.90 | 0.141 | 1.31 | 1.19 | 4.2 | 62 | 3.79 | 39 | 86 | 6.4 | 0.0 | 0 | 0.2 | 0.2 | 0.0 | 0.0 |
|
||||
| Shades of Gray | gradient, photo-like | 9.26 | 9.06 | – | – | – | 16.5 | 695 | – | 51 | – | – | – | – | 0.0 | – | 0.5 | 0.0 |
|
||||
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.37 | 0.063 | 1.02 | 2.17 | 1.4 | 27 | 2.69 | 39 | 68 | 0.2 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 12.12 | 0.197 | 1.00 | 105.53 | 1.8 | 17 | 1.48 | 15 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.22 | 0.523 | 2.23 | 2.21 | 1.2 | 14 | 1.64 | 11 | 75 | 0.2 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | lines | 2.65 | 9.88 | 0.093 | 0.91 | 105.75 | 5.4 | 78 | 3.92 | 36 | – | – | – | – | – | – | 0.1 | 0.0 |
|
||||
| Technical Drawing | polygons, star, letter A, lines | 2.55 | 9.72 | 0.092 | 1.06 | 105.75 | 4.8 | 56 | 3.67 | 71 | 74 | 0.7 | – | – | – | – | 1.3 | 0.0 |
|
||||
| Black and White Logo | circle, ellipses, badge with a hole, letter D, polygons, star, letter A | 2.58 | 9.33 | 0.059 | 1.02 | 2.22 | 1.4 | 29 | 2.88 | 39 | 69 | 0.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | circle, ellipses, badge with a hole, letter D, lines | 2.71 | 11.98 | 0.186 | 1.00 | 105.61 | 1.8 | 17 | 1.47 | 16 | 86 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | circle, ellipses, badge with a hole, letter D | 2.98 | 19.28 | 0.524 | 2.19 | 2.18 | 1.2 | 14 | 1.65 | 11 | 79 | 0.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | lines | 2.62 | 9.44 | 0.055 | 0.96 | 105.82 | 5.4 | 46 | 2.29 | 41 | – | – | – | – | – | – | 0.1 | 0.0 |
|
||||
| Technical Drawing | polygons, star, letter A, lines | 2.54 | 9.65 | 0.084 | 1.02 | 105.82 | 4.8 | 46 | 3.23 | 71 | 73 | 0.8 | – | – | – | – | 1.2 | 0.0 |
|
||||
| Outline | lines | 2.93 | 15.91 | 0.310 | 1.84 | 106.00 | 5.0 | 81 | 4.03 | 15 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Flat Logo | two touching halves, four-colour badge | 0.43 | 3.53 | 3.106 | 88.50 | 0.78 | 69.9 | 352 | 38.92 | 38 | 80 | 237.0 | 0.0 | 0 | 13.8 | – | 59.1 | 0.2 |
|
||||
|
||||
## 2. Each preset on each kind of input (clean, three sizes)
|
||||
|
||||
| preset | inputs | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|
||||
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| Default | curved shapes | 0.09 | 3.51 | 0.062 | 0.64 | 0.78 | 1.8 | 15 | 1.64 | 13 | 83 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | polygons | 0.05 | 2.13 | 0.036 | 0.44 | 1.24 | 2.0 | 14 | 1.26 | 89 | 90 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | thin lines | 0.56 | 4.77 | 0.061 | 0.47 | 105.57 | 3.3 | 24 | 1.18 | 43 | – | – | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | flat colour art | 19.95 | 19.00 | 0.199 | 0.53 | 179.52 | 1.5 | 19 | 1.18 | 14 | 0 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | gradient, photo | 39.14 | 38.78 | – | – | – | 3.2 | 593 | – | 55 | – | – | – | – | 18.6 | 30.6 | 0.0 | 0.0 |
|
||||
| Default | curved shapes | 0.09 | 3.47 | 0.061 | 0.64 | 0.78 | 1.8 | 15 | 1.62 | 13 | 83 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | polygons | 0.05 | 2.12 | 0.035 | 0.43 | 1.24 | 2.0 | 14 | 1.26 | 88 | 89 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | thin lines | 0.53 | 4.28 | 0.045 | 0.47 | 105.51 | 3.3 | 23 | 1.15 | 48 | – | – | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | flat colour art | 19.91 | 17.97 | 0.172 | 0.51 | 179.34 | 1.5 | 16 | 1.17 | 14 | 0 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | gradient, photo | 39.03 | 38.65 | – | – | – | 3.2 | 601 | – | 54 | – | – | – | – | 19.1 | 38.4 | 0.0 | 0.0 |
|
||||
| Auto Color | curved shapes | 0.06 | 2.45 | 0.043 | 0.27 | 0.28 | 1.8 | 17 | 1.78 | 15 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | polygons | 0.05 | 2.09 | 0.035 | 0.51 | 1.34 | 2.0 | 15 | 1.33 | 88 | 89 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | thin lines | 0.69 | 5.23 | 0.057 | 0.48 | 105.59 | 5.0 | 33 | 1.63 | 57 | – | – | 4.0 | 3216 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | flat colour art | 0.12 | 2.70 | 0.057 | 0.31 | 0.30 | 3.0 | 28 | 2.00 | 43 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | gradient, photo | 2.07 | 2.38 | – | – | – | 159.3 | 4430 | – | 19 | – | – | – | – | 22.7 | 32.4 | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | curved shapes | 0.04 | 1.66 | 0.026 | 0.18 | 0.20 | 1.2 | 19 | 2.12 | 20 | 100 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | polygons | 0.05 | 1.88 | 0.031 | 0.51 | 1.34 | 1.7 | 16 | 1.43 | 88 | 89 | 0.0 | 0.0 | 0 | 0.0 | – | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | thin lines | 0.34 | 5.96 | 0.075 | 0.85 | 4.31 | 40.3 | 339 | 16.93 | 37 | – | – | 27.3 | 4490 | 0.0 | – | 19.0 | 0.0 |
|
||||
@@ -151,31 +160,36 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
|
||||
| Shades of Gray | thin lines | 0.55 | 6.55 | 0.083 | 0.86 | 45.40 | 10.3 | 73 | 3.67 | 50 | – | – | 8.0 | 4272 | 0.0 | – | 0.0 | 0.0 |
|
||||
| Shades of Gray | flat colour art | 7.33 | 11.24 | 0.257 | 1.43 | 179.39 | 4.7 | 200 | 18.45 | 22 | 50 | 0.3 | 3.7 | 173367 | 0.0 | – | 0.0 | 0.0 |
|
||||
| Shades of Gray | gradient, photo | 9.71 | 9.55 | – | – | – | 18.2 | 748 | – | 51 | – | – | – | – | 0.0 | – | 0.5 | 0.0 |
|
||||
| Black and White Logo | curved shapes | 0.04 | 1.51 | 0.024 | 0.50 | 0.59 | 1.2 | 21 | 2.29 | 20 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | polygons | 0.04 | 1.83 | 0.030 | 0.43 | 1.24 | 1.7 | 16 | 1.39 | 90 | 90 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | thin lines | 0.60 | 6.10 | 0.111 | 0.50 | 77.21 | 10.3 | 48 | 2.38 | 83 | – | – | – | – | – | – | 3.3 | 0.0 |
|
||||
| Black and White Logo | flat colour art | 19.94 | 18.71 | 0.192 | 0.32 | 179.52 | 1.0 | 20 | 1.22 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | gradient, photo | 39.11 | 38.71 | – | – | – | 9.0 | 574 | – | 67 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | curved shapes | 0.14 | 5.77 | 0.112 | 0.47 | 0.47 | 1.2 | 12 | 1.38 | 16 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | polygons | 0.12 | 4.81 | 0.088 | 0.52 | 1.05 | 1.7 | 13 | 1.18 | 85 | 88 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | thin lines | 0.40 | 5.10 | 0.106 | 0.80 | 52.93 | 4.3 | 39 | 1.93 | 35 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | flat colour art | 20.32 | 21.13 | 0.179 | 0.87 | 179.64 | 1.5 | 24 | 1.43 | 16 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | gradient, photo | 45.54 | 45.39 | – | – | – | 1.0 | 237 | – | 43 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | curved shapes | 0.36 | 14.54 | 0.282 | 0.71 | 0.71 | 1.2 | 12 | 1.34 | 15 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | polygons | 0.35 | 13.93 | 0.261 | 0.67 | 0.69 | 1.7 | 14 | 1.26 | 76 | 85 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | thin lines | 1.03 | 15.78 | 0.304 | 1.09 | 18.10 | 5.0 | 47 | 2.33 | 34 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | flat colour art | 23.86 | 33.99 | 0.224 | 0.77 | 188.38 | 1.0 | 11 | 0.78 | 24 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | gradient, photo | 61.57 | 61.34 | – | – | – | 0.2 | 2 | – | 42 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | curved shapes | 0.04 | 1.86 | 0.032 | 0.54 | 0.62 | 1.2 | 16 | 1.75 | 19 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | polygons | 0.05 | 1.90 | 0.031 | 0.40 | 1.24 | 1.7 | 14 | 1.26 | 90 | 90 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | thin lines | 0.54 | 4.39 | 0.049 | 0.47 | 105.75 | 3.3 | 35 | 1.77 | 41 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | flat colour art | 19.94 | 18.73 | 0.193 | 0.50 | 179.52 | 1.0 | 43 | 2.40 | 22 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | gradient, photo | 39.12 | 38.72 | – | – | – | 7.7 | 448 | – | 57 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | curved shapes | 0.04 | 1.51 | 0.024 | 0.50 | 0.59 | 1.2 | 21 | 2.29 | 20 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | polygons | 0.04 | 1.83 | 0.030 | 0.43 | 1.24 | 1.7 | 16 | 1.38 | 91 | 91 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | thin lines | 0.53 | 5.83 | 0.125 | 0.50 | 55.75 | 15.3 | 61 | 3.03 | 88 | – | – | – | – | – | – | 8.3 | 0.0 |
|
||||
| Technical Drawing | flat colour art | 19.94 | 18.71 | 0.192 | 0.32 | 179.52 | 1.0 | 20 | 1.22 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | gradient, photo | 39.08 | 38.68 | – | – | – | 20.0 | 593 | – | 68 | – | – | – | – | – | – | 2.7 | 0.0 |
|
||||
| Black and White Logo | curved shapes | 0.03 | 1.49 | 0.024 | 0.51 | 0.59 | 1.2 | 21 | 2.23 | 20 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | polygons | 0.04 | 1.78 | 0.029 | 0.42 | 1.24 | 1.7 | 16 | 1.42 | 89 | 89 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | thin lines | 0.63 | 6.33 | 0.117 | 0.50 | 77.21 | 10.3 | 53 | 2.63 | 89 | – | – | – | – | – | – | 2.7 | 0.0 |
|
||||
| Black and White Logo | flat colour art | 19.90 | 17.76 | 0.157 | 0.30 | 179.51 | 1.0 | 18 | 1.12 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | gradient, photo | 38.98 | 38.54 | – | – | – | 9.0 | 573 | – | 70 | – | – | – | – | – | – | 1.2 | 0.0 |
|
||||
| Sketched Art | curved shapes | 0.14 | 5.67 | 0.109 | 0.46 | 0.46 | 1.2 | 12 | 1.38 | 16 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | polygons | 0.12 | 4.65 | 0.084 | 0.53 | 1.06 | 1.7 | 13 | 1.19 | 86 | 89 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | thin lines | 0.41 | 4.87 | 0.099 | 0.96 | 52.90 | 4.3 | 35 | 1.77 | 38 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | flat colour art | 20.25 | 19.55 | 0.146 | 1.15 | 179.63 | 1.5 | 26 | 1.53 | 16 | 0 | 0.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | gradient, photo | 45.43 | 45.21 | – | – | – | 1.0 | 240 | – | 44 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | curved shapes | 0.35 | 14.47 | 0.280 | 0.69 | 0.69 | 1.2 | 12 | 1.33 | 15 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | polygons | 0.34 | 13.60 | 0.255 | 0.65 | 0.69 | 1.7 | 14 | 1.22 | 77 | 85 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | thin lines | 1.10 | 15.03 | 0.296 | 1.37 | 17.91 | 5.0 | 37 | 1.83 | 44 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | flat colour art | 23.83 | 33.06 | 0.200 | 0.69 | 188.27 | 1.0 | 11 | 0.77 | 25 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | gradient, photo | 61.57 | 61.32 | – | – | – | 0.2 | 2 | – | 47 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | curved shapes | 0.04 | 1.85 | 0.032 | 0.54 | 0.62 | 1.2 | 16 | 1.76 | 19 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | polygons | 0.05 | 1.86 | 0.030 | 0.39 | 1.24 | 1.7 | 15 | 1.29 | 88 | 89 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | thin lines | 0.52 | 3.94 | 0.036 | 0.47 | 105.71 | 3.3 | 28 | 1.38 | 45 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | flat colour art | 19.90 | 17.83 | 0.162 | 0.42 | 179.43 | 1.0 | 18 | 1.30 | 21 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | gradient, photo | 38.98 | 38.54 | – | – | – | 7.7 | 471 | – | 57 | – | – | – | – | – | – | 0.2 | 0.0 |
|
||||
| Technical Drawing | curved shapes | 0.03 | 1.49 | 0.024 | 0.51 | 0.59 | 1.2 | 21 | 2.23 | 20 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | polygons | 0.04 | 1.79 | 0.029 | 0.42 | 1.24 | 1.7 | 16 | 1.41 | 90 | 90 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | thin lines | 0.53 | 5.80 | 0.121 | 0.50 | 55.74 | 15.3 | 70 | 3.50 | 88 | – | – | – | – | – | – | 7.7 | 0.0 |
|
||||
| Technical Drawing | flat colour art | 19.90 | 17.76 | 0.157 | 0.30 | 179.51 | 1.0 | 18 | 1.12 | 23 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | gradient, photo | 38.94 | 38.51 | – | – | – | 20.0 | 627 | – | 71 | – | – | – | – | – | – | 11.3 | 0.0 |
|
||||
| Outline | curved shapes | 23.38 | 44.70 | 0.498 | 0.90 | 0.52 | 1.2 | 16 | 1.76 | 19 | 83 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | polygons | 17.94 | 44.20 | 0.494 | 0.83 | 0.74 | 1.7 | 15 | 1.29 | 88 | 89 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | thin lines | 0.99 | 14.15 | 0.310 | 1.82 | 106.00 | 3.3 | 88 | 4.40 | 25 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | flat colour art | 26.69 | 32.18 | 0.494 | 0.92 | 178.93 | 1.0 | 18 | 1.30 | 21 | 0 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | gradient, photo | 39.57 | 39.32 | – | – | – | 7.5 | 421 | – | 56 | – | – | – | – | – | – | 0.0 | 0.8 |
|
||||
| Flat Logo | curved shapes | 0.06 | 2.14 | 0.036 | 0.25 | 0.25 | 1.8 | 31 | 3.38 | 19 | 100 | 0.0 | 0.0 | 0 | 6.8 | – | 0.0 | 0.0 |
|
||||
| Flat Logo | polygons | 0.09 | 3.44 | 0.060 | 0.35 | 1.03 | 2.0 | 35 | 3.17 | 67 | 94 | 0.0 | 0.0 | 0 | 6.8 | – | 0.0 | 0.0 |
|
||||
| Flat Logo | thin lines | 1.17 | 11.78 | 0.050 | 0.49 | all | 3.0 | 189 | 9.45 | 28 | – | – | 0.0 | 0 | 5467.1 | – | 0.0 | 0.0 |
|
||||
@@ -186,11 +200,16 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
|
||||
|
||||
| preset | variant | ΔE | edge ΔE | off mean | off max | missed max | paths | anchors | × art | lines % | corners % | kinks | odd colours | odd px | gap px | overlap px | specks | outside px |
|
||||
|:--|:--|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|--:|
|
||||
| Default | clean | 0.07 | 2.92 | 0.051 | 0.64 | 1.24 | 1.9 | 15 | 1.48 | 46 | 89 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | clean | 0.07 | 2.89 | 0.050 | 0.64 | 1.24 | 1.9 | 15 | 1.46 | 45 | 88 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | aliased | 0.37 | 20.23 | 0.233 | 1.02 | 1.35 | 1.9 | 15 | 1.45 | 21 | 96 | 0.0 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | blurred | 0.82 | 16.64 | 0.078 | 0.97 | 2.17 | 1.9 | 25 | 2.42 | 5 | 21 | 1.4 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | noisy | 1.74 | 4.50 | 0.050 | 0.54 | 1.24 | 1.9 | 16 | 1.64 | 39 | 67 | 0.0 | – | – | 2.3 | 0.6 | 0.0 | 0.0 |
|
||||
| Default | scanned | 15.06 | 20.19 | 0.132 | 0.86 | 2.17 | 1.9 | 25 | 2.50 | 0 | 25 | 1.1 | – | – | 3.6 | 2.9 | 0.0 | 0.0 |
|
||||
| Default | blurred | 0.81 | 16.57 | 0.078 | 0.98 | 2.22 | 1.9 | 24 | 2.34 | 5 | 19 | 1.3 | – | – | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Default | noisy | 1.74 | 4.53 | 0.054 | 0.54 | 1.32 | 1.9 | 15 | 1.47 | 41 | 84 | 0.0 | – | – | 1.9 | 0.3 | 0.0 | 0.0 |
|
||||
| Default | scanned | 15.06 | 20.17 | 0.114 | 0.79 | 2.14 | 1.9 | 26 | 2.53 | 2 | 25 | 1.3 | – | – | 1.6 | 4.2 | 0.0 | 0.0 |
|
||||
| Auto Color | clean | 0.12 | 2.70 | 0.057 | 0.31 | 0.30 | 3.0 | 28 | 2.00 | 43 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | aliased | 0.29 | 10.43 | 0.298 | 1.00 | 1.00 | 3.0 | 27 | 2.05 | 43 | 100 | 0.0 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | blurred | 0.61 | 9.31 | 0.208 | 1.33 | 1.21 | 6.0 | 119 | 6.53 | 39 | 50 | 15.5 | 0.0 | 0 | 0.0 | 0.0 | 0.0 | 0.0 |
|
||||
| Auto Color | noisy | 0.85 | 2.61 | 0.076 | 0.52 | 0.58 | 3.0 | 34 | 2.34 | 28 | 88 | 0.0 | 0.0 | 0 | 0.3 | 0.5 | 0.0 | 0.0 |
|
||||
| Auto Color | scanned | 1.73 | 4.42 | 0.230 | 1.20 | 0.94 | 7.0 | 108 | 6.02 | 28 | 62 | 20.5 | 0.0 | 0 | 9.6 | 7.5 | 0.0 | 0.0 |
|
||||
| High Fidelity Photo | clean | 1.80 | 2.04 | – | – | – | 134.7 | 11321 | – | 39 | – | – | – | – | 0.0 | – | 7.3 | 0.0 |
|
||||
| High Fidelity Photo | aliased | 1.77 | 2.00 | – | – | – | 73.0 | 8004 | – | 40 | – | – | – | – | 0.0 | – | 5.5 | 0.0 |
|
||||
| High Fidelity Photo | blurred | 1.59 | 1.95 | – | – | – | 72.0 | 7388 | – | 40 | – | – | – | – | 0.0 | – | 0.5 | 0.0 |
|
||||
@@ -221,31 +240,36 @@ All sizes and variants (clean, aliased, blurred, noisy, scanned) of those inputs
|
||||
| Shades of Gray | blurred | 9.66 | 9.32 | – | – | – | 11.5 | 577 | – | 55 | – | – | – | – | 0.0 | – | 0.5 | 0.0 |
|
||||
| Shades of Gray | noisy | 9.74 | 9.45 | – | – | – | 19.5 | 643 | – | 45 | – | – | – | – | 0.0 | – | 0.0 | 0.0 |
|
||||
| Shades of Gray | scanned | 6.58 | 6.46 | – | – | – | 19.0 | 762 | – | 47 | – | – | – | – | 0.0 | – | 1.0 | 0.0 |
|
||||
| Black and White Logo | clean | 0.04 | 1.64 | 0.027 | 0.50 | 1.24 | 1.4 | 19 | 1.90 | 50 | 89 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | clean | 0.04 | 1.62 | 0.026 | 0.51 | 1.24 | 1.4 | 19 | 1.89 | 50 | 88 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | aliased | 0.39 | 20.35 | 0.172 | 1.02 | 1.35 | 1.4 | 22 | 2.32 | 53 | 96 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | blurred | 0.80 | 16.29 | 0.045 | 0.82 | 2.15 | 1.4 | 38 | 3.62 | 21 | 21 | 0.9 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | noisy | 1.72 | 3.90 | 0.028 | 0.51 | 1.24 | 1.4 | 22 | 2.18 | 40 | 67 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | scanned | 15.05 | 20.10 | 0.115 | 0.86 | 2.17 | 1.4 | 51 | 5.02 | 11 | 25 | 0.9 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | clean | 0.19 | 5.64 | 0.111 | 0.80 | 52.93 | 1.9 | 18 | 1.49 | 19 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | aliased | 0.46 | 19.57 | 0.213 | 1.00 | 105.53 | 1.6 | 11 | 1.10 | 26 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | blurred | 1.07 | 19.72 | 0.274 | 0.70 | 105.49 | 1.6 | 17 | 1.53 | 10 | 50 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | noisy | 1.76 | 6.16 | 0.119 | 0.95 | 1.03 | 2.0 | 19 | 1.58 | 12 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | scanned | 15.12 | 22.50 | 0.442 | 0.86 | 105.27 | 1.6 | 20 | 1.65 | 2 | 50 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | clean | 0.36 | 14.54 | 0.282 | 0.71 | 0.71 | 1.2 | 12 | 1.34 | 15 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | blurred | 0.80 | 16.24 | 0.049 | 0.84 | 2.22 | 1.4 | 40 | 3.92 | 21 | 19 | 1.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | noisy | 1.72 | 3.86 | 0.027 | 0.49 | 1.32 | 1.4 | 21 | 2.12 | 42 | 84 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Black and White Logo | scanned | 15.05 | 20.00 | 0.090 | 0.79 | 2.10 | 1.4 | 63 | 6.11 | 9 | 25 | 1.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | clean | 0.19 | 5.51 | 0.107 | 0.96 | 52.90 | 1.9 | 17 | 1.46 | 20 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | aliased | 0.47 | 19.58 | 0.213 | 1.00 | 105.54 | 1.6 | 11 | 1.10 | 26 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | blurred | 1.05 | 19.32 | 0.242 | 0.76 | 105.55 | 1.6 | 19 | 1.59 | 9 | 50 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | noisy | 1.76 | 6.13 | 0.116 | 0.90 | 1.06 | 2.0 | 18 | 1.48 | 12 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Sketched Art | scanned | 15.10 | 22.32 | 0.407 | 0.78 | 105.61 | 1.6 | 21 | 1.75 | 2 | 50 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | clean | 0.35 | 14.47 | 0.280 | 0.69 | 0.69 | 1.2 | 12 | 1.33 | 15 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | aliased | 0.39 | 21.69 | 0.289 | 1.00 | 1.00 | 1.2 | 9 | 0.99 | 12 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | blurred | 1.21 | 28.11 | 0.785 | 1.45 | 1.92 | 1.2 | 15 | 1.82 | 11 | 25 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | noisy | 2.11 | 10.96 | 0.284 | 0.69 | 0.69 | 1.2 | 11 | 1.27 | 7 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | scanned | 16.05 | 30.19 | 1.458 | 2.23 | 2.21 | 1.2 | 25 | 3.37 | 3 | 0 | 1.2 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | clean | 0.54 | 4.39 | 0.049 | 0.47 | 105.75 | 3.3 | 35 | 1.77 | 41 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | blurred | 1.24 | 28.75 | 0.818 | 1.44 | 1.92 | 1.2 | 14 | 1.72 | 10 | 50 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | noisy | 2.11 | 11.05 | 0.284 | 0.68 | 0.69 | 1.2 | 11 | 1.28 | 9 | 100 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Silhouettes | scanned | 16.03 | 30.07 | 1.438 | 2.19 | 2.18 | 1.2 | 28 | 3.55 | 3 | 0 | 2.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | clean | 0.52 | 3.94 | 0.036 | 0.47 | 105.71 | 3.3 | 28 | 1.38 | 45 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | aliased | 0.67 | 14.44 | 0.112 | 0.73 | 55.44 | 14.0 | 59 | 2.95 | 87 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | blurred | 1.63 | 17.15 | 0.136 | 0.89 | 105.57 | 3.0 | 143 | 7.15 | 4 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | noisy | 1.04 | 4.35 | 0.045 | 0.48 | 56.06 | 8.0 | 53 | 2.65 | 38 | – | – | – | – | – | – | 1.0 | 0.0 |
|
||||
| Line Art | scanned | 13.60 | 20.06 | 0.214 | 0.91 | 105.64 | 3.0 | 188 | 9.40 | 1 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | clean | 0.17 | 2.83 | 0.054 | 0.50 | 55.75 | 5.1 | 27 | 1.79 | 90 | 91 | 0.0 | – | – | – | – | 2.1 | 0.0 |
|
||||
| Line Art | blurred | 1.56 | 16.20 | 0.068 | 0.96 | 105.82 | 3.0 | 53 | 2.65 | 19 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Line Art | noisy | 1.02 | 3.98 | 0.030 | 0.50 | 56.08 | 8.0 | 56 | 2.80 | 42 | – | – | – | – | – | – | 1.0 | 0.0 |
|
||||
| Line Art | scanned | 13.55 | 19.60 | 0.069 | 0.87 | 105.78 | 3.0 | 70 | 3.50 | 4 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | clean | 0.17 | 2.79 | 0.052 | 0.50 | 55.74 | 5.1 | 30 | 1.93 | 89 | 90 | 0.0 | – | – | – | – | 1.9 | 0.0 |
|
||||
| Technical Drawing | aliased | 0.43 | 18.51 | 0.204 | 1.02 | 55.44 | 6.8 | 33 | 2.06 | 95 | 94 | 0.0 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | blurred | 1.04 | 16.59 | 0.082 | 1.06 | 105.75 | 2.0 | 114 | 7.50 | 24 | 28 | 2.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | noisy | 1.42 | 4.30 | 0.055 | 0.50 | 31.23 | 7.8 | 38 | 2.40 | 85 | 90 | 0.0 | – | – | – | – | 2.8 | 0.0 |
|
||||
| Technical Drawing | scanned | 14.45 | 20.15 | 0.144 | 0.91 | 105.74 | 2.0 | 123 | 8.33 | 23 | 34 | 2.3 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | blurred | 1.03 | 16.48 | 0.076 | 0.96 | 105.82 | 2.0 | 58 | 4.73 | 26 | 25 | 2.7 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Technical Drawing | noisy | 1.41 | 4.24 | 0.053 | 0.50 | 31.79 | 7.8 | 41 | 2.51 | 85 | 86 | 0.0 | – | – | – | – | 2.8 | 0.0 |
|
||||
| Technical Drawing | scanned | 14.44 | 19.98 | 0.100 | 0.87 | 105.79 | 2.0 | 98 | 7.51 | 23 | 34 | 2.7 | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | clean | 0.99 | 14.15 | 0.310 | 1.82 | 106.00 | 3.3 | 88 | 4.40 | 25 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | aliased | 0.79 | 17.03 | 0.300 | 1.08 | 55.51 | 11.0 | 93 | 4.65 | 14 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | blurred | 1.66 | 18.46 | 0.311 | 1.84 | 105.72 | 3.0 | 68 | 3.40 | 0 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | noisy | 1.50 | 12.92 | 0.304 | 1.30 | 56.25 | 8.0 | 74 | 3.70 | 11 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Outline | scanned | 13.60 | 20.48 | 0.323 | 1.79 | 105.55 | 3.0 | 65 | 3.25 | 9 | – | – | – | – | – | – | 0.0 | 0.0 |
|
||||
| Flat Logo | clean | 0.07 | 1.53 | 0.065 | 0.31 | 0.27 | 3.0 | 36 | 3.51 | 47 | 100 | 0.0 | 0.0 | 0 | 6.8 | – | 0.0 | 0.0 |
|
||||
| Flat Logo | aliased | 0.13 | 5.87 | 0.218 | 0.65 | 0.65 | 3.0 | 31 | 1.96 | 44 | 50 | 0.0 | 0.0 | 0 | 5.8 | – | 0.0 | 0.0 |
|
||||
| Flat Logo | blurred | 0.54 | 8.33 | 0.052 | 0.62 | 0.78 | 3.0 | 28 | 1.78 | 43 | 50 | 0.0 | 0.0 | 0 | 5.8 | – | 0.0 | 0.0 |
|
||||
@@ -282,89 +306,104 @@ One trace at a time on this machine (all cores); memory is the peak allocated ab
|
||||
|
||||
| preset | image | megapixels | result |
|
||||
|:--|:--|--:|:--|
|
||||
| Default | photo-like | 1 | 0.04 s, 29 paths, 6888 anchors, 15 MB |
|
||||
| Default | four-colour badge | 1 | 0.02 s, 3 paths, 96 anchors, 15 MB |
|
||||
| High Fidelity Photo | photo-like | 1 | 0.21 s, 968 paths, 114383 anchors, 238 MB |
|
||||
| High Fidelity Photo | four-colour badge | 1 | 0.06 s, 6 paths, 172 anchors, 36 MB |
|
||||
| Low Fidelity Photo | photo-like | 1 | 0.11 s, 129 paths, 22929 anchors, 144 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 136 anchors, 36 MB |
|
||||
| 3 Colors | photo-like | 1 | 0.08 s, 9 paths, 3367 anchors, 26 MB |
|
||||
| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 88 anchors, 21 MB |
|
||||
| 6 Colors | photo-like | 1 | 0.08 s, 78 paths, 9008 anchors, 42 MB |
|
||||
| 6 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 33 MB |
|
||||
| 16 Colors | photo-like | 1 | 0.10 s, 178 paths, 20466 anchors, 102 MB |
|
||||
| 16 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 88 anchors, 33 MB |
|
||||
| Shades of Gray | photo-like | 1 | 0.08 s, 81 paths, 6698 anchors, 65 MB |
|
||||
| Shades of Gray | four-colour badge | 1 | 0.06 s, 10 paths, 733 anchors, 56 MB |
|
||||
| Black and White Logo | photo-like | 1 | 0.04 s, 71 paths, 5465 anchors, 9 MB |
|
||||
| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 8 MB |
|
||||
| Sketched Art | photo-like | 1 | 0.04 s, 5 paths, 2971 anchors, 9 MB |
|
||||
| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 131 anchors, 8 MB |
|
||||
| Silhouettes | photo-like | 1 | 0.04 s, 2 paths, 791 anchors, 11 MB |
|
||||
| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 25 anchors, 9 MB |
|
||||
| Line Art | photo-like | 1 | 0.04 s, 54 paths, 4421 anchors, 9 MB |
|
||||
| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 232 anchors, 8 MB |
|
||||
| Technical Drawing | photo-like | 1 | 0.04 s, 221 paths, 7680 anchors, 9 MB |
|
||||
| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 52 anchors, 8 MB |
|
||||
| Default | photo-like | 1 | 0.04 s, 29 paths, 6701 anchors, 12 MB |
|
||||
| Default | four-colour badge | 1 | 0.02 s, 3 paths, 64 anchors, 12 MB |
|
||||
| Auto Color | photo-like | 1 | 0.15 s, 732 paths, 45410 anchors, 19 MB |
|
||||
| Auto Color | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
|
||||
| High Fidelity Photo | photo-like | 1 | 0.22 s, 968 paths, 114383 anchors, 170 MB |
|
||||
| High Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 172 anchors, 23 MB |
|
||||
| Low Fidelity Photo | photo-like | 1 | 0.11 s, 129 paths, 22929 anchors, 105 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 1 | 0.05 s, 6 paths, 136 anchors, 23 MB |
|
||||
| 3 Colors | photo-like | 1 | 0.07 s, 9 paths, 3367 anchors, 18 MB |
|
||||
| 3 Colors | four-colour badge | 1 | 0.05 s, 4 paths, 88 anchors, 13 MB |
|
||||
| 6 Colors | photo-like | 1 | 0.08 s, 78 paths, 9008 anchors, 24 MB |
|
||||
| 6 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 84 anchors, 12 MB |
|
||||
| 16 Colors | photo-like | 1 | 0.11 s, 178 paths, 20466 anchors, 31 MB |
|
||||
| 16 Colors | four-colour badge | 1 | 0.05 s, 5 paths, 88 anchors, 12 MB |
|
||||
| Shades of Gray | photo-like | 1 | 0.08 s, 81 paths, 6698 anchors, 42 MB |
|
||||
| Shades of Gray | four-colour badge | 1 | 0.06 s, 10 paths, 733 anchors, 35 MB |
|
||||
| Black and White Logo | photo-like | 1 | 0.04 s, 71 paths, 5047 anchors, 8 MB |
|
||||
| Black and White Logo | four-colour badge | 1 | 0.02 s, 1 paths, 47 anchors, 6 MB |
|
||||
| Sketched Art | photo-like | 1 | 0.04 s, 5 paths, 2880 anchors, 8 MB |
|
||||
| Sketched Art | four-colour badge | 1 | 0.02 s, 2 paths, 114 anchors, 6 MB |
|
||||
| Silhouettes | photo-like | 1 | 0.03 s, 2 paths, 573 anchors, 9 MB |
|
||||
| Silhouettes | four-colour badge | 1 | 0.02 s, 1 paths, 24 anchors, 6 MB |
|
||||
| Line Art | photo-like | 1 | 0.04 s, 54 paths, 4579 anchors, 8 MB |
|
||||
| Line Art | four-colour badge | 1 | 0.02 s, 1 paths, 38 anchors, 6 MB |
|
||||
| Technical Drawing | photo-like | 1 | 0.04 s, 221 paths, 6443 anchors, 8 MB |
|
||||
| Technical Drawing | four-colour badge | 1 | 0.02 s, 1 paths, 47 anchors, 6 MB |
|
||||
| Outline | photo-like | 1 | 0.05 s, 45 paths, 3875 anchors, 16 MB |
|
||||
| Outline | four-colour badge | 1 | 0.03 s, 1 paths, 38 anchors, 11 MB |
|
||||
| Flat Logo | photo-like | 1 | refused after 0.09 s (not flat art), 40 MB |
|
||||
| Flat Logo | four-colour badge | 1 | 0.64 s, 5 paths, 78 anchors, 146 MB |
|
||||
| Default | photo-like | 4 | 0.18 s, 48 paths, 17801 anchors, 60 MB |
|
||||
| Default | four-colour badge | 4 | 0.09 s, 3 paths, 178 anchors, 64 MB |
|
||||
| High Fidelity Photo | photo-like | 4 | 0.72 s, 4475 paths, 341272 anchors, 981 MB |
|
||||
| High Fidelity Photo | four-colour badge | 4 | 0.24 s, 6 paths, 200 anchors, 152 MB |
|
||||
| Low Fidelity Photo | photo-like | 4 | 0.38 s, 304 paths, 61093 anchors, 568 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 152 anchors, 152 MB |
|
||||
| 3 Colors | photo-like | 4 | 0.28 s, 20 paths, 8453 anchors, 104 MB |
|
||||
| 3 Colors | four-colour badge | 4 | 0.18 s, 4 paths, 122 anchors, 84 MB |
|
||||
| 6 Colors | photo-like | 4 | 0.37 s, 115 paths, 23128 anchors, 168 MB |
|
||||
| 6 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 92 anchors, 128 MB |
|
||||
| 16 Colors | photo-like | 4 | 0.32 s, 353 paths, 52749 anchors, 408 MB |
|
||||
| 16 Colors | four-colour badge | 4 | 0.19 s, 5 paths, 96 anchors, 128 MB |
|
||||
| Shades of Gray | photo-like | 4 | 0.29 s, 138 paths, 18794 anchors, 224 MB |
|
||||
| Shades of Gray | four-colour badge | 4 | 0.27 s, 10 paths, 1265 anchors, 220 MB |
|
||||
| Black and White Logo | photo-like | 4 | 0.20 s, 208 paths, 17188 anchors, 36 MB |
|
||||
| Black and White Logo | four-colour badge | 4 | 0.09 s, 1 paths, 64 anchors, 36 MB |
|
||||
| Sketched Art | photo-like | 4 | 0.20 s, 9 paths, 6884 anchors, 36 MB |
|
||||
| Sketched Art | four-colour badge | 4 | 0.09 s, 2 paths, 236 anchors, 36 MB |
|
||||
| Silhouettes | photo-like | 4 | 0.19 s, 2 paths, 1951 anchors, 44 MB |
|
||||
| Silhouettes | four-colour badge | 4 | 0.10 s, 1 paths, 32 anchors, 36 MB |
|
||||
| Line Art | photo-like | 4 | 0.19 s, 127 paths, 12091 anchors, 36 MB |
|
||||
| Line Art | four-colour badge | 4 | 0.09 s, 1 paths, 419 anchors, 36 MB |
|
||||
| Technical Drawing | photo-like | 4 | 0.20 s, 931 paths, 23132 anchors, 36 MB |
|
||||
| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 64 anchors, 36 MB |
|
||||
| Flat Logo | four-colour badge | 1 | 0.65 s, 5 paths, 78 anchors, 146 MB |
|
||||
| Default | photo-like | 4 | 0.18 s, 48 paths, 15720 anchors, 49 MB |
|
||||
| Default | four-colour badge | 4 | 0.09 s, 3 paths, 72 anchors, 48 MB |
|
||||
| Auto Color | photo-like | 4 | 0.44 s, 1469 paths, 148161 anchors, 89 MB |
|
||||
| Auto Color | four-colour badge | 4 | 0.18 s, 5 paths, 92 anchors, 46 MB |
|
||||
| High Fidelity Photo | photo-like | 4 | 0.70 s, 4475 paths, 341272 anchors, 634 MB |
|
||||
| High Fidelity Photo | four-colour badge | 4 | 0.21 s, 6 paths, 200 anchors, 97 MB |
|
||||
| Low Fidelity Photo | photo-like | 4 | 0.36 s, 304 paths, 61093 anchors, 401 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 4 | 0.23 s, 6 paths, 152 anchors, 97 MB |
|
||||
| 3 Colors | photo-like | 4 | 0.26 s, 20 paths, 8453 anchors, 73 MB |
|
||||
| 3 Colors | four-colour badge | 4 | 0.18 s, 4 paths, 122 anchors, 51 MB |
|
||||
| 6 Colors | photo-like | 4 | 0.26 s, 115 paths, 23128 anchors, 100 MB |
|
||||
| 6 Colors | four-colour badge | 4 | 0.18 s, 5 paths, 92 anchors, 46 MB |
|
||||
| 16 Colors | photo-like | 4 | 0.28 s, 353 paths, 52749 anchors, 124 MB |
|
||||
| 16 Colors | four-colour badge | 4 | 0.18 s, 5 paths, 96 anchors, 46 MB |
|
||||
| Shades of Gray | photo-like | 4 | 0.29 s, 138 paths, 18794 anchors, 157 MB |
|
||||
| Shades of Gray | four-colour badge | 4 | 0.26 s, 10 paths, 1265 anchors, 134 MB |
|
||||
| Black and White Logo | photo-like | 4 | 0.18 s, 208 paths, 13717 anchors, 32 MB |
|
||||
| Black and White Logo | four-colour badge | 4 | 0.08 s, 1 paths, 57 anchors, 24 MB |
|
||||
| Sketched Art | photo-like | 4 | 0.18 s, 9 paths, 5358 anchors, 32 MB |
|
||||
| Sketched Art | four-colour badge | 4 | 0.08 s, 2 paths, 196 anchors, 24 MB |
|
||||
| Silhouettes | photo-like | 4 | 0.18 s, 2 paths, 1457 anchors, 36 MB |
|
||||
| Silhouettes | four-colour badge | 4 | 0.08 s, 1 paths, 28 anchors, 24 MB |
|
||||
| Line Art | photo-like | 4 | 0.19 s, 127 paths, 11461 anchors, 32 MB |
|
||||
| Line Art | four-colour badge | 4 | 0.08 s, 1 paths, 42 anchors, 24 MB |
|
||||
| Technical Drawing | photo-like | 4 | 0.18 s, 931 paths, 21061 anchors, 32 MB |
|
||||
| Technical Drawing | four-colour badge | 4 | 0.08 s, 1 paths, 57 anchors, 24 MB |
|
||||
| Outline | photo-like | 4 | 0.23 s, 113 paths, 10455 anchors, 64 MB |
|
||||
| Outline | four-colour badge | 4 | 0.12 s, 1 paths, 42 anchors, 44 MB |
|
||||
| Flat Logo | photo-like | 4 | refused after 0.10 s (not flat art), 40 MB |
|
||||
| Flat Logo | four-colour badge | 4 | 0.65 s, 5 paths, 69 anchors, 146 MB |
|
||||
| Default | photo-like | 16 | 1.04 s, 127 paths, 43656 anchors, 256 MB |
|
||||
| Default | four-colour badge | 16 | 0.47 s, 3 paths, 250 anchors, 240 MB |
|
||||
| High Fidelity Photo | photo-like | 16 | 2.85 s, 31125 paths, 1175671 anchors, 3856 MB |
|
||||
| High Fidelity Photo | four-colour badge | 16 | 1.11 s, 6 paths, 274 anchors, 576 MB |
|
||||
| Low Fidelity Photo | photo-like | 16 | 1.49 s, 2036 paths, 181832 anchors, 1972 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 16 | 1.07 s, 6 paths, 187 anchors, 576 MB |
|
||||
| 3 Colors | photo-like | 16 | 1.22 s, 76 paths, 22878 anchors, 416 MB |
|
||||
| 3 Colors | four-colour badge | 16 | 0.82 s, 4 paths, 190 anchors, 336 MB |
|
||||
| 6 Colors | photo-like | 16 | 1.13 s, 470 paths, 60539 anchors, 672 MB |
|
||||
| 6 Colors | four-colour badge | 16 | 0.82 s, 5 paths, 126 anchors, 500 MB |
|
||||
| 16 Colors | photo-like | 16 | 1.18 s, 1973 paths, 145173 anchors, 1625 MB |
|
||||
| 16 Colors | four-colour badge | 16 | 0.84 s, 5 paths, 128 anchors, 504 MB |
|
||||
| Shades of Gray | photo-like | 16 | 1.48 s, 686 paths, 53872 anchors, 852 MB |
|
||||
| Shades of Gray | four-colour badge | 16 | 1.40 s, 9 paths, 2216 anchors, 800 MB |
|
||||
| Black and White Logo | photo-like | 16 | 1.15 s, 1128 paths, 52547 anchors, 144 MB |
|
||||
| Black and White Logo | four-colour badge | 16 | 0.47 s, 1 paths, 95 anchors, 128 MB |
|
||||
| Sketched Art | photo-like | 16 | 1.16 s, 15 paths, 16298 anchors, 144 MB |
|
||||
| Sketched Art | four-colour badge | 16 | 0.47 s, 2 paths, 414 anchors, 128 MB |
|
||||
| Silhouettes | photo-like | 16 | 1.34 s, 3 paths, 4516 anchors, 176 MB |
|
||||
| Silhouettes | four-colour badge | 16 | 0.53 s, 1 paths, 40 anchors, 144 MB |
|
||||
| Line Art | photo-like | 16 | 1.04 s, 713 paths, 43982 anchors, 144 MB |
|
||||
| Line Art | four-colour badge | 16 | 0.45 s, 1 paths, 707 anchors, 128 MB |
|
||||
| Technical Drawing | photo-like | 16 | 1.21 s, 4578 paths, 84475 anchors, 144 MB |
|
||||
| Technical Drawing | four-colour badge | 16 | 0.46 s, 1 paths, 95 anchors, 128 MB |
|
||||
| Flat Logo | photo-like | 16 | refused after 0.11 s (not flat art), 40 MB |
|
||||
| Flat Logo | four-colour badge | 16 | 0.69 s, 5 paths, 67 anchors, 146 MB |
|
||||
| Default | photo-like | 16 | 0.90 s, 127 paths, 34766 anchors, 199 MB |
|
||||
| Default | four-colour badge | 16 | 0.48 s, 3 paths, 88 anchors, 184 MB |
|
||||
| Auto Color | photo-like | 16 | 1.58 s, 5391 paths, 449395 anchors, 279 MB |
|
||||
| Auto Color | four-colour badge | 16 | 0.83 s, 5 paths, 126 anchors, 184 MB |
|
||||
| High Fidelity Photo | photo-like | 16 | 3.72 s, 31125 paths, 1175671 anchors, 880 MB |
|
||||
| High Fidelity Photo | four-colour badge | 16 | 1.05 s, 6 paths, 274 anchors, 257 MB |
|
||||
| Low Fidelity Photo | photo-like | 16 | 1.75 s, 2036 paths, 181832 anchors, 754 MB |
|
||||
| Low Fidelity Photo | four-colour badge | 16 | 1.04 s, 6 paths, 187 anchors, 257 MB |
|
||||
| 3 Colors | photo-like | 16 | 1.11 s, 76 paths, 22878 anchors, 290 MB |
|
||||
| 3 Colors | four-colour badge | 16 | 0.81 s, 4 paths, 190 anchors, 205 MB |
|
||||
| 6 Colors | photo-like | 16 | 1.12 s, 470 paths, 60539 anchors, 298 MB |
|
||||
| 6 Colors | four-colour badge | 16 | 0.79 s, 5 paths, 126 anchors, 184 MB |
|
||||
| 16 Colors | photo-like | 16 | 1.10 s, 1973 paths, 145173 anchors, 303 MB |
|
||||
| 16 Colors | four-colour badge | 16 | 0.81 s, 5 paths, 128 anchors, 184 MB |
|
||||
| Shades of Gray | photo-like | 16 | 1.56 s, 686 paths, 53872 anchors, 407 MB |
|
||||
| Shades of Gray | four-colour badge | 16 | 1.30 s, 9 paths, 2216 anchors, 270 MB |
|
||||
| Black and White Logo | photo-like | 16 | 1.10 s, 1128 paths, 45117 anchors, 129 MB |
|
||||
| Black and White Logo | four-colour badge | 16 | 0.36 s, 1 paths, 69 anchors, 88 MB |
|
||||
| Sketched Art | photo-like | 16 | 0.94 s, 15 paths, 11568 anchors, 128 MB |
|
||||
| Sketched Art | four-colour badge | 16 | 0.37 s, 2 paths, 361 anchors, 88 MB |
|
||||
| Silhouettes | photo-like | 16 | 1.31 s, 3 paths, 3358 anchors, 144 MB |
|
||||
| Silhouettes | four-colour badge | 16 | 0.41 s, 1 paths, 32 anchors, 95 MB |
|
||||
| Line Art | photo-like | 16 | 1.01 s, 713 paths, 36212 anchors, 129 MB |
|
||||
| Line Art | four-colour badge | 16 | 0.35 s, 1 paths, 49 anchors, 88 MB |
|
||||
| Technical Drawing | photo-like | 16 | 1.14 s, 4578 paths, 79049 anchors, 130 MB |
|
||||
| Technical Drawing | four-colour badge | 16 | 0.36 s, 1 paths, 69 anchors, 88 MB |
|
||||
| Outline | photo-like | 16 | 1.28 s, 632 paths, 31715 anchors, 256 MB |
|
||||
| Outline | four-colour badge | 16 | 0.55 s, 1 paths, 49 anchors, 176 MB |
|
||||
| Flat Logo | photo-like | 16 | refused after 0.12 s (not flat art), 40 MB |
|
||||
| Flat Logo | four-colour badge | 16 | 0.66 s, 5 paths, 67 anchors, 146 MB |
|
||||
|
||||
## Revision history
|
||||
|
||||
| date | change |
|
||||
|:--|:--|
|
||||
| 2026-10-13 | Ignore Color; the Auto Color and Outline presets, measured from here on (Outline is the first measure of Create: Strokes) |
|
||||
| 2026-10-13 | Thin lines: black and white read against the image's darkest black and the paper near each edge |
|
||||
| 2026-10-13 | Memory: layers traced within the box of their pixels and a few at a time (16 megapixel photo: 3.9 GB to 0.9 GB) |
|
||||
| 2026-10-13 | Shared edges: outlines are cut at their junctions and each piece is fitted once, the same from either side |
|
||||
| 2026-10-13 | Photos: continuous-tone images are smoothed within their tones before quantising, and stacked layers go by lightness |
|
||||
| 2026-10-13 | The palette: made of the picture's own colours, noise-made colours merged, blend pixels settled, cuts where colours differ most, colours compared in Oklab |
|
||||
|
||||
+3
-2
@@ -2977,8 +2977,9 @@ included) the fonts in DirectWrite's system font collection, such as those Adobe
|
||||
`/Network/Library/Fonts`, `~/Library/Fonts` and downloaded system fonts, plus in the desktop app and `vectorcraft-cli`
|
||||
the fonts CoreText's font manager lists outside them, such as those apps and font managers register from their own
|
||||
folders; Linux and BSD: `/usr/share/fonts`,
|
||||
`/usr/local/share/fonts`, `~/.fonts` and the XDG data folders' `fonts`, `~/.local/share/fonts` among them, and in a
|
||||
Flatpak sandbox the host's fonts). Faces without outlines VectorCraft draws (no `glyf`, `CFF`, `CFF2` or `VARC` table, such
|
||||
`/usr/local/share/fonts`, `~/.fonts` and the XDG data folders' `fonts`, `~/.local/share/fonts` among them, in a
|
||||
Flatpak sandbox the host's fonts, and the folders fontconfig's configuration lists (`<dir>` in `/etc/fonts/fonts.conf`
|
||||
and the files it includes: where packages add theirs, such as TeX fonts under `/usr/share/texmf/fonts`)). Faces without outlines VectorCraft draws (no `glyf`, `CFF`, `CFF2` or `VARC` table, such
|
||||
as bitmap-only fonts) are left out. The scan also reads the folder the `fontsFolder` preference names (Preferences › Type ›
|
||||
Additional Fonts Folder) with its subfolders and, in the desktop app and `vectorcraft-cli`, VectorCraft's own `Fonts` folder
|
||||
next to the preferences: `~/Library/Application Support/VectorCraft/Fonts` on macOS, `%APPDATA%\VectorCraft\Fonts` on
|
||||
|
||||
Reference in new issue
Block a user