line_drawing.ps


NAME

      line_drawing.ps - Line Drawing in PostScript


SYNOPSIS

 %!PS-Adobe-2.0
 %%EndComments
 %%BeginProlog
 (/home/wherever/ps/lib/line_drawing.ps) run
 (/home/wherever/ps/lib/colours.ps) run
 %%EndProlog
 %%Page: 1 1
 /brow { 67 mm 200 mm } def   % define some Points in x,y
 /nose { 34 mm 100 mm } def
 /ear { 124 mm 145 mm } def
 /nose2brow { 10 20 60 1 /bridge 15 -65 15 50 60 5 } def   % define the Curves
 /brow2ear  { 10 20 60 1 /bridge 15 -45 } def
 /eye brow nose .4 brow .4 ear .2 interpolate3 pointdef
 /darkskin paleorange .4 brown .6 rgbmix rgbdef  % mix some Colours
 newpath nosetip moveto  % run the Curves ...
 [ nose2brow ] brow longcurveto [ brow2ear ] ear longcurveto
 % make closedpaths round areas; clip, fill, fuzzyrgbstroke & fuzzyfill
 closepath darkskin setrgbcolor fill
 % run the Curves again, and this time stroke or fuzzyrgbstroke them
 black setrgbcolor
 newpath nose moveto [ nose2brow ] brow longcurveto stroke
 showpage
 %%EOF

DESCRIPTION

This module implements various routines to do in PostScript what an artist does when sketching with, say, pencil and paper.

The artist thinks something like "well, starting at this point, first there's about this distance of that curvature to the left, then about that distance of a much tighter curvature to the left, then a much longer listance of this fairly gentle curve to the right, etc, etc, ending up finally at that point."   The procedure longcurveto implements that process in PostScript.

Using line_drawing.ps is of course much slower than sketching with pencil and paper, it's closer to some of the older print-making techniques like mezzotint (as used by Escher) offering complete control of greyscale, extremely fine detail, and perfect reproducibility.

Newer additions fractal_interpolate and points2curve add important functionality.


TUTORIAL

