G'MIC exercises

Here is a more interesting example along the lines of exchanging bytes at wholesale volumes:

  1. Create a temporary file name in G’MIC,
  2. Pass it as an argument to the python script that is run by exec
  3. Python creates the file with the file name given by G’MIC , fills it with data, then closes it.
  4. Following exec G’MIC checks if a file with the temporary file name actually exists.
  5. If it does, G’MIC opens it – here, as a 2 × n comma separated values file and runs with it.

The use case is giving G’MIC access to Structured Vector Graphics data through a delegate, a Python script which has access to svgpathtools, a pretty mature and robust module for accessing SVG files. The motivation for reaching out to Python is not performance, but accessing functionality — right now! — without re-inventing that particular wheel in G’MIC.

Here’s the Python delegate, svghelper.py:

svghelper.py
 #! /usr/bin/python

import argparse
import svgpathtools
import sys

RESOL = 10

def normalize(dest) :
    """deset: Scale the dataset so that the greatest translation
       goes to 1.0; center of gravity goes to origin."""
    # Center of gravity
    xc     = yc = 0.0
    dl     = len(dset)
    min_r  = sys.maxsize
    max_r  = -sys.maxsize
    min_i  = sys.maxsize
    max_i  = -sys.maxsize
    
    for k in range(0, dl, 1) :
        xc += dset[k].real
        yc += dset[k].imag
        if min_r > dset[k].real :
            min_r = dset[k].real
        if max_r < dset[k].real :
            max_r = dset[k].real
        if min_i > dset[k].imag :
            min_i = dset[k].imag
        if max_i < dset[k].imag :
            max_i = dset[k].imag
    cen  = complex(xc/dl, yc/dl)
    ur   = complex(max_r, max_i)
    ll   = complex(min_r, min_i)
    maxd = abs(ur - ll)
    for k in range(0, dl, 1) :
        dset[k]     = (dset[k] - cen)/maxd 

if __name__ == '__main__':

    parser = argparse.ArgumentParser(description='Plot points from SVG Path.')
    parser.add_argument('-d', '--density', type=int, action='store',  help='Plot point quantity as powers of two; 4 → 2⁴ → 16. Larger → denser. Defaults to 10 (1024)', default=RESOL)
    parser.add_argument('-o', '--output',  type=str, action='store',  help='Text file for output plots.')
    parser.add_argument('-n', '--normalize',         action='store_const', const='y', default=None, help='Normalize plots. (1) Center of gravity maps to origin. (2) Scaling of largest distance to 1, (3) Preserves aspect ratio.')
    parser.add_argument('fname', type=str, nargs=1, help='SVG file, 1.1 or lower')
    args=parser.parse_args()
    fresolv      = 2**int(args.density)
    paths, attrs = svgpathtools.svg2paths(args.fname[0])
    path_0       = paths[0]
    scale        = 1.0/(fresolv-1)
    dset         = [path_0.point(k).conjugate() for k in [scale*j for j in range(fresolv)]]
    if type(args.normalize) == type('y') and args.normalize == 'y':
        normalize(dset)
    fd           = open(args.output, 'w', encoding='UTF-8')
    for i in range(len(dset)):
        fd.write("{0:03.9g},{1:03.9g}\n".format(dset[i].real, dset[i].imag))
    fd.close()
args.output[0]

This G’MIC module uses svghelper.py to read an SVG file and compute plots. Coupling between G’MIC and Python is very loose — they exchange a temporary file and have no idea or concern how the other operates. G’MIC’s exec runs Python as a child process and rendezvous on the temporary file. To the extent that there is coordination between the two, it’s the agreement that the rendezvous file follows a text-based, comma separated value format. G’MIC has a dedicated reader for .csv files.

svg2plots.gmic
#@cli gtutor_getsvg : "file",_resolution
#@cli (1) Retrieve the first path in the SVG 1.1 $file
#@cli argument and render it (2) at the indicated
#@cli resolution, a power of 2: $_resolution = 8 → 2⁸ → 256.
#@cli Resolution defaults to 10; paths are plotted
#@cli with 1,204 points.
gtutor_getsvg:
   check "isfile('${1}') && isint(${2=10}) && ${2}>0"

   # Python helper writes plots to a text file.
   # We furnish a temporary file to receive its
   # output.
   
      file_rand
      outfile=${}
      fname=$1
      res=$2
   
      # Put "svghelper.py" in a folder on your path.
      # Mark it executable.
      # See svghelper.py for details.
   
       helpercommand="svghelper.py                \
                      --normalize                 \
                      --density "$res"            \
                      --output "$outfile" "$fname
    
       exec $helpercommand
       if !isfile('$outfile')
          error "Could not process "$outfile"!"
       fi
       input_csv $outfile,0
       delete $outfile


#@cli gtutor_plot : [plotdata]
#@cli Draw on the selected image the polygon plotted on an
#@cli origin-centered unit square with the diagonal
#@cli [-1,-1]…[1,1].
gtutor_plot :
      is_img=${"is_image_arg $1"}
      if ${is_img}
         pass$1
         => canvas,plotdata
      else
         error "Provide image parameter."
      fi
      sz={0.9*min(w#$canvas,h#$canvas)}
      ox={(w#$canvas-$sz)/2}
      oy={(h#$canvas-$sz)/2}
      permute[plotdata] cyzx
      fill[plotdata] "begin(
                        sw=get('sz');
                        ox=get('ox');
                        oy=get('oy');
                        id=eye(3);
                        id[0]=sw;
                        id[2]=ox+sw/2;
                        id[4]=-sw;
                        id[5]=sw/2+oy;
                       );
                  (id*[I(x,y),1])[0,2]
                 "
      permute[plotdata] cyzx
      eval[canvas] "begin(PV=crop(#$plotdata);
                    polygon(#$canvas,
                            -int(size(PV)/2),
                            PV,
                            1,
                            0xffffffff,
                            255
                           )
                         )
                   "
      keep[canvas]

Here’s a typical command line:

gmic svg2plots.gmic gtutor_getsvg "ampersand_circ.svg",10 1024,1024,1,1 gtutor_plot. [-2] keep[-1]

Here’s a SVG play file — yes, the dreaded Goudy Bookletter 1911 ampersand, which I will never cease afflicting on people.

ampersand_circ

That’s it for now. There’s a half-baked tutorial on this Python - G`MIC exchange on the skids, coming down the ways I-don’t-know-when. Tomorrow looks quiet. Might get some writing done.

2 Likes