Menu

#833 Full dashtype support for libgd terminals and improved antialiasing quality

Version 6
pending-accepted
nobody
5
7 days ago
2026-01-22
Sethur
No

This patch enhances the libgd-based terminals (e.g. png, and other gd variants) by adding full dashtype support and improving antialiasing quality.

For dashed lines, it introduces a PNG_dashtype() implementation that maps gnuplot’s built-in dashtypes (dash, dot, dash-dot, dash-dot-dot) and user-defined custom dash patterns into a gdImageSetStyle() array (color/transparent run lengths), scaled by both linewidth and the terminal’s dashlength factor. The code tracks dash phase across connected vectors so dashes remain continuous across polyline segments.

For TrueColor output with butt caps, dashed lines are rendered through a new custom antialiased dashed-line path because libgd’s gdStyled rendering does not antialias. A small AA rasterizer for thick segments is added, including buffering/flush logic to reduce visible “seam striping” when curves are approximated by many short segments. Axis/grid dotted rendering remains a special case as before.

I did not benchmark the performance impact directly, but solid "lw 1" lines should render as fast as before. I developed this patch because I needed to generate a large amount of PNG plots including dashed lines and the pngcairo terminal was orders of magnitude slower then the old png terminal, at least on my system.

Full disclosure: I used recent LLMs (Claude Opus 4.5 and OpenAI GPT 5.2) to help with the code generation.

1 Attachments

