/* Portions Copyright (C) 2001 artofcode LLC.
   Portions Copyright (C) 1996, 2001 Artifex Software Inc.
   Portions Copyright (C) 1988, 2000 Aladdin Enterprises.
   This software is based in part on the work of the Independent JPEG Group.
   All Rights Reserved.

   This software is distributed under license and may not be copied, modified
   or distributed except as expressly authorized under the terms of that
   license.  Refer to licensing information at http://www.artifex.com/ or
   contact Artifex Software, Inc., 101 Lucas Valley Road #110,
   San Rafael, CA  94903, (415)492-9861, for further information. */

/****************************************************************************/
/*	Ghostscript printer driver for Epson Color Photo, Photo EX, Photo 700	*/
/****************************************************************************/

#include "gdevprn.h"
#include <math.h>

/****************************************************************************/
/*								Legend										*/
/****************************************************************************/

/*

HISTORY
~~~~~~~

8 June 1999 Zoltán Kócsi (aka Kocsonya) zoltan@bendor.com.au

	Initial revision. 
	No shingling, depletion.
	Colour only.
	Dither matrix is blatantly copied from gslib.c.

17 April 2000 Zoltán Kócsi

	After much play worked out a reasonably simple colour mapping
	that gives fairly good results. It has some very hairy things 
	in it but ot seems to work reasonably well on a variety of natural
	as well as artificial images.
	
		
LEGALISE
~~~~~~~~

The usual disclaimer applies, neither me (Zoltán Kócsi) nor 
Bendor Research Pty. Ltd. assume any liability whatsoever in 
relation to events arising out of or related to the use of 
the software or the included documentation in any form, way 
or purpose. This software is not guaranteed to work, you
get it "as is" and use it for your own risk.

This code has been donated to Aladdin Enterprises, see their
license for details.

CREDIT
~~~~~~
This driver was written from scratch, however, I have used the 
HP/BJ driver very heavily as a reference (GhostScript's documentation
needs some working :-). In addition, I got some help in understanding
the more arcane features of the printer by digging into the colour
Epson driver and its documentation (documentation for the Photo EX 
did not exist). I thank to the authors of these drivers and the 
related docs. 

I do also hereby express my despising Epson, Inc. who try to enlarge 
Microsoft's monopoly by witholding programming information about such 
a commodity item as a printer.

KNOWN BUGS/LIMITATIONS
~~~~~~~~~~~~~~~~~~~~~~
- Monochrome driver is not finished yet
- The driver is not optimised for speed
- The driver does not support TIFF compression
- Shingling and depletion is not implemented
- The colour correction and ink transfer curve are hardcoded 
- The dither matrix is straight stolen from Ghostscript
- The alternative error diffusion included but does not work (yet)

I plan to attend these issues later, however, I don't promise any timeframe
for I have a lot else to do for bread & butter too.

PREFACE
~~~~~~~
The Epson Stylus Photo EX is a colour ink-jet printer.
It can handle papers up to A3. It uses 6 inks, black in one cartridge
and cyan, magenta, yellow, light cyan and light magenta in an other 
cartridge. The head has 32 nozzles, with 1/90" spacing. 
The maximal resolution is 1440 dpi horizontal 720 dpi vertical.
In 720x720 and 360x360 dpi it supports microweave. To achieve
1440x720 you must use software weaving. It has only one built-in font,
namely 12pt Courier; the printer in general havily relies on the
driver software. It comes with (what else ?) Windows 9x and Mac drivers.

The printer uses the ESC/P Raster protocol. This protocol is somewhat
similar to the ESC/P2 one. Initially Epson refused to give any info
about it. Later (unfortunately after I had already spent lot of time
to reverse engineer it) they released its definition. It could be 
found on their website (http://www.ercipd.com/isv/level1/6clr_98b.pdf).
Alas, they removed it, so at the moment I do not know about any existing
docs of the printer. 
There are still a few commands which are not covered by the docs
and for example the Windows driver uses them. There are others which
are in the docs, saying that you can find them in other docs but you 
can't. Fortunately, these commands apparently have no effect on the 
printing process so this driver simply ignores them. Tricky business.

By the way, my personal experience is that Epson tech support is 
a joke, or in Usenet lingvo it sucks big time - they know absolutely
nothing about the product they supposed to support. Epson's webpage
contains false info as well (they state that the Photo EX uses ESC/P2,
which is simply not true).

This driver should in theory support the Stylus 700 and the Stylus Photo
as well but I have not tested it on them.

If you think that you can get some useful info from me above of what you
can find below, feel free to email me at zoltan@bendor.com.au.
If you enhance the driver or find a bug *please* send me info about 
it. 

DRIVER
~~~~~~
The driver was written under Ghostscript 5.10.
This file should contain two drivers, one for colour mode and one for B&W.
The devices are "photoex" and "photoexm". The mono device driver is
catered for (that is, the rendering part knows how to render for B&W)
but it is not finished yet (no device structure and gray colour mapping
procedures) mainly because all my B&W needs are fairly well satisfied 
by our laser printer.

The driver features the following:

Supported resolutions

	 360x360	Y weaving (not that micro :-) by the printer
	 720x720	Y microweave by the driver (quicker than the printer)
	1440x720	Y and X microweave by the driver
	
	Resolutions other than these will result in a rangecheck error.
	
Papersize:
	
	Whatever Ghostscript supports. The printer docs say that if you load
	multiple sheets of transparencies into the tray you should at least
	have 30mm or 1.2" top margin. The driver always sets the smallest 
	possible top margin (3mm or 0.12"), it's up to you to comply.
	
	In addition, the printer says that the bottom margin is at least
	14mm or 0.54". I violate it by setting it to 0.5" or 12.7mm.
	0.5" seems to be a common margin value for documents and you 
	would hate it when the last line of your page gets printed on the
	top of the next sheet ...
	
Options:
	
	-dDotSize=n
	
		n = 0		Let the driver choose a dotsize
		n = 1		small dots
		n = 2		more ink
		n = 3		ink flood
		n = 4		'super microdots' (whatever they are, they are *big*)
		
		The default is 0 which is n=1 for 1440x720, 2 for 720x720 and
		3 for 360x360. Do not use large dots if you don't have to, you
		will soak the paper. If you print 720x720 on normal paper, try
		using n=1.
		
	-dRender=n
	
		n = 0		Floyd-Steinbeck error diffusion
		n = 1		Clustered dither
		n = 2		Bendor's error diffusion (experimental, do not use)
		
		Default is Floyd-Steinbeck error diffusion
		
	-dLeakage=nn
	
		nn is between 0 and 25. It only effects Bendor's error diffusion.
		It sets the percentage of the error which is left to 'leak', that
		is it is the coefficient of an exponential decay of the error.
		Experiments show that it can be beneficial on image quality.
		Default is 0 (no leakage).
		
	-dSplash=nn
	
		nn is between 0 and 100. It only affects Bendor's error diffusion.
		The ED routine tries to take the increase of dot diameter on certain
		paper types into account. 
		It sets the percentage of the ink dot size increase as it splashes
		onto the paper and spreads. 0 means no splashing, 100 means that 
		the dot is twice as large as it should be. 
		Default is 0.
		
	-dBinhibit=n
	
		If n is 1, then if black ink is deposited to a pixel, it will
		inhibit the deposition of any other ink to the same pixel.
		If 0, black ink may be deposited together with other inks.
		Default is on (1).
		 
ESC/P RASTER DOCS
~~~~~~~~~~~~~~~~~
The parts of the ESC/P Raster protocol which I've managed to decipher, 
and which are actually used in this driver can be found below.
nn, mm, xx, etc. represent a single byte with a binary value in it.
nnnn, xxxx etc. represent a 16-bit binary number, sent in two bytes,
in little endian order (low byte first). 2-digit numbers are a single 
byte in hex. Other chars are themselves.
Quite a few commands are identical to the ESC/P2 commands, these are 
marked with (P2).

ESC @								(P2)	
	
	Resets the printer.


ESC ( U 01 00 nn					(P2)

	Sets the unit to 3600/nn dpi. Note that 1440 can not be set !


ESC ( C 02 00 nnnn					(P2)

	Sets the page (paper) length to nnnn units
	

ESC ( c 04 00 bbbb tttt				(P2)

	Sets the top margin to tttt units, the bottom margin to
	bbbb units. The bottom margin is measured from the top
	of the page not from the bottom of the page !
		
	
ESC	U nn							(P2)

	Unidirectional printing
	
	nn
	00	off
	01	on
	30	off (this is ASCII 0)
	31	on	(this is ASCII 1)
	
		
ESC	( i 01 00 nn					(P2)

	Microweave
	
	nn
	00	off
	01	on
	30	off (this is ASCII 0)
	31	on	(this is ASCII 1)
	
	Turns microweave on for 720x720 dpi printing.
				
ESC r nn							(P2)

	Select colour
	
	nn
	01		Cyan
	02		Magenta
	04		Yellow
	08		Black
		

ESC ( G 01 00 nn					(P2)

	Selects graphics mode:
	
	nn
	00		Off
	01		On
	30		Off
	31		On
	
	
ESC ( v 02 00 dddd					(P2)

	Advance the paper by dddd units defined by ESC ( U


ESC . cc vv hh nn mmmm <data>		(P2)

	Sends graphics data to the printer.
	
	cc	Encoding mode
	
		00	Raw data
		01	Run-length encoded data
	
	vv	Vertical resolution
	
		28	  90 dpi	*interleave*
		14	 180 dpi	*interleave*
		0a	 360 dpi
		05	 720 dpi
		
	hh	Horizontal resolution
	
		0a	 360 dpi
		05	 720 dpi
		
	nn	Number of nozzles
	
		It should be set to 32 (normal printing) or 1 (microweave)
		
	mmmm Number of collumns of data (not number of data bytes !)
	
	<data>
	
		The data should contain as many bytes as needed to fill the
		mmmm * nn pixels. Data is presented horizontally, that is,
		the bits of a byte will be represented by eight pixels in
		a row. If the number of collumns is not an integer multiple 
		of eight, then some bits from the last byte belonging to the
		row will be discarded and the next row starts on a byte boundary.
		If a bit in a byte is '1' ink is deposited, if '0' not.
		The leftmost pixel is represented by the MSB, rightmost by LSB.
		In case of raw data that's about it.
		
		In case of run-length encoded data, the following is done:
		The first byte is a counter. If the counter is <= 127 then
		the following counter+1 bytes are uncompressed data.
		If the counter is >= 128 then the following single byte should
		be repeated 257-counter times. 
		
	There are resolution restrictions:
	
		360x360 nozzle= 1 microweave on
		360x360 nozzle=32 microweave off
		720x 90 nozzle=32 microweave off
		720x720	nozzle= 1 microweave on

	Other combinations are not supported.

ESC ( e 02 00 00 nn

	Sets the amount of ink spat onto the paper.
	
	nn
	01		microdots (faint printing)
	02		normal dots (not so faint printing)
	03		double dots (full inking)
	04		super microdots (ink is continuously dripping :-)
	
	Values other than that have apparently no effect.
	
ESC ( K 02 00 xxxx

	This command is sent by the Windows driver but it is not used
	in the Epson test images. I have not found it having any effect
	whatsoever. The driver does not use it. The Epson docs don't
	mention it.
	
ESC ( r 02 00 nn mm					
	
	Selects the ink according to this:

	nn mm
	00 00	black
	00 01	magenta
	00 02	cyan
	00 04	yellow
	01 01	light magenta
	01 02	light yellow


ESC ( \ 04 00 xxxx llll

	Horizontal positioning of the head.
	
	Moves the head to the position llll times 1/xxxx inches from
	the left margin. 
	On the example images xxxx was always set to 1440.
	I tried other values in which case the command was ignored,
	so stick to 1440.

	
ESC ( R ll 00 00 <text> <cc> xxxx nn .. nn 
ESC 00 00 00

	This is supposedly sets the printer into 'remote' mode.
	ll is the length of the <text> + 1 which consists of ASCII
	characters (e.g. REMOTE1). 
	<cc> is a two-character code, for example "SN" or "LD".
	xxxx is the number of bytes (nn -s) which will follow.
	After that there's either a new <cc> xxxx nn .. nn sequence or
	the ESC 00 00 00.
	I have absolutely no idea about this command and the Epson document
	says that it's in an other document. It's not in that other one.
	The driver does not use it. The printer does not miss it.
	The Epson test images use it and the Windows driver uses it too.
	They send different <cc>-s and different values for identical <cc>-s.
	Go figure.
	
DRIVER INTERNALS
~~~~~~~~~~~~~~~~
First, some comments.
Anything I know about the printer can be found above. 
Anything I know about Ghostscript internals (not much) can be 
found in the comments in the code. I do not believe in the 'it was hard 
to write, it should be hard to read' principle since I once had to 
understand my own code.
Therefore, the code has lots of comments in it, sometimes apparently
superfluous but I find it easier to understand the program 6 months 
later that way.
I did not follow the Ghostscript or GNU style guide, I write code the way
I like it - I'm a lazy dog :-) I use hard tabs at every 4th position,
I use a *lot* of whitespace (as recommended by K&R in their original
C book) and I have a formatting style similar to the K&R with the 
notable exception that I do not indent variable declarations that follow 
the curly. Anyway, you can run your favourite C formatter through the 
source.

In addition to the above, the driver is not hand-optimised, it assumes 
that it is compiled with a good optimising compiler which will handle
common subexpression ellimination, move loop independent code out of
the loop, transform repeated array accesses to cached pointer arithmetics
and so on. The code is much more readable this way and gcc is fairly 
good at doing optimisation. Feel free to hand-optimise it.

So, the driver works the following way:

When it has to render a page, first it sets up the basics such as margins
and papersize and alike.

Line scheduling
---------------

Then it calls the line scheduler. To see why do we have a scheduler, you
have to understand weaving. The printer head has 32 nozzles which are
spaced at 8 line intervals. Therefore, it prints 32 lines at a time but they
are distributed over a 256 line high area. Obviously, if you want to print 
all the lines under the head, you should pass over the paper 8 times.  
You can do it the obvious way:
Print, move down by one line, print ... repeat 8 times then move down
by 256 - 8 lines and start again. Unfortunately, this would result in
stripy images due to the differences between individual nozzles.
Lines 0-7 would be printed by nozzle 0, 8-15 by nozzle 1 and so on. An
8 line band has a visible height, so difference between nozzles will
cause 8-line high bands to appear on the image.

The solution is 'microweave', a funny way of doing interlaced printing.
Instead of moving down 1, 1, 1, 1, .. 1, 248, 1, 1 .. you move down
a constant, larger amount (called a band). This amount must be chosen 
in such a way that each line will be printed and preferably it will be 
printed only once.

Let for example the move down amount (the band) be 31. Let's say, 
in band N nozzle 31 is over line 300, in which case nozzle 30 is over
line 292. We move the head down by 31 lines, then line 299 will be 
under nozzle 27 and line 307 under nozzle 28.
Next move, nozzle 23 will print line 298 and nozzle 24 line 306, then
19/297 20/305, 15/296 16/304, 11/295 12/303, 7/294 8/302, 3/293 4/302,
0/292 3/301 which covers the entire area between 292 and 307. 
The same will apply to any other area on the page. Also note that 
adjacent lines are always printed by different nozzles. 
You probably have realised that line 292 was printed in the first pass
and in the last one. In this case, of course, the line must not be printed 
twice, one or the other pass should not deliver data to the nozzle which
passes over this line.

Now there's a twist. When the horizontal resolution is 1440 dpi you have
to print each line twice, first depositing all even pixels then offset
the head by 1/1440" and deposit all odd pixels (the printer can only
print with 720 dpi but you can initially position the head with 1440 dpi
resolution). You could do it the easy way, passing over the same area 
twice but you can do better. You can find a band size which will result 
each line being printed twice. Instead of suppressing the double print, 
you use this mechanism to print the odd and the even pixels.
Now if you print one line's odd pixels, obviously, all lines belonging
to the 31 other nozzles of the head will have their odd pixels printed too.
Therefore, you have to keep track which lines have been printed in which
phase and try to find an odd-even phase assignment to bands so that each line
has both groups printed (and each group only once). 
The added bonus is that even the same line will be printed by two different
nozzles thus effects of nozzle differences can be decreased further.

The whole issue is further complicated with the beginning of the page and 
the end of the page. When you print the first 8 lines you *must* use the
print, down by 1, print ... method but then you have to switch over to the
banding method. To do it well, you should minimise the number of lines which
are printed out of band. This optimisation is not complex but not trivial 
either. Our solution is to employ precalculated tables for the first 8 lines.
(Epson's solution is not to print the 'problematic' lines at all - they
warn you in the manual that at the top and bottom you may have "slight
distortions". Analyzing their output reveals the reason ... ).
The bottom is different. It is easier, because you are already banding, so 
you can't screw up the rest of the image. On the other hand, you can't use
tables because these tables would depend on the page height which you don't
know a priori. Our solution is to switch to single line mode when we can 
not do the banding any more and try to finish the page with the minimal 
amount of passes.

So, first the driver calls the scheduler which returns a list of lines which 
it dispatched to print in the current band. Then the driver checks if it has 
all these lines halftoned. Since the head covers an area of 256 lines, we 
have to buffer that many lines (actually, 256-7). As the head moves down, 
we can flush lines which it has left and halftone the new ones.


Colour transformations
----------------------

The next important issue is the colour transformation. The reason for doing
this is that the ink is not perfect. Ideally, you have 3 inks, namely cyan
magenta and yellow. Mixing these you can have all colours. Now the inks
are not pure, that is the cyan ink contains some particles that have a
colour other than the ideal cyan and so on. In addition, the inks are
not exactly cyan, magenta and yellow. Therefore, you have to do some
transformations that will map the ideal C, M, Y values to amounts of
ink of the real kind. You also have a black ink. Although in theory
mixing C, M, Y in equal amount will give you black, it doesn't exactly
work that way. In addition, black ink is cheap compared to the colour
so if you can use black, you rather use that. On top of all that, 
because of other effects (ink splashing on the paper and things like that) 
you have to apply some non-linear functions to get reasonable colours.

Halftoning
----------

The driver has different halftoning methods.
There is the classic Floyd-Stenberg error diffusion. There is an other
ED, of which I'm hammering the matrix. The matrix is larger than the
FS one and IMHO results in somewhat lower halftoning noise. However,
it completely screws up some flat colours so don't use it.
There is also dithering, which is quick but noisy.

For any halftoning method, it is assumed that the haltoning can be 
done on the 4 colours (CMYK) separately and all interdependencies are
already handled. It is an optimistic assumption, however, close enough.

You can add any halftoning method you like by writing a halftoner
module. A halftoner module consists of 4 functions:

- Init, which is called before halftoning starts.
- Threshold, which should return a number which tells the driver how many
  empty lines needed before halftoning can be stopped (i.e. for how many 
  lines will a line affect halftoning of subsequent lines).
- Halftone, which halftones one colour of one line
- EndOfLine which is called when all colours of a scanline are halftoned,
  you can do your housekeeping functions here.

For example, in the case of ED init() clears the error buffers, threshold()
returns ~5 (5 empty lines are enough for the accumulated error to go to 
almost zero), endofline() shuffles the error buffers and halftone() itself
does the error diffusion. In case of dithering, threshold is 0 (dithering
has no memory), init and endofline do nothing and halftone simply
dithers a line.

A few options are available for all halftoners:

- the black is rendered first. Now this black line is presented to all
  further passes. If a pixel is painted black, there's no point to
  deposit any other colour on it, even if the halftoning itself would do.
  Therefore, an already set black pixel can block the halftoning of colours
  for that pixel. Whether this thing is activated or not is a command line
  switch (default is on). Your halftoner may choose to ignore this flag.
  
- the intensity value of the light-cyan and light-magenta ink can be
  set from the command line. My experience is that the default 127 is
  good enough, but you can override it if you want to.
  
Apart from these features, each halftoner can have all sorts of other 
switches. Currently there are switches for the Bendor ED, see the 
comments in front of the BendorLine() function to see what they are.

Postprocessing
--------------

After lines are halftoned, they are packed into bitstreams. If you use
1440x720 then the 2 passes for the horizontal interleave are separated.
Postprocessing should also do the shingling/depletion, but it is not
yet done.

Compression
-----------

The driver, before it sends the data to the printer, compresses it using
RLE (run-length encoding) compression. It is not very effective but still
more than nothing. I have not yet ventured into using TIFF as output format,
it may come later.

*/

