ContributorMural::SilhouetteTracer
Turns an image file into a SilhouetteMask.
Crystal has no image decoders, and adding one per format would mean a dependency per format. librsvg already reads all of them — it is the PNG rasterizer this action ships with — so the image is wrapped in a one-element SVG and rendered, and the only thing this has to decode is what comes back: cairo's 8-bit RGBA PNG, which is a zlib stream and five scanline filters.
Up to three passes. With ink: auto the image is first stretched over the
whole canvas to decide what counts as ink: stretched, every transparent
pixel is the image's own, where letterboxed it could be the canvas showing
through. Then it is fitted into the canvas to find where the shape is, and
last just that box is rendered, sized so every cell of the lattice lands on
exactly SAMPLES × SAMPLES pixels. A tall, narrow logo is sampled as finely
as a square one, and deciding a cell is a plain average rather than an
interpolation.
Constants
How much of a stretched image may be transparent before it is treated as a cut-out. Anti-aliasing and a stray pixel cost a sliver; a logo on a transparent background leaves whole corners.
A pixel counts toward the shape's bounding box from here, or from
threshold when that is lower. Half-covered anti-aliasing at the rim
should not widen the box by a pixel either side — but a threshold
lowered to keep a thin stroke has to keep its outer edge in the box too.
Ink below this is paper: JPEG noise, a faint tint, anti-aliasing.
The image travels to librsvg as a base64 data URI in an XML attribute, and libxml2 refuses attribute values past ten million characters. Base64 costs a third on top, which puts the real ceiling at 7.5 MB.
An SVG with a width and height but no viewBox has no aspect ratio for
meet to keep: librsvg stretches it over the whole canvas instead. Its
own width and height are what a viewBox would have said, so say it —
when they are plain numbers. A size in other units is left alone.
The root tag is the first one past the prolog, not the first <svg
anywhere: a comment or DOCTYPE that mentions one came first and got the
viewBox instead. Quote-aware, so a > inside an attribute value does not
end the tag early.
Class methods
{width, height, RGBA bytes}. Only as much PNG as librsvg writes: 8-bit RGBA or RGB, not interlaced. RGB is widened to opaque RGBA.