downsample
Mojo function 🡭
downsample
fn def downsample(source: Canvas, factor: Int) -> CanvasShrink source by factor, which must evenly divide both source.width and source.height – raises rather than truncating a partial edge block away. factor=1 is a valid no-op copy, so a caller sweeping factor across several values needn’t branch around 1.
Alpha is the rounded mean of the source block. Color is averaged with alpha weights and returned as straight RGBA, so transparent samples do not darken a shape’s edge or contribute hidden color. A block whose alpha rounds to zero becomes transparent black. Factor 1 copies every byte, including color under zero alpha.
Supersampling with it: pixel (px, py) is centered at (px, py), and
output pixel p averages source pixels factor * p through
factor * p + factor - 1, whose center is factor * p + (factor - 1) / 2. A drawing scaled by factor alone therefore
shows up (factor - 1) / (2 * factor) px early after the shrink –
3/8 px at 4 – for every primitive. Translate by (factor - 1) / 2
before the scale and it lands where a scale-1 drawing does:
big.translate((f - 1) / 2, (f - 1) / 2)
big.scale(f, f)
... draw ...
var out = downsample(big, f)
The recipe places every primitive, Int rects included, where a
factor-1 drawing places it (within about 0.01 px for circles,
ellipses, lines, paths, and text). Output is not byte-identical
across factors: antialiased edges legitimately differ between a
supersampled render and a plain one.
The recipe moves geometry, not snapping. fill_rect snaps its
edges in device space after the transform, so a fractional
logical edge that resolves to one gray column at factor 1 resolves
to factor finer steps instead, and that edge stops matching the
factor-1 output. A caller who wants hard edges under supersampling
snaps each edge to the nearest half-integer in logical space before
drawing, and derives the size from the two snapped edges rather
than snapping a position and a size separately, which lets two
rounding errors accumulate in the width. Every factor then
produces the same crisp edge in the same place. Lines, paths, and
text are never snapped and need nothing.
Args:
- source (
Canvas): Canvas to shrink. - factor (
Int): Integer shrink factor; must evenly divide bothsource.widthandsource.height.
Returns:
Canvas: A new canvas, source.width // factor by
source.height // factor.
Raises:
Error: factor isn’t positive, or doesn’t evenly divide
source’s dimensions.