From 001c627f5246318db96a9404fae28ba744833daf Mon Sep 17 00:00:00 2001 From: bea4dev Date: Fri, 10 Oct 2025 18:34:53 +0900 Subject: [PATCH] reimpl SDF rounded corner --- kitty/decorations.c | 161 +++++++++++++++++++------------------------- 1 file changed, 68 insertions(+), 93 deletions(-) diff --git a/kitty/decorations.c b/kitty/decorations.c index 1d24240c5..cf1bc002a 100644 --- a/kitty/decorations.c +++ b/kitty/decorations.c @@ -7,6 +7,7 @@ #include "decorations.h" #include "state.h" +#include typedef uint32_t uint; @@ -314,6 +315,9 @@ add_vholes(Canvas *self, uint level, uint num) { } } +static double clamp_double(double value, double lower, double upper); +static double smoothstep(double edge0, double edge1, double x); + static Range hline_limits(Canvas *self, uint y, uint level) { uint sz = thickness(self, level, false); @@ -324,6 +328,8 @@ hline_limits(Canvas *self, uint y, uint level) { static void draw_hline(Canvas *self, uint x1, uint x2, uint y, uint level) { + if (x1 >= x2 || y >= self->height) return; + // Draw a horizontal line between [x1, x2) centered at y with the thickness given by level and self->supersample_factor Range r = hline_limits(self, y, level); for (uint y = r.start; y < r.end; y++) { @@ -340,7 +346,6 @@ vline_limits(Canvas *self, uint x, uint level) { return r; } - static void draw_vline(Canvas *self, uint y1, uint y2, uint x, uint level) { // Draw a vertical line between [y1, y2) centered at x with the thickness given by level and self->supersample_factor @@ -362,6 +367,19 @@ half_height(Canvas *self) { // align with non-supersampled co-ords return self->supersample_factor * (self->height / 2 / self->supersample_factor); } +static double +clamp_double(double value, double lower, double upper) { + if (value < lower) return lower; + if (value > upper) return upper; + return value; +} + +static double +smoothstep(double edge0, double edge1, double x) { + if (edge0 == edge1) return x < edge0 ? 0.0 : 1.0; + double t = clamp_double((x - edge0) / (edge1 - edge0), 0.0, 1.0); + return t * t * (3.0 - 2.0 * t); +} static void half_hline(Canvas *self, uint level, bool right_half, uint extend_by) { @@ -428,12 +446,6 @@ half_vline(Canvas *self, uint level, bool bottom_half, uint extend_by) { draw_vline(self, y1, y2, half_width(self), level); } -static void -fractional_vline(Canvas *self, uint level, uint y1, uint y2) { - draw_vline(self, y1, y2, half_width(self), level); -} - - static void hline(Canvas *self, uint level) { half_hline(self, level, false, 0); @@ -1290,94 +1302,57 @@ fading_vline(Canvas *self, uint level, uint num, Edge fade) { } } -typedef struct Rectircle Rectircle; - -typedef struct Rectircle { - double a, b, yexp, xexp, x_sign, y_sign, x_start, y_start; - double x_prime_coeff, x_prime_exp, y_prime_coeff, y_prime_exp; -} Rectircle; - -static double -rectircle_x(const void *v, double t) { - const Rectircle *r = v; - return r->x_start + r->x_sign * r->a * pow(cos(t * (M_PI / 2.0)), r->xexp); -} - -static double -rectircle_x_prime(const void *v, double t) { - const Rectircle *r = v; - t *= (M_PI / 2.0); - return r->x_prime_coeff * pow(cos(t), r->x_prime_exp) * sin(t); -} - -static double -rectircle_y_prime(const void *v, double t) { - const Rectircle *r = v; - t *= (M_PI / 2.0); - return r->y_prime_coeff * pow(sin(t), r->y_prime_exp) * cos(t); -} - -static double -rectircle_y(const void *v, double t) { - const Rectircle *r = v; - return r->y_start + r->y_sign * r->b * pow(sin(t * (M_PI / 2.0)), r->yexp); -} - -static Rectircle -rectcircle(Canvas *self, Corner which) { - /* - Return two functions, x(t) and y(t) that map the parameter t which must be - in the range [0, 1] to x and y coordinates in the cell. The rectircle equation - we use is: - (|x| / a) ^ (2a / r) + (|y| / b) ^ (2b / r) = 1 - where 2a = width, 2b = height and r is radius - See https://math.stackexchange.com/questions/1649714 - - This is a super-ellipse, its parametrized form is: - x = ± a * (cos(theta) ^ (r / a)); y = ± b * (sin(theta) ^ (r / b)); theta is in [0, pi/2] - https://en.wikipedia.org/wiki/Superellipse - The plus minus signs are chosen to give the four quadrants. - - The entire rectircle fits in four cells, each cell being one quadrant - of the full rectircle and the origin being the center of the rectircle. - The functions we return do the mapping for the specified cell. - ╭╮ ╭─╮ - ╰╯ │ │ - ╰─╯ - */ - double radius = self->width / 2., a = self->width / 2., b = self->height / 2.; - Rectircle ans = { - .a = a, .b = b, - .xexp = radius / a, .yexp = radius / b, - .x_prime_coeff = radius, .x_prime_exp = radius / a - 1., - .y_prime_coeff = radius, .y_prime_exp = radius / b - 1., - .x_sign = which & RIGHT_EDGE ? 1. : -1, - .x_start = which & RIGHT_EDGE ? 0. : 2 * a, - .y_start = which & BOTTOM_EDGE ? 0. : 2 * b, - .y_sign = which & BOTTOM_EDGE ? 1. : -1, - }; - - return ans; -} - static void rounded_corner(Canvas *self, uint level, Corner which) { - Rectircle r = rectcircle(self, which); - double line_width = thickness_as_float(self, level, true); - uint cell_width_is_odd = (self->width / self->supersample_factor) & 1; - uint cell_height_is_odd = (self->height / self->supersample_factor) & 1; - // adjust for odd cell dimensions to line up with box drawing lines - double x_offset = cell_width_is_odd ? 0 : 0.5, y_offset = cell_height_is_odd ? 0 : 0.5; - ClipRect cr = {.x_end=self->width, .y_end=self->height}; - if (which & TOP_EDGE) cr.top = hline_limits(self, half_height(self), level).start; - else cr.y_end = hline_limits(self, half_height(self), level).end; - if (which & LEFT_EDGE) cr.left = vline_limits(self, half_width(self), level).start; - else cr.x_end = vline_limits(self, half_width(self), level).end; - draw_parametrized_curve_with_derivative_and_antialiasing( - self, &r, line_width, rectircle_x, rectircle_y, rectircle_x_prime, rectircle_y_prime, x_offset, y_offset, &cr); - // make the vertical stems be same brightness as straightline segments - if (which & TOP_EDGE) fractional_vline(self, level, self->height - self->width / 2, self->height); - else fractional_vline(self, level, 0, self->width / 2); + // Render a rounded box corner. + const uint Hx = half_width(self); + const uint Hy = half_height(self); + const Range hori_line_range = hline_limits(self, Hy, level); + const Range vert_line_range = vline_limits(self, Hx, level); + const uint hori_line_height = hori_line_range.end - hori_line_range.start; + const uint vert_line_width = vert_line_range.end - vert_line_range.start; + double adjusted_Hx = (double)Hx; + double adjusted_Hy = (double)Hy; + if (hori_line_height % 2 != 0) adjusted_Hy += 0.5; + if (vert_line_width % 2 != 0) adjusted_Hx += 0.5; + const double stroke = (double)max(hori_line_height, vert_line_width); + const double corner_radius = fmin(adjusted_Hx, adjusted_Hy); + const double bx = adjusted_Hx - corner_radius; + const double by = adjusted_Hy - corner_radius; + + // Anti-aliasing on corner + const double aa_corner = (double)self->supersample_factor * 0.5; + const double half_stroke = 0.5 * stroke; + + const double x_shift = (which & RIGHT_EDGE) ? adjusted_Hx : -adjusted_Hx; + const double y_shift = (which & TOP_EDGE) ? -adjusted_Hy : adjusted_Hy; + + for (uint y = 0; y < self->height; y++) { + const double sample_y = (double)y + y_shift + 0.5; + const double pos_y = sample_y - adjusted_Hy; + const uint row_off = y * self->width; + + for (uint x = 0; x < self->width; x++) { + const double sample_x = (double)x + x_shift + 0.5; + const double pos_x = sample_x - adjusted_Hx; + + const double qx = fabs(pos_x) - bx; + const double qy = fabs(pos_y) - by; + const double dx = qx > 0.0 ? qx : 0.0; + const double dy = qy > 0.0 ? qy : 0.0; + const double dist = hypot(dx, dy) + fmin(fmax(qx, qy), 0.0) - corner_radius; + + const double aa = (qx > 1e-7 && qy > 1e-7) ? aa_corner : 0.0; + const double outer = half_stroke - dist; + const double inner = -half_stroke - dist; + const double alpha = smoothstep(-aa, aa, outer) - smoothstep(-aa, aa, inner); + + if (alpha <= 0.0) continue; + const uint8_t value = (uint8_t)lrint(clamp_double(alpha, 0.0, 1.0) * 255.0); + uint8_t *p = &self->mask[row_off + x]; + if (value > *p) *p = value; + } + } } static void