From 51467231ee86051477ef5c188afbef8cb9f42914 Mon Sep 17 00:00:00 2001 From: flamingcow Date: Tue, 4 Aug 2026 21:09:12 -0700 Subject: [PATCH] Fill what a rounded rectangle contains outright as spans instead of pixel by pixel --- fb.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 15 deletions(-) diff --git a/fb.go b/fb.go index 673566e..0338558 100644 --- a/fb.go +++ b/fb.go @@ -352,29 +352,68 @@ func (fb *framebuffer) rect(x0, y0, w, h int, c rgb) { } } -// Distance to the rectangle the corner radius sweeps around, which is zero -// across the whole flat middle and grows only near a corner. Taking coverage -// from that rather than from a plain inside test keeps the curves smooth -// instead of stepped. +// The rectangle the corner radius sweeps around. Distance to it is zero across +// the whole flat middle and grows only near a corner. Taking coverage from that +// rather than from a plain inside test keeps the curves smooth instead of +// stepped. +type sweep struct { + ix0, iy0, ix1, iy1 float64 + r int +} + +func (s sweep) pixel(fb *framebuffer, x, y int, c rgb) { + fx, fy := float64(x), float64(y) + dx := math.Max(math.Max(s.ix0-fx, fx-s.ix1), 0) + dy := math.Max(math.Max(s.iy0-fy, fy-s.iy1), 0) + cov := float64(s.r) - math.Sqrt(dx*dx+dy*dy) + 0.5 + if cov <= 0 { + return + } + fb.blend(x, y, c, uint8(math.Min(cov, 1)*255)) +} + +// Partial coverage reaches no further than the corner blocks and the one line +// of pixels the curve runs tangent to along each flat edge. The sweep contains +// everything else outright, which fills as spans instead of a pixel at a time. func (fb *framebuffer) roundRect(x0, y0, w, h, r int, c rgb) { // A radius past half the shorter side has no meaning and would put the // swept rectangle inside out, which matters while something is growing from // nothing. r = min(r, min(w, h)/2) - ix0, iy0 := float64(x0+r), float64(y0+r) - ix1, iy1 := float64(x0+w-1-r), float64(y0+h-1-r) - for y := y0; y < y0+h; y++ { - for x := x0; x < x0+w; x++ { - fx, fy := float64(x), float64(y) - dx := math.Max(math.Max(ix0-fx, fx-ix1), 0) - dy := math.Max(math.Max(iy0-fy, fy-iy1), 0) - cov := float64(r) - math.Sqrt(dx*dx+dy*dy) + 0.5 - if cov <= 0 { - continue + s := sweep{float64(x0 + r), float64(y0 + r), + float64(x0 + w - 1 - r), float64(y0 + h - 1 - r), r} + + // With no radius the sweep never reaches a whole pixel, so the sliver it + // leaves is partly covered throughout rather than solid anywhere. + if r == 0 { + for y := y0; y < y0+h; y++ { + for x := x0; x < x0+w; x++ { + s.pixel(fb, x, y, c) } - fb.blend(x, y, c, uint8(math.Min(cov, 1)*255)) + } + return + } + + for j := 0; j < r; j++ { + for i := 0; i < r; i++ { + s.pixel(fb, x0+i, y0+j, c) + s.pixel(fb, x0+w-1-i, y0+j, c) + s.pixel(fb, x0+i, y0+h-1-j, c) + s.pixel(fb, x0+w-1-i, y0+h-1-j, c) } } + for x := x0 + r; x < x0+w-r; x++ { + s.pixel(fb, x, y0, c) + s.pixel(fb, x, y0+h-1, c) + } + for y := y0 + r; y < y0+h-r; y++ { + s.pixel(fb, x0, y, c) + s.pixel(fb, x0+w-1, y, c) + } + + fb.rect(x0+r, y0+1, w-2*r, h-2, c) + fb.rect(x0+1, y0+r, r-1, h-2*r, c) + fb.rect(x0+w-r, y0+r, r-1, h-2*r, c) } // Blends src over the existing pixel, with cov as 0-255 coverage.