/****************************************************************************/
/*						Device specific definitions							*/
/****************************************************************************/

/*
*	Device limits
*/

#define	MAX_WIDTH	11.46			/* Maximum printable width, 8250 dots	*/
#define	MAX_PIXELS	8250
#define	MAX_BYTES	(MAX_PIXELS+7)/8

/*
*	Margins (in inch)
*/

#define	MARGIN_L	0.12			/* Left margin							*/
#define	MARGIN_R	0.12			/* Right margin							*/
#define	MARGIN_T	0.12			/* Top margin							*/
#define	MARGIN_B	0.50			/* Bottom margin (should be 0.54 !)		*/

/*
*	We default to 720x720 dpi
*/

#define	Y_DPI		720				/* Default vertical resolution	[dpi]	*/
#define	X_DPI		720				/* Default horizontal resolution [dpi]	*/

/*
*	Encoding of resolutions. Does *not* work with 1440 dpi !
*/

#define	RESCODE( x )	(3600/(x))

/*
*	The device has 6 different inks
*/

#define	DCOLN			6

/*
*	Device colour codes
*	CAVEAT: if you change them change the SendColour() procedure too !
*/

#define	DEV_BLACK		0
#define	DEV_CYAN		1
#define	DEV_MAGENTA		2
#define	DEV_YELLOW		3
#define	DEV_LCYAN		4
#define	DEV_LMAGENTA	5

/*
*	The head has 32 nozzles, with 8 x 1/720" spacing
*/

#define	NOZZLES			32
#define	HEAD_SPACING	8

/*
*	Some ASCII control characters
*/

#define	CR				13			/* Carriage return						*/
#define	FF				12			/* Form feed							*/
#define	ESC				"\033"		/* Escape								*/

/****************************************************************************/
/*						Internally used definitions							*/
/****************************************************************************/

#ifndef	TRUE
#define	TRUE	1
#endif

#ifndef	FALSE
#define	FALSE	0
#endif

/*
*	Since the printer is CMYK, we use 4 colours internally
*/

#define	ICOLN			4

/*
*	This is the maximum number of error lines needed by any 
*	currently implemented rendering function.
*	If you need more, increase it.
*/

#define	MAX_ED_LINES	3

/*
*	If this is defined to !0 then we use Adobe's CMYK -> RGB mapping, 
*	Ghostscript's otherwise. Ghostscript claims that their mapping
*	is better. The mapping of CMYK to RGB according to Adobe is:
*	
*		R = 1.0 - min( 1.0, C + K )
*		G = 1.0 - min( 1.0, M + K )
*		B = 1.0 - min( 1.0, Y + K )
*
*	while Ghostscript uses this:
*	
*		R = ( 1.0 - C ) * ( 1.0 - K )
*		G = ( 1.0 - M ) * ( 1.0 - K )
*		B = ( 1.0 - Y ) * ( 1.0 - K )
*/

#define	MAP_RGB_ADOBE	0

/*
*	We store a CMYK value in a 32 bit entity, each component being 8 bit.
*	These macros pack and unpack these blocks.
*	Ghostscript guarantees that when we get them back the unsigned long
*	will be placed in memory in a big-endian format (regardless of the
*	actual architecture it's running on), so we declare the colour offsets
*	accordingly.
*/

#define	OFFS_C		0
#define	OFFS_M		1
#define	OFFS_Y		2
#define	OFFS_K		3

#define	DECOMPOSE_CMYK( index, c, m, y, k ) \
	{										\
		(k) = (index) & 255;				\
		(y) = ( (index) >> 8 ) & 255;		\
		(m) = ( (index) >> 16 ) & 255;		\
		(c) = ( (index) >> 24 ) & 255; 		\
	}

#define	BUILD_CMYK( c, m, y, k ) \
	((((long)(c)&255)<<24)|(((long)(m)&255)<<16)|\
	(((long)(y)&255)<<8)|((long)(k)&255))

/*
*	This structure is for colour compensation
*/
	
typedef	struct {

	int		ra;						/* Real colour angle (hue)				*/
	int		ia;						/* Theoretical ink colour angle 		*/
	int		c;						/* Cyan component						*/
	int		m;						/* Magenta component					*/
	int		y;						/* Yellow component						*/
	
} CCOMP;

/*
*	Our device structure has some extensions
*/

typedef	struct gx_photoex_device_s {

	gx_device_common;				/* This macro defines a graphics dev.	*/
	gx_prn_device_common;			/* This macro extends for printer dev.	*/
	int		shingling;				/* Shingling (multipass, overlap) mode	*/
	int		depletion;				/* Excess dot removal					*/
	int		halftoner;				/* Rendering type						*/
	int		splash;					/* Splashing compensation factor		*/
	int		leakage;				/* Error leakage (percentage)			*/
	int		mono;					/* Monochrome mode (black only)			*/
	int		pureblack;				/* Black ink blocks others				*/
	int		midcyan;				/* Light cyan ink value					*/
	int		midmagenta;				/* Light magenta ink value				*/
	int		dotsize;				/* Size of the ink dot					*/

} gx_photoex_device;

