Fill what a rounded rectangle contains outright as spans instead of pixel by pixel

This commit is contained in:
flamingcow
2026-08-04 21:09:12 -07:00
parent ce44fe9866
commit 51467231ee
+53 -14
View File
@@ -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 // The rectangle the corner radius sweeps around. Distance to it is zero across
// across the whole flat middle and grows only near a corner. Taking coverage // the whole flat middle and grows only near a corner. Taking coverage from that
// from that rather than from a plain inside test keeps the curves smooth // rather than from a plain inside test keeps the curves smooth instead of
// instead of stepped. // 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) { 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 // 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 // swept rectangle inside out, which matters while something is growing from
// nothing. // nothing.
r = min(r, min(w, h)/2) r = min(r, min(w, h)/2)
ix0, iy0 := float64(x0+r), float64(y0+r) s := sweep{float64(x0 + r), float64(y0 + r),
ix1, iy1 := float64(x0+w-1-r), float64(y0+h-1-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 y := y0; y < y0+h; y++ {
for x := x0; x < x0+w; x++ { for x := x0; x < x0+w; x++ {
fx, fy := float64(x), float64(y) s.pixel(fb, x, y, c)
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
}
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. // Blends src over the existing pixel, with cov as 0-255 coverage.