Ilya Zakharevich on Fri, 18 Oct 2019 12:11:50 +0200


[Date Prev] [Date Next] [Thread Prev] [Thread Next] [Date Index] [Thread Index]

Resend: [PATCH v2.11.1] High-resolution PS plotting


As Bill says, the original message was too long for the list.  I’m
resending without one of the attachments, available at

  http://ilyaz.org/software/tmp/tst-plot-new.pdf

==================== Date: Wed, 16 Oct 2019 20:09:36 -0700

After the high-quality plotting was removed from gp/PARI (around v2.0.22,
IIRC), only very rough hardcopy plots are available.¹⁾

  ¹⁾ Math::Pari comes with patches which restore this functionality, up
     to v2.3.* of GP/PARI.

The main problems are:

  • Very low resolution (worse than VGA; causes “staircases” everywhere);
  • Spurious spikes (due to very high default value of the miter-limit in
    Postscript);
  • The conflict between the visibility requiring thick lines, and
    detalization requiring very thin lines (especially visible for
    fractal-like graphs — such as the graphs of modular symbols).

The first attached graph demonstrates these defects.  It is made with

  \\ 9 is eblue, 10-16... are R,G,B,Orange,Purple,Brown,Violet;
  \\ 17-22 are parula: B/orange/yellow/purple/G/light-blue/R
  default(graphcolormap,["white", "black", "gray", "violetred", "red", "green", "blue", "gainsboro", "purple", "#0080ff", "#ed2d2e", "#008c47", "#1859a9", "#f37d22", "#662c91", "#a11d20", "#b33893", "#0072bd", "#d95319", "#edb120", "#7e2f8e", "#77ac30", "#4dbeee", "#a2142f"])
  default(graphcolors,[16,11,12,10,14])
  write("tst-plot.ps",s=plothexport("ps",X=-0.001,0.01, \
        X*[1,-1,if(X,sin(1/X),0),if(X,cos(1.1/X),0)],,5000));

The second attached graph shows how these problems are fixed by the patch
below:

  • It increases PS resolution 1000 times.  This leads to longer numbers
    written to the PS file (the size of PS file grows less than 2x).  This
    also causes a comparable increase in the size of generated PDF files.

    (I think that one could live with 200–300 taken instead of 1000 — but
     I wanted some margin to be future-proof.  The overhead in size of
     files is minuscule.)

  • It switches to round linejoins (restoring the default when drawing
    rectangles).

  • The PostScript driver causes every line in the graph to be drawn
    several times with different thicknesses.  Without zooming in, only the
    thickiest lines are visibible (so there is no visible change w.r.t. the
    old way).  However, after zooming in the hairlines become visible,
    allowing visualization of finer behavior of the plots.

    (The thickness of the extra lines and their number is controlled by
     an array in the generated PS file.  Making this array empty restores
     the old behaviour.)

    Unfortunately, this PS code is unwound in the generated PDF files, so by
    default, their size grows EXTRA 3 times.  Still, I think this is
    negligible by today’s standards — comparing to the benefits.

Enjoy,
Ilya