/*
*	These can save some typing
*/

typedef	gx_device			DEV;
typedef	gx_device_printer	PDEV;
typedef	gx_photoex_device	EDEV;
typedef	gx_color_index		CINX;
typedef	gx_color_value		CVAL;
typedef	gs_param_list		PLIST;
typedef	gs_param_name		PNAME;

/*
*	How many lines do we have to think ahead
*/

#define	MAX_MARK		((NOZZLES)*(HEAD_SPACING))

/*
*	This structure stores a device scanline for one colour
*/

typedef	struct	{

	int		first;					/* Index of the first useful byte	*/
	int		last;					/* Index of the last useful byte	*/
	byte	data[ MAX_BYTES ];		/* Actual raw data					*/

} RAWLINE;

/*
*	These definitions are used by the microweave scheduler.
*	These are the band height definitions. Do not fiddle with them,
*	they are the largest number with which no lines are skipped 
*	and the unused nozzles in the head for each band is minimal.
*	They, of course, depend on the number of nozzles in the head
*	and their spacing, these numbers are for 32 and 8, respectively.
*/

#define	BAND_1440		13			/* Band height for 1440dpi, double scan	*/
#define	BAND_720		31			/* Band height for 720dpi, single scan	*/
#define	BAND_360		1			/* Band height for 360dpi, single scan	*/

#define	NOZZLE_1440		(NOZZLES)	/* Number of nozzles used for 1440dpi	*/
#define	NOZZLE_720		(NOZZLES)	/* Number of nozzles used for 720dpi	*/
#define	NOZZLE_360		1			/* Number of nozzles used for 360dpi	*/

/*
*	This structure is used to generate the line scheduling data.
*	Input/output refers to the scheduler I/F: input means data 
*	given to the scheduler, output is what it gives back. Unspecified
*	data is scheduler private.
*/

typedef	struct {

	int		last;					/* Input	Last line to print			*/
	int		resol;					/* Input	X Resolution				*/
	int		nozzle;					/* Output	Number of nozzles			*/
	int		down;					/* Output	Lines to move down			*/
	int		head[ NOZZLES ];		/* Output	Which lines to be sent		*/
	int		offset;					/* Output	Offset line by 1/1440"		*/
	int		top;					/* 			Head position now			*/
	int		markbeg;				/* 			First marked line			*/
	byte	mark[ MAX_MARK ];		/* 			Marks already printed lines	*/
	
} SCHEDUL;

/*
*	These macros are used to access the printer device
*/

#define	SendByte( s, x )	fputc( (x), (s) )

#define	SendWord( s, x )	SendByte((s), (x) & 255); \
							SendByte((s), ((x) >> 8 ) & 255);

/*
*	This structure stores all the data during rendering
*/

typedef	struct {
	
	EDEV	*dev;					/* The actual device struct			*/
	FILE	*stream;				/* Output stream					*/
	int		yres;					/* Y resolution						*/
	int		xres;					/* X resolution						*/
	int		start;					/* Left margin in 1/1440 inches		*/	
	int		width;					/* Input data width in pixels		*/
	int		lines;					/* Number of lines					*/
	int		mono;					/* Black only						*/
	byte	*dbuff;					/* Data buffer 						*/
	int		htone_thold;			/* Halftoner restart threshold		*/
	int		htone_last;				/* Last line halftoned				*/
	SCHEDUL	schedule;				/* Line scheduling info				*/
	
	/* These are the error buffers for error diffusion. MAX_PIXELS*2
	   is needed for 1440 dpi printing. */
	
	short	err[ MAX_ED_LINES ][ ICOLN ][ MAX_PIXELS*2 ];
	
	/* Error buffer pointers. I love C :-) */
	
	short	( *error[ MAX_ED_LINES ] )[ MAX_PIXELS*2 ];
	
	/* This stores the halftoning result for a line, 
	   not yet in device format. (It's CMYK 1 byte/pixel/colour) */
	
	byte	res[ ICOLN ][ MAX_PIXELS*2 ];
	
	/* This is the buffer for rendered lines, converted
	   to raw device data (not yet run-length encoded).
	   That is, it's 6 colours, 1 bit/pixel/colour.
	   The first index is the 1440 dpi X-weave phase. */
	   
	RAWLINE	raw[ 2 ][ DCOLN ][ MAX_MARK ];	
	
	/* This buffer stores a single line of one colour,
	   run-length encoded, ready to send to the printer */
	
	byte	rle[ MAX_PIXELS * 2 ];
	
} RENDER;

/*
*	This is the sctructure used by the actual halftoner algorithms
*/

typedef	struct	{
	
	RENDER	*render;				/* Render info, if needed				*/
	byte	*data;					/* Input data							*/
	int 	step;					/* Steps on input data					*/
	byte	*res;					/* Result								*/
	byte	*block;					/* Blocking data						*/
	short	**err;					/* Pointers to error buffers			*/
	int		lim1; 					/* Halftoning lower limit				*/
	int		lim2; 					/* Halftoning upper limit				*/
	int		mval; 					/* Level represented by 'light' colour	*/
	
} HTONE;	

/*
*	Halftoner function table
*/

typedef	struct {

	int		(*hthld)( RENDER *rend );
	void	(*hstrt)( RENDER *rend, int line );
	void	(*hteol)( RENDER *rend, int line );
	void	(*htone)( HTONE *htone, int line );

} HFUNCS;

/*
*	Number of known halftoning methods
*/

#define	MAXHTONE		3

/*
*	Dither matrix size
*/

#define	DMATRIX_X		16
#define	DMATRIX_Y		16
	
/****************************************************************************/
/*							Prototypes										*/
/****************************************************************************/

private int		photoex_open( gx_device *pdev );
private	int		photoex_print_page( PDEV *dev, FILE *prn_stream );
private	CINX	photoex_map_rgb_color( DEV *dev, CVAL r, CVAL g, CVAL b );
private int		photoex_map_color_rgb( DEV *dev, CINX index, CVAL prgb[3] );
private	int		photoex_get_params( DEV *dev, PLIST *plist );
private	int		photoex_put_params( DEV *dev, PLIST *plist );

private int 	PutInt( PLIST *plist, PNAME name, int *val,
						int minval, int maxval, int code );
private	int		GetInt( PLIST *list, PNAME name, int *value, int code );

private	int		Cmy2A( int c, int m, int y );

private	void	SchedulerInit( SCHEDUL *p );
private	int		ScheduleLines( SCHEDUL *p );
private	void	ScheduleLeading( SCHEDUL *p );
private	void	ScheduleMiddle( SCHEDUL *p );
private	void	ScheduleTrailing( SCHEDUL *p );
private	void	ScheduleBand( SCHEDUL *p, int mask );

private	void	RenderPage( RENDER *p );
private	void	RenderLine( RENDER *p, int line );
private	int		IsScanlineEmpty( RENDER *p, byte *line );

private	int		RleCompress( RAWLINE *raw, int min, int max, byte *rle_data );
private	int		RleFlush( byte *first, byte *reps, byte *now, byte *out );

private	void	SendReset( FILE *stream );
private	void	SendMargin( FILE *stream, int top, int bot );
private	void	SendPaper( FILE *stream, int length );
private	void	SendGmode( FILE *stream, int on );
private void	SendUnit( FILE *stream, int res );
private	void	SendUnidir( FILE *stream, int on );
private	void	SendMicro( FILE *stream, int on );
private void	SendInk( FILE *stream, int x );
private	void	SendDown( FILE *stream, int x );
private	void	SendRight( FILE *stream, int amount );
private	void	SendColour( FILE *stream, int col );
private void	SendData( FILE *stream, int hres, int vres, int noz, int col );
private	void	SendString( FILE *stream, const char *s );

private	void	HalftonerStart( RENDER *render, int line );
private	int		HalftoneThold( RENDER *render );
private	void	HalftoneLine( RENDER *render, int line, byte *data );

private	int		BendorThold( RENDER *p );
private	void	BendorStart( RENDER *p, int line );
private	void	BendorEol( RENDER *p, int line );
private	void	BendorLine( HTONE *htone, int y );

private	int		FloydSThold( RENDER *p );
private	void	FloydSStart( RENDER *p, int line );
private	void	FloydSEol( RENDER *p, int line );
private	void	FloydSLine( HTONE *htone, int y );

private	int		DitherThold( RENDER *p );
private	void	DitherStart( RENDER *p, int line );
private	void	DitherEol( RENDER *p, int line );
private	void	DitherLine( HTONE *htone, int y );

/****************************************************************************/
/*							Static data										*/
/****************************************************************************/

/*
*	Halftoner function table
*/

private	const HFUNCS	htable[ MAXHTONE ] = {

	{ FloydSThold, FloydSStart, FloydSEol, FloydSLine },
	{ DitherThold, DitherStart, DitherEol, DitherLine },
	{ BendorThold, BendorStart, BendorEol, BendorLine }
};

/*
*	Define the printer procedures.
*	The definition is based on GS macros, the only real stuff that we 
*	define here are the photoex_ functions.
*/

private	gx_device_procs photoex_device_procs = prn_color_params_procs(

	photoex_open,					/* Opens the device						*/
	gdev_prn_output_page,
	gdev_prn_close,
	photoex_map_rgb_color,			/* Maps an RGB pixel to device colour	*/
	photoex_map_color_rgb,			/* Maps device colour back to RGB		*/
	photoex_get_params,				/* Gets device parameters				*/
	photoex_put_params				/* Puts device parameters				*/
);

/*
*	Device descriptor structure - this is what GhostScript looks
*	for and uses to identify our device.
*	Do not make it private (or static) !
*/

gx_photoex_device far_data gs_photoex_device = {
	
	/* This is a macro that fills GS specific fields in the struct */
	
	prn_device_body(
	
		gx_photoex_device,			/* Device struct type					*/
		photoex_device_procs, 		/* Procedure table						*/
		"photoex",					/* Name of the device					*/
		DEFAULT_WIDTH_10THS,		/* Default width						*/
		DEFAULT_HEIGHT_10THS,		/* Default height						*/
		X_DPI, 						/* Vertical resolution					*/
		Y_DPI,						/* Horizontal resolution				*/
		MARGIN_L, 					/* Left margin							*/
		MARGIN_B, 					/* Bottom margin						*/
		MARGIN_R,					/* Right margin							*/
		MARGIN_T, 					/* Top margin							*/
		ICOLN,						/* Number of colours (4:CMYK)			*/
		32,							/* Bit per pixel for the device(!)		*/
		255,						/* Max. gray level						*/
		255, 						/* Max. colour level					*/
		256,						/* Number of gray gradations			*/
		256,						/* Number of colour gradations			*/
		photoex_print_page			/* Print page procedure					*/
	),
	
	/* Here come our extensions */
		
	0,								/* Shingling off, not implemented		*/
	0,								/* Depletion off, not implemented		*/
	0,								/* Dither type: FS ED					*/
	0,								/* No splash correction					*/
	0,								/* No leakage							*/
	0,								/* Not monochrome						*/
	1,								/* Colour inhibition on black			*/
	127,							/* Mid level cyan						*/
	127,							/* Mid level magenta					*/
	0								/* Automatic dot size setting			*/
};

/*
*	This table contains the line scheduling table for the first 
*	few runs if we are in 720 dpi mode.
*/

private	const int	start_720[ HEAD_SPACING ][ NOZZLES ] = {

	{	  0,	  8,	 16,	 24,	 32,	 40,	 48,	 56,
		 64,	 72,	 80,	 88,	 96,	104,	112,	120,
		128,	136,	144,	152,	160,	168,	176,	184,
		192,	200,	208,	216,	224,	232,	240,	248 },

	{	  1,	  9,	 17,	 25,	 33,	 41,	 49,	 57,
		 65,	 73,	 81,	 89,	 97,	105,	113,	121,
		129,	137,	145,	153,	161,	169,	177,	185,
		193,	201,	209,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  2,	 10,	 18,	 26,	 34,	 42,	 50,	 58,
		 66,	 74,	 82,	 90,	 98,	106,	114,	122,
		130,	138,	146,	154,	162,	170,	178,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  3,	 11,	 19,	 27,	 35,	 43,	 51,	 59,
		 67,	 75,	 83,	 91,	 99,	107,	115,	123,
		131,	139,	147,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  4,	 12,	 20,	 28,	 36,	 44,	 52,	 60,
		 68,	 76,	 84,	 92,	100,	108,	116,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  5,	 13,	 21,	 29,	 37,	 45,	 53,	 61,
		 69,	 77,	 85,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  6,	 14,	 22,	 30,	 38,	 46,	 54,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  7,	 15,	 23,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 }
};


