line_drawing.ps - Line Drawing in PostScript
%!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
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.
Hints for drawing, in nine easy steps (-;
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 . . .
longcurveto,
longcurve,
reversecurve,
points2curve,
pointdef,
pointadd,
pointdup,
interpolate2,
interpolate3,
fractal_interpolate,
fuzzygraystroke,
fuzzygrayfill,
fuzzyrgbstroke,
fuzzyrgbfill.
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
userpath
s.
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
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.
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
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
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
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
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.
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.
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
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.
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.
This is a deprecated synonym for 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.
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.
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 gitlab:
git clone
https://gitlab.com/peterbillam/postscriptlib
Peter J Billam www.pjb.com.au/comp/contact.html
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)
Back to P J B Computing or to www.pjb.com.au . . .