@@ -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+
415421static void
416422hline (Canvas * self , uint level ) {
417423 half_hline (self , level , false, 0 );
@@ -726,6 +732,69 @@ 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+ for (uint px = 0 ; px < self -> width ; px ++ ) {
769+ // Center of the current pixel
770+ double pixel_center_x = (double )px + 0.5 ;
771+ double pixel_center_y = (double )py + 0.5 ;
772+
773+ double min_dist_sq = -1.0 ;
774+
775+ // Find the closest point on the curve to the pixel center by sampling the curve.
776+ for (uint i = 0 ; i < num_samples ; ++ i ) {
777+ double dx = x_samples [i ] - pixel_center_x ;
778+ double dy = y_samples [i ] - pixel_center_y ;
779+ double dist_sq = dx * dx + dy * dy ;
780+ if (min_dist_sq < 0 || dist_sq < min_dist_sq ) min_dist_sq = dist_sq ;
781+ }
782+
783+ double distance = sqrt (min_dist_sq );
784+
785+ // Calculate alpha based on the distance from the curve.
786+ // This creates the anti-aliased edge. The distance from the center
787+ // of the pixel to the edge of the stroke is used.
788+ // We assume a pixel has a width of 1.0 for this calculation.
789+ double alpha_unclamped = half_thickness - distance + 0.5 ;
790+
791+ // Clamp the alpha value to the [0, 1] range and scale to [0, 255]
792+ uint8_t alpha = (uint8_t )(MAX (0.0 , MIN (alpha_unclamped , 1.0 )) * 255.0 );
793+ self -> mask [py * self -> width + px ] = alpha ;
794+ }
795+ }
796+ }
797+
729798static void
730799draw_parametrized_curve_with_derivative (
731800 Canvas * self , void * curve_data , double line_width , curve_func xfunc , curve_func yfunc , curve_func x_prime , curve_func y_prime ,
@@ -1333,12 +1402,16 @@ rectcircle(Canvas *self, Corner which) {
13331402static void
13341403rounded_corner (Canvas * self , uint level , Corner which ) {
13351404 Rectircle r = rectcircle (self , which );
1405+ double line_width = thickness_as_float (self , level , true);
13361406 uint cell_width_is_odd = (self -> width / self -> supersample_factor ) & 1 ;
13371407 uint cell_height_is_odd = (self -> height / self -> supersample_factor ) & 1 ;
13381408 // 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 );
1409+ double x_offset = cell_width_is_odd ? 0 : 0.5 , y_offset = cell_height_is_odd ? 0 : 0.5 ;
1410+ draw_parametrized_curve_with_derivative_and_antialiasing (
1411+ self , & r , line_width , rectircle_x , rectircle_y , rectircle_x_prime , rectircle_y_prime , x_offset , y_offset );
1412+ // make the vertical stems be same brightness as straightline segments
1413+ if (which & TOP_EDGE ) fractional_vline (self , level , self -> height - self -> width / 2 , self -> height );
1414+ else fractional_vline (self , level , 0 , self -> width / 2 );
13421415}
13431416
13441417static void
@@ -1509,7 +1582,8 @@ sextant(Canvas *self, uint which) {
15091582void
15101583render_box_char (char_type ch , uint8_t * buf , unsigned width , unsigned height , double dpi_x , double dpi_y , double scale ) {
15111584 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 ;
1585+ ss .mask = buf + width * height ; ss .supersample_factor = SUPERSAMPLE_FACTOR ;
1586+ ss .width *= SUPERSAMPLE_FACTOR ; ss .height *= SUPERSAMPLE_FACTOR ;
15131587 fill_canvas (& canvas , 0 );
15141588 Canvas * c = & canvas ;
15151589
@@ -1790,31 +1864,31 @@ START_ALLOW_CASE_RANGE
17901864 C (L'' , fading_vline , 1 , 5 , BOTTOM_EDGE );
17911865 C (L'' , fading_vline , 1 , 5 , TOP_EDGE );
17921866
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 ));
1867+ C (L'' , rounded_corner , 1 , TOP_LEFT );
1868+ C (L'' , rounded_corner , 1 , TOP_RIGHT );
1869+ C (L'' , rounded_corner , 1 , BOTTOM_LEFT );
1870+ C (L'' , rounded_corner , 1 , BOTTOM_RIGHT );
1871+
1872+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , BOTTOM_LEFT ));
1873+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , TOP_LEFT ));
1874+ CC (L'' , rounded_corner (c , 1 , BOTTOM_LEFT ), rounded_corner (c , 1 , TOP_LEFT ));
1875+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , BOTTOM_RIGHT ));
1876+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , TOP_RIGHT ));
1877+ CC (L'' , rounded_corner (c , 1 , TOP_RIGHT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1878+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , TOP_RIGHT ));
1879+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , TOP_LEFT ));
1880+ CC (L'' , rounded_corner (c , 1 , TOP_LEFT ), rounded_corner (c , 1 , TOP_RIGHT ));
1881+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , BOTTOM_RIGHT ));
1882+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , BOTTOM_LEFT ));
1883+ CC (L'' , rounded_corner (c , 1 , BOTTOM_LEFT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1884+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , BOTTOM_LEFT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1885+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , TOP_LEFT ), rounded_corner (c , 1 , TOP_RIGHT ));
1886+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , TOP_RIGHT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1887+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , BOTTOM_LEFT ), rounded_corner (c , 1 , TOP_LEFT ));
1888+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , TOP_LEFT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1889+ CC (L'' , vline (c , 1 ); rounded_corner (c , 1 , TOP_RIGHT ), rounded_corner (c , 1 , BOTTOM_LEFT ));
1890+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , TOP_LEFT ), rounded_corner (c , 1 , BOTTOM_RIGHT ));
1891+ CC (L'' , hline (c , 1 ); rounded_corner (c , 1 , TOP_RIGHT ), rounded_corner (c , 1 , BOTTOM_LEFT ));
18181892
18191893#define P (ch , lines ) S(ch, commit, lines, true); S(ch+1, commit, lines, false);
18201894 P (L'' , 0 );
@@ -1837,10 +1911,10 @@ START_ALLOW_CASE_RANGE
18371911#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);
18381912 Q (L'┌' , BOTTOM_RIGHT ); Q (L'┐' , BOTTOM_LEFT ); Q (L'└' , TOP_RIGHT ); Q (L'┘' , TOP_LEFT );
18391913#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 );
1914+ C (L'╭' , rounded_corner , 1 , TOP_LEFT );
1915+ C (L'╮' , rounded_corner , 1 , TOP_RIGHT );
1916+ C (L'╰' , rounded_corner , 1 , BOTTOM_LEFT );
1917+ C (L'╯' , rounded_corner , 1 , BOTTOM_RIGHT );
18441918
18451919 case L'┼' ... L'┼' + 15 : cross (c , ch - L'┼' ); break ;
18461920#define T (q , func ) case q ... q + 7: func(c, q, ch - q); break;
0 commit comments