Here is a more interesting example along the lines of exchanging bytes at wholesale volumes:
- Create a temporary file name in G’MIC,
- Pass it as an argument to the python script that is run by
exec
- Python creates the file with the file name given by G’MIC , fills it with data, then closes it.
- Following
exec
G’MIC checks if a file with the temporary file name actually exists. - 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.
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.