[comp.sources.misc] v09i050: popi, The Digital Darkroom, Part04/09

richb@sunaus.sun.oz.AU (Rich Burridge) (12/13/89)

Posting-number: Volume 9, Issue 50
Submitted-by: Rich Burridge <richb@sunaus.sun.oz.AU>
Archive-name: popi/part04

---- Cut Here and unpack ----
#!/bin/sh
# this is part 4 of a multipart archive
# do not concatenate these parts, unpack them in order with /bin/sh
# file run.c continued
#
CurArch=4
if test ! -r s2_seq_.tmp
then echo "Please unpack part 1 first!"
     exit 1; fi
( read Scheck
  if test "$Scheck" != $CurArch
  then echo "Please unpack part $Scheck next!"
       exit 1;
  else exit 0; fi
) < s2_seq_.tmp || exit 1
echo "x - Continuing file run.c"
sed 's/^X//' << 'Funky_Stuff' >> run.c
X	    ++rp;
X	}
X
X#if	SEQPAR
X	if (m_myid == 0)
X#endif	/* SEQPAR */
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X
X#if 0
X    if (nrange != 0)
X	FPRINTF(stderr,
X	    "Total of %d range errors corrected\n",
X	    nrange);
X#endif
X}
X
Xvoid
Xrun()
X{
X    int	y;
X#if	SEQPAR
X    static int	parked = 0;
X
X    if (parked)
X	m_rele_procs();
X    m_fork(prun);
X    m_kill_procs();	/* For some reason, parking doesn't work properly ...
X    m_park_procs();
X    parked = 1;		/* so it can be released above */
X#else	/* ! SEQPAR */
X    prun();
X#endif	/* ! SEQPAR */
X
X    disp_percentdone(100);	/* Can rely on '100' to terminate */
X
X    if (disp_active)
X    {
X	disp_imgstart();
X	for (y = 0; y < Ysize; ++y)
X	    disp_putline(src[CURNEW].pix[y], y);
X	disp_imgend();
X    }
X
X    SwapOldNew();
X}
Funky_Stuff
echo "File run.c is complete"
chmod 0444 run.c || echo "restore of run.c fails"
set `wc -c run.c`;Sum=$1
if test "$Sum" != "8692"
then echo original size 8692, current size $Sum;fi
echo "x - extracting popi.prj (Text)"
sed 's/^X//' << 'Funky_Stuff' > popi.prj &&
X
X
XEXPR
XIBMPC
XIO
XLEX
XMAIN
XPOLAR
XRUN
XSPECIAL
Xgraphics.lib
Funky_Stuff
chmod 0444 popi.prj || echo "restore of popi.prj fails"
set `wc -c popi.prj`;Sum=$1
if test "$Sum" != "56"
then echo original size 56, current size $Sum;fi
echo "x - extracting powerc.prj (Text)"
sed 's/^X//' << 'Funky_Stuff' > powerc.prj &&
X# Project file for Mix Software's Power C Compiler
X# Compile with "pc powerc.prj"
Xmain	popi.h main.c
Xexpr	popi.h expr.c
Xio	popi.h io.c
Xlex	popi.h lex.c
Xpolar	popi.h polar.c
Xrun	popi.h run.c
Xspecial	popi.h special.c
Xibmpc	popi.h ibmpc.c
Xpopi.exe	main expr io lex polar run special ibmpc
Funky_Stuff
chmod 0444 powerc.prj || echo "restore of powerc.prj fails"
set `wc -c powerc.prj`;Sum=$1
if test "$Sum" != "286"
then echo original size 286, current size $Sum;fi
echo "x - extracting popi.1 (Text)"
sed 's/^X//' << 'Funky_Stuff' > popi.1 &&
X.\" @(#)popi.1 1.8 89/12/11
X.\"OPTIONS man
X.TH POPI 1L "12 December 1989"
X.tr @"
X.\"These macros always explicitly return to font 1, rather than the
X.\"previous font, because previous may have been altered.
X.de C
X.\" Start constant width
X.sp 0.5
X.nf
X.po +2c
X.ft CW
X..
X.\" End constant width
X.de E
X.sp 0.5
X.po
X.ft 1
X.fi
X..
X.de CW
X\&\f(CW\\$1\&\f1\\$2\&
X..
X.ie t .ds Mu \(mu
X.el .ds Mu x
X.ie t .ds +- \(+-
X.el .ds +- +-
X.\"-------------------------------------------------------------------------
X.SH NAME
Xpopi \- perform interactive digital image transformations
X.SH SYNOPSIS
X.B "popi [option] ... [file] ..."
X.SH DESCRIPTION
X.I Popi
Xis a program which allows arbitrary transformations to be
Xinteractively applied to digital images.
XIt is based on the program described in "Beyond Photography
X\- The Digital Darkroom" by Gerald J. Holzmann.
X.P
XThe meanings of the available command line options are as follows:
X.TP
X.BI \-V
XPrint program version identification message and exit.
X.TP
X.BI \-x Width
XSet the image width (number of pixels per scanline) to
X.I Width .
X.TP
X.BI \-y Height
XSet the image height (number of scanlines) to
X.I Height .
X.TP
X.BI \-v+
XSet the verbose flag on.
XBe chatty about a number of things.
X.TP
X.BI \-v\-
XTurn the verbose flag off.
XBe silent about everything except error messages.
X.TP
X.BI \-a+
XTurn auto-display on.
XAfter each image transformation, the image will be displayed.
XEquivalent to the
X.CW "#display +"
Xcommand.
X.TP
X.BI \-a\-
XTurn auto-display off.
XEquivalent to the
X.CW "#display \-"
Xcommand.
XImages can still be displayed explicitly with the
X.CW "#display \f2image\fP"
Xcommand.
X.TP
X.BI \-p [ncpus]
XOn a Sequent multiprocessor, use the specified number of cpus
Xto perform the transform.
XIf this option is given without a number, the maximum number
Xavailable is used.
XThis option has no effect on uniprocessor computers.
X.TP
X.BI \-l [logfile]
XEnables logging of all input, error messages, and some
Xresponses.
XLogging is done on the named file, or the file "popi.log" if no
Xname is given.
XThe
X.CW "#logfile"
Xcommand may also be used to turn logging on and off.
X.P
XAn image transform is described by a transform statement.
XThe statement basically takes the form
X.C
X\f2dest\fP = \f2expression\fP
X.E
Xwhere
X.I dest
Xindicates the new image and
X.I expression
Xdescribes a transformation.
XThis statement is executed over the range of image
Xcoordinates.
XFor a 512 \*(Mu 512 image, it will be executed 262144 times.
XAn image is normally referenced in a statement by its name
Xand an index.
XA cartesian index is specified by using the x and y
Xcoordinates in square brackets after the image name.
XThus
X.CW dmr[0,0]
Xis the top left pixel in the image named
X.CW dmr .
XPolar coordinates may be specified by giving a radius and
Xangle (in degrees) in curly brackets.
XThus
X.CW dmr{0,0}
Xrefers to the centre pixel in the
Ximage.
XThe symbols
X.CW x
Xand
X.CW y
Xmay appear in the transform statement.
XThese take on values corresponding to a different pixel each
Xtime the transform is executed.
XInternally the algorithm is as follows
X.sp 0.5
X.nf
Xfor y varying from 0 to Y
X    for x varying from 0 to X
X        execute transform statement
X.sp 0.5
X.fi
Xwhere
X.CW Y
Xis the maximum y coordinate and
X.CW X
Xis the maximum x coordinate.
XThe image size can be set on the command line as described
Xlater.
X.P
XThe value of a pixel varies from 0 to 255 in the current
Ximplementation.
XAny value outside this range is treated as modulo 256 when
Xbeing stored in an image (ie. it wraps around).
XLow numeric values represent dark colours, while large values
Xrepresent light colours.
XA simple transform statement to set an image to completely
Xblack would be
X.C
Xnew[x,y] = 0        ; set image to black
X.E
XThe name
X.CW new
Xis one of the two default images always maintained internally.
XThe other is
X.CW old .
XThe images referred to by these names are swapped after each
Xoperation, so a succession of transforms may modify the "old"
Ximage to produce a "new" image.
XThus the statement
X.C
Xnew[x,y] = old[x,y] / 2      ; darken image
X.E
Xwill reduce all the pixel intensity values by a half,
Xdarkening the existing image.
XThe semicolon (`;') character introduces a comment.
XAnything from this to the end of the line is ignored.
X.P
XAll the standard arithmetic, relational and boolean operators
Xfrom the C language are available, with precedence generally
Xas per the C language.
XSome additional operators, predefined variables and math
Xfunctions are also available.
X.P
XHere is a listing of the available symbols and their
Xmeanings.
X.TP
X\f2name\fP
XRefers to an image name.
XIf not followed by index brackets, the index
X.CW "[x,y]"
Xis assumed.
XThe currently available names are those shown by the
X.CW #list
Xdisplay, and also
X.CW old
Xand
X.CW new .
X.TP
X.CW $\f2n\fP
XWhere \f2n\fP is a digit, this is an alternative method of
Xreferring to an image.
XIn fact this may be the only method if the image name contains
Xpunctuation characters.
XThe
X.CW #list
Xcommand shows the correspondence between image names and this
Xform of reference.
X.TP
X.CW X
XThe maximum x coordinate value.
XThis is one less than the number of pixels per scanline
X(image width), which may be set with the
X.B \-x
Xcommand line option.
X.TP
X.CW Y
XThe maximum y coordinate value.
XThis is one less than the number of scanlines (the height of
Xthe image), which may be set with the
X.B \-y
Xcommand line option.
X.TP
X.CW x
XThe current cartesian x coordinate.
XVaries from 0 (left of image) to 
X.CW X
X(right of image).
X.TP
X.CW y
XThe current cartesian y coordinate.
XVaries from 0 (top of image) to
X.CW Y
X(bottom of image).
X.TP
X.CW r
XThe radius component of the polar coordinate which corresponds to the
Xcurrent cartesian (x,y) coordinate.
XThis is equivalent to
X.CW "sqrt(x*x + y*y)"
Xwith a translation of the origin.
X.TP
X.CW a
XThe angle component of the polar coordinate which corresponds to the
Xcurrent cartesian (x,y) coordinate.
XThis is equivalent to (and a shorthand for)
X.CW "atan(y/x)" .
X.sp
XNote that the first time in a popi session you use an expression with either
X.CW r
Xor
X.CW a
Xin it, you may notice an extra delay before the image
Xtransformation begins.
XThis is due to precalculation of an array of polar coordinates
Xthat happens the first time you use one.
X.TP
X.CW R
XThe maximum radius value of a polar coordinate.
X.TP
X.CW A
XThe maximum angle value of a polar coordinate (this is just 360).
X.TP
X.CW Z
XThe maximum intensity value of a pixel (corresponding to white).
XCurrently, this is always 255.
X.TP
X.CW **
XThe exponentiation (raise to the power) operator.
XThe expression
X.CW "x ** 2"
Xmeans x squared (x * x).
X.TP
X.CW *
XThe multiplication operator.
XThis has a higher precedence than division to avoid underflow
Xin expressions with multiplication and division, since normally
Xexpressions are evaluated as integers.
X.TP
X.CW /
XInteger division.
XIf the divisor is 0, the result is set to Z.
X.TP
X.CW %
XModulo.
XIf the divisor (right hand operand) is 0, the result is set to 0.
X.TP
X.CW "+"
XAddition.
X.TP
X.CW "\-"
XSubtraction.
XThis may be a unary or a binary operator.
X.TP
X.CW "<<"
XLeft shift.
XThe left operand is shifted left the number of bits specified by the
Xright operand.
X.TP
X.CW ">>"
XRight shift.
XThe left operand is shifted right the number of bits specified by the
Xright operand.
X.TP
X.CW ">"
XGreater than.
XAs with all the relational operators, if the specified relation between
Xthe operands is true, the result is 1, otherwise the result is 0.
X.TP
X.CW "<"
XLess than.
X.TP
X.CW ">="
XGreater than or equal to.
X.TP
X.CW "<="
XLess than or equal to.
X.TP
X.CW "=="
XTest for equality.
X.TP
X.CW "!="
XNot equal to.
X.TP
X.CW "&"
XBitwise (arithmetic) AND operator.
X.TP
X.CW "^"
XBitwise exclusive OR (XOR) operator.
X.TP
X.CW "|"
XBitwise (arithmetic) OR operator.
X.TP
X.CW "&&"
XLogical AND operator.
XThe result is 1 if both of the operands are true (non-zero);
Xotherwise the result is 0.
XUnlike the && operator of the C language, this operator is currently not
Xconditional.
XBoth operands are always evaluated.
X.TP
X.CW "||"
XLogical OR operator.
XThe result is 1 if both either (or both) of the operands are true
X(non-zero); otherwise the result is 0.
XUnlike the || operator of the C language, this operator is currently not
Xconditional.
XBoth operands are always evaluated.
X.TP
X.CW sin(\f2expr\fP)
XThis gives the value of the trigonometric sine of the expression,
Xmultiplied by Z.
XThis is required because all operations are performed with integers.
X.TP
X.CW cos(\f2expr\fP)
XThis gives the value of the trigonometric cosine of the expression,
Xmultiplied by Z.
XThis is required because all operations are performed with integers.
X.TP
X.CW atan(\f2y-expr\fP,\f2x-expr\fP)
XThis returns the trigonometric arctangent (inverse tangent) of
X.I y-expr/x-expr
Xin degrees (ie the value returned will be between 0 and 360).
X.TP
X.CW log(\f2expr\fP)
XReturns the natural logarithm of the expression.
X.TP
X.CW sqrt(\f2expr\fP)
XReturns the square root of the expression.
X.TP
X.CW rand()
XReturns a positive random number.
XThis will usually be used with the modulo or bitwise AND
Xoperator to restrict the range of the result.
XFor example,
X.C
Xnew[x,y] = old[x,y] + rand() % 20 \- 10
X.E
Xwill adjust the brightness levels of each pixel in the old image by a
Xrandom amount (up to 10 levels brighter or darker).
X.\".TP
X.\".CW rand(\f2expr\fP)
X.\"Returns a positive random number between 0 and the value of
X.\"the parameter.
X.\"This is provided as a more efficient alternative to
X.\".CW "rand() % \f2expr\fP"
X.\"The previous example could thus be rewritten as:
X.\".C
X.\"new[x,y] = old[x,y] + rand(20) \- 10
X.\".E
X.P
XAs well as allowing transformation statements, a number of special
Xcommands, each starting with a
X.CW #
Xcharacter, are allowed.
XThese are described here.
X.TP
X.CW "#read @\f2filename\fP@ \f1[\fP\f2image-name\fP\f1]\fP"
XRead a raw data file into the named image.
XIf no image name is specified, the name is derived from the base name
Xcomponent of the filename.
XIf the name already exists, that image is overwritten in memory,
Xotherwise a new image is created.
XIf the filename does not exist, but the same name with a ".Z"
Xsuffix does exist, it is assumed that the file is compressed
Xand the "zcat" program is used to read the compressed image.
XNote that when the image name defaults to the file name, only
Xthe basename (portion after the last '/' character, if any) is
Xused to name the image.
XIf this resulting name starts with a digit, or contains any
Xcharacters other than alphabetic, digits and the underscore,
Xyou will not be able to reference the image by name, but will
Xhave to use the alternative "$n" syntax.
X.TP
X.CW "#write @\f2filename\fP@ \f1[\fP\f2image-name\fP\f1]\fP"
XWrite a raw data file from the named image.
XThe image name is optional, and
Xif not specified, image "old" is used.
XIf the first character of the filename is `|', the rest of the filename
Xis interpreted as a command to be run, with the raw image data being fed
Xto it as standard input.
X.TP
X.CW "#list"
XAll the named images (other than "old" and "new" are listed).
X.TP
X.CW "#genps @\f2filename\fP@ \f1[\fP\f2image-name\fP\f1]\fP"
XGenerate postscript to produce the image.
XThe image name is optional, and
Xif not specified, image "old" is used.
XThe image will be scaled to fit whatever paper size is being used.
XAs with the
X.CW #write
Xcommand, if the first character of the filename is a `|' symbol, the
Xrest of the string will be treated as a command to be executed, with the
Xpostscript being piped to its standard input.
XThis is convenient for printing images on a PostScript printer.
X.TP
X.CW #undo
XThe
X.CW old
Xand
X.CW new
Ximages are swapped.
XThis has the effect of restoring the image that existed
Xbefore the last transformation.
X.TP
X.CW "#genepson @\f2filename\fP@ \f1[\fP\f2image-name\fP\f1]\fP"
XGenerate data for an Epson (or compatible) printer to produce
Xthe image.
XThe image name is optional, and if not specified, image "old" is used.
XUnder MSDOS, the device name can be used directly (eg "PRN").
X.sp
XThe existing code is for a 24-pin printer such as the LQ-500.
XTo generate data for an 8-pin printer requires a couple of
Xminor modifications to function
X.CW genepson()
Xin file
X.CW special.c .
X.TP
X.CW #undo
XThe
X.CW old
Xand
X.CW new
Ximages are swapped.
XThis has the effect of restoring the image that existed
Xbefore the last transformation.
X.TP
X.CW "#display \f2image-name\fP"
XThe named image will be displayed.
XThis is the fastest way to display an image, and works regardless of the
Xstate of auto-display.
X.TP
X.CW "#display +"
XTurn auto-display on (equivalent to the command-line option
X.B \-a+ ).
X.TP
X.CW "#display \-"
XTurn auto-display off (equivalent to the command-line option
X.B \-a\- ).
X.TP
X.CW "#truncate +"
XAll assignments to an image with values outside the range
X.CW 0
Xto
X.CW Z
Xwill be truncated to the appropriate boundary, instead of
Xwrapping as described in the book.
XAn example of using this feature would be for a simplistic
Xlightening of an image by adding a constant value.
XNormally, very white areas would wrap around and become black,
Xbut enabling truncation prevents this.
X.TP
X.CW "#truncate \-"
XAll assignments to an image will be done modulo
X.CW "(Z+1)" .
XThis is the default.
X.TP
X.CW "#verbose +"
XTurn on verbose mode.
XEquivalent to the
X.CW "\-v+" command line option.
XCertain warning messages will be printed.
XThis turns on the "percent done" feature of some drivers (on
Xothers it is always active).
X.TP
X.CW "#truncate"
XReport whether truncation is currently in effect or not.
X.TP
X.CW "#verbose \-"
XTurn off verbose mode.
XEquivalent to the
X.CW "\-v\-" command line option.
X.TP
X.CW "#verbose"
XReport the state of the verbose flag.
X.TP
X.CW "#logfile +"
XEnable logging on the current log file (default "popi.log").
XIf logging is already enabled, the file is closed and re-opened
Xwith a new timestamp appended.
X.TP
X.CW "#logfile \-"
XDisable logging"
X.TP
X.CW "#logfile @\f2filename\fP@"
XEnable logging on the specified file.
X.sp
XWhenever a log file is opened, it is always opened in append
Xmode, so existing data is never truncated.
XUpon opening a log file, a timestamp is written.
XAll user input is logged, along with error messages and some
Xprogram responses.
X.TP
X.CW "#free \f2imagename\fP"
XThe memory associated with the named image is freed.
XAny further access to this image becomes impossible.
XThis command may be useful in situations where memory is very
Xlimited, such as a PC.
X.P
XThe remaining special commands are used to obtain special built-in
Xtransformations, as described in Chapter 6 of "Beyond Photography".
XIn each of these cases, the image name is optional, and defaults to
X"old".
XIn each case (except for
X.CW #melt ),
Xthe result is stored in "new", and then "old" and "new" are swapped as
Xusual.
XIn the case of
X.CW #melt ,
Xthe transformation is done in place and no swap occurs.
X.TP
X.CW "#oil \f2image-name\fP"
X.TP
X.CW "#shear \f2image-name\fP"
X.TP
X.CW "#slice \f2image-name\fP"
X.TP
X.CW "#tile \f2image-name\fP"
X.TP
X.CW "#melt \f2image-name\fP"
X.TP
X.CW "#matte \f2image-name\fP \f2gamma\fP"
XIn this case,
X.I gamma
Xis an optional floating point number (defaults to 7.5).
XSee the book for details.
X.SH DIFFERENCES
XThere are a number of differences between the Pico interpreter, for
Xwhich the examples in the book are written, and the Popi interpreter as
Ximplemented here.
X.P
XInteger evaluation stack.
XThe current version of the interpreter has an integer evaluation stack.
XFor this reason, the
X.CW sin()
Xand
X.CW cos()
Xfunctions, have their results multiplied by Z automatically in order to
Xbe significant.
XA future version of the interpreter will provide the option for a
Xfloating point stack, and a syntax which will handle both cases.
X.P
XPolar coordinates.
XIn the book, both cartesian and polar coordinates are specified with
Xsquare brackets.
XThe decision of whether to interpret the indices as polar or cartesian
Xis based on context;
Xif the symbols
X.CW r
Xand
X.CW a
Xare used in the index expressions, the coordinates are interpreted as
Xpolar, otherwise they are interpreted as rectangular.
XThus, in order to use a radius or angle in a cartesian coordinate
Xexpression, it must be cast, as is done on page 48 of the book.
XBy providing a separate syntax for polar and cartesian coordinate
Xreferences, the problem never occurs.
X.SH EXAMPLES
XThese examples are mainly taken from the book, with syntax modifications
Xwhere required.
XThe images in the book are usually 1000 \*(Mu 1000 pixels.
XTo produce the same results on a smaller image, you may need to multiply
Xany occurences of
X.CW x ,
X.CW y
Xand
X.CW r
Xby an appropriate scaling factor.
XThe first example image generation in the book is
X.C
Xnew[x,y] = x + y  ; page 17 (3.1)
X.E
Xwhich produces a number of diagonal stripes, each fading from black to
Xwhite.
XOn a 512 \*(Mu 512 image, to get the same number of stripes, use the
Xtransform
X.C
Xnew[x,y] = x*2 + y*2
X.E
XA series of ripples can be produced with
X.C
Xnew[x,y] = (x * y) % (Z + 1)  ; page 18 (3.2)
X.E
Xor more simply, because all brightness values are inherently modulo
X.CW "(Z + 1)"
Xunless truncation is turned on
X.C
Xnew[x,y] = x * y
X.E
XA single smooth transition can be accomplished with
X.C
Xnew[x,y] = (Z * x * y) / ((X\-1) * (Y\-1))  ; page 18 (3.3)
X.E
XThe transformation
X.C
Xnew[x,y] = x % y
X.E
Xis the same as
X.CW "new[x,y] = x"
Xwhen
X.CW "x < 0"
X(ie in the lower left triangle of the image, with a different effect in
Xthe upper right half.
X.P
XThe trig functions allow transforms such as
X.C
Xnew[x,y] = y + sin(x) / 2  ; page 19 (3.5)
X.E
Xwhich produce a series of sine waves (remember that the range of
X.CW "sin(x)"
Xis
X.CW "\*(+-Z" .
X.P
XAn image reminiscent of a radar sweep can be produced by
X.C
Xnew[x,y] = atan(y \- Y/2, x \- X/2) * Z / 360  ; page 19 (3.6)
X.E
XThe
X.CW atan()
Xfunction has a range of 0 .. 360, so the
X.CW "*Z/360"
Xrescales to 0 ..
X.CW Z .
XThis transform is overall providing an intensity relative to the angle
Xof a point in polar coordinates.
XThe transform language provides the current angle as the variable
X.CW a ,
Xso this statement can be rewritten as
X.C
Xnew[x,y] = a * Z / 360
X.E
XPolar coordinates can be used to produce an image which varies from
Xblack in the centre to white on the outside
X.C
Xnew[x,y] = r * Z / R  ; page 21 (3.11)
X.E
Xor a spiraling effect
X.C
Xnew[x,y] = (((a + r) % 16) \- 8) * Z / 16 + Z/2  ; page 21 (3.12)
X.E
XThe conditional operator can be used to provide a filled black circle
X.C
Xnew[x,y] = r > R/2 ? 0 : Z
X.E
Xor black and white patterns such as
X.C
Xnew[x,y] = ((x % (5 + y/25)) > 5) ? 0 : Z  ; page 20 (3.9)
Xnew[x,y] = (Z * abs(x % sin(y)) > 10) ? 0 : Z  ; page 20 (3.10)
X.E
X.P
XWe can also modify existing images using these transforms.
XThe previous image can always be referred to as
X.CW old ,
Xor an explicitly named image can be used.
XWe can read an image (eg of Dennis Ritchie) using
X.C
X#read "dmr"  ; read the image in the file "dmr" and name it dmr.
X.E
Xand then use it in a transform, such as
X.C
Xnew[x,y] = Z \- dmr[x,y]  ; page 22 (3.13)
X.E
XThis produces a negative, which can be written to a file with
X.C
X#write "dmr_neg"
X.E
Xor converted to PostScript and printed with
X.C
X#genps "| lp \-dalw"
X.E
XSince the
X.CW new
Ximage built during a transformation becomes the
X.CW old
Ximage of the following transform,
Xthe negative image can be re-reversed to produce the original with
X.C
Xnew[x,y] = Z \- old[x,y]  ; reverse the process
X.E
XIn the following examples, we will use
X.CW old
Xin most of the transforms, rather than a particular image name.
XIn practice, you would probably use a specifically named image instead.
X.P
XProvide a circular frame for an image
X.C
Xnew[x,y] = r > R/2 ? 0 : old[x,y]
X.E
XA solarisation process, that fades in from left to right
X.C
Xnew[x,y] = (old[x,y] > (Z*x) / (2 * X)) ? old[x,y] : Z\-old[x,y]  ; page 22 (3.16)
X.E
XGenerate a relief map
X.C
Xnew[x,y] = old[x,y] + (Z/2 \- old[x+2,y+2])  ; page 24 (3.19)
X.E
XShrink an image
X.C
Xnew[x,y] = old[x*2,y*2]  ; page 25 (3.24)
X.E
X.P
XAn interesting caricature is produced by
X.C
Xnew[x,y] = old{sqrt(r * R),a}  ; page 34
X.E
XNote the use of polar coordinates.
XThe reverse transform gives a fisheye lens effect:
X.C
Xnew[x,y] = old{(r*r)/R, a}  ; page 60
X.E
XThe following transform illustrates how an expression can be used for
Xthe indices of the destination matrix.
X.C
Xnew[x, y\-old[x,y]/4] = old[x,y]  ; page 40
X.E
XAn image can be swirled about the centre with
X.C
Xnew[x,y] = old{r, a + r/3}
X.E
XThe following transform uses polar coordinate values in a cartesian
Xreference, resulting in something that looks like what you'd see in a
Xcylindrical mirror
X.C
Xnew[x,y] = old[a * X/A, r * Y/R]  ; page 48
X.E
XThe image generated by
X.C
Xnew[x,y] = old{ (r/16)*16, (a/16)*16 }  ; page 72
X.E
Xis very interesting, in that it is completely unrecognisable
Xwhen viewed up close, but from a distance of a few metres it
Xwill resolve into the original.
X.C
Xnew[x,y] = old{r, a + old{r,a)/8}  ; page 68
X.E
XThis image is a swirl, but with the skew dependant on the
Xbrightness of the current point.
X.C
Xnew[x,y] = x < X/2 ? old[x,y] : old[X-x,y]
Xnew[x,y] = x > X/2 ? old[x,y] : old[X-x,y]
X.E
XThese transformations do a horizontal mirror reversal about the
Xcentre of the image.
XBecause faces are usually close to, but not exactly, centered,
Xthis transform of a face can be interesting.
X.C
Xnew[x,y] = old[x+(x%32)-16, y]  ; page 58
Xnew[x,y] = old[x+((a+r/10)%32)-16, y]
X.E
XThese transforms applied to a face produce what looks like
Xsomeone peering through a bathroom window.
X.tr @@
X.SH DRIVERS
X.TP
X.CW nulldev
XThe null device driver is mainly for people with no graphics
Xdisplay device for which a driver exists.
XUsing this device, data for a PostScript or Epson printer can
Xstill be generated.
X.TP
X.CW atariterm
XThe atari driver is for use with "TERM", a multiplexing
Xterminal program for the Atari ST, written by Peter Collinson
Xof the University of Kent at Canterbury.
XIt is not a driver for running native on an atari.
X.TP
X.CW kermit
XThis is a driver for MS-Kermit 2.32 on an IBM PC, which is capable of
Xemulating a Tektronix 4010. This provides three levels of contrast
X(nothing, normal and bold).
X.TP
X.CW MGR
XThis driver is for the Bell Core MGR window system. It is visually
Xidentical to the SunView version, but is currently hardwired to
Xmonochrome.
X.TP
X.CW NeWS
XThis is the driver that will work with NeWS v1.1 and OpenWindows v1.0.
XIt is also visually identical to the SunView version.
X.TP
X.CW pcturbo
XThis is a driver for running popi native on a PC with
XBorland's Turbo C compiler.
XIt uses the graphics library supplied with Turbo C, which
Xauto-detects most types of graphics display.
XNo attempt at using a colourmap is done \- a single display
Xdot is used for each image pixel, with simple dithering.
XOnly directly accesible memory is used, which drastically
Xrestricts the size of images that can be handled.
X.TP
X.CW SunView
XThis driver is the initial version to work with the SunView
Xgraphics package available on Sun Workstations.
XThere is
Xcurrently minimal functionality.
XThe output is in 256 greyscales
Xon colour machines, and an 8\*(Mu8 dither on monochrome screens.
X.TP
X.CW X11
XA graphics implementation for use with MIT's X11 window system.
XThis driver is visually identical to the SunView version.
X.TP
X.CW XView
XA conversion of the SunView graphics driver to use the XView X11
Xtoolkit. This driver is visually identical to the SunView version.
X.SH SEE ALSO
Xipscript(1L), imavg(1L).
X.SH AUTHOR
XGerald J. Holzmann, AT&T Bell Laboratories, Murray Hill, New
XJersey.
X.sp
XModifications and additional functionality, Atari, PC, PostScript,
XEpson and null drivers by
XStephen Frede, Softway Pty Ltd, Australia.
X.br
XPopi maintainance, SunView, X11, NeWS, MGR and XView graphics drivers by
XRich Burridge, Sun Microsystems, Australia.
X.br
XKermit graphics driver by
XFrank Crawford, Q.H. Tours.
X.br
XAmiga graphics driver by
XPeter Chubb, Softway Pty Ltd, Australia.
X.br
XApollo driver by
XTim Lambert, University of New South Wales.
X.SH BUGS
XFunctions which require popen() (ie auto reading of compressed
Xfiles and writing to pipes) don't work in the sequent
Xmultiprocessor version.
X.sp
XProbably more \- please notify richb@sunaus.oz.au of any found.
Funky_Stuff
chmod 0444 popi.1 || echo "restore of popi.1 fails"
set `wc -c popi.1`;Sum=$1
if test "$Sum" != "23958"
then echo original size 23958, current size $Sum;fi
echo "x - extracting special.c (Text)"
sed 's/^X//' << 'Funky_Stuff' > special.c &&
X
X/*  @(#)special.c 1.11 89/12/12
X *
X *  Special transformations used by the popi program.
X *
X *  Popi was originally written by Gerard J. Holzmann - AT&T Bell Labs.
X *  This version is based on the code in his Prentice Hall book,
X *  "Beyond Photography - the digital darkroom," ISBN 0-13-074410-7,
X *  which is copyright (c) 1988 by Bell Telephone Laboratories, Inc. 
X *
X *  Permission is given to distribute these extensions, as long as these
X *  introductory messages are not removed, and no monies are exchanged.
X *
X *  No responsibility is taken for any errors or inaccuracies inherent
X *  either to the comments or the code of this program, but if reported
X *  (see README file) then an attempt will be made to fix them.
X */
X
X/*      Special transformations from chapter 6 of BP.
X *
X *	The way this is done is fairly nasty at the moment,
X *		but it does work.
X */
X
X#ifndef AMIGA
X# include <sys/types.h>
X#endif /* AMIGA */
X#include <ctype.h>
X#include <time.h>
X
X#ifdef      BSD
X# include <sys/timeb.h>
X#endif   /* BSD */
X#include "popi.h"
X
X#define	GAMMA		7.5	/* default gamma for matte() */
X#define	TILESIZE	25	/* default tile size for tile() */
X#define N    		3
X#define New  		src[CURNEW].pix
X
X/* prototypes for local functions */
Xstruct SRC *	parseimg P((void));
Xbool		parsefname P((void));
Xvoid		oil P((void));
Xvoid		shear P((void));
Xvoid		slice P((void));
Xvoid		tile P((void));
Xvoid		melt P((void));
Xvoid		matte P((void));
Xvoid		genps P((void));
Xvoid		genepson P((void));
Xvoid		list P((void));
Xvoid		readimg P((void));
Xvoid		writeimg P((void));
Xvoid		freeimg P((void));
Xvoid		displayimg P((void));
Xvoid		CloseLog P((void));
Xvoid		dolog P((void));
Xvoid		debug P((void));
Xvoid		undo P((void));
Xvoid		verbose P((void));
Xvoid		trunc P((void));
X
X/*
X * convenience function, since most of these routines have
X * an image as their 1st parameter.
X */
Xstatic struct SRC *
Xparseimg()
X{
X    lex();
X
X    if (lat == '\n' || lat == ')')
X    {
X	pushback(lat);
X	return &src[CUROLD];
X    }
X
X    if (lat != INAME)
X    {
X	SPRINTF(ErrBuf, "Expected image name");
X	error(ERR_PARSE);
X	return (struct SRC *) 0;
X    }
X
X    return &src[lexval + 1];
X}
X
Xstatic bool
Xparsefname()
X{
X    lex();
X
X    if (lat == '\n')
X	pushback(lat);
X
X    if (lat != FNAME)
X    {
X	SPRINTF(ErrBuf, "Expected file name");
X	error(ERR_PARSE);
X	return FALSE;
X    }
X    return TRUE;
X}
X
Xstatic void
Xoil()
X{
X    register int	x, y;
X    register int	dx, dy;
X    pixel_t		mfp;
X    static int		*histo = 0;
X    struct SRC          *srcp;
X    pixel_t		**img;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    img = srcp->pix;
X
X    if
X    (
X	histo == (int *) 0
X	&&
X	(histo = (int *) LINT_CAST(Emalloc((unsigned)Zsize * sizeof (int)))) == (int *) 0
X    )
X	return;
X
X    if (disp_active)
X	disp_imgstart();
X
X    for (y = N; y < Ysize-N; y++)
X    {
X        for (x = N; x < Xsize-N; x++)
X	{
X	    for (dx = 0; dx < Zsize; dx++)
X		histo[dx] = 0;
X	    for (dy = y-N; dy <= y+N; dy++)
X		for (dx = x-N; dx <= x+N; dx++)
X		    histo[img[dy][dx]]++;
X	    for (dx = dy = 0; dx < Zsize; dx++)
X		if (histo[dx] > dy)
X	        {
X		    dy = histo[dx];
X		    mfp = (pixel_t) dx;
X	        }
X	    New[y][x] = mfp;
X	}
X
X	if (disp_active)
X	    disp_putline(New[y], y);
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X
X    if (disp_active)
X	disp_imgend();
X
X    SwapOldNew();
X}
X
X
Xstatic void
Xshear()
X{
X    register int	x, y, r;
X    int			dx, dy;
X    static int		*yshift = 0;
X    struct SRC          *srcp;
X    pixel_t		**img;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X
X    img = srcp->pix;
X
X    if
X    (
X	yshift == 0
X	&&
X	(yshift = (int *) LINT_CAST(Emalloc((unsigned)Xsize * sizeof(int)))) == 0
X    )
X	return;
X
X    if (disp_active)
X	disp_imgstart();
X
X    for (x = r = 0; x < Xsize; x++)
X    {
X	if (RANDOM % Zsize < 128)
X	    r--;
X	else
X	    r++;
X	yshift[x] = r;
X    }
X
X    for (y = 0; y < Ysize; y++)
X    {
X	if (RANDOM % Zsize < 128)
X	    r--;
X	else
X	    r++;
X
X	for (x = 0; x < Xsize; x++)
X	{
X	    dx = x + r;
X	    dy = y + yshift[x];
X	    if (dx >= Xsize || dy >= Ysize || dx < 0 || dy < 0)
X		continue;
X	    New[y][x] = img[dy][dx];
X	}
X
X	if (disp_active)
X	    disp_putline(New[y], y);
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X
X    if (disp_active)
X	disp_imgend();
X
X    SwapOldNew();
X}
X
X
Xstatic void
Xslice()
X{
X    register int	x, y, r;
X    int			dx, dy;
X    struct SRC          *srcp;
X    pixel_t		**img;
X    static int		*xshift = 0,
X			*yshift = 0;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    img = srcp->pix;
X
X    if
X    (
X	xshift == 0
X	&&
X	(xshift = (int *) LINT_CAST(Emalloc((unsigned)Ysize * sizeof (int)))) == 0
X    )
X	return;
X    if
X    (
X	yshift == 0
X	&&
X	(yshift = (int *) LINT_CAST(Emalloc((unsigned)Xsize * sizeof (int)))) == 0
X    )
X	return;
X
X    if (disp_active)
X	disp_imgstart();
X
X    for (x = dx = 0 ; x < Xsize; x++)
X    {
X        if (dx == 0)
X        {
X            r = (RANDOM & 63) - 32;
X            dx = 8 + RANDOM & 31;
X        }
X        else
X	    dx--;
X        yshift[x] = r;
X    }
X    for (y = dy = 0; y < Ysize; y++)
X    {
X        if (dy == 0)
X        {
X            r = (RANDOM & 63) - 32;
X            dy = 8 + RANDOM & 31;
X        }
X        else
X	    dy--;
X        xshift[y] = r;
X    }
X
X    for (y = 0; y < Ysize; y++)
X    {
X        for (x = 0; x < Xsize; x++)
X        {
X            dx = x + xshift[y];
X            dy = y + yshift[x];
X            if (dx < Xsize && dy < Ysize && dx >= 0 && dy >= 0)
X                New[y][x] = img[dy][dx];
X        }
X
X	if (disp_active)
X	    disp_putline(New[y], y);
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X
X    if (disp_active)
X	disp_imgend();
X
X    SwapOldNew();
X}
X
X
Xstatic void
Xtile()
X{
X    register int	x,
X			y,
X			dx,
X			dy;
X    int			ox,
X			oy,
X			nx,
X			ny,
X			TileSize = TILESIZE;
X    struct SRC          *srcp;
X    pixel_t		**img;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    img = srcp->pix;
X
X    for (y = 0; y < Ysize-TileSize; y += TileSize)
X    {
X	for (x = 0; x < Xsize-TileSize; x += TileSize)
X        {
X	    ox = (RANDOM & 31) - 16 ;       /* Displacement. */
X	    oy = (RANDOM & 31) - 16;
X
X	    for (dy = y; dy < y+TileSize; dy++)
X	        for (dx = x; dx < x+TileSize; dx++)
X	        {
X	            nx = dx + ox;
X	            ny = dy + oy;
X	            if (nx >= Xsize || ny >= Ysize || nx < 0 || ny < 0)
X			continue;
X	            New[ny][nx] = img[dy][dx];
X		}
X        }
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X
X    if (disp_active)
X    {
X	disp_imgstart();
X	for (y = 0; y < Ysize; y++)
X	    disp_putline(New[y], y);
X	disp_imgend();
X    }
X
X    SwapOldNew();
X}
X
X
X/*
X *	Note: affects source image in situ.
X *	Buffers not swapped after processing.
X */
Xstatic void
Xmelt()
X{
X    register int	x,
X			y,
X			k,
X			NumPixels;
X    pixel_t		val;
X    struct SRC          *srcp;
X    pixel_t		**img;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    img = srcp->pix;
X
X    NumPixels = Xsize * Ysize;
X    for (k = 0; k < NumPixels; k++)
X    {
X        x = RANDOM % Xsize;
X        y = RANDOM % (Ysize - 1);
X
X        while (y < Ysize-1 && img[y][x] <= img[y+1][x])
X	{
X	    val = img[y][x];
X	    img[y][x] = img[y+1][x];
X	    img[y+1][x] = val;
X	    y++;
X	}
X	disp_percentdone(k * 100 / (NumPixels-1));
X    }
X    if (disp_active)
X    {
X	disp_imgstart();
X	for (y = 0; y < Ysize; y++)
X	    disp_putline(img[y], y);
X	disp_imgend();
X    }
X}
X
X
Xstatic void
Xmatte()
X{
X    struct SRC          *srcp;
X    pixel_t		**img;
X    double		gamma;
X    register		x,
X			y;
X    static pixel_t	*lookup = (pixel_t *) 0;
X    static double	lastgamma = 0.0;
X
X    DEBUG((Debug, "matte()\n"));
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    img = srcp->pix;
X
X    lex();
X    if (lat == '\n' || lat == ')')
X    {
X	gamma = GAMMA;
X	pushback(lat);
X    }
X    else
X	gamma = lexval + lexfract;
X
X    if
X    (
X	lookup == 0
X	&&
X	(lookup = (pixel_t *) Emalloc((unsigned)Zsize)) == (pixel_t *) 0
X    )
X	return;
X
X    if (lastgamma != gamma)
X    {
X	for (x = 0; x < Zsize; ++x)
X	    lookup[x] = ((double)Zmax * pow(x / (double)Zmax, gamma) < 3.0)
X		    ? Zmax : 0;
X	lastgamma = gamma;
X    }
X
X    if (disp_active)
X	disp_imgstart();
X    for (y = 0; y < Ysize; y++)
X    {
X	for (x = 0; x < Xsize; x++)
X	    New[y][x] = lookup[img[y][x]];
X	if (disp_active)
X	    disp_putline(New[y], y);
X	disp_percentdone(y * 100 / (Ysize-1));
X    }
X    if (disp_active)
X	disp_imgend();
X}
X
Xstatic void
Xgenps()
X{
X    int			x,
X			y;
X    FILE		*ostr;
X    struct SRC		*srcp;
X    pixel_t		**img,
X			*ip;
X    time_t		time P((time_t *)),
X			clock;
X#if unix
X    char		*getlogin P((void));
X#endif /* unix */
X
X    if (!parsefname())
X	return;
X
X    if ((ostr = EfopenW(text)) == NULL)
X	return;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X
X    img = srcp->pix;
X    clock = time((time_t *) 0);
X    
X    FPRINTF(ostr, "%%!PS-Adobe-1.0\n");
X    FPRINTF(ostr, "%%%%Title: popi bit image '%s'\n", srcp->str);
X    FPRINTF(ostr, "%%%%DocumentFonts:\n");
X    FPRINTF(ostr, "%%%%Creator: popi\n");
X    FPRINTF(ostr, "%%%%CreationDate: %s", ctime(&clock)); /* includes \n */
X    FPRINTF(ostr, "%%%%Pages: 1\n");
X#if	unix
X    FPRINTF(ostr, "%%%%For: %s\n", getlogin());
X#endif	/* unix */
X    FPRINTF(ostr, "%%%%EndComments\n");
X    FPRINTF(ostr, "clippath pathbbox pop pop translate\n");
X    FPRINTF(ostr, "clippath pathbbox pop exch pop exch sub dup scale\n");
X    FPRINTF(ostr, "/picstr %d string def\n", Xsize);
X    FPRINTF(ostr, "/doimage {\n");
X    FPRINTF(ostr, "%d %d %d [ %d 0 0 -%d 0 %d ]\n",
X	Xsize, Ysize, BITSPERPIXEL, Xsize, Ysize, Ysize);
X    FPRINTF(ostr, "{currentfile picstr readhexstring pop}image}bind def\n");
X    FPRINTF(ostr, "%%%%EndProlog\n%%%%Page 0 0\ndoimage\n");
X
X    for(y = 0; y < Ysize; ++y)
X    {
X	ip = &img[y][0];
X	for(x = 0; x < Xsize; ++x)
X	{
X	    if (x % 40 == 0)		/* formatting only */
X		PUTC('\n', ostr);
X	    FPRINTF(ostr, "%02x", *ip++);
X	}
X	PUTC('\n', ostr);
X    }
X
X    FPRINTF(ostr, "showpage\n");
X    Efclose(ostr);
X}
X
X/*
X * Although this is set up to use one particular graphics mode
X * of a 24-pin printer, everything should be generic enough
X * that it can be changed to work with a different graphics
X * mode on an 8-pin printer, just by changing a few numbers.
X */
Xstatic void
Xgenepson()
X{
X    struct SRC		*srcp;
X    char		*PinVals;
X    pixel_t		*PinThresh,
X			ThreshStep;
X    FILE		*ostr;
X    register int	x;
X    register pixel_t	**img;
X    int			y,
X			yn;
X    int			pin;
X    int			BytesPerChunk,
X			PinsPerPixel,
X			BytesPerColumn,
X			yPixelsPerByte,
X			xPinsPerPixel,
X			yPinsPerPixel;
X
X    if (parsefname() ==	0)
X	return;
X
X    if ((ostr =	EfopenW(text)) == NULL)
X	return;
X
X    if ((srcp =	parseimg()) == (struct SRC *) 0)
X	return;
X
X    img	= srcp->pix;
X
X    /* printer specific	*/
X    xPinsPerPixel = 4;
X    yPinsPerPixel = 4;
X    BytesPerColumn = 24	/ BITSINBYTE;
X
X    PinsPerPixel = xPinsPerPixel * yPinsPerPixel;
X    BytesPerChunk = xPinsPerPixel * BytesPerColumn;
X    yPixelsPerByte = BITSINBYTE	/ yPinsPerPixel;
X
X    /* Must be whole number of pixels (y direction) per	byte. */
X    assert(yPinsPerPixel * yPixelsPerByte == BITSINBYTE);
X
X    /* Reallocate these	each time, as changing the print mode
X     * may change the sizes of these arrays.
X     */
X    if
X    (
X	(PinVals = Emalloc((unsigned)BytesPerChunk)) ==	0
X	||
X	(PinThresh = (pixel_t *) Emalloc((unsigned)PinsPerPixel * sizeof(pixel_t))) == 0
X    )
X	return;
X
X    ThreshStep = (pixel_t) (Zsize / (PinsPerPixel + 1));
X    for	(pin = 0; pin <	PinsPerPixel; ++pin)
X	PinThresh[pin] = (pixel_t) ((pin + 1) * ThreshStep);
X
X    for	(y = 0;	y < Ysize; y = yn)
X    {
X
X	/* printer specific */
X	/*
X	 * This	print line is width Xsize pixels, and (Xsize * xPinsPerPixel)
X	 * pin positions (dots on the page).
X	 * No. of dots vertical	is (BytesPerColumn * BITSINBYTE)
X	 * which is yPinsPerPixel times	the no.	of image scanlines.
X	 */
X	FPRINTF(ostr,
X	    "\033*%c%c%c",
X	    39,		/* 180 dpi in both directions */
X	    (Xsize * xPinsPerPixel) % 256,
X	    (Xsize * xPinsPerPixel) / 256
X	    );
X
X	for (x = 0; x <	Xsize; ++x)
X	{
X	    register int	ycur;
X	    int			ByteCount,
X				xpin;
X	    char		*dp;
X
X	    /* Clear the PinVals array */
X	    for
X	    (
X		ByteCount = 0, dp = PinVals;
X		ByteCount < BytesPerChunk;
X		++ByteCount
X	    )
X		*dp++ =	0;
X
X	    dp = PinVals;
X
X	    /* For each	byte-sized row of the print head,
X	     * collect 1 pixel width of	data.
X	     */
X	    for
X	    (
X		ByteCount = 0, dp = PinVals, ycur = y;
X		ByteCount < BytesPerColumn;
X		++ByteCount, dp	+= xPinsPerPixel
X	    )
X	    {
X		register unsigned char	bit;
X
X		yn = ycur + yPixelsPerByte;
X		if (yn > Ysize)
X		    yn = Ysize;
X
X		/* For the current byte	row of the print-head
X		 * (ie yPixelsPerByte image scanlines),
X		 * collect a pixel width of data.
X		 */
X		for (bit = 0x80; ycur <	yn; ++ycur)
X		{
X		    pixel_t			val;
X		    int				ypin;
X
X		    val	= img[ycur][x];
X
X		    /* Now use an appropriate no. of pins to simulate
X		     * the greyscale value.
X		     */
X		    for
X		    (
X			ypin = 0, pin =	0;
X			ypin < yPinsPerPixel;
X			++ypin
X		    )
X		    {
X			for (xpin = 0; xpin < xPinsPerPixel; ++xpin, ++pin)
X			{
X			    if (val < PinThresh[pin])
X				dp[xpin] |= bit;
X			}
X			/* xpin	== xPinsPerPixel */
X			bit >>=	1;
X		    }
X		    /* ypin == YpinsPerPixel */
X		}
X		/* ycur	== y */
X	    }
X	    /* ByteCount == BytesPerColumn */
X
X	    /* We have set up enough columns for a single pixel	in
X	     * the x direction.	Now print them in the correct order.
X	     */
X	    for	(xpin =	0; xpin	< xPinsPerPixel; ++xpin)
X	    {
X		for (ByteCount = 0; ByteCount <	BytesPerColumn;	++ByteCount)
X		{
X		    PUTC(PinVals[ByteCount * xPinsPerPixel + xpin], ostr);
X		}
X	    }
X	    /* xpin == xPinsPerPixel */
X	}
X	/* x ==	Xsize */
X
X	/* Printer specific */
X	FPRINTF(ostr, "\033J%c\r", 24);
X    }
X    /* y == Ysize */
X
X    Efclose(ostr);
X    free(PinVals);
X    free((char *) PinThresh);
X}
X
Xstatic void
Xlist()
X{
X    showfiles();
X}
X
X/*
X *	#read "filename" [imagename]
X */
Xstatic void
Xreadimg()
X{
X    char	filename[512],
X		*imgname = (char *) 0;
X
X    if (parsefname() == 0)
X	return;
X    
X    STRCPY(filename, text);
X
X    lex();
X
X    if (lat == '\n' || lat == ')')
X    {
X	pushback(lat);
X    }
X    else if (lat != NAME && lat != INAME)
X    {
X	SPRINTF(ErrBuf, "Expected image name");
X	error(ERR_PARSE);
X    }
X    else
X	imgname = text;
X
X    getpix(filename, imgname);
X}
X
Xstatic void
Xwriteimg()
X{
X    struct SRC	*srcp;
X
X    if (parsefname() == 0)
X	return;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X
X    putpix(srcp, text);
X}
X
Xstatic void
Xfreeimg()
X{
X    struct SRC	*srcp;
X
X    if ((srcp = parseimg()) == (struct SRC *) 0)
X	return;
X    
X    if (srcp == &src[CUROLD] || srcp == &src[CURNEW])
X    {
X	SPRINTF(ErrBuf, "Cannot free 'old' or 'new'");
X	error(0);
X	return;
X    }
X
X    ImgFree(srcp);
X}
X
Xstatic void
Xdisplayimg()
X{
X    pixel_t		**img;
X    int			y;
X
X
X    lex();
X
X    if (lat == '+')
X    {
X	disp_active = 1;
X	return;
X    }
X    if (lat == '-')
X    {
X	disp_active = 0;
X	return;
X    }
X    if (lat == '\n')
X    {
X	pushback(lat);
X	img = src[CUROLD].pix;
X    }
X    else if (lat == INAME)
X	img = src[lexval + 1].pix;
X    else if (lat == NEW)
X	img = src[CURNEW].pix;
X    else
X    {
X	SPRINTF(ErrBuf, "Expected +, - or image name");
X	error(ERR_PARSE);
X	return;
X    }
X
X    disp_imgstart();
X
X    for (y = 0; y < Ysize; y++)
X	disp_putline(img[y], y);
X
X    disp_imgend();
X}
X
Xstatic void
XCloseLog()
X{
X    if (LogStr == NULL)
X	return;
X
X    FPRINTF(LogStr, "\n---\n");
X    FCLOSE(LogStr);
X    LogStr = NULL;
X}
X
Xvoid
XOpenLog()
X{
X    time_t		time(),
X			clock;
X
X    CloseLog();
X
X    if ((LogStr = fopen(LogFile, "a")) == NULL)
X    {
X	SPRINTF(ErrBuf,
X	    "Can't open log file '%s' - logging is off",
X	    LogFile);
X	error(ERR_SYS);
X	return;
X    }
X
X    clock = time((time_t *) 0);
X    FPRINTF(LogStr, "==>> %s", ctime(&clock)); /* includes '\n' */
X}
X
Xstatic void
Xdolog()
X{
X    static char	*buf = (char *) 0;
X
X    lex();
X
X    if (lat == '+')
X	OpenLog();
X    else if (lat == '-')
X	CloseLog();
X    else if (lat == FNAME)
X    {
X	if (buf == (char *) 0 && (buf = Emalloc((unsigned int) 512)) == (char *) 0)
X	    return;
X	STRCPY(buf, text);
X	LogFile = buf;
X	OpenLog();
X    }
X    else
X	pushback(lat);
X    
X    if (LogStr)
X	PRINTF("Logging is active on file '%s'\n", LogFile);
X    else
X	PRINTF("Logging is off\n");
X}
X
Xstatic void
Xdebug()
X{
X    static char	*buf = (char *) 0;
X
X    lex();
X
X    if (lat == '+')
X	OpenLog();
X    else if (lat == '-')
X	CloseLog();
X    else if (lat == FNAME)
X    {
X	if (buf == (char *) 0 && (buf = Emalloc((unsigned int) 512)) == (char *) 0)
X	    return;
X	STRCPY(buf, text);
X	LogFile = buf;
X	OpenLog();
X    }
X    else
X	pushback(lat);
X    
X    if (LogStr)
X	PRINTF("Logging is active on file '%s'\n", LogFile);
X    else
X	PRINTF("Logging is off\n");
X}
X
Xstatic char *HelpMsg[] =
X{
X    "binary ops: ** * /  % + - << >> < > >= <= == != & ^ | && ||",
X    "funcs: sin(deg) cos(deg) atan(y, x) log(val) sqrt(val) abs(val) rand()",
X    "values: x y r a X Y R A Z",
X    "special funcs",
X    "\t#read \"filename\"",
X    "\t#write \"filename\"",
X    "\t#genps \"filename\" [image]",
X    "\t#display [image]",
X    "\t#display +",
X    "\t#display -",
X    (char *) 0
X};
X
Xvoid
Xhelp()
X{
X    PrStrs(HelpMsg);
X}
X
Xstatic void
Xundo()
X{
X    SwapOldNew();
X}
X
Xstatic void
Xverbose()
X{
X    lex();
X
X    if (lat == '+')
X	Verbose = 1;
X    else if (lat == '-')
X	Verbose = 0;
X    else
X	pushback(lat);
X    
X    PRINTF("Verbose is %s\n", Verbose ? "on" : "off");
X}
X
Xstatic void
Xtrunc()
X{
X    lex();
X
X    if (lat == '+')
X	Truncate = 1;
X    else if (lat == '-')
X	Truncate = 0;
X    else
X	pushback(lat);
X    
X    PRINTF("Truncation is %s\n", Truncate ? "on" : "off");
X}
X
Xstruct SpecialOp
X{
X    char	*name;
X    void	(*func) P((void));
X};
X
Xstatic struct SpecialOp SpecialOps[] =
X{
X    { "matte",		matte },
X    { "oil",		oil },
X    { "slice",		slice },
X    { "shear",		shear },
X    { "tile",		tile },
X    { "melt",		melt },
X    { "read",		readimg },
Funky_Stuff
echo "End of part 4"
echo "File special.c is continued in part 5"
echo "5" > s2_seq_.tmp
exit 0