The fitter accepted a piece at depth 14, or with fewer than six points,
whatever its error, so a traced outline could stay farther than the
tolerance from its points. Synthetic 2 px block noise at Paths 100 and
Corners 0 kept a piece 2.7 px off at a tolerance of 0.1.
Such a piece is now accepted only when each point is within the tolerance
of the curve. Otherwise a short run is cut at its worst point, down to
two points, and a run at the depth limit is cut in the middle, which keeps
the recursion shallow. Every other piece is unchanged. Noisy art can get
more anchors.
a_traced_pixel_that_straightens_to_no_area_... now traces at Paths 80. At
Paths 90 that pixel's no-area result came from pieces over the tolerance.
Regions are a linked list of pixels with a union-find over region ids instead of a Vec<usize> per region and BTreeMaps per merge. Same merge order and output; about 2.7x less peak memory and up to 1.9x faster on 25 MP noisy labels.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The bilateral smoothing pass worked out a squared colour distance, a powi, a
division and a weight for every pixel and neighbour, and called round() per
channel. The distance is a whole number up to 195,075, so the weights come from
a table built once per trace, and the rounding is a truncation plus a compare
that gives the same level as round() for 0..=255. Rows are read through slices.
The output is byte for byte what it was; tests compare the pass with the old
formula on several images and on every pair of grey levels.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Found while testing crates/trace/src/params.rs:248-251 (enhanced_preset)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Tracing a photo or a large image ran inside imageTrace.make on the UI
thread, so the window froze for as long as the trace took, with nothing
to say it was working.
A trace the user asks for (Object > Image Trace > Make and Make and
Expand, the task bar's Image Trace, the Image Trace panel's Trace and
presets) now runs on a thread of its own, as heavy plug-in filters do.
The status bar says so, and after 0.4 s a progress window shows how
many of the image's colors are traced, with Cancel and Continue in
Background (imageTrace.cancelRuns, imageTrace.hideRuns).
The result goes back to the command it was prepared for
(cmd::TraceRun, Traced::hand_in), which takes it only for the same
object, image and settings, so the edit is one undo step as before. A
newer trace of the same object replaces one still running, and a trace
whose image or settings changed meanwhile changes nothing. The control
channel, MCP, scripts and the web trace at once, as before.
The tracer reports its stages and color layers to a callback and stops
when it says so (trace_watched).
The loop that keeps rare colors stopped at the first color past the
budget of six, so a cut-edge blend listed after it was not recorded as
tracing as its rare color, even when that rare color was kept, and its
pixels went to the nearest color instead. The loop now goes on past the
budget and only stops keeping new rare colors. It moves into
`kept_rare`, which a new test calls with seven rare colors and a blend
of the first.
The test of an outline over a cut edge in the color of other art also
checks that the dark paths reach the top and the bottom of the disc,
which no bar comes near: before, the bars alone met its checks.
The test of a color whose every path straightens to no area traced a
pale blue row with a library of a light color, black and [40, 120, 240].
With Document Library weighing hue and chroma three times as much as
lightness, the row takes the light color instead of that blue, so the
test no longer traced any blue. The library blue is now [60, 130, 235],
which the row still maps to; the assertions are unchanged.
With the Document Library palette, each pixel took the library color
nearest in plain Oklab distance. A 50% gray lies nearer in that distance
to a saturated blue of the same lightness than to black or a light
cream, and a muted brown is also nearer to the blue than to either. With
a black, cream and blue library, both traced as the blue.
This changes how Document Library matches pixels: each pixel now takes
the library color nearest with differences in hue and chroma (Oklab a
and b) counted three times as much as lightness. The same measure picks
which library colors are used. Neutral and muted tones take a neutral
library color, and the other palettes are unchanged.
With Method Overlapping, each color layer covered every pixel of the colors
above it in the stack. A small dot or square on a background was then traced
once in its own color and again, at the same bounds, in every color between
the background and it, and with Shapes on each copy became its own live shape.
Those layers also ended on edges they never touched and showed through the
anti-aliased rim there.
Each layer now leaves out every 4-connected part of its stacked area that holds
no pixel of its own color. This changes the result of many Overlapping traces,
the photo presets included: fewer paths (High Fidelity Photo 121 to 69.6 on its
own quality inputs, Low Fidelity Photo 37.1 to 26.1) and no rim, around a
shape, of a color that sits apart from it. One existing test that counted the
extra copies now expects one layer per area, and another that compared layer
pixel counts now checks that the bottom layer covers the whole image.
Since main leaves a color with no paths out of the trace's palette, an
empty palette no longer shows that Ignore Color left out every color
keepColors matches. The trace result now says so in keep_ignored, and
the warning reads it, so a matched color that produces no paths is
reported as such with Ignore White on.
A filled path that straightens to no area is left out of the trace
(#3949). When that removed every path of a color, the color stayed in
the trace's palette, so the Image Trace panel's Colors count and the
`colors` that `imageTrace.make` returns included it. The palette now
lists only the colors that have a path.
The check for colors the palette cannot explain counted a pixel of a
bar two pixels wide only when its neighbors on both sides along the
bar were bar pixels. In a bar three pixels long only the two pixels of
its middle row passed, so its color had fewer than the three pixels a
color needs and was not added to the palette. A pixel now counts when
it lies at either end or in the middle of three such pixels in a row,
so all six pixels of a 2 × 3 bar count.
Flat Logo counted the anchors of every fitted outline and hole before it
left out the ones with no area, so a trace whose kept paths fit the
anchor limit could still be refused as too complex. The limit now counts
the anchors of the subpaths that go into the result.
The limit on outlines per color also counted holes, which are cut only
with Ignore White on. Holes now count toward it only when they are cut.
When Flat Logo finds the colors itself (no colors named), a flat bar
two pixels wide (whole pixels of one color) on a ground beside other
art was lost or traced in another color. No bar pixel has a uniform
3 x 3 neighborhood, so neither the first clustering nor the check for
unexplained colors took it. A gray bar on white beside black art was
traced as white, and a red or green bar was traced as black. Bars
three pixels wide or more were already found.
The check for unexplained colors now also takes the pixels of such a
bar: two rows or columns of one color (within about 3.5 in RGB
distance), three pixels long, with the same ground on both sides. They
count when the known colors do not explain them, and also when their
color lies less than two thirds as far from the ground's known color
as from every other known color, where the bar would otherwise be
traced as the ground. A gray bar darker than about #9B9B9B on white
beside black art stays black, #808080 included, because a black stroke
1 to 1.6 px wide on the pixel grid gives the same pixels. Named colors
are unchanged.
The fit of a thin outline can lose all its area. Its two sides can
straighten onto one line, which leaves two anchors whose segments run
back and forth along it. Or the fit can be a single cubic, which becomes
one anchor and has no segment. Both occur when Flat Logo traces noise
whose cells are one pixel in size. The trace kept such an outline as a
selectable path that paints nothing.
Flat Logo now leaves out an outline whose area is below 1e-6 px^2,
together with its holes, and a hole whose area is below 1e-6 px^2. The
area is summed about the outline's first anchor, as the other trace
modes already do for a filled loop (crates/trace/src/lib.rs:996-1001).
Outlines with an area of 1e-6 px^2 or more, thin slivers included, are
kept as before.
One test traces a black and cream noise with both colors named exactly
and checks that no subpath of the result lacks an area. Two more build
shapes from straight cubics: a hole without area is left out, and an
outline without area is left out with its hole.
Flat Logo traces a copy of the image at most 1024 pixels on a side,
averaged down by a whole factor, so a 1025-pixel image is traced at
513 pixels. The trace's warnings did not say so, because they compared
the image only with the decoded raster. For an image over 1500 pixels
they said instead that it is traced more slowly, which Flat Logo does
not do.
The trace result now carries Flat Logo's working size when it averaged
the image down. imageTrace.make and imageTrace.makeAndExpand then name
both sizes in their warnings, and the status bar shows that note, in
place of the slower-trace and megapixel notes. Other modes keep their
warnings. The message has a row in each catalog that translates the
other Image Trace warnings.
Found while testing crates/trace/src/logo/palette.rs:9-15 (parse_hex)
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A trace whose kept colors were traced and produced no paths gave no
warning, or, with Ignore Color on, said that Ignore Color left those
colors out. The Ignore Color warning now shows only when no kept color
was traced at all, and otherwise the first warning says that the colors
that match keepColors produce no paths. The new message has a row in
each complete-message catalog.
Whether Ignore Color or keepColors leaves a palette entry out is now
decided once per entry, so tracing a layer no longer scans the
keepColors list for each pixel.
Docs: docs/image-trace-adjustments.md.
Auto Grouping compared the box of every traced path with the box of every
path that can hold something, and with Overlapping it also compared each
grouped path with every path below it. On a 1400 x 1400 px noise image
traced with Limited 8, Overlapping (18,653 paths), parents() made
442,998,298 box tests and took 3.0 s in a release build.
parents() now keeps the holders and, for stacked traces, the paths below in
a grid of about one cell per path. A path's candidates are the holders in the
cell of its box center, smallest first, and the paths below it come from the
cells its box reaches. When a box reaches more cells or entries than there
are paths below it, the old scan is used. The cells grow larger when the
boxes would otherwise be listed in more than 32 cells each on average. The
same trace now takes 142,289 box tests and 0.08 s, and every parent is the
same as before. The 20,000-path limit is unchanged.
The tests compare parents() with the pairwise version on rings, faces and
noise traced Abutting and Overlapping, check that a dense trace takes at most
64 box tests per path while the pairwise version takes more, check that 300
nested discs take no more box tests than the pairwise version, and check
that boxes covering the whole image make the cells larger.
A pixel on the edge of a cut-out image with an alpha of 128 or more was
traced with its own color. When that color was a darkened edge tone, it
took whichever palette color lay nearest, and the edge traced as 1 px
slivers of a color that appears nowhere near it.
In Color and Grayscale, a pixel with an alpha of 128 to 254 next to a
pixel under half opaque now takes a color of the fully opaque pixels
within two pixels of it when its own color is that color or a darker
shade of it, counting only pixels it reaches without crossing a
transparent gap; of several such colors, it takes the one the most of
those pixels have. It keeps its own color when one of those pixels has
it, or when its color is not a shade of any of theirs, so a thin
semi-transparent outline of another color stays as it is. Ties go to the color nearest its own, by its gray
in Grayscale. Opaque images trace as before.
Fitting straightens the pieces of an outline that lie close to a line, and
Snap Curves To Lines straightens more of them. The outline of a single
pixel, or of a region one pixel thick along an edge such as an opaque row
of a mid tone under a light shape, can then have all its pieces on one
line. The trace held a selectable path with no area, for example a path
140 x 0 px with Snap Curves To Lines.
In a Color, Grayscale or Black and White trace with Fills, a fitted
outline or hole whose area is below 1e-6 px^2 is now left out. A path
with no area paints nothing, so the traced result paints the same pixels
as before. Without Fills, the 1 px stroke that outlines each area draws
such a loop, so it is kept. A single closed loop keeps its area as
before.
Image Trace had no way to trace some of an image's colors and leave the
rest out. Ignore Color drops one color, so tracing only the line work of
a multi-color image took a full trace and then deleting the other
colors' paths by hand.
With "keepColors": ["#rrggbb", ...] in the params of imageTrace.make and
imageTrace.makeAndExpand, only the palette colors within about 30 levels
of a listed color are traced, the distance Ignore Color matches by. With
Method Abutting, the kept colors' paths are the same as in a trace of
every color. With Method Overlapping, a kept color's paths do not reach
under the colors left out, as with Ignore Color. An empty list traces
every color, and a value that is not a six-digit hex color is refused.
Flat Logo mode does not use the list.
The reply's warnings name each listed color that matches none of the
colors the trace found, and say when nothing was traced; the status bar
shows the first. The warnings are translated in every complete catalog.
The params help of imageTrace.make lists keepColors and the new
warnings, and the help of imageTrace.makeAndExpand lists the warnings
it returns.
The Automatic palette counts flat art's colours by area (a colour needs
about 1% of the picture, 0.5% at Colors 100) and fits that count with
median cut and area-weighted k-means. A stem, a highlight, or a near
white beside a pale background (within 24 levels, so in one box) was
averaged into a large neighbour; mixed-colour merging and Noise could
then remove what was left.
On the flat route only, after the count is fitted, rare_colours finds
the colours too rare or too close for it and keeps those that are
distinct (at least 2.5 in Oklab x 100 from every palette colour and from
the main colour each stands for), flat (mostly one histogram bin) and
drawn (a 4-connected region of their own unblended pixels of at least 8
pixels, or three of at least 4); a colour lying between two others, as
anti-aliasing mixes them, must also have an inside. At most six, largest
region first. They stay fixed while the other colours are refitted
around them; merge_mixed leaves them alone; denoise_keeping keeps their
regions of 4 pixels or more whatever Noise is (smaller specks still go).
Full Tone, Limited and Automatic's continuous-tone route are unchanged,
and denoise without kept labels is as before.
Methods cited in the code: median cut (Heckbert 1982), k-means (Lloyd
1982), Oklab (Ottosson 2020), salient-colour preservation (Fukuda et al.
2019, doi:10.1117/12.2521607). The rule and its numbers are VectorCraft's
own; Adobe does not document how its palettes are chosen.
Tests (code-drawn): a rare stem or patch over 2-8 flat colours, hard
and anti-aliased at 1x and 2x; near-white highlights on #FFF4E0 kept and
still paths at Noise 16; a 3 px #909090 stem on #808080; scattered
single pixels of the same areas add no colour; denoise_keeping at Noise
0-32.
Measured on 66 synthetic flat logos (22 designs x hard 1x, anti-aliased
1x and 2x; local inputs): every key colour and no spurious one in 66
(was 39); missing key colours 0 (was 43), spurious 0 (was 2), merged
pairs 0 (was 46); rendered dE00 0.348 -> 0.138. Four public-domain scan
crops take the continuous-tone route and are unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
Raster::new checked its RGBA length with assert_eq!, so bytes of the
wrong length (from a file, a plug-in or a control message) would abort
instead of reporting an error. It now returns Result<Raster, TraceError>
and gives TraceError::Decode naming the byte count and the size (the
existing variant, whose text is already translated, instead of a new
message every language would need). The size product is checked for
overflow.
Raster::pixel indexed the bytes directly; it now gives transparent
black outside the raster, or past the end of a raster whose public
fields were set to a size its bytes don't have (which mosaic reads).
The public signature changes: every caller in the workspace was a test
(trace unit tests, Flat Logo tests and the quality harness), updated to
unwrap. No shipped code built a Raster with new; decode, decode_to_fit
and from_fn always make the right length, and trace_within already
refuses a raster whose fields disagree.
Test (code-drawn): a_raster_of_the_wrong_length_is_an_error_not_a_crash
(short, long, empty, zero-width and overflowing sizes; pixel, trace and
mosaic on a raster whose fields disagree).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018tBcdwNhptqtmX32JCGFzB
Place junction clusters at their deepest pixel and prune terminal branches
by significance beyond their maximal discs. Merge overlapping junctions
and split crossings with continuing outer arms, then join degree-two runs.
Bound nodes, branches and simplification work, using checked data access.
Keep whole-blob selection, ink refinement, cap detection and fitting rules.
Test junction degrees, crossings, 24-angle lines, pruning, graph caps and
random-blob determinism. Record improved junction degrees and remaining
spur and extra-junction limitations in the centre-lines measurements.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI <noreply@openai.com>
Compute exact squared Euclidean distances per blob with separable parabola
lower envelopes. Thin with bounded Guo–Hall active sets and retain diagonal
ends during staircase cleanup. Keep the whole-blob width rule and existing
graph, ink, cap and fitting rules, with checked access in the moved module.
Verify EDT against brute force, active sets against plain rounds, and both
diagonal slopes for length and connectivity. Record the centre-lines rerun
and its remaining limitations without changing feature scores.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: OpenAI <noreply@openai.com>