Files
slate32/test/round_rect_test.cpp
T
evan 36953bb140 fix(gui): anti-aliased rounded rects from one distance field
Corners were drawn by two disagreeing algorithms: a hand-rolled per-row inset for
the gradient fill and TFT_eSPI's Bresenham arc for the border, so they missed each
other by a pixel and left a halo. The inset was also wrong, truncating the sqrt and
ignoring pixel centres, which over-cut the top row by 2px into a visible chamfer.

gui.roundRect now derives fill and border from the same signed distance, blending
edge pixels by coverage, and replaces fillRoundRect, drawRoundRect and
fillRectGradient. The geometry moved to src/gfx/round_rect.h so round_rect_test.cpp
can check the arc on the host, since only the eye ever checked the old one.
2026-08-01 11:04:35 -04:00

73 lines
3.0 KiB
C++

// Build & run: c++ -std=c++17 test/round_rect_test.cpp -o /tmp/round_rect_test && /tmp/round_rect_test
#include "../src/gfx/round_rect.h"
#include <cassert>
#include <cstdio>
using namespace gfx;
static float cornerCoverage(int w, int h, float radius, int column, int row) {
float halfWidth = w * 0.5f, halfHeight = h * 0.5f;
return coverage(roundRectDistance(column + 0.5f - halfWidth, row + 0.5f - halfHeight,
halfWidth, halfHeight, radius));
}
int main() {
const int w = 296, h = 24;
const float radius = 6;
// A straight edge is pixel aligned, so it must be fully lit; a fringe there would
// show up as a washed-out border down the sides of every button.
assert(cornerCoverage(w, h, radius, 0, h / 2) == 1.0f);
assert(cornerCoverage(w, h, radius, w - 1, h / 2) == 1.0f);
assert(cornerCoverage(w, h, radius, w / 2, 0) == 1.0f);
// Well inside is solid, well outside the corner is empty.
assert(cornerCoverage(w, h, radius, w / 2, h / 2) == 1.0f);
assert(cornerCoverage(w, h, radius, 0, 0) == 0.0f);
// The corner is a real arc: partial pixels exist, and coverage grows monotonically
// along the diagonal instead of stepping like the old integer inset did.
bool sawPartial = false;
float previous = -1.0f;
for (int i = 0; i < (int)radius; i++) {
float value = cornerCoverage(w, h, radius, i, i);
if (value > 0.0f && value < 1.0f) sawPartial = true;
assert(value >= previous);
previous = value;
}
assert(sawPartial && "an anti-aliased corner must produce partial coverage");
// The arc must stay within the radius: the pixel just past it is already solid.
assert(cornerCoverage(w, h, radius, (int)radius, (int)radius) == 1.0f);
// Symmetry across both axes, so no corner is fatter than another.
for (int row = 0; row < (int)radius; row++) {
for (int column = 0; column < (int)radius; column++) {
float topLeft = cornerCoverage(w, h, radius, column, row);
assert(topLeft == cornerCoverage(w, h, radius, w - 1 - column, row));
assert(topLeft == cornerCoverage(w, h, radius, column, h - 1 - row));
assert(topLeft == cornerCoverage(w, h, radius, w - 1 - column, h - 1 - row));
}
}
// radius 0 keeps square corners fully lit, which is what the mono theme pins.
assert(cornerCoverage(w, h, 0, 0, 0) == 1.0f);
// Blending endpoints must be exact, or repeated repaints would drift the colour.
const uint16_t black = 0x0000, white = 0xFFFF;
assert(blend565(black, white, 0.0f) == black);
assert(blend565(black, white, 1.0f) == white);
uint16_t half = blend565(black, white, 0.5f);
assert(half > black && half < white);
assert(blend565(white, white, 0.5f) == white);
// A gradient must reach both stops exactly, so a button's edges match its theme.
assert(lerp565(black, white, 0, h - 1) == black);
assert(lerp565(black, white, h - 1, h - 1) == white);
assert(lerp565(black, white, 1, 0) == black); // degenerate height must not divide by zero
printf("ok\n");
return 0;
}