Discussion

  • Sethur

    Sethur - 2026-01-22

    As an example of what is possible with the new dashtype support, I have attached the output of the follwing example script:

    set samples 500
    set xrange [0:4*pi]
    set yrange [-1.5:7.5]
    
    set xlabel "x"
    set ylabel "y"
    set title "Antialiased Dashed Lines Test" font ",14"
    
    set key right top box opaque
    set grid
    
    # Define line styles with different dash types and colors
    set style line 1 lc rgb "#E41A1C" lw 1 dt solid    # Red - solid
    set style line 2 lc rgb "#377EB8" lw 2 dt 2        # Blue - dashed
    set style line 3 lc rgb "#4DAF4A" lw 3 dt 3        # Green - dotted
    set style line 4 lc rgb "#984EA3" lw 4 dt 4        # Purple - dash-dot
    set style line 5 lc rgb "#FF7F00" lw 5 dt 5        # Orange - dash-dot-dot
    set style line 6 lc rgb "#A65628" lw 6 dt 1        # Brown - another solid
    
    # Also test thicker lines
    set style line 7 lc rgb "#F781BF" lw 7 dt 2        # Pink - thick dashed
    set style line 8 lc rgb "#999999" lw 8 dt 3        # Gray - thick dotted
    
    # Define the plot command as a macro for reuse
    plot_cmd = "plot sin(x)       ls 1 title 'solid (dt solid)', \
         sin(x) + 1   ls 2 title 'dt 2 (dashed)', \
         sin(x) + 2   ls 3 title 'dt 3 (dotted)', \
         sin(x) + 3   ls 4 title 'dt 4 (dash-dot)', \
         sin(x) + 4   ls 5 title 'dt 5 (dash-dot-dot)', \
         sin(x) + 5   ls 6 title 'dt 1 (solid)', \
         sin(x) + 6   ls 7 title 'dt 2 lw 4 (thick dashed)', \
         sin(x) + 7   ls 8 title 'dt 3 lw 4 (thick dotted)'"
    
    set terminal png size 1600,1200 enhanced font "Arial,10" truecolor
    set output "test_dashes.png"
    set title "Dashed Lines - PNG (libgd)" font ",14"
    eval plot_cmd
    
     
  • Sethur

    Sethur - 2026-01-26

    Are patches still considered? Should I have posted this somewhere else?

     
  • Ethan Merritt

    Ethan Merritt - 2026-01-26

    I'm traveling and will have alook ar your patch when I return next week. In the meantime, just a question. IsYour code something that could be added to the libgd library itself? If so that would benefit not just gnuplot but all other users of the library. I have contributed fixes to libgd before so I know they are receptive.
    Thanks for your contribution.

     
  • Sethur

    Sethur - 2026-01-26

    It would have been possible to add large parts of this patch directly to libgd, which indeed would have been the better solution, but gnuplot would still have to be patched to utilize the new capabilities. Additionally, the changes in how antialiasing with this patch works in general are significant and I found it quite unlikely that such a long-established library would adopt them.

    Also, I do not have a lot of experience with these kind of libraries and although I have tested this patch quite a bit and think that I have solved most issues, the code is probably still not as clean as it should be for something like libgd.

     
  • Ethan Merritt

    Ethan Merritt - 2026-02-11

    Back from vacation, looking at the patch now...
    I don't have any previous experience with AI-generated code, so I don't know how much paranoia to approach this with.

    First thoughts

    • I get a couple of compiler warnings, but those are minor and I won't bother with them now.
    • It seems like a lot of code. Maybe that's necessary.

    Major issue

    Unfortunately I don't think this implementation actually does what is needed. It fails to follow the continuous path of a line; instead it connects only line segment endpoints. Thus it only works for straight lines, not for curves. The defect is less noticeable when the line segments are short, as in your original test output.

    The comment in the patch code quoted below may be relevant

    + * To avoid visible seams ("striping") when a curve is approximated by many
    + * short polyline segments, we accumulate a dash piece across segment
    + * boundaries and render it once at the dash->gap transition. 
    

    Attached are three png files generated by the qt, cairo, and patched gd terminals using the test script below.

    set term qt size 800,500
    plot sinc(x)     lc 'blue' lw 4 dt '.-_' title "lw 4 dt '.-_'", \
         sinc(x)-0.2 lt 2 lw 2 dt '.-_' title "lt 2 lw 4 dt '.-_'
    pause -1 "please save display to file qt_verion.png"
    set term png size 800,500
    set output 'libgd_version.png'
    replot
    set term pngcairo size 800,500
    set output 'cairo_version.png'
    replot
    

    Please let me know if you think this is a fixable problem.

    Secondary Issues

    My personal preference for line styling is to use "rounded" endcaps rather than "butt" or "square". This is particularly true for dashed lines. The gd.trm code claims to support rounded/butt as a terminal option but given that it didn't support dashed lines at all, the endcap property was pretty much irrelevant. I would hope that if dashed line support is added, it will properly implement the "rounded" property. The patch code mentions rounded caps in the comments but does not actually contain code to deal with it.

     +      /* Non-TrueColor or rounded caps: use gdStyled/gdStyledBrushed */
    

    I would guess rounded caps can be added without too much work, but I don't think it is worth looking at further before the major issue is addressed first.

    Test output looks terrible if the output is not set to "truecolor". That is understandable since anti-aliasing makes no sense in that context. Probably if the truecolor attribute is not set,
    the code should fall back to solid lines only.

     
  • Sethur

    Sethur - 2026-02-12

    Thanks for checking out the patch. Your concerns about this approach of rendering the dashes as straight tangents to the curve are definitely warranted. I personally did not see the issue because when I used the patched png terminal to produce my plots (which had more rapidly changing slopes than in the example above) I was using only relatively thin lines (max lw = 3) and short dashes or dot patterns.

    For your example script, the issue was confounded because you were using a custom dash pattern, which in itself should not have been a problem, but there was another bug in the patch were these custom patterns are not scaled correctly, and thus the dashes that the modified png terminal was producing here were much too long, which made it look particularly ugly.

    Nevertheless, a proper solution must - of course - use dashes that follow the curve and are not just tangents to it. I was looking into fixing this already today, but this will require rewriting much of the approach above. I will post here again if I come up with something performant that will not require the expensive rendering approach that pngcairo uses.

     
  • Sethur

    Sethur - 2026-02-13

    Hi Ethan,

    I have put considerable effort into fixing this and just wanted to let you know that I have a working fix. It addresses all of the comments you had and it generates output that looks very similar to what is generated by pngcairo at a fraction of the computational cost. The fixed patch now:

    • Supports dashes for all truecolor capable libgd terminals, but falls back to solid lines when truecolor is turned off.
    • Supports rounded, butt and square endcaps and uses round joins at steep angles to prevent gaps in the lines. There would be the option to include support for miter or bevel joins, if you think this is needed. It seems that pngcairo currently uses miter joins one butt/square endcaps and round joins on rounded endcaps, but the rendering is so different there compared to what is done in gd.trm that the visual impression will not be 100% identical anyway.
    • Will not overdraw at the graph frame borders (pngcairo does this depending on endcap selection)

    Since I don't want to waste any more of your time by supplying another subpar patch, I will test this thoroughly before I submit it. Feel free to point me towards a collection of demo scripts that you would like to have investigated and I will include them.

    PS: You are right to be suspicious of AI generated code. I tried to use AI only in a way that makes me understand the code base faster and find the right insertion points and help with things that I would maybe have overseen regarding the interaction of different parts of the code. I can and am actually frequently using C, I was just not very versed with vector rendering, so I had to learn a few things about endcaps, join types and path rendering until I could fix all issues with the patch above.

    EDIT: Typos.

     

    Last edit: Sethur 2026-02-13
  • Sethur

    Sethur - 2026-03-06

    Hi @sfeam !

    As promised, I have now tested and benchmarked my patch thoroughly and am confident to post it here again. The main issue I had to fight with was the fact the the integer resolution used in gd.trm was not sufficient to produce properly curve-aligned thick line-pieces. I had to use a similar approach to what is done in pngcairo to get around this, namely oversampling the coordinate space to increase the resolution for rendering and AA calculations in gd.trm.

    I have tested this against pngcairo and the unmodified png terminal on both Linux (WSL Ubuntu 24.04) and Windows 11 with:

    • The custom test scripts that I have attached here together with their output PNGs
    • 639 of the script inside the demo directory that produce output that I could channel towards the patched png terminal, pngcairo and the original png terminal.

    In this patch, I have deactivated dashtype support for all libgd terminals when truecolor is not set, as suggested in your last comment. For convenience, the patch also fixes a bug in src/datablock.c that prevented compilation on Windows.

    I have also benchmarked the patch on both platforms against both pngcairo and the old png terminal and attached the results in tabular form. For the benchmarks, I used the CLI gnuplot binaries and ran them on each separate script, which of course introduces additional overhead, but the results are still useful to make sure that there is no regression in performance.

    Ironically, it turned out that the initial reason why I wanted this patch only applies to Windows since the difference in performance between png and pngcairo is much lower on Linux. On Windows, png is more than two times faster, but on Linux, it was only about 12%. Interestingly, Windows seems to be much slower in both terminals as a whole, but this might also come from higher overhead costs when spinning up the gnuplot process compared to Linux.

    Regarding AI use: For this patch, I worked excursively with Claude Opus 4.6 (Anthropic). I reviewed all code and intervened many times to arrive at the current solution. I let the AI produce a commented documentation (attached as well) of all the changes that were done and manually reviewed and edited it where necessary.

    I have also directly attached some of the custom test script results to demonstrate the changes compared to the flawed previous approach.

    Although I used AI to help me produce this patch and likely would not have had the time to do it all manually, I have spend many hours to arrive at this solution and would be happy if you consider merging this feature.

     
  • Sethur

    Sethur - 2026-03-06

    PS: I can upload the output of the demo scripts as well, but that is 174 MB. Not sure if this is the right place.

     
  • Ethan Merritt

    Ethan Merritt - 2026-03-12

    Nice test figures!

     
  • Sethur

    Sethur - 2026-03-14

    If you are interested, I can upload the output of the demo scripts for the terminals: png (with patch), png (original) and pngcairo, but it would be quite large.

     
    • Ethan Merritt

      Ethan Merritt - 2026-03-15

      No need to attach output. It's actually easier for me to test with kittycairo vs kittygd. That way I can run a test in side-by-side windows to compare the png and gd output rather than collecting and pairing up separate output files for display.

      I'll probably apply this version after going over the code to see if there's anything that can be tightened up or abstracted.

      The quality of the output is very good, with one exception that is not a deal-breaker. The algorithm used here has the imperfection that when drawing with a partially transparent line color (non-zero alpha channel), the alpha-blending is done separately for each segment of the curve rather than once for the whole curve. That means it is applied multiple times to some pixels wherever the curve crosses itself or where it is composed of overlapping shorter segments. This results in a blotchy line rather than smooth transparency. This fault exists in other terminals also (although not in cairo).
      FWIW this could be avoided by first rendering with a solid-color line onto a blank canvas and then composing that entire canvas back onto the target image with the specified alpha blend parameter.

      Thanks

       
  • Sethur

    Sethur - 2026-03-15

    My bad, I wasn't aware that one could even choose the alpha value for line colors in Gnuplot, but apparently, this was implemented quite some time ago.

    I will see if I can come up with a solution for that. Your suggestion of redering everything on a blank canvas sounds reasonable.

    As a side note: Didn't the old libgd terminal also have this issue, at least with thick lines where overlaps should have happened before when rendering the segments?

     
    • Ethan Merritt

      Ethan Merritt - 2026-03-15

      Yes there have been problems with thick lines all along. Left to itself, libgd does a really poor job with them. So gnuplot draws a thick line by filling a circular area around each point along the line. This is done by drawing the line with gdImageSetBrush() and a brush constructed to be the width requested for the line. And, as you say, this causes transparent thick lines to be way less transparent than requested because every single point leads to overlaps. So yes there is extra darkening at points where the curve crosses over itself. But unlike your dashed line algorithm, the darkening due to overlap is uniform everywhere else along the line since the overlap arises at each point along the line, not just near the segment endpoints.

      And hmm, this leads me to realize that the current code could be improved by modifying the alpha component of thick lines by making transparency proportional to the line thickness. I will try that!

       
  • Ethan Merritt

    Ethan Merritt - 2026-03-19

    I am truly impressed by the quality of the output.
    Let's go with this version.

    • Could you provide one more patch to apply on top of this to give yourself credit at the top of gd.trm for this major revision (a line or two to summary the work very briefly)?

    • Also it would be nice if you provide an overall description to use as a commit message when I add it to the git repository. I could write something myself but you obviously know better than I do how to properly describe it.

    • What should the author credit of the commit read - "Sethur sethris@users.sf.net" or something else?

    Tangential question: your patches contain a change to datablock.c that seems to revert a previous Windows/MINGW specific chunk of code to open a temp file. This chunk of code was originally copied from command.c. I'm not a Windows guy, so I have no idea whether either of these special-case code spots is still relevant or needed. Did you find this revesion necessary on your systems? Did you have to change the code in command.c also?

    I still think the gdlib library itself would benefit from this work. I encourage you to think about contributing there eventually.

     
  • Sethur

    Sethur - 7 days ago

    Hi @sfeam

    I have attached a patch to gd.trm that adds a short description. It also includes my e-mail address, but feel free to remove it or list it exclusively if this otherwise collides with the aesthetics of the comment block.

    As overall commit message, I would suggest the following, but feel free to adapt it to your preferred length. I have not included new lines in the paragraphs intentionally, so you can conveniently add them were you want them.

    gd terminal:  antialiased thick lines, dash patterns, and transparent line support
    
    Replace libgd's native thick-line rendering (which can suffers from staircasing and zebra-stripe artifacts) with a custom signed-distance-field rasterizer that supports four endcap modes (hard, butt, round, square).
    
    Add general dash pattern support (TERM_CAN_DASH) with five built-in patterns matching the cairo terminal, plus custom dash patterns. A dash walker tracks position across PNG_vector calls so dashes follow polyline curves faithfully.  Join discs fill the wedge gap at angled polyline junctions.  A deferred endcap mechanism delays the final line cap until the path actually ends.
    
    Introduce coordinate oversampling (initially set to 20×), matching pngcairo's approach so that sub-pixel precision is available for the SDF rasterizer and gnuplot's core layout calculations.  All libgd drawing calls convert inflated coordinates to pixel space at the API boundary.
    
    Semi-transparent lines are rendered onto an offscreen alpha layer at full opacity using MAX compositing (most-opaque-wins), then blended onto the main image at the target alpha.  This eliminates the overlap darkening artifacts that occur when sub-segments are alpha-blended individually.
    
    The "square" terminal option is added alongside "butt" and "rounded".
    

    Regarding the datablock.c temp file changes:

    The patch does not revert the Windows/MINGW temp file fallback, it just fixes some bugs in it. The code in command.c was already correct and did not need changes. The Windows fallback block in datablock.c (save_set_to_datablock) had copy-paste errors, probably from when it was copied from command.c. The broken code was:

    GetTempPathA(sizeof(tempname), buf);       // 'buf' does not exist in this function
    strcat(buf, "gnuplot-save.tmp");           // same — should be 'tempname'
    fp = fopen(tempname, "wt+, ccs=UTF-8");   // 'tempname' was never filled; also dubious mode string
    fp = fopen(buf, "w+");                     // second fopen silently overwrites the first
    

    The local variable in datablock.c is called tempname, but the code referenced buf (which is the variable name used in the command.c version). There were also two conflicting fopen() calls.

    The patch just fixes this by:

    1. Fixing the variable name, making all references now consistently use tempname:
    2. Adding the missing #include <windows.h> (guarded by #ifdef _WIN32), which is needed for GetTempPathA and PATH_MAX to resolve on Windows.

    The corresponding code in command.c already uses buf consistently and includes the necessary headers. It compiles and works correctly as-is, so no changes were needed there.

    I did a little research whether the tempfile fallback on Windows is still needed, and yes, apparently. tmpfile() on Windows has been broken since Vista/Windows 7. It attempts to create a file in the root of the system drive, which will fail without elevated privileges. Both fallback blocks (in datablock.c and command.c) are still needed.

     
  • Ethan Merritt

    Ethan Merritt - 7 days ago

    Got it.
    Thanks very much.

     
  • Ethan Merritt

    Ethan Merritt - 7 days ago
    • status: open --> pending-accepted
     

Log in to post a comment.

MongoDB Logo MongoDB