package media import "image" // fitDims scales (w, h) so the longer side equals limit, preserving aspect // ratio with round-half-up on the shorter side, floored at 1 pixel. func fitDims(w, h, limit int) (int, int) { if w >= h { return limit, max((h*limit+w/2)/w, 1) } return max((w*limit+h/2)/h, 1), limit } // downscale resizes src to dw x dh using area averaging (a box filter): each // destination pixel is the mean of its corresponding source region. // // Why hand-rolled: the stdlib has no scaler and ADR-0007 bars // golang.org/x/image without a new ADR. Area averaging is dependency-free, // alias-resistant when shrinking (every source pixel contributes exactly // once), and entirely adequate quality for vision-model input. It is only // ever called to shrink — Normalize never upscales. func downscale(src image.Image, dw, dh int) *image.RGBA { b := src.Bounds() sw, sh := b.Dx(), b.Dy() dst := image.NewRGBA(image.Rect(0, 0, dw, dh)) for dy := 0; dy < dh; dy++ { // Integer box edges: destination pixel dy covers source rows // [dy*sh/dh, (dy+1)*sh/dh), widened to at least one row. sy0 := dy * sh / dh sy1 := max((dy+1)*sh/dh, sy0+1) for dx := 0; dx < dw; dx++ { sx0 := dx * sw / dw sx1 := max((dx+1)*sw/dw, sx0+1) var r, g, bl, a uint64 for sy := sy0; sy < sy1; sy++ { for sx := sx0; sx < sx1; sx++ { pr, pg, pb, pa := src.At(b.Min.X+sx, b.Min.Y+sy).RGBA() r += uint64(pr) g += uint64(pg) bl += uint64(pb) a += uint64(pa) } } n := uint64((sy1 - sy0) * (sx1 - sx0)) i := dst.PixOffset(dx, dy) // RGBA() returns 16-bit channels; average, then drop to 8 bits. dst.Pix[i+0] = uint8(r / n >> 8) dst.Pix[i+1] = uint8(g / n >> 8) dst.Pix[i+2] = uint8(bl / n >> 8) dst.Pix[i+3] = uint8(a / n >> 8) } } return dst }