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.
This commit is contained in:
@@ -0,0 +1,72 @@
|
||||
// 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;
|
||||
}
|
||||
Reference in New Issue
Block a user