remove_stray_pixels
Replace isolated stray pixels with the most common colour of their neighbours to clear shading noise from pixel art, and optionally erase detached specks outside the silhouette.
Instructions
Replace pixels that have no neighbour of their own colour with the colour around them.
A stray pixel is one whose eight neighbours are all a different colour. They are what
a shading pass leaves behind at a band boundary, and at any zoom they read as dirt
rather than as texture. `assess_sprite` counts them as `isolated_pixels`; this is what
to do about the count.
Each stray takes the most common colour among its opaque neighbours, so **no new
colour can appear**: the result uses a subset of the colours already there, and art on
a ramp stays on it. Transparent pixels are left alone, so the silhouette does not
change.
A stray with no opaque neighbour at all has no colour to take, so by default it is
skipped: inventing one would be drawing rather than cleaning. That is the right answer
inside the art and the wrong one for the commonest dirt an effects pass leaves, which
is a lone pixel *outside* the art on empty canvas. `erase_isolated` is for that.
Args:
protect: Colours never to replace. A one-pixel eye highlight or a specular dot is
a stray by this definition and is meant to be there, so name its colour. Also
keeps a cluster `erase_isolated` would otherwise erase, if the colour is in it.
erase_isolated: Erase a cluster that stands clear of everything else instead of
skipping it. **Opt-in, because this changes the silhouette**, which is the one
thing the tool otherwise never does: a one-pixel spark, a floating highlight,
the dot of an "i" drawn as its own element and a dither sparser than a
checkerboard are all clusters by this rule and are all meant to be there. What
it erases is reported as `erased`, apart from `replaced`, for the same reason.
min_cluster: How many pixels a detached cluster may have and still count as dirt.
1, the default, is a lone pixel, which is all "isolated" means by itself; 2
catches the two-pixel speck these passes usually leave, where neither pixel is
isolated because each has the other for company. Capped low, and the refusal
names the cap: past a handful of pixels a thing standing clear of the artwork
is a mark somebody drew. Only means anything with `erase_isolated`, and is
refused without it rather than ignored.
Erasure is decided over clusters of **opaque** pixels, connected in all eight
directions, that have nothing but transparency around them. That is what makes it safe
at any `min_cluster`: the artwork is connected to itself, so it is never a cluster, and
only something standing clear of it can go. It is also why the two halves of this tool
barely meet: a cluster touches no other opaque pixel, so nothing else's replacement can
be reading a colour off one. Where they do meet, erasing wins, since recolouring dirt
on its way out is work with no result.
A pixel whose only same-colour neighbour is **diagonal** is part of a dither pattern,
not dirt, and is left alone. `assess_sprite` counts isolation orthogonally, which is
the stricter reading, so a dithered sprite still reports some `isolated_pixels` after
this has run and that count is the dithering rather than anything to fix.
This is not Aseprite's own Despeckle, which is a median filter: that one averages
neighbourhoods, introduces colours that were not in the palette, and on a measured
test left *more* stray pixels than it found. This changes only the pixels that are
strays, and only to colours already next to them.
Returns how many were replaced, so a second call can be skipped when it says 0. With
`erase_isolated` it also returns `erased` and `erased_clusters`, which are absent
otherwise: a count of work nobody asked for reads as a finding.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| frame | No | Default: 1. | |
| layer | No | ||
| protect | No | ||
| filename | Yes | ||
| min_cluster | No | Default: 1. | |
| erase_isolated | No | Default: false. |