--- pari-2.11.1-build32/src/graph/plotport.c-pre	2018-10-31 15:38:08.000000000 -0700
+++ pari-2.11.1-build32/src/graph/plotport.c	2019-10-16 18:10:24.369566200 -0700
@@ -81,6 +81,8 @@ DTOL(double t) { return (long)(t + 0.5);
 
 static const long PS_WIDTH = 1120 - 60; /* 1400 - 60 for hi-res */
 static const long PS_HEIGH = 800 - 40; /* 1120 - 60 for hi-res */
+static const long PS_SCALE = 1000; /* Allowing 64x zoom (max on many viewers) on 500ppi display requires about 450 */
+static const char* const PS_EXTRASTROKES = "65 25"; /* Tuned to be very visible on 100ppi displays with 64x zoom */
 
 static void
 _psdraw_scale(PARI_plot *T, GEN w, GEN x, GEN y)
@@ -97,12 +99,12 @@ _psdraw(PARI_plot *T, GEN w, GEN x, GEN 
 static void
 pari_get_psplot(PARI_plot *T)
 {
-  T->width  = PS_WIDTH;
-  T->height = PS_HEIGH;
-  T->fheight= 15;
-  T->fwidth = 6;
-  T->hunit  = 5;
-  T->vunit  = 5;
+  T->width  = PS_WIDTH*PS_SCALE;
+  T->height = PS_HEIGH*PS_SCALE;
+  T->fheight= 15*PS_SCALE;
+  T->fwidth = 6*PS_SCALE;
+  T->hunit  = 5*PS_SCALE;
+  T->vunit  = 5*PS_SCALE;
   T->dwidth = 0;
   T->dheight= 0;
   T->draw = NULL;
@@ -2121,7 +2123,7 @@ static void
 ps_rect(void *data, long x, long y, long w, long h)
 {
   pari_str *S = (pari_str*)data;
-  str_printf(S,"%ld %ld m %ld %ld l %ld %ld l %ld %ld l closepath stroke\n",
+  str_printf(S,"%ld %ld m %ld %ld l %ld %ld l %ld %ld l closepath currentlinejoin 0 setlinejoin stroke setlinejoin\n",
              y,x, y,x+w, y+h,x+w, y+h,x);
 }
 
@@ -2129,7 +2131,7 @@ static void
 ps_fillrect(void *data, long x, long y, long w, long h)
 {
   pari_str *S = (pari_str*)data;
-  str_printf(S,"%ld %ld m %ld %ld l %ld %ld l %ld %ld l closepath fill\n",
+  str_printf(S,"%ld %ld m %ld %ld l %ld %ld l %ld %ld l closepath currentlinejoin 0 setlinejoin fill setlinejoin\n",
              y,x, y,x+w, y+h,x+w, y+h,x);
 }
 
@@ -2145,9 +2147,9 @@ ps_lines(void *data, long nb, struct plo
 {
   pari_str *S = (pari_str*)data;
   long i;
-  str_printf(S,"%ld %ld m\n",p[0].y,p[0].x);
-  for (i=1; i<nb; i++) str_printf(S, "%ld %ld l\n", p[i].y, p[i].x);
-  str_printf(S,"stroke\n");
+  str_printf(S,"%ld %ld mm\n",p[0].y,p[0].x);
+  for (i=1; i<nb; i++) str_printf(S, "%ld %ld ll\n", p[i].y, p[i].x);
+  str_printf(S,"mlstroke\n");
 }
 
 static void
@@ -2174,30 +2176,69 @@ rect2ps_i(GEN w, GEN x, GEN y, PARI_plot
   PARI_plot U;
   pari_str S;
   double xs = 0.65, ys = 0.65;
+  int rescale = 1;
+  const char *extra = "";
   if (T) /* res wrt T dimens */
   {
     if (plotps)
       xs = ys = 1;
     else
     {
-      xs *= ((double)PS_WIDTH) / T->width;
-      ys *= ((double)PS_HEIGH) / T->height;
+      xs *= ((double)PS_WIDTH*PS_SCALE) / T->width;
+      ys *= ((double)PS_HEIGH*PS_SCALE) / T->height;
+      rescale = PS_SCALE;
+      extra = PS_EXTRASTROKES;
     }
   }
   else
   {
     T = &U; pari_get_psplot(T);
+    rescale = PS_SCALE;
+    extra = PS_EXTRASTROKES;
   }
   str_init(&S, 1);
   /* Definitions taken from post terminal of Gnuplot. */
   str_printf(&S, "%%!\n\
 50 50 translate\n\
-/p {moveto 0 2 rlineto 2 0 rlineto 0 -2 rlineto closepath fill} def\n\
+1 %d div 1 %d div scale\n\
+%d setlinewidth\n\
+1 setlinejoin\n\
+/p {moveto 0 2 rlineto 2 0 rlineto 0 -2 rlineto closepath currentlinejoin 0 setlinejoin fill setlinejoin} def\n\
 /c0 {0 0 0 setrgbcolor} def\n\
-/c {setrgbcolor} def\n\
 /l {lineto} def\n\
-/m {moveto} def\n"
-"/Times-Roman findfont %ld scalefont setfont\n", DTOL(T->fheight * xs));
+/m {moveto} def\n\
+/postLW %% linewidths of extra passes (colors alternate: white and orig)\n\
+  [ %s ] def %% empty is OK; second width down to 15 is still comfortable\n\
+postLW length 0 gt {\n\
+  /mlN 0 def\n\
+  /mlD 20 dict def\n\
+  /lMulti {%% [coords] --> -\n\
+    dup length 2 sub 0 2 3 2 roll {%% [coords] idxCoord --> [coords]\n\
+      2 copy 2 copy get 3 1 roll 1 add get l pop } for pop} def\n\
+  %% curveArray=[[start] [[coords] [coords] ...] [color]] --> curveArray\n\
+  /replot { dup 0 get aload pop m  dup 1 get { lMulti } forall } def\n\
+  /postmlCurve {%% needColor idxCurve --> needColor\n\
+    mlD exch string get 1 index {dup 2 get aload pop setrgbcolor} if\n\
+    replot pop stroke } def\n\
+  /postmlPass {%% idxPass --> -\n\
+    dup postLW exch get setlinewidth 2 mod 0 eq dup { 1 1 1 setrgbcolor} if\n\
+    not 1 1 mlN { postmlCurve } for pop } def\n\
+  /postml { 0 1 postLW length 1 sub { postmlPass } for } def\n\
+  /C 3 array def\n\
+  /c {C astore aload pop setrgbcolor} def\n\
+  /mlstroke { ] ] [ C aload pop ] 3 array astore /mlN mlN 1 add def\n\
+     mlN string exch mlD begin store end stroke} def\n\
+  /ll {2 copy lineto /ptN ptN 1 add def ptN 5 ge { /ptN 0 def ] [ } if} def\n\
+  /mm {2 copy 2 array astore 3 1 roll moveto /ptN 0 def [ [ } def\n\
+} {\n\
+  /c {setrgbcolor} def\n\
+  /ll {lineto} def\n\
+  /mm {moveto} def\n\
+  /mlstroke {stroke} def\n\
+  /postml {} def\n\
+} ifelse\n"
+"/Times-Roman findfont %ld scalefont setfont\n",
+     rescale, rescale, rescale, extra, DTOL(T->fheight * xs));
 
   pl.sc = &ps_sc;
   pl.pt = &ps_point;
@@ -2212,7 +2253,7 @@ rect2ps_i(GEN w, GEN x, GEN y, PARI_plot
 
   if (plotps) str_printf(&S,"0 %ld translate -90 rotate\n", T->height - 50);
   gen_draw(&pl, w, x, y, xs, ys);
-  str_puts(&S,"stroke showpage\n");
+  str_puts(&S,"stroke postml showpage\n");
   *S.cur = 0; return S.string;
 }
 char *


----- End forwarded message -----


----- End forwarded message -----

Attachment: tst-plot.pdf
Description: Adobe PDF document