/*
*	This table contains the scheduling table for the first 
*	few lines if we are in 1440 dpi mode
*/

private	const int	start_1440[ 2 ][ HEAD_SPACING ][ NOZZLES ] = {
  {
	{	  0,	  8,	 16,	 24,	 32,	 40,	 48,	 56,
		 64,	 72,	 80,	 88,	 96,	104,	112,	120,
		128,	136,	144,	152,	160,	168,	176,	184,
		192,	200,	208,	216,	224,	232,	240,	248 },

	{	  1,	  9,	 17,	 25,	 33,	 41,	 49,	 57,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  2,	 10,	 18,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  3,	 11,	 19,	 27,	 35,	 43,	 51,	 59,
		 67,	 75,	 83,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  4,	 12,	 20,	 28,	 36,	 44,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  5,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  6,	 14,	 22,	 30,	 38,	 46,	 54,	 62,
		 70,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  7,	 15,	 23,	 31,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

  },
  {
	{	  0,	  8,	 16,	 24,	 32,	 40,	 48,	 56,
		 64,	 72,	 80,	 88,	 96,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  1,	  9,	 17,	 25,	 33,	 41,	 49,	 57,
		 65,	 73,	 81,	 89,	 97,	105,	113,	121,
		129,	137,	145,	153,	161,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  2,	 10,	 18,	 26,	 34,	 42,	 50,	 58,
		 66,	 74,	 82,	 90,	 98,	106,	114,	122,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  3,	 11,	 19,	 27,	 35,	 43,	 51,	 59,
		 67,	 75,	 83,	 91,	 99,	107,	115,	123,
		131,	139,	147,	155,	163,	171,	179,	187,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  4,	 12,	 20,	 28,	 36,	 44,	 52,	 60,
		 68,	 76,	 84,	 92,	100,	108,	116,	124,
		132,	140,	148,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  5,	 13,	 21,	 29,	 37,	 45,	 53,	 61,
		 69,	 77,	 85,	 93,	101,	109,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  6,	 14,	 22,	 30,	 38,	 46,	 54,	 62,
		 70,	 78,	 86,	 94,	102,	110,	118,	126,
		134,	142,	150,	158,	166,	174,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

	{	  7,	 15,	 23,	 31,	 39,	 47,	 55,	 63,
		 71,	 79,	 87,	 95,	103,	111,	119,	127,
		135,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,
		 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1,	 -1 },

  }
};

/*
*	This is the dither matrix we use for ordered dither
*	It is a shameless copy of Ghostscript's own ...
*/

private	byte	dmatrix[ DMATRIX_Y ][ DMATRIX_X ] = {
	{
		0x0e, 0x8e, 0x2e, 0xae, 0x06, 0x86, 0x26, 0xa6,
		0x0c, 0x8c, 0x2c, 0xac, 0x04, 0x84, 0x24, 0xa4
	},
	{
		0xce, 0x4e, 0xee, 0x6e, 0xc6, 0x46, 0xe6, 0x66,
		0xcc, 0x4c, 0xec, 0x6c, 0xc4, 0x44, 0xe4, 0x64
	},
	{
		0x3e, 0xbe, 0x1e, 0x9e, 0x36, 0xb6, 0x16, 0x96,
		0x3c, 0xbc, 0x1c, 0x9c, 0x34, 0xb4, 0x14, 0x94
	},
	{
		0xfe, 0x7e, 0xde, 0x5e, 0xf6, 0x76, 0xd6, 0x56,
		0xfc, 0x7c, 0xdc, 0x5c, 0xf4, 0x74, 0xd4, 0x54
	},
	{
		0x01, 0x81, 0x21, 0xa1, 0x09, 0x89, 0x29, 0xa9,
		0x03, 0x83, 0x23, 0xa3, 0x0b, 0x8b, 0x2b, 0xab
	},
	{
		0xc1, 0x41, 0xe1, 0x61, 0xc9, 0x49, 0xe9, 0x69,
		0xc3, 0x43, 0xe3, 0x63, 0xcb, 0x4b, 0xeb, 0x6b
	},
	{
		0x31, 0xb1, 0x11, 0x91, 0x39, 0xb9, 0x19, 0x99,
		0x33, 0xb3, 0x13, 0x93, 0x3b, 0xbb, 0x1b, 0x9b
	},
	{
		0xf1, 0x71, 0xd1, 0x51, 0xf9, 0x79, 0xd9, 0x59,
		0xf3, 0x73, 0xd3, 0x53, 0xfb, 0x7b, 0xdb, 0x5b
	},
	{
		0x0d, 0x8d, 0x2d, 0xad, 0x05, 0x85, 0x25, 0xa5,
		0x0f, 0x8f, 0x2f, 0xaf, 0x07, 0x87, 0x27, 0xa7
	},
	{
		0xcd, 0x4d, 0xed, 0x6d, 0xc5, 0x45, 0xe5, 0x65,
		0xcf, 0x4f, 0xef, 0x6f, 0xc7, 0x47, 0xe7, 0x67
	},
	{
		0x3d, 0xbd, 0x1d, 0x9d, 0x35, 0xb5, 0x15, 0x95,
		0x3f, 0xbf, 0x1f, 0x9f, 0x37, 0xb7, 0x17, 0x97
	},
	{
		0xfd, 0x7d, 0xdd, 0x5d, 0xf5, 0x75, 0xd5, 0x55,
		0xff, 0x7f, 0xdf, 0x5f, 0xf7, 0x77, 0xd7, 0x57
	},
	{
		0x02, 0x82, 0x22, 0xa2, 0x0a, 0x8a, 0x2a, 0xaa,
		0x01, 0x80, 0x20, 0xa0, 0x08, 0x88, 0x28, 0xa8
	},
	{
		0xc2, 0x42, 0xe2, 0x62, 0xca, 0x4a, 0xea, 0x6a,
		0xc0, 0x40, 0xe0, 0x60, 0xc8, 0x48, 0xe8, 0x68
	},
	{
		0x32, 0xb2, 0x12, 0x92, 0x3a, 0xba, 0x1a, 0x9a,
		0x30, 0xb0, 0x10, 0x90, 0x38, 0xb8, 0x18, 0x98
	},
	{
		0xf2, 0x72, 0xd2, 0x52, 0xfa, 0x7a, 0xda, 0x5a,
		0xf0, 0x70, 0xd0, 0x50, 0xf8, 0x78, 0xd8, 0x58
	}
};

/*
*	This is the (minimalistic) colour compensation table
*/

static	CCOMP	ctable[] = {

	{ -255, -255,   0,   0, 255 },		/* same as green */
	{  102,    0, 255,   0,   0 },		/* cyan */
	{  255,  255, 255, 255,   0 },		/* blue */
	{  560,  512,   0, 255,   0 },		/* magenta */
	{  765,  765,   0, 255, 255 },		/* red */
	{ 1045, 1020,   0,   0, 255 },		/* yellow */
	{ 1275, 1275, 255,   0, 255 },		/* green */
	{ 1632, 1530, 255,   0,   0 }		/* same as cyan */
};

/*
*	This is the ink transfer function.
*	We use only one for all inks, this may be wrong.
*/

static const unsigned char	xtrans[ 256 ] = {

	  0,   0,   0,   0,   0,   0,   0,   0,   
	  0,   0,   0,   0,   0,   0,   0,   0, 
	  0,   0,   0,   0,   0,   0,   0,   0,   
	  0,   0,   0,   0,   0,   0,   0,   0, 
	  0,   0,   0,   0,   1,   1,   1,   1,   
	  1,   1,   1,   1,   1,   1,   1,   1, 
	  1,   1,   1,   1,   2,   2,   2,   2,   
	  2,   2,   2,   2,   2,   2,   3,   3, 
	  3,   3,   3,   3,   3,   4,   4,   4,   
	  4,   4,   4,   5,   5,   5,   5,   5, 
	  6,   6,   6,   6,   6,   7,   7,   7,   
	  7,   8,   8,   8,   8,   9,   9,   9, 
	 10,  10,  10,  11,  11,  11,  12,  12,  
	 12,  13,  13,  13,  14,  14,  14,  15, 
	 15,  16,  16,  17,  17,  17,  18,  18,  
	 19,  19,  20,  20,  21,  21,  22,  22, 
	 23,  23,  24,  24,  25,  26,  26,  27,  
	 27,  28,  29,  29,  30,  30,  31,  32, 
	 32,  33,  34,  34,  35,  36,  37,  37,  
	 38,  39,  40,  40,  41,  42,  43,  44, 
	 44,  45,  46,  47,  48,  49,  50,  51,  
	 51,  52,  53,  54,  55,  56,  57,  58, 
	 59,  60,  61,  62,  63,  64,  65,  67,  
	 68,  69,  70,  71,  72,  73,  74,  76, 
	 77,  78,  79,  80,  82,  83,  84,  86,  
	 87,  88,  89,  91,  92,  94,  95,  96, 
	 98,  99, 101, 102, 103, 105, 106, 108, 
	109, 111, 112, 114, 116, 117, 119, 120, 
	122, 124, 125, 127, 129, 130, 132, 134, 
	136, 137, 139, 141, 143, 145, 146, 148, 
	150, 152, 154, 156, 158, 160, 162, 164, 
	166, 168, 170, 172, 174, 176, 178, 180
};

/****************************************************************************/
/*							Device opening									*/
/****************************************************************************/

private int		photoex_open( DEV *pdev )
{
double	height;
double	width;
float	margins[ 4 ];						/* L, B, R, T					*/

	height = pdev->height / pdev->y_pixels_per_inch;
	width  = pdev->width  / pdev->x_pixels_per_inch;
	
	margins[ 0 ] = 0.12;
	margins[ 1 ] = 0.5;
	margins[ 2 ] = 0.12;
	margins[ 3 ] = ( width > 11.46+0.12 ) ? width - (11.46+0.12) : 0.12;
	
	gx_device_set_margins( pdev, margins, true );
	return( gdev_prn_open( pdev ) );
}

/****************************************************************************/
/*							Colour procedures								*/
/****************************************************************************/

/*
*	Map an RGB colour to device colour. 
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	Since we present ourselves to Ghostscript as if we were a 
*	full colour resolution RGB device, we calculate the CMYK 
*	values and pack them into the result. This depends on
*	color_index being at least 32 bit !!!
*/

private	CINX	photoex_map_rgb_color( DEV *dev, CVAL r, CVAL g, CVAL b )
{
int		c, y, m, k;
int		a, s, f;
EDEV	*edev;
int		i;

	edev = (EDEV *) dev;
		
	/* White and black are treated on their own */
	
	if ( ( r & g & b ) == ( 1 << gx_color_value_bits ) - 1 ) {
	
		/* White */
		
		return( BUILD_CMYK( 0, 0, 0, 0 ) );	
	}
	
	if ( ( r | g | b ) == 0 ) {
	
		/* Black */
		
		return( BUILD_CMYK( 0, 0, 0, xtrans[ 0xff ] ) );
	}

	/* Map RGB to 8 bit/colour CMY */
	
	c = 255 - ( r >> ( gx_color_value_bits - 8 ) );
	m = 255 - ( g >> ( gx_color_value_bits - 8 ) );
	y = 255 - ( b >> ( gx_color_value_bits - 8 ) );
	
	k = xtrans[ min( c, min( m, y ) ) ] * 0.8; /* FIXME:empirical constant */
	c -= k;
	m -= k;
	y -= k;
	
	s = max ( c, max( y, m ) );
			
	/* Map the colour to an angle and find the relevant table range */
	
	a = Cmy2A( c, m, y );
	for ( i = 1 ; a > ctable[ i ].ra ; i++ );

	/* Now map c, m, y. */
	
	f = ((a - ctable[ i-1 ].ra) << 16 ) / (ctable[ i ].ra - ctable[ i-1 ].ra);
	c = (( ctable[i-1].c << 16 ) + ( ctable[i].c - ctable[i-1].c ) * f ) >> 16;
	m = (( ctable[i-1].m << 16 ) + ( ctable[i].m - ctable[i-1].m ) * f ) >> 16;
	y = (( ctable[i-1].y << 16 ) + ( ctable[i].y - ctable[i-1].y ) * f ) >> 16;
	
	s = xtrans[ s ];
	c = ( c * s ) >> 8;
	m = ( m * s ) >> 8;
	y = ( y * s ) >> 8;
	
	return( BUILD_CMYK( c, m, y, k ) );
}

/*
*	Map a device colour value back to RGB. 
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	CAVEAT:
*	This mapping is *not* the inverse of the RGB->CMYK.
*	It does not do any ink transfer compensation, colour compensation etc.
*/

private int		photoex_map_color_rgb( DEV *dev, CINX index, CVAL prgb[3] )
{
uint	c, m, y, k;
CVAL	r, g, b;

	/* Let's separate the colours */
	
	DECOMPOSE_CMYK( index, c, m, y, k );
	
	k = index & 255;
	y = ( index >> 8 ) & 255;
	m = ( index >> 16 ) & 255;
	c = ( index >> 24 ) & 255;
	
	/* Depending on whether we use Adobe or Ghostscript mapping,
	   calculate the colours */
	   	
	if ( MAP_RGB_ADOBE ) {

		r = gx_max_color_value * ( 1.0 - min( 1.0, (c / 255.0 + k / 255.0) ) );
		g = gx_max_color_value * ( 1.0 - min( 1.0, (m / 255.0 + k / 255.0) ) );
		b = gx_max_color_value * ( 1.0 - min( 1.0, (y / 255.0 + k / 255.0) ) );
	}
	else {
	
		r = gx_max_color_value * ( 1.0 - c / 255.0 ) * ( 1.0 - k / 255.0);
		g = gx_max_color_value * ( 1.0 - m / 255.0 ) * ( 1.0 - k / 255.0);
		b = gx_max_color_value * ( 1.0 - y / 255.0 ) * ( 1.0 - k / 255.0);
	}
	
	prgb[ 0 ] = r;
	prgb[ 1 ] = g;
	prgb[ 2 ] = b;
	
	return( 0 );
}

/*
*	This function maps a (c,m,y) triplet into an angle.
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	Angle:    0 cyan	C=255 M=  0 Y=  0 
*			255 blue    C=255 M=255 Y=  0
*			510 magenta	C=  0 M=255 Y=  0
*			765 red     C=  0 M=255 Y=255
*		   1020 yellow	C=  0 M=  0 Y=255
*		   1275 green   C=255 M=  0 Y=255
*		   1530 cyan 
*/

private	int		Cmy2A( int c, int m, int y )
{
int		black;
int		maxim;
int		a;

	/* Calculate the black level */
	
	black = min( c, min( m, y ) );
	 
	/* Remove the black from the colours themselves */
	
	c -= black;
	m -= black;
	y -= black;
	
	/* If all 3 remaining colours are 0, then it is a gray: special case */
	
	if ( ! c && ! m && ! y ) return( 0 );
	
	/* Normalise the colours. At least one at most two of them is 0
	   and at least one at most two of them is 255 */
	
	maxim = max( c, max( m, y ) );
	
	c = ( 255 * c ) / maxim;
	m = ( 255 * m ) / maxim;
	y = ( 255 * y ) / maxim;
	
	if ( c == 255 ) {
	
		if ( ! y )
		
			a = m;					/* cyan - blue */
		else
			a = 1530 - y;			/* green - cyan */
	}
	else if ( m == 255 ) {
	
		if ( ! c )
			
			a = 510 + y;			/* magenta - red */
		else
			a = 510 - c;			/* blue - magenta */
	}
	else {
	
		if ( ! m )
			
			a = 1020 + c;			/* yellow - green */
		else
			a = 1020 - m;			/* red - yellow */
	}
	
	return( a );
}

/****************************************************************************/
/*						Device parameter handling							*/
/****************************************************************************/

/*
*	Tell Ghostscript all about our extra device parameters
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		photoex_get_params( DEV *device, PLIST *plist )
{
int		code;
EDEV	*dev;

	dev  = (EDEV *) device;
	
	code = gdev_prn_get_params( device, plist );
	
	code = GetInt( plist, "Depletion",	&dev->depletion, code );
	code = GetInt( plist, "Shingling",	&dev->shingling, code );
	code = GetInt( plist, "Render",  	&dev->halftoner, code );
	code = GetInt( plist, "Splash",		&dev->splash,    code );
	code = GetInt( plist, "Leakage",	&dev->leakage,   code );
	code = GetInt( plist, "Binhibit",	&dev->pureblack, code );
	code = GetInt( plist, "DotSize",	&dev->dotsize,	 code );	
	return( code );
}

/*
*	Get all extra device-dependent parameters
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		photoex_put_params( DEV *device, PLIST *plist )
{
int		code;
EDEV	*dev;

	dev  = (EDEV *) device;
	code = 0;
	
	code = PutInt( plist, "Depletion",	&dev->depletion, 0,		    2, code );
	code = PutInt( plist, "Shingling",	&dev->shingling, 0, 	    2, code );
	code = PutInt( plist, "Render",		&dev->halftoner, 0,MAXHTONE-1, code );
	code = PutInt( plist, "Splash",		&dev->splash,    0,		   50, code );
	code = PutInt( plist, "Leakage",	&dev->leakage,   0,		   25, code );
	code = PutInt( plist, "Binhibit",	&dev->pureblack, 0,		    1, code );
	code = PutInt( plist, "DotSize",	&dev->dotsize,   0,		    4, code );

	if ( code < 0 )
	
		return( code );
	else
		return( gdev_prn_put_params( device, plist ) );	
}

/*
*	Reads a named integer from Ghostscript
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private int 	PutInt( PLIST *plist, PNAME name, int *val,
						int minval, int maxval, int code )
{
int		new;

	/* If code is already an error, we return it and do nothing. */
	
	if ( code ) return( code );
	
	/* Otherwise we try to read the value */
	
	new = *val;
		
	switch ( code = param_read_int( plist, name, &new ) ) {
	
		case 1:						/* No such parameter defined, it's OK	*/
		
			code = 0;
			break;
		
		case 0:						/* We have received a value, rangecheck	*/
	
			if ( minval > new || new > maxval )
			
				param_signal_error( plist, name, gs_error_rangecheck );
			else
				*val = new;
				
			break;
		
		default:					/* Error								*/
			break;
	}
	
	return( code );
}

/*
*	Writes a named integer to Ghostscript
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		GetInt( PLIST *list, PNAME name, int *value, int code )
{
	if ( code < 0 ) return( code );
	return( param_write_int( list, name, value ) );
}

/****************************************************************************/
/*							Page rendering									*/
/****************************************************************************/

/*
*	This is the function that Ghostscript calls to render a page
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		photoex_print_page( PDEV *device, FILE *stream )
{
int			pixels;						/* Length of the line 				*/
int			x;							/* Work vars						*/
EDEV		*dev;						/* Our device						*/
RENDER		*render;					/* Rendering info					*/

int			xres, yres;
int			start, width;
int			unit;
double		psize;

	dev = (EDEV *) device;	
	
	/* Check if the resolution is one of the supported ones */
	
	yres = (int) dev->y_pixels_per_inch;
	xres = (int) dev->x_pixels_per_inch;
	
	if ( ! ( ( xres ==  360 && yres == 360 ) ||
			 ( xres ==  720 && yres == 720 ) ||
			 ( xres == 1440 && yres == 720 ) ) )
			 
		return( gs_error_rangecheck );
		
	pixels = gdev_prn_raster( device ) / sizeof( long );
	psize  = device->height / device->y_pixels_per_inch;
	
	/* Check if the requested width is within device limits.
	   The calculations are in 1440 dpi units. */
	
	start = 1440.0 * dev_l_margin( device );

	x = xres == 360 ? 4 : xres == 720 ? 2 : 1;
	
	if ( start + x * pixels > 2 * MAX_PIXELS ) {
	
		/* We're over the limit, clip width to the required level */
		
		width = ( 2 * MAX_PIXELS - start ) / x;
		
		/* It is rather inprobable that someone would set up a 
		   left margin wider than the printer, still ... */
		
		if ( width <= 0 ) return( gs_error_rangecheck );
	}
	else {
	
		/* We accept the width as it is */
		
		width = pixels;
	}
	
	/* Now try to get the memory we need. It's actually quite a lot,
	   since we have to cache 256 processed lines at 6kbyte each plus
	   we need error buffers and stuff. All in all, we'll request
	   about 1.5 ~ 2M. */
		
	if ( ! ( render = (RENDER *) gs_malloc( 1, sizeof( RENDER ), "PhotoEX" )))
		
		return_error( gs_error_VMerror );
	
	if ( ! ( render->dbuff = (byte *) gs_malloc( pixels, sizeof( long ), 
			"PhotoEX" ) ) ) {
			
		gs_free( render, 1, sizeof( RENDER ), "PhotoEX" );
		return_error( gs_error_VMerror );
	}
	
	/* We've done every possible check and preparation, now 
	   do the work. Fill the rest of the structure so we can pass 
	   it to the actual render routine. */
	
	render->dev		= dev;
	render->yres	= yres;
	render->xres	= xres;
	render->width	= width;
	render->lines	= dev->height;
	render->stream	= stream;		
	render->mono	= dev->mono;
	
 	/* Initialise the printer */
				
	SendReset( stream );
	SendReset( stream );
	SendGmode( stream, 1 );
	
	/* Set up units */
	
	unit = ( yres == 360 ) ? 360 : 720;
	SendUnit( stream, RESCODE( unit ) );
	
	/* Set up papersize and margins */
	
	SendPaper( stream, device->height / device->y_pixels_per_inch * unit );
	SendMargin( stream, ( psize - dev_b_margin( device ) ) * unit, 
					    dev_t_margin( device ) * unit );

	/* Dot size as per user setting */
	
	if ( dev->dotsize )
	
		SendInk( stream, dev->dotsize );
	else
		SendInk( stream, yres == 360 ? 3 : ( xres == 720 ? 2 : 1 ) );
	
	/* Microveawe is off, unidirectional printing on */
	
	SendMicro( stream, 0 );
	SendUnidir( stream, 1 );
	
	/* Render the page and send image data to printer */
	
	RenderPage( render );
		   			
	/* Eject the paper, reset printer */
	
	SendByte( stream, FF );
	SendReset( stream );
	
	/* Release the memory and return */
	
	gs_free( render->dbuff, pixels, sizeof( long ), "PhotoEX" );
	gs_free( render, 1, sizeof( RENDER ), "PhotoEX" );
	return( 0 );
}