Hints for drawing, in nine easy steps (-;

  1. at each junction of 3 colours, put a Point (eg "nosetip", "brow")
  2. at each boundary of 2 colours, put a Curve (eg "nosetip2brow")
  3. define at least 3 Points in x,y (eg "/brow { 67 mm 200 mm } def")
  4. define the Curves (eg "/nosetip2brow { 10 20 60 1 /bridge 15 -45 } def")
  5. define any remaining Points by interpolate2, interpolate3 and pointadd
  6. define any Colours ("/darkskin paleorange .4 brown .6 rgbmix rgbdef")
  7. run all the Curves and interpolations to calculate all Point positions (eg "nosetip moveto [ nosetip2brow ] brow longcurveto")
  8. make closedpaths round areas; clip, fill, fuzzyrgbstroke ∓ fuzzyfill in them
  9. run the Curves again, and this time stroke or fuzzyrgbstroke them
As examples, see sample1.ps.txt and sample2.ps.txt and sample3.ps.txt . . . Save the file to your local disc, rename it to sample1.ps or sample2.ps or sample3.ps and edit it so that the run statement(s) near the beginning point to the directory where line_drawing.ps and colours.ps are installed on your system. Then use GhostView or equivalent to view it.

If you wish to print it out, feel free to use something like include_run to roll the run files in.

For easy viewing, PDF versions of the results are in sample1.pdf and sample2.pdf and sample3.pdf . . .


PROCEDURES

    longcurveto,   longcurve,   reversecurve,   points2curve,
    pointdef,   pointadd,   pointdup,   interpolate2,   interpolate3,
    fractal_interpolate,   fuzzygraystroke,   fuzzygrayfill,   fuzzyrgbstroke,   fuzzyrgbfill.

[ l c l c (pushxy) l c /apoint l c ]   xto yto   longcurveto

Where the arguments are:
l is the nominal length of the segment in mm (will be scaled !)
c is the rightwards curvature of the segment in Radians/Metre (ditto)
pushxy causes the x,y at that point to be left on the stack; afterwards it can be used for example by moveto
/apoint any nametype (e.g. /thispoint) causes that name to be defined, as in a pointdef
xto, yto cause the curve to be scaled and rotated to end at position xto, yto

longcurveto starts at the current point. Firstly, without affecting the currentpath, it draws the curve as specified, i.e. this distance at this curvature, that distance at that curvature, and so on; it then notes the point at which it ends up, and scales and rotates so as to transform this into the desired endpoint xto yto. Then, secondly, it redraws the curve, adding it to the currentpath, and defining points such as /apoint as it goes. It leaves the currentpoint at xto yto.

longcurveto, longcurve and reversecurve can, unfortunately, not be used to create a userpath; they append to the currentpath. See the PostScript Language Reference Manual for more about userpaths.

angle [ l c l c l c (pushxy) l c l c ]   longcurve

Where the arguments are:
the curve starts at angle angle from the current point
l is the length of the segment in mm
c is the rightwards curvature of the segment in Radians per Metre
pushxy causes the x,y at that point to be left on the stack afterwards
any nametype (e.g. /thispoint) causes it to be defined as in a pointdef

[ l1 c1 l2 c2 /name l3 c3 ]   reversecurve

The above invocation leaves [ l3 -c3 /name l2 -c2 l1 -l1 ] on the stack. When a curve (as used by longcurveto) has been defined, it is often useful to be able to use it in either direction; e.g. once in outlining the area to one side of it, and once in outlining the area to the other side. reversecurve offers an easy way to reverse a longcurveto curve.

[x1 y1 x2 y2 ... xN yN]   points2curve
[x1 y1 ... xN yN]   initialangle finalangle   points2curve

points2curve constructs a smooth path through an array of points.   It can be called in two different ways: if called with just the array of points then it makes up its own starting and ending angles;   or you may specify those two angles after the array of points, which you must do, eg: for drawing closed curves that join up smoothly.
This procedure makes it easy to copy a smooth curve from a .jpg or .png, because you just note the pixel-positions at a few places along the curve and let the algorithm do the rest. If it's not right yet, insert an extra pixel-position into your list.
In the first of these examples, explicit angles are needed so the start and end of the closed curve get the same gradient:
  [ 220 50 100 100 100 200 200 250 300 210 320 100 220 50 ] 170 170
    points2curve stroke
  [ 400 50 380 100 430 200 360 300 450 400 380 500 410 600 400 700 ] 120 90
    points2curve stroke
  [ 250 300 240 400 260 390 250 600 ]
    points2curve stroke

/name_of_point   x y   pointdef

The above invocation leaves name_of_point defined as { x y } so it's like a two-dimensional version of def. For example,
    /point_name currentpoint pointdef
Note that because a point has two components, the common /x exch def idiom doesn't work with pointdef; you'll need something like this instead:
    /mynewpoint 3 1 roll pointdef

x1 y1   x2 y2   pointadd

The above invocation leaves x1_plus_x2 y1_plus_y2 on the stack, and is often useful to place a second point at a known increment away from a first. It's like a two-dimensional version of add

x y   pointdup

The above invocation leaves x y x y on the stack, and is handy if a point needs to be used now but also left on the stack to come back to later. It's like a two-dimensional version of dup

x1 y1 weight1   x2 y2 weight2   interpolate2

The above invocation leaves on the stack, ready for moveto or pointdef, the x y of a point which is on the straight line between x1 y1 and x2 y2. If weight1 and weight2 are both 0.5 then the interpolated point will be half way, but if weight1 is 0.9 and weight2 is 0.1 then the interpolated point will be much closer to x1 y1.

The weights don't have to add up to one, they get normalised to total 1.0 automatically.

x1 y1 weight1   x2 y2 weight2   x3 y3 weight3   interpolate3

The above invocation leaves on the stack, ready for moveto or pointdef, the x y of a point which is in the area between x1 y1 and x2 y2 and x3 y3. If weight1 and weight2 and weight3 are all 0.333 then the interpolated point will be in the middle, but if weight1 is 0.7 and weight2 is 0.2 and weight2 is 0.1 then the interpolated point will be much closer to x1 y1.

The weights don't have to add up to one, they get normalised to total 1.0 automatically.

x_array y_array d_array   { lineto }   n_iters   fractal_interpolate

fractal_interpolate is useful for drawing mountain ranges, forest horizons, and, if using r, theta coordinates, for drawing all sorts of irregular (but possibly symmetrical) organic shapes such as leaves, butterflies, animal-skins, and so on.
See: Chapter 6, Michael Barnsley "Fractals Everywhere", Academic Press 1988.
x_array and y_array   are arrays containing the series of points. These two arrays must have equal lengths.
d_array   is an array of the vertical-scaling-factors (see p214-215) to be applied to the invervals between points. Because each vertical-scaling-factor describes the scaling between two points, this array must be one shorter than x_array and y_array.
{ lineto }   is the interpolation-function, which will be invoked with the next point x y on the stack. Therefore this function must eat its top two stack-items.
n_iters   is the number of iterations. CPU usage grows exponentially with n_iters, so be careful.

In the following example, the x value actually represents the angle theta and the y value represents the radius, so the interpolation-function calculates y*cos(theta) and y*sin(theta) to actually draw the line:
  /xmid currentpagedevice (PageSize) get 0 get .5 mul def
  /ymid currentpagedevice (PageSize) get 1 get .5 mul def
  xmid ymid translate xmid .2 mul ymid .2 mul scale
  /x_array [ 0 90 180 270 360 ] def   % five points
  /y_array [ 1 2 3 2 1 ] def
  /d_array [ -0.2 0.35 0.35 -0.2 ] def  % four gaps
  newpath 0 -1 moveto .10 setlinewidth
  x_array y_array d_array { % x y; we need -y.cos(x) -y.sin(x)
    1 index sin 1 index mul -1.0 mul
    3 1 roll exch cos mul -1.0 mul lineto
  } 6 fractal_interpolate
  stroke

innergray outgray   inwidth outwidth   fuzzygraystroke

The above invocation strokes a fuzzy grey line along the currentpath. At its centre, the line will be of a innergray greyness, and this shade will be of width inwidth. At its outer fuzzy edges, which will be of width outwidth, the line will be of a outgray greyness; so that will usually be chosen to be the same shade as the background. This subroutine is useful for shading.

innergray outergray   fuzzwidth   fuzzygrayfill

The above invocation fills the currentpath with innergray. Outside the currentpath, the greyness fades, over the width fuzzwidth, into an outgray greyness; so that will usually be chosen to be the same shade as the background. This subroutine is also useful for shading.

fuzzystroke

This is a deprecated synonym for fuzzyrgbstroke.

incolour outcolour   inwidth outwidth   fuzzyrgbstroke

The above invocation strokes a fuzzy coloured line along the currentpath. At its centre, the line will be of a incolour rgb colour, and this shade will be of width inwidth. At its outer fuzzy edges, which will be of width outwidth, the line will be of a outcolour colour; so that will usually be chosen to be the same colour as the background. This subroutine is useful for shading in colour. See also colours.ps.

innercolour outercolour   fuzzwidth   fuzzyrgbfill

The above invocation fills the currentpath with innercolour. Outside the currentpath, this colour shades, over the width fuzzwidth, into outcolour; so that will usually be chosen to be the same colour as the background. This subroutine is also useful for shading in colour. See also colours.ps.


INSTALL

To install: go to www.pjb.com.au/comp/free/line_drawing.ps.txt and save the file to your local disc.
Rename it to line_drawing.ps and move it into some appropriate directory such as ~/ps/lib . . .
Or, first change directory to where you keep your PostScript libraries:
    cd /home/wherever/ps/lib/
(or wherever)   and then either:
    wget -O line_drawing.ps http://www.pjb.com.au/comp/free/line_drawing.ps.txt
or:
    curl http://www.pjb.com.au/comp/free/line_drawing.ps.txt -o line_drawing.ps

Or, get it from github:
    git clone https://github.com/peterbillam/postscriptlib


AUTHOR

    Peter J Billam   www.pjb.com.au/comp/contact.html


CHANGES

 20160828 revise fuzzygraystroke and fuzzygrayfill
 20160827 scope interpolate2 and interpolate3 properly
 20160826 /points2curve starts with a lineto if an open subpath exists
 20160825 /points2curve starts with a lineto if currentpoint exists
 20160817 final_angle in /points2curve calculated like initial_angle
 20160816 probably fixed a bug in /points2curve
 20160815 add /points2curve
 20160813 add /fractal_interpolate
 20031122 in longcurve, prevent div by 0 if curvature=0
 20030313 roll some stuff out to colours.ps
 20010116 add subroutine pointdup
 20010108 move gray0..gray9 defs in
 20001224 add interpolate2
 20001221 /name first in pointdef and rgbdef
 20001218 rgb is now the default, not gray
 20001216 add reversecurve
 20001214 fuzzyrgbfill fuzzyrgbstroke and colours.ps
 20001212 copyright notice
 20001212 /names allowed in longcurve arrays
 20001211 fixed various bugs in pointdef
 20001211 fuzzystroke, fuzzyfill, longcurveto
 20001209 more consistent calling syntax
 20001208 Line-Drawing PS Library (smooth+longcurve)

SEE ALSO


Back to P J B Computing or to www.pjb.com.au . . .