Skip to content

Commit da6f26d

Browse files
committed
Improve rendering of rounded rectangles
Uses the standard distance to curve via sampling method of antialiasing instead of super sampled antialiasing. Need to investigate behavior very thin lines <= 3 pixels. Also need to check if we should migrate the other call sites of draw_parametrized_curve_with_derivate() Fixes #9000
1 parent 982b515 commit da6f26d

File tree

2 files changed

+111
-33
lines changed

2 files changed

+111
-33
lines changed

docs/changelog.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ Detailed list of changes
176176
- Fix updating panel configuration on visibility toggle and via remote control
177177
not working (:iss:`8984`)
178178

179+
- Improve rendering of rounded rectangles (:pull:`9000`)
180+
179181
- Wayland: Update bundled copy of libwayland to 1.24 from 1.23.1 because the
180182
just released mesa 25.2.0 breaks with libwayland < 1.24 (:iss:`8884`)
181183

kitty/decorations.c

Lines changed: 109 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,12 @@ half_vline(Canvas *self, uint level, bool bottom_half, uint extend_by) {
412412
draw_vline(self, y1, y2, half_width(self), level);
413413
}
414414

415+
static void
416+
fractional_vline(Canvas *self, uint level, uint y1, uint y2) {
417+
draw_vline(self, y1, y2, half_width(self), level);
418+
}
419+
420+
415421
static void
416422
hline(Canvas *self, uint level) {
417423
half_hline(self, level, false, 0);
@@ -726,6 +732,71 @@ static bool cmpr_point(Point a, Point b) { return a.val == b.val; }
726732
vt_cleanup(&seen); \
727733
}
728734

735+
static void
736+
draw_parametrized_curve_with_derivative_and_antialiasing(
737+
Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc,
738+
curve_func x_prime, curve_func y_prime, double x_offset, double y_offset
739+
) {
740+
line_width = fmax(1., line_width);
741+
double half_thickness = line_width / 2.0;
742+
uint i=0, larger_dim = MAX(self->height, self->width);
743+
double t = 0;
744+
double step = 1.0 / larger_dim;
745+
uint cap = 2 * larger_dim;
746+
const double min_step = step / 1000., max_step = step;
747+
RAII_ALLOC(double, x_samples, malloc(sizeof(double) * cap));
748+
RAII_ALLOC(double, y_samples, malloc(sizeof(double) * cap));
749+
while (true) {
750+
x_samples[i] = xfunc(curve_data, t) + x_offset;
751+
y_samples[i] = yfunc(curve_data, t) + y_offset;
752+
if (t >= 1.0) break;
753+
// Dynamically adjust step size based on curve's derivative
754+
double dx = x_prime(curve_data, t), dy = y_prime(curve_data, t);
755+
double d = sqrt(dx * dx + dy * dy);
756+
step = 1.0 / fmax(1e-6, d);
757+
step = fmax(min_step, fmin(step, max_step));
758+
t = fmin(t + step, 1.0);
759+
i++;
760+
if (i >= cap) {
761+
cap *= 2;
762+
x_samples = realloc(x_samples, sizeof(x_samples[0]) * cap);
763+
y_samples = realloc(y_samples, sizeof(x_samples[0]) * cap);
764+
}
765+
}
766+
const uint num_samples = i;
767+
for (uint py = 0; py < self->height; py++) {
768+
uint ypos = self->width * py;
769+
for (uint px = 0; px < self->width; px++) {
770+
// Center of the current pixel
771+
double pixel_center_x = (double)px + 0.5;
772+
double pixel_center_y = (double)py + 0.5;
773+
774+
double min_dist_sq = -1.0;
775+
776+
// Find the closest point on the curve to the pixel center by sampling the curve.
777+
for (uint i = 0; i < num_samples; ++i) {
778+
double dx = x_samples[i] - pixel_center_x;
779+
double dy = y_samples[i] - pixel_center_y;
780+
double dist_sq = dx * dx + dy * dy;
781+
if (min_dist_sq < 0 || dist_sq < min_dist_sq) min_dist_sq = dist_sq;
782+
}
783+
784+
double distance = sqrt(min_dist_sq);
785+
786+
// Calculate alpha based on the distance from the curve.
787+
// This creates the anti-aliased edge. The distance from the center
788+
// of the pixel to the edge of the stroke is used.
789+
// We assume a pixel has a width of 1.0 for this calculation.
790+
double alpha_unclamped = half_thickness - distance + 0.5;
791+
792+
uint offset = ypos + px;
793+
uint8_t old_alpha = self->mask[offset];
794+
double alpha = MAX(0.0, MIN(alpha_unclamped, 1.0));
795+
self->mask[offset] = (uint8_t)(alpha * 255 + (1 - alpha) * old_alpha); // alpha blend
796+
}
797+
}
798+
}
799+
729800
static void
730801
draw_parametrized_curve_with_derivative(
731802
Canvas *self, void *curve_data, double line_width, curve_func xfunc, curve_func yfunc, curve_func x_prime, curve_func y_prime,
@@ -1333,12 +1404,16 @@ rectcircle(Canvas *self, Corner which) {
13331404
static void
13341405
rounded_corner(Canvas *self, uint level, Corner which) {
13351406
Rectircle r = rectcircle(self, which);
1407+
double line_width = thickness_as_float(self, level, true);
13361408
uint cell_width_is_odd = (self->width / self->supersample_factor) & 1;
13371409
uint cell_height_is_odd = (self->height / self->supersample_factor) & 1;
13381410
// adjust for odd cell dimensions to line up with box drawing lines
1339-
int x_offset = -(cell_width_is_odd & 1), y_offset = -(cell_height_is_odd & 1);
1340-
double line_width = thickness_as_float(self, level, true);
1341-
draw_parametrized_curve_with_derivative(self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset, 0.1);
1411+
double x_offset = cell_width_is_odd ? 0 : 0.5, y_offset = cell_height_is_odd ? 0 : 0.5;
1412+
draw_parametrized_curve_with_derivative_and_antialiasing(
1413+
self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset);
1414+
// make the vertical stems be same brightness as straightline segments
1415+
if (which & TOP_EDGE) fractional_vline(self, level, self->height - self->width / 2, self->height);
1416+
else fractional_vline(self, level, 0, self->width / 2);
13421417
}
13431418

13441419
static void
@@ -1509,7 +1584,8 @@ sextant(Canvas *self, uint which) {
15091584
void
15101585
render_box_char(char_type ch, uint8_t *buf, unsigned width, unsigned height, double dpi_x, double dpi_y, double scale) {
15111586
Canvas canvas = {.mask=buf, .width = width, .height = height, .dpi={.x=dpi_x, .y=dpi_y}, .supersample_factor=1u, .scale=scale}, ss = canvas;
1512-
ss.mask = buf + width*height; ss.supersample_factor = SUPERSAMPLE_FACTOR; ss.width *= SUPERSAMPLE_FACTOR; ss.height *= SUPERSAMPLE_FACTOR;
1587+
ss.mask = buf + width*height; ss.supersample_factor = SUPERSAMPLE_FACTOR;
1588+
ss.width *= SUPERSAMPLE_FACTOR; ss.height *= SUPERSAMPLE_FACTOR;
15131589
fill_canvas(&canvas, 0);
15141590
Canvas *c = &canvas;
15151591

@@ -1790,31 +1866,31 @@ START_ALLOW_CASE_RANGE
17901866
C(L'', fading_vline, 1, 5, BOTTOM_EDGE);
17911867
C(L'', fading_vline, 1, 5, TOP_EDGE);
17921868

1793-
S(L'', rounded_corner, 1, TOP_LEFT);
1794-
S(L'', rounded_corner, 1, TOP_RIGHT);
1795-
S(L'', rounded_corner, 1, BOTTOM_LEFT);
1796-
S(L'', rounded_corner, 1, BOTTOM_RIGHT);
1797-
1798-
SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
1799-
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT));
1800-
SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
1801-
SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
1802-
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
1803-
SS(L'', rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
1804-
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
1805-
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT));
1806-
SS(L'', rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
1807-
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
1808-
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
1809-
SS(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1810-
SS(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1811-
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
1812-
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
1813-
SS(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
1814-
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1815-
SS(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
1816-
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1817-
SS(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
1869+
C(L'', rounded_corner, 1, TOP_LEFT);
1870+
C(L'', rounded_corner, 1, TOP_RIGHT);
1871+
C(L'', rounded_corner, 1, BOTTOM_LEFT);
1872+
C(L'', rounded_corner, 1, BOTTOM_RIGHT);
1873+
1874+
CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
1875+
CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT));
1876+
CC(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
1877+
CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
1878+
CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
1879+
CC(L'', rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
1880+
CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT));
1881+
CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT));
1882+
CC(L'', rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
1883+
CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_RIGHT));
1884+
CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT));
1885+
CC(L'', rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1886+
CC(L'', vline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1887+
CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, TOP_RIGHT));
1888+
CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_RIGHT));
1889+
CC(L'', hline(c, 1); rounded_corner(c, 1, BOTTOM_LEFT), rounded_corner(c, 1, TOP_LEFT));
1890+
CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1891+
CC(L'', vline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
1892+
CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_LEFT), rounded_corner(c, 1, BOTTOM_RIGHT));
1893+
CC(L'', hline(c, 1); rounded_corner(c, 1, TOP_RIGHT), rounded_corner(c, 1, BOTTOM_LEFT));
18181894

18191895
#define P(ch, lines) S(ch, commit, lines, true); S(ch+1, commit, lines, false);
18201896
P(L'', 0);
@@ -1837,10 +1913,10 @@ START_ALLOW_CASE_RANGE
18371913
#define Q(ch, which) C(ch, corner, t, t, which); C(ch + 1, corner, f, t, which); C(ch + 2, corner, t, f, which); C(ch + 3, corner, f, f, which);
18381914
Q(L'┌', BOTTOM_RIGHT); Q(L'┐', BOTTOM_LEFT); Q(L'└', TOP_RIGHT); Q(L'┘', TOP_LEFT);
18391915
#undef Q
1840-
S(L'╭', rounded_corner, 1, TOP_LEFT);
1841-
S(L'╮', rounded_corner, 1, TOP_RIGHT);
1842-
S(L'╰', rounded_corner, 1, BOTTOM_LEFT);
1843-
S(L'╯', rounded_corner, 1, BOTTOM_RIGHT);
1916+
C(L'╭', rounded_corner, 1, TOP_LEFT);
1917+
C(L'╮', rounded_corner, 1, TOP_RIGHT);
1918+
C(L'╰', rounded_corner, 1, BOTTOM_LEFT);
1919+
C(L'╯', rounded_corner, 1, BOTTOM_RIGHT);
18441920

18451921
case L'┼' ... L'┼' + 15: cross(c, ch - L'┼'); break;
18461922
#define T(q, func) case q ... q + 7: func(c, q, ch - q); break;

0 commit comments

Comments
 (0)