/*
*	Renders a page
*	~~~~~~~~~~~~~~
*/

private	void	RenderPage( RENDER *p )
{
int		last_done;					/* The last line rendered				*/
int		last_need;					/* The largest line number we need		*/
int		move_down;					/* Amount of delayed head positioning	*/
int		last_band;					/* Indicates the last band				*/
int		min, max;					/* Min/max active bytes in a raw line	*/
int		phase;						/* 1440dpi X weave offset				*/
int		i, j, l, col;

	p->htone_thold = HalftoneThold( p );
	p->htone_last  = -1 - p->htone_thold;
	
	p->schedule.top   = -1;
	p->schedule.resol = p->xres;
	p->schedule.last  = p->lines;
	
	last_done = -1;
	move_down = 0;
		
	do {
	
		/* Schedule the next batch of lines */
		
		last_band = ScheduleLines( &p->schedule );
		
		/* Find the largest line number we have to process and
		   halftone all lines which have not yet been done */
		
		last_need = last_done;
		for ( i = NOZZLES-1 ; i >= 0 && p->schedule.head[ i ] == -1 ; i-- );
		if ( i >= 0 ) last_need = p->schedule.head[ i ];
		while ( last_need > last_done ) RenderLine( p, ++last_done );
		
		/* Now loop through the colours and build the data stream */
		
		phase = p->schedule.offset;
		
		for ( col = 0 ; col < DCOLN ; col++ ) {
		
			/* First see if we have to send any data at all */
			
			min = MAX_BYTES;
			max = 0;
				
			for ( i = 0 ; i < NOZZLES && i < p->schedule.nozzle ; i++ ) {
			
				if ( ( j = p->schedule.head[ i ] ) != -1 ) {
				
					j %= MAX_MARK;
					
					if ( p->raw[ phase ][ col ][ j ].first < min )
	
						min = p->raw[ phase ][ col ][ j ].first;
						
					if ( p->raw[ phase ][ col ][ j ].last > max )
	
						max = p->raw[ phase ][ col ][ j ].last;
				}
			}
			
			if ( min <= max ) {
			
				max++;
				
				/* We have to send data to the printer. If we have 
				   to position the head, do so now */
				
				if ( move_down ) {
				
					SendDown( p->stream, move_down );
					move_down = 0;
				}
				
				/* Set the desired colour */
				
				SendColour( p->stream, col );
				
				/* Move the head to the desired position */
				
				if ( p->xres == 360 )
				
					SendRight( p->stream, 4 * 8 * min );
					
				else if ( p->xres == 720 )
				
					SendRight( p->stream, 2 * 8 * min );
				else
					SendRight( p->stream, 8 * min + phase );
				
				/* Send the data */
				
				SendData( p->stream, p->xres, p->yres, p->schedule.nozzle, 
						  ( max-min ) * 8 );
				
				for ( i = 0 ; i < p->schedule.nozzle ; i++ ) {
				
					if ( ( j = p->schedule.head[ i ] ) == -1 ||
						 ( p->raw[ phase ][ col ][ j % MAX_MARK ].last <
						   p->raw[ phase ][ col ][ j % MAX_MARK ].first ) ) {

						l = RleCompress( NULL, min, max, p->rle );
					}
					else {
					
						l = RleCompress( p->raw[ phase ][ col ] + j % MAX_MARK,
									   min, max, p->rle );
					}
					
					fwrite( p->rle, l, 1, p->stream );
				}
				
				SendByte( p->stream, CR );
			}
		}
		
		/* Note the amount the head should go down before it prints the
		   next band */
		
		move_down += p->schedule.down;
	
	} while	( ! last_band );
}

/*
*	Render the the next scanline
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	If it finds a continuous sequence of empty lines, it renders
*	the first htone_thold number of them then stops calling the
*	actual rendering function (which is computationally expensive).
*	When it sees a nonempty line again, it restarts the renderer.
*/

private	void	RenderLine( RENDER *p, int line )
{
byte	*data;
int		i;

	/* Get the line from Ghostscript and see if its empty */
	
	gdev_prn_get_bits( (PDEV *) p->dev, line, p->dbuff, &data );

	if ( IsScanlineEmpty( p, data ) ) {
	
		if ( line - p->htone_last > p->htone_thold ) {
		
			/* The line is empty and is farer from the last nonempty
			   line than the threshold, no need to render it. */
			   
			for ( i = 0 ; i < DCOLN ; i++ ) {
			
				p->raw[ 0 ][ i ][ line % MAX_MARK ].first = MAX_BYTES;
				p->raw[ 0 ][ i ][ line % MAX_MARK ].last  = 0;
				p->raw[ 1 ][ i ][ line % MAX_MARK ].first = MAX_BYTES;
				p->raw[ 1 ][ i ][ line % MAX_MARK ].last  = 0;
				
			}
		}
		else {
			
			/* The line is empty but it is within the threshold, so we 
			   have to render it. We do not move the index, though */
			   
			HalftoneLine( p, line, data );
		}
	}
	else {
	
		/* This line is not empty */
		
		if ( line - p->htone_last >= p->htone_thold ) {
		
			/* Previous lines were empty and we have already stopped 
			   rendering them. We have to restart the renderer */
			   
			HalftonerStart( p, line );
		}
		
		/* Render the line and move the last active index to this line */
		
		HalftoneLine( p, line, data );
		p->htone_last = line;
	}
}

/*
*	This function tests if a scanline is empty
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		IsScanlineEmpty( RENDER *r, byte *line )
{
int		i;
long	*p;
	
	p = (long *) line;
	
	for ( i = 0 ; i < r->width ; i++ ) {
	
		if ( *p++ ) return( FALSE );
	}
	
	return( TRUE );	
}

/****************************************************************************/
/*						Microweaved line scheduling							*/
/****************************************************************************/

/*
*	Schedule head data for the next band
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	This function fills the SCHEDUL structure with information
*	about what to print. The head field will contain the line numbers
*	which are assigned to the nozzles in the head. -1 means that
*	no active data is assigned to the nozzle in this band.
*	The offset field is only used for horizontal microweaving, if it
*	is set then the line should be offseted by 1/1440".
*	The down field contains the number of units which the head should
*	move down when printing of the band is finished. Other fields are
*	mainly for the routine's internal use. At the first call, however,
*	the top field should be set to -1, the resol field should be set 
*	to 360, 720 or 1440 and the last field should contain the number
*	of lines to print (that is, last + 1 :-). 
*
*	The routine returns a flag indicating if this was the last print
*	for the page.
*/
	
private	int	ScheduleLines( SCHEDUL *p )
{
int		i;

	if ( p->top == -1 ) {
	
		/* First call, init everything, then fall through to the rest */
		
		SchedulerInit( p );
	}
	
	/* If nozzle is one, just schedule the next line and that's it.
	   You can use this feature for hardware microweave at 720 dpi,
	   the driver uses it for 360 dpi. */
	
	if ( p->nozzle == 1 ) {
	
		p->head[ 0 ] = p->top;
		p->down = 1;
		p->top++;
		return( p->top == p->last );
	}
	
	/* Release all expired entries in the mark array */
	
	for ( i = p->markbeg ; i < p->top ; i++ ) p->mark[ i % MAX_MARK ] = 0;
	p->markbeg = p->top;
	
	/* If top is less than the the head spacing, then create the image 
	   by single steps. This will cause banding on the very top, but
	   there's nothing we can do about it. We're still better than
	   Epson's driver which simply ignores the first few lines,
	   it does not even try to schedule them ... */
	   	
	if ( p->top < HEAD_SPACING ) {
	
		ScheduleLeading( p );
		return( FALSE );
	}
	
	/* See if we are almost at the end. If yes, we will advance line by
	   line. */
	
	if ( p->top + p->resol + (NOZZLES) * HEAD_SPACING > p->last ) {
		
 		ScheduleTrailing( p );
		
		if ( p->down )
		
			return( p->top + (NOZZLES-1) * HEAD_SPACING >= p->last );
		else
			return( FALSE );
	}
		
	/* Otherwise we're in the middle of the page, just do the
	   simple banding and selecting as many lines as we can. */

	ScheduleMiddle( p );
	return( FALSE );
}

/*
*	Initialise the scheduler
*	~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	SchedulerInit( SCHEDUL *p )
{
int		i;
			
	p->top = 0;
		
	switch ( p->resol ) {
		
		case 360:	
			p->offset = 0;
			p->resol  = BAND_360; 
			p->nozzle = NOZZLE_360;
			break;
			
		case 720:	
			p->offset = 0;
			p->resol  = BAND_720; 
			p->nozzle = NOZZLE_720;
			break;
			
		case 1440:	
			p->offset = 1;			/* Need to be set for the algorithm! */
			p->resol  = BAND_1440; 
			p->nozzle = NOZZLE_1440;
			break;
	}
	
	for ( i = 0 ; i < NOZZLES  ; i++ ) p->head[ i ] = -1;
	for ( i = 0 ; i < MAX_MARK ; i++ ) p->mark[ i ] = 0;
	p->markbeg = 0;
}

/*
*	Scheduling the first BAND lines for the image
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	ScheduleLeading( SCHEDUL *p )
{
int		i;

	if ( p->resol == BAND_720 ) {

		/* Copy the line scheduling data to the struct */
			
		memcpy( p->head, start_720[ p->top ], sizeof( int ) * NOZZLES );
			
		/* Mark all lines to be set */
			
		for ( i = 0 ; i < NOZZLES ; i++ )
			
			if ( p->head[ i ] != -1 ) 
				
				p->mark[ p->head[ i ] % MAX_MARK ] = 1;
			
		/* We move down by one line except at the end */
				
		if ( p->top == HEAD_SPACING - 1 ) {
			
			p->down = BAND_720 - p->top;
			p->top  = BAND_720;
		}
		else {
			
			p->down = 1;
			p->top++;
		}
	}
	else {
			
		/* 1440 dpi version, two passes needed for each scanline */
				
		if ( p->offset ) {
						
			/* Copy the non-offseted scheduling data to the struct */
			
			memcpy( p->head, start_1440[0][p->top], sizeof( int ) * NOZZLES );
			
			/* Mark all lines to be set */
			
			for ( i = 0 ; i < NOZZLES ; i++ )
			
				if ( p->head[ i ] != -1 )
				
					p->mark[ p->head[ i ] % MAX_MARK ] = 1;
			
			/* This is the non-offseted line, do not move ! */
				
			p->offset = 0;
			p->down = 0;
		}
		else {
			
			/* Copy the non-offseted schduling data to the struct */
			
			memcpy( p->head, start_1440[1][p->top], sizeof( int ) * NOZZLES );
			
			/* Mark all lines to be set */
			
			for ( i = 0 ; i < NOZZLES ; i++ )
			
				if ( p->head[ i ] != -1 )
	
					p->mark[ p->head[ i ] % MAX_MARK ] |= 2;
			
			/* We move down by one line except at the end and set offset */
				
			if ( p->top == HEAD_SPACING - 1 ) {
			
				p->down = BAND_1440 - p->top;
				p->top  = BAND_1440;
			}
			else {
			
				p->down = 1;
				p->top++;
			}
			
			p->offset = 1;
		}
	}	
}

/*
*	Scheduling the bulk of the image
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	ScheduleMiddle( SCHEDUL *p )
{
int		ph0, ph1;
int		line, mask;
int		i;

	if ( p->resol == BAND_720 ) {
	
		/* 720 DPI printing. See which lines should we print and
		   fill the head array accordingly, then move down a band. */
		
		ScheduleBand( p, 1 );
		p->down = BAND_720;
		p->top += BAND_720;
	}
	else {
	
		/* 1440 dpi printing. This is a bit more complex than the
		   720 dpi one. First, see how many lines in each phase
		   has already been printed. */
		
		ph0 = ph1 = 0;
		
		for ( line = p->top, i=0 ; i < NOZZLES ; i++, line += HEAD_SPACING ) {
		
			line = p->top + i * HEAD_SPACING;
			ph0 += p->mark[ line % MAX_MARK ] & 1;
			ph1 += p->mark[ line % MAX_MARK ] & 2;
		}
		
		ph1 >>= 1;
	
		/* Choose the phase which has less lines in it. */
		
		if ( ph0 <= ph1 ) {
		
			p->offset = 0;
			mask = 1;
		}
		else {
		
			p->offset = 1;
			mask = 2;
		}
		
		/* Fill the line array and mark the phase.
		   We should check here if moving down the head will leave
		   any line empty, but we do not because we *know* that it
		   won't - the BAND_1440 is selected by finding a value 
		   which guarantees that it will cover every line. */
		
		ScheduleBand( p, mask );
		p->down = BAND_1440;
		p->top += BAND_1440;
	}
}

/*
*	Scheduling the last lines of the image
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	ScheduleTrailing( SCHEDUL *p )
{
int		mask;

	if ( p->down > 1 ) {
		
		/* This is the first time we came here. */
		
		p->offset = 1;
	}
	
	if ( p->resol == BAND_720 ) {
		
		p->offset = 0;
		p->down   = 1;
		mask	  = 1;
	}
	else {
		
		if ( p->offset ) {
			
			p->offset = 0;
			p->down	  = 0;
			mask	  = 1;
		}
		else {
			
			p->offset = 1;
			p->down	  = 1;
			mask	  = 2;
		}
	}
	
	ScheduleBand( p, mask );
	p->top += p->down;
}

/*
*	Select lines from a given set
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	ScheduleBand( SCHEDUL *p, int mask )
{
int		i;
int		line;

	for ( line = p->top, i = 0 ; i < NOZZLES ; i++, line += HEAD_SPACING ) {
			

		if ( p->mark[ line % MAX_MARK ] & mask ) {
			
			p->head[ i ] = -1;
		}
		else {
			
			p->head[ i ] = line;
			p->mark[ line % MAX_MARK ] |= mask;
		}
	}
}

/****************************************************************************/
/*						Formatting printer data								*/
/****************************************************************************/

/*
*	Packs a line to raw device format
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	Reads pixnum pixels and if the pixel is lev_on, then sets the
*	appropriate bit in the resulting datastream. The length of the
*	result is pixnum/8 (rounded up).
*/

private	void	PackLine( byte *input, int pixnum, int lev_on, int step,
						  RAWLINE *line )
{
byte	bits;
char	*result;
int		i, j, k;

	result = line->data;
	line->first = MAX_PIXELS;
	line->last  = 0;
		
	for ( j = 0x80, bits = k = i = 0 ; i < pixnum ; i += step, input += step ){
	
		if ( *input == lev_on ) bits |= j;
		
		if ( ! ( j >>= 1 ) ) {
		
			if ( bits ) {
			
				if ( line->first > k ) line->first = k;
				if ( line->last  < k ) line->last  = k;
			}
			
			*result++ = bits;
			j		  = 0x80;
			bits	  = 0;
			k++;
		}
	}
	
	if ( j != 0x80 ) {
	
		*result = bits;
		
		if ( bits ) {
			
			if ( line->first > k ) line->first = k;
			if ( line->last  < k ) line->last  = k;
		}
	}
}

/*
*	Compresses (run-length encodes) a line
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	Returns the length of the RLE data.
*/
								 
private	int		RleCompress( RAWLINE *raw, int min, int max, byte *rle_data )
{
int		i, n;
byte	pbyte;
byte	*start, *rstrt;
int		length;
byte	*input;
int 	len;

	if ( ! raw ) {
	
		/* This is an empty line */
	
		for ( n = 0, i = max - min ; i >= 129 ; i -= 129 ) {
		
			*rle_data++ = 128;
			*rle_data++ = 0;
			n += 2;
		}
		
		if ( i >= 2 ) {
		
			*rle_data++ = 257 - i;
			*rle_data++ = 0;
			n += 2;
		}
		else if ( i ) {
		
			*rle_data++ = 0;
			*rle_data++ = 0;
			n+= 2;
		}
		
		return( n );
	}
	
	/* There's data, set up encoding parameters */
	
	input = raw->data + min;
	len   = max - min;
		
	/* Create a run-length encoded version. We do it even if no pixel
	   was set because it may be that this line is just part of a 
	   multi-line band. */
	
	length = 0;
	start  = input;
	rstrt  = NULL;
	pbyte  = *input++;
	
	for ( i = 1 ; i < len ; i++, input++ ) {
		
		if ( *input == pbyte ) {
		
			/* This byte is identical to the previous one(s). */
			
			if ( ! rstrt ) {
			
				/* This is the start of a new repeating sequence */
				
				rstrt = input - 1;
			}
		}
		else {
		
			/* Different byte than the previous one(s) */
			
			if ( rstrt ) {
			
				/* There was a repetitive sequence. */
				
				if ( rstrt - input < 4 ) {
				
					/* For less than four bytes it isn't worth
					   to do RLE, we discard them */
					
					rstrt = NULL;
				}
				else {
				
					/* We must flush */
					
					n = RleFlush( start, rstrt, input, rle_data );
					rle_data  += n;
					length += n;
					
					/* Initialise again */
					
					start = rle_data;
					rstrt = NULL;
				}
			}						
			
			pbyte = *rle_data;
		}
	}
	
	/* We flush whatever is left over */
	
	length += RleFlush( start, rstrt, input, rle_data );
	
	return( length );
}
			
/*
*	This function flushes the RLE encoding buffer
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	Assumes that it gets a nonrepetitive pattern followed by a repetitive
*	one. 'first' points to the start of the non-repetitive part.
*	'reps' points to the first byte in the repetitive sequence or it
*	may be NULL, if there were no repetitve bytes. 'now' points to
*	one after the last byte in the sequence.
*	It puts the result into 'out' and returns the number of bytes
*	written out.
*
*	There is one possible performance penalty in using this method:
*	If the repetitive sequence is n*128+1 byte long, then the last
*	byte will be written out as single byte. If the following sequence
*	has a nonrepetitive start, this byte could be combined into that
*	but it isn't. This can cause some penalty, however, we will live
*	with that for now.
*/
				
private	int		RleFlush( byte *first, byte *reps, byte *now, byte *out )
{
int		count;
int		l;

	if ( ! first ) return( 0 );
	
	if ( ! reps ) reps = now;
	
	count = 0;
		
	/* Write the nonrepetitve pattern first */

	while ( ( l = reps - first ) ) {
	
		if ( l > 128 ) {
		
			/* More than 128 consecutive bytes, write out a 128 byte chunk */
			
			*out++ = 127;
			memcpy( out, first, 128 );
			out   += 128;
			first += 128;
			count += 129;
		}
		else {
		
			/* There are not more than 128 bytes, write them into a 
			   single chunk */
			
			*out++ = l - 1;
			memcpy( out, first, l );
			count += l + 1;
			first += l;
			out   += l;
		}
	}				
					
	/* Now write the repeated pattern */
	
	while ( ( l = now - reps ) ) {
	
		if ( l > 128 ) {
		
			/* More than 128 bytes are identical, write out a
			   129 byte chunk */
			   
			*out++ = 128;
			*out++ = *reps;
			count += 2;
			reps  += 129;
		}
		else {
		
			if ( l == 1 ) {
			
				/* There is only one byte left, write it out as a
				   nonrepetitive chunk */
				   
				*out++ = 0;
				*out++ = *reps;
				count += 2;
				reps++;
			}
			else {
				
				/* What remains is at least 2 bytes but not larger than what
				   can be written in a single chunk */
				   
				*out++ = 257 - l;
				*out++ = *reps;
				count += 2;
				reps   = now;
			}
		}
	}
	
	return( count );
}

/****************************************************************************/
/*		Low level procedures to send various commands to the printer		*/
/****************************************************************************/

private	void	SendReset( FILE *stream )
{
	SendString( stream, ESC "@" );
}

private	void	SendMargin( FILE *stream, int top, int bot )
{
	SendString( stream, ESC "(c" );
	SendWord( stream, 4 );
	SendWord( stream, bot );
	SendWord( stream, top );
}

private	void	SendPaper( FILE *stream, int length )
{
	SendString( stream, ESC "(C" );
	SendWord( stream, 2 );
	SendWord( stream, length );
}

private	void	SendGmode( FILE *stream, int on )
{
	SendString( stream, ESC "(G" );
	SendWord( stream, 1 );
	SendByte( stream, on );
}

private void	SendUnit( FILE *stream, int res )
{
	SendString( stream, ESC "(U" );
	SendWord( stream, 1 );
	SendByte( stream, res );
}

private	void	SendUnidir( FILE *stream, int on )
{
	SendString( stream, ESC "U" );
	SendByte( stream, on );
}

private	void	SendMicro( FILE *stream, int on )
{
	SendString( stream, ESC "(i" );
	SendWord( stream, 1 );
	SendByte( stream, on );
}

private void	SendInk( FILE *stream, int x )
{
	SendString( stream, ESC "(e" );
	SendWord( stream, 2 );
	SendByte( stream, 0 );
	SendByte( stream, x );
}

private	void	SendDown( FILE *stream, int x )
{
	SendString( stream, ESC "(v" );
	SendWord( stream, 2 );
	SendWord( stream, x );
}

private	void	SendRight( FILE *stream, int amount )
{
	SendString( stream, ESC "(\\" );
	SendWord( stream, 4 );
	SendWord( stream, 1440 );
	SendWord( stream, amount );
}

private	void	SendColour( FILE *stream, int col )
{
static	int	ccode[] = { 0x000, 0x200, 0x100, 0x400, 0x201, 0x101 };

	SendString( stream, ESC "(r" );
	SendWord( stream, 2 );
	SendWord( stream, ccode[ col ] );
}

private void	SendData( FILE *stream, int hres, int vres, int noz, int col ) 
{
	SendString( stream, ESC "." );
	SendByte( stream, 1 );				/* Run-length encoded data */
	
	/* If we use 1 nozzle, then vertical resolution is what it is.
	   Otherwise it must be set to 90 dpi */
	   
	if ( noz == 1 )
	
		SendByte( stream, RESCODE( vres ) );
	else
		SendByte( stream, RESCODE( 90 ) );

	/* The horizontal resolution is max. 720 dpi */
	
	if ( hres > 720 )
	
		SendByte( stream, RESCODE( 720 ) );
	else
		SendByte( stream, RESCODE( hres ) );
		
	SendByte( stream, noz );
	SendWord( stream, col );
}
		
private	void	SendString( FILE *stream, const char *s )
{
	while ( *s ) SendByte( stream, *s++ );
}

/****************************************************************************/
/*					Halftoning wrapper functions							*/
/****************************************************************************/

/*
*	Calls the start function of the choosen halftoner
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	HalftonerStart( RENDER *render, int line )
{
	(*(htable[ render->dev->halftoner ].hstrt))( render, line );
}

/*
*	Returns the restart threshold for the given halftoner
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		HalftoneThold( RENDER *render )
{
	return( (*(htable[ render->dev->halftoner ].hthld))( render ) );
}

/*
*	This function renders a line
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	This function has one fundamental assumption: halftoning of separate
*	colours is independent of each other.
*	
*	It calls the mono halftoner with the K, C, M, Y components.
*/

private	void	HalftoneLine( RENDER *render, int line, byte *data )
{
void		(*htone)( HTONE *, int );
EDEV		*dev;
int			offs;
HTONE		hdata;
short		*errs[ MAX_ED_LINES ];
int			i;

	/* Get the rendering function */
	
	dev   = render->dev;
	htone = htable[ render->dev->halftoner ].htone;
	offs  = render->mono ? 0 : OFFS_K;
	
	if ( dev->mono ) {
	
		/* Monochrome, do only the black */
			
		for ( i = 0 ; i < MAX_ED_LINES ; i++ ) 
	
			errs[ i ] = render->error[ i ][ OFFS_K ];
		
		hdata.render = render;
		hdata.data   = data + OFFS_K;
		hdata.step	 = sizeof( byte );
		hdata.res	 = render->res[ OFFS_K ];
		hdata.block  = NULL;
		hdata.err	 = errs;
		hdata.mval	 = 255;
		
		(*htone)( &hdata, line );
	}
	else {
		
		/* Colour. D black first */
	
		for ( i = 0 ; i < MAX_ED_LINES ; i++ ) 
			
			errs[ i ] = render->error[ i ][ OFFS_K ];
		
		hdata.render = render;
		hdata.step	 = sizeof( long );
		hdata.data   = data + OFFS_K;
		hdata.res	 = render->res[ OFFS_K ];
		hdata.block  = NULL;
		hdata.err	 = errs;
		hdata.mval	 = 255;
		
		(*htone)( &hdata, line );
		
		/* Yellow has no intermediate ink. The already done black
		   may inhibit it. */
	
		for ( i = 0 ; i < MAX_ED_LINES ; i++ ) 
		
			errs[ i ] = render->error[ i ][ OFFS_Y ];
			
		hdata.render = render;
		hdata.step	 = sizeof( long );
		hdata.data   = data + OFFS_Y;
		hdata.res	 = render->res[ OFFS_Y ];
		hdata.block  = dev->pureblack ? render->res[ OFFS_K ] : NULL;
		hdata.err	 = errs;
		hdata.mval	 = 255;

		(*htone)( &hdata, line );
	
		/* Cyan and magenta has intermediate colour ink, black may inhibit */
	
		for ( i = 0 ; i < MAX_ED_LINES ; i++ ) 
		
			errs[ i ] = render->error[ i ][ OFFS_C ];
			
		hdata.data   = data + OFFS_C;
		hdata.res	 = render->res[ OFFS_C ];
		hdata.block  = dev->pureblack ? render->res[ OFFS_K ] : NULL;
		hdata.mval	 = dev->midcyan;

		(*htone)( &hdata, line );

		for ( i = 0 ; i < MAX_ED_LINES ; i++ ) 
		
			errs[ i ] = render->error[ i ][ OFFS_M ];
			
		hdata.data   = data + OFFS_M;
		hdata.res	 = render->res[ OFFS_M ];
		hdata.block  = dev->pureblack ? render->res[ OFFS_K ] : NULL;
		hdata.mval	 = dev->midmagenta;

		(*htone)( &hdata, line );
	}
	
	/* Here we have create the raw device format scanlines */
	
	if ( dev->mono ) {
	
		if ( render->xres == 1440 ) {
		
			PackLine( render->res[ OFFS_K ], render->width, 255, 2, 
					  render->raw[ 0 ][ DEV_BLACK ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_K ]+1, render->width-1, 255, 2, 
					  render->raw[ 1 ][ DEV_BLACK ]+ line % MAX_MARK );
		}
		else {
		
			PackLine( render->res[ OFFS_K ], render->width, 255, 1, 
					  render->raw[ 0 ][ DEV_BLACK ]+ line % MAX_MARK );
		}
	}
	else {
	
		if ( render->xres == 1440 ) {
		
			PackLine( render->res[ OFFS_K ], render->width, 255, 2, 
					  render->raw[ 0 ][ DEV_BLACK ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_K ]+1, render->width-1, 255, 2, 
					  render->raw[ 1 ][ DEV_BLACK ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ], render->width, 255, 2, 
					  render->raw[ 0 ][ DEV_CYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ]+1, render->width-1, 255, 2, 
					  render->raw[ 1 ][ DEV_CYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_M ], render->width, 255, 2, 
					  render->raw[ 0 ][ DEV_MAGENTA ]+ line % MAX_MARK);
					  
			PackLine( render->res[ OFFS_M ]+1, render->width-1, 255, 2, 
					  render->raw[ 1 ][ DEV_MAGENTA ]+ line % MAX_MARK);
					  
			PackLine( render->res[ OFFS_Y ], render->width, 255, 2, 
					  render->raw[ 0 ][ DEV_YELLOW ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_Y ]+1, render->width-1, 255, 2, 
					  render->raw[ 1 ][ DEV_YELLOW ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ], render->width, dev->midcyan, 
					  2, render->raw[ 0 ][ DEV_LCYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ]+1, render->width-1, dev->midcyan, 
					  2, render->raw[ 1 ][ DEV_LCYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_M ], render->width, dev->midmagenta, 
					  2, render->raw[0][ DEV_LMAGENTA ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_M ]+1, render->width-1,dev->midmagenta,
					  2, render->raw[1][ DEV_LMAGENTA ]+ line % MAX_MARK );
		}
		else {
		
			PackLine( render->res[ OFFS_K ], render->width, 255, 1, 
					  render->raw[ 0 ][ DEV_BLACK ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ], render->width, 255, 1, 
					  render->raw[ 0 ][ DEV_CYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_M ], render->width, 255, 1, 
					  render->raw[ 0 ][ DEV_MAGENTA ]+ line % MAX_MARK);
					  
			PackLine( render->res[ OFFS_Y ], render->width, 255, 1, 
					  render->raw[ 0 ][ DEV_YELLOW ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_C ], render->width, dev->midcyan, 
					  1, render->raw[ 0 ][ DEV_LCYAN ]+ line % MAX_MARK );
					  
			PackLine( render->res[ OFFS_M ], render->width, dev->midmagenta, 
					  1, render->raw[0][ DEV_LMAGENTA ]+ line % MAX_MARK );
		}
	}
	
	/* Call the halftoner specific end-of-line function */
	
	(*htable[ render->dev->halftoner ].hteol)( render, line );
}

/****************************************************************************/
/*					Floyd - Steinberg error diffusion						*/
/****************************************************************************/

/*
*	This function returns the empty range threshold
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		FloydSThold( RENDER *p )
{
	return( 5 );
}

/*
*	This function initialises the halftoner
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	FloydSStart( RENDER *p, int line )
{
	memset( p->err, 0, ICOLN * MAX_PIXELS*2 );
	p->error[ 0 ] = p->err[ 0 ];
}

/*
*	This function does the end-of-line processing
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	FloydSEol( RENDER *p, int line )
{
	/* Since we use single error buffering, nothing to do */
}

/*
*	This is the classical Floyd-Steinberg error diffusion.
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	The matrix is the following:
*
*          *    7/16	r
*   3/16  5/16  1/16
*
*	r is the residual (0, in theory). 
*	Absolutely nothing fancy is done here.
*
*/

private	void   FloydSLine( HTONE *htone, int y )
{
int		x;							/* Counts the pixels					*/
int		pixel;						/* Current pixel value					*/
int		pixerr;						/* Error value							*/
int		length;						/* Number of pixels to process			*/
byte	*res;						/* Result								*/
byte	*data;						/* Input data							*/
byte	*block;						/* Block pixel							*/
int		lim1, lim2;					/* Limits								*/
short	e0, e1;						/* Propagating errors in current line	*/
short	*l0;						/* Error buffer pointer					*/

	length  = htone->render->width;

	res		= htone->res;
	data	= htone->data;
	block	= htone->block;
	
	lim1	= htone->mval / 2;
	lim2	= ( htone->mval + 256 ) / 2;
	
	l0		= htone->err[ 0 ];
	
	e0		= l0[ 1 ];
	e1		= l0[ 2 ];
	
	l0[ 1 ] = 0;
	l0[ 2 ] = 0;
		
	for ( x = 0 ; x < length ; x++ ) {
	
		/* First, clear the res byte. It is needed for the black */
		
		*res = 0;
		
		/* Add the actual error to the pixel, normalise, init, whatever. */
		
		pixel = ( ( *data << 4 ) + e0 );
		e0 = e1;
		e1 = l0[ 3 ] + ( pixel & 15 );			/* This is the residual */
		
		l0[ 3 ] = 0;
		pixel >>= 4;
		
		if ( ( block && *block ) || ( pixel < lim1 ) )
		
			*res = 0;

		else if ( pixel >= lim2 )
		
			*res = 255;
		else
			*res = htone->mval;
		
		/* Calculate the err */
		
		pixerr = pixel - *res;
		
		/* Diffuse the err */
	
		e0		+= ( pixerr << 3 ) - pixerr;	/* 7/16		*/
		l0[ 0 ] += ( pixerr << 2 ) - pixerr;	/* 3/16		*/
		l0[ 1 ] += ( pixerr << 2 ) + pixerr;	/* 5/16		*/
		l0[ 2 ] += pixerr;						/* 1/16		*/
				
		/* We have done everything, move the pointers */
		
		res++;
		if ( block ) block++;
		data += htone->step;
		l0++;
	}
}

/****************************************************************************/
/*							Ordered dither									*/
/****************************************************************************/

/*
*	This function returns the empty range threshold
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		DitherThold( RENDER *p )
{
	return( 0 );
}

/*
*	This function initialises the halftoner
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	DitherStart( RENDER *p, int line )
{
	/* Nothing to initialise */
}

/*
*	This function does the end-of-line processing
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	DitherEol( RENDER *p, int line )
{
	/* Nothing to do - dithering has no memory */
}

/*
*	Clustered dither of a particular colour of a line
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void   DitherLine( HTONE *htone, int y )
{
int		x;							/* Counts the pixels					*/
int		pixel;						/* Current pixel value					*/
int		length;						/* Number of pixels to process			*/
byte	*res;						/* Result								*/
byte	*data;						/* Input data							*/
byte	*block;						/* Block pixel							*/
byte	*matrix;					/* Dither matrix's current line			*/
int		mx;							/* Matrix index							*/
int		lval, hval;					/* Halftoned high/low values			*/

	length  = htone->render->width;

	res		= htone->res;
	data	= htone->data;
	block	= htone->block;
	
	matrix	= dmatrix[ y % DMATRIX_Y ];
		
	for ( mx = x = 0 ; x < length ; x++ ) {
	
		/* First, clear the res byte. It is needed for the black */
		
		*res = 0;
		
		/* Next, see if the pixel is above the mval */
		
		if ( ( pixel = *data ) > htone->mval ) {
		
			lval = htone->mval;
			hval = 255;
			
			if ( htone->mval == 127 )

				pixel = ( ( pixel - htone->mval ) * 2 - 1 ) / 2;
			else
				pixel = ( pixel - htone->mval ) * 255 / ( 255 - htone->mval );
		}
		else {
		
			lval = 0;
			hval = htone->mval;
			
			if ( htone->mval != 255 ) {
			
				if ( htone->mval == 127 )
				
					pixel = ( pixel * 4 + 1 ) / 2;
				else
					pixel = pixel * 255 / htone->mval;
			}
		}
		
		if ( block && *block ) {
		
			*res = 0;
		}
		else {
		
			if ( pixel >= matrix[ mx ] )
		
				*res = hval;
			else
				*res = lval;
		}
		
		res++;
		if ( ++mx == DMATRIX_X ) mx = 0;
		if ( block ) block++;
		data += htone->step;
	}
}

/****************************************************************************/
/*					Bendor's error diffusion								*/
/****************************************************************************/

/*
*	This function returns the empty range threshold
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	int		BendorThold( RENDER *p )
{
	return( 5 );
}

/*
*	This function initialises the halftoner
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	BendorStart( RENDER *p, int line )
{
	memset( p->err, 0, 2 * ICOLN * MAX_PIXELS*2 );
	p->error[ 0 ] = p->err[ 0 ];
	p->error[ 1 ] = p->err[ 1 ];
}

/*
*	This function does the end-of-line processing
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*/

private	void	BendorEol( RENDER *p, int line )
{
void	*x;

	x = p->error[ 0 ];
	p->error[ 0 ] = p->error[ 1 ];
	p->error[ 1 ] = x;
}

/*
*	Error diffusion of a particular colour of a line
*	~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
*	This is not yet finished (the matrix is bad, actually).
*
*	The matrix is the following (the normalisation factor is 1/128, 
*	'*' represents the current pixel, r is the truncation residual):
*
*				 *	20	10  r
*		8	14	20	14	 8
*		4	 8	10	 8	 4
*
*	We also try to take the splashing effect into account (the ink disperses
*	when it hits the paper so it partially covers surrounding pixels).
*	We use an other matrix for that, which is very simple:
*
*			*	3
*		2	3	2
*
*	and the normalisation factor can be set by the user. 
*	The splash matrix is only applied if we have actually deposited 
*	ink and the amount added to the errors is independent that of the 
*	actual image value, it only depends on the ink applied.
*	Of course, the ink spreads up and left as well and we could compensate
*	for this for a certain extent by keeping track of the errors caused in
*	previous pixels and lines and modifying them accordingly but it
*	would lead to a horrible code mess and it wouldn't be worth the effort.
*
*	A further enhancement that we allow the error to 'leak'. Experimental 
*	results show that with a 5-15% loss of error the image quality 
*	increases and the colour distortion remains very low. If you think
*	about it, this, in effect stops the error to spread its effect over
*	large areas but it will have almost undisturbed effect on neighbouring 
*	areas (you allow for an exponential error decay). 
*	This parameter is user definable, too.
*/

private	void   BendorLine( HTONE *htone, int y )
{
int		x;							/* Counts the pixels					*/
int		pixel;						/* Current pixel value					*/
int		pixerr;						/* Error value							*/
int		pixe14;						/* 14 * err value						*/
int		sval;						/* Splash correction value				*/
int		splash;						/* Splash factor						*/
int		leakage;					/* Leakage factor						*/
int		length;						/* Number of pixels to process			*/
byte	*res;						/* Result								*/
byte	*data;						/* Input data							*/
byte	*block;						/* Block pixel							*/
int		lim1, lim2;					/* Limits								*/
short	e0, e1;						/* Propagating errors in current line	*/
short	*l0, *l1;					/* Error buffer pointers				*/

	splash  = htone->render->dev->splash;
	leakage = htone->render->dev->splash;
	length  = htone->render->width;

	res		= htone->res;
	data	= htone->data;
	block	= htone->block;
	
	lim1	= htone->mval / 2;
	lim2	= ( htone->mval + 256 ) / 2;
	
	l0		= htone->err[ 0 ];
	l1		= htone->err[ 1 ];
	
	e0		= l0[ 2 ];
	e1		= l0[ 3 ];
	
	l0[ 2 ] = 0;
	l0[ 3 ] = 0;
		
	for ( x = 0 ; x < length ; x++ ) {
	
		/* First, clear the res byte. It is needed for the black */
		
		*res = 0;
		
		/* Add the actual error to the pixel, normalise, init, whatever. */
		
		pixel = ( ( *data << 7 ) + e0 );
		e0 = e1;
		e1 = l0[ 4 ] + ( pixel & 127 );			/* This is the residual */
		
		l0[ 4 ] = 0;
		pixel >>= 7;
		
		if ( ( block && *block ) || ( pixel < lim1 ) )
		
			*res = 0;

		else if ( pixel >= lim2 )
		
			*res = 255;
		else
			*res = htone->mval;
		
		/* Calculate the err */
		
		pixerr = pixel - *res;
		
		/* If leakage is defined, apply it */
		
		if ( leakage ) pixerr -= ( pixerr * leakage ) / 100;
		
		/* Diffuse the err */
	
		pixerr <<= 1;							/* Multiplier is 2	*/
		pixe14 = pixerr;						/* pixe14 now 2		*/
		pixerr <<= 1;							/* Multiplier is 4	*/
		pixe14 += pixerr;						/* pixe14 now 6		*/
		
		l0[ 0 ] += pixerr;
		l0[ 4 ] += pixerr;
		
		pixerr <<= 1;							/* Multiplier is 8	*/
		pixe14 += pixerr;						/* pixe14 now 14	*/
		
		l0[ 1 ] += pixerr;
		l0[ 3 ] += pixerr;
		l1[ 0 ] += pixerr;
		l1[ 4 ] += pixerr;
		
		pixerr += pixerr >> 2;					/* Multiplier is 10	*/
		
		l0[ 2 ] += pixerr;
		e1		+= pixerr;
		
		pixerr <<= 1;							/* Multiplier is 20 */
		
		l1[ 2 ] += pixerr;
		e0		+= pixerr;
		
		/* pixe14 already contains 14 * err */
		
		l1[ 1 ] += pixe14;
		l1[ 3 ] += pixe14;
		
		/* If splashing is defined, apply the splash matrix.
		   The splash value is normalised to the same level as the err */
		
		if ( splash && *res ) {
		
			sval = splash * *res;				/* This is the 2x value	*/
			
			l1[ 1 ] -= sval;
			l1[ 3 ] -= sval;
			
			sval += sval >> 1;					/* This represents 3x	*/
			
			e0		-= sval;
			l1[ 2 ] -= sval;
		}
		
		/* We have done everything, move the pointers */
		
		res++;
		if ( block ) block++;
		data += htone->step;
		l0++, l1++;
	}
}
