@@ -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,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+
729800static void
730801draw_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) {
13331404static void
13341405rounded_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
13441419static void
@@ -1509,7 +1584,8 @@ sextant(Canvas *self, uint which) {
15091584void
15101585render_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