/* $Header: /usr/people/sam/tiff/tools/RCS/sgigt.c,v 1.65 1995/10/10 00:35:22 sam Exp $ */

/*
 * Copyright (c) 1988-1995 Sam Leffler
 * Copyright (c) 1991-1995 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and 
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 * 
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY 
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.  
 * 
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF 
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE 
 * OF THIS SOFTWARE.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <gl.h>
#include <device.h>

#include "tiffio.h"

#ifndef TRUE
#define	TRUE	1
#define	FALSE	0
#endif

/* XXX fudge adjustment for window borders */
#define	YFUDGE	20
#define	XFUDGE	20

static	tileContigRoutine putContig;
static	tileSeparateRoutine putSeparate;
static	uint32 width, height;		/* window width & height */
static	uint32* raster = NULL;		/* displayable image */

extern	Colorindex greyi(int);
static	void setupColormapSupport(TIFFRGBAImage*);
static	void putContigAndDraw(TIFFRGBAImage*, uint32*,
    uint32, uint32, uint32, uint32, int32, int32, unsigned char*);
static	void putSeparateAndDraw(TIFFRGBAImage*, uint32*,
    uint32, uint32, uint32, uint32, int32, int32,
    unsigned char*, unsigned char*, unsigned char*, unsigned char*);

static	int prevImage(char* argv[], int ix, int b, int e, int wrap);
static	int nextImage(char* argv[], int ix, int b, int e, int wrap);
static	void usage(void);
static	uint16 photoArg(const char*);
static	void beep(void);

extern	char* optarg;
extern	int optind;

int
main(int argc, char* argv[])
{
    static Cursor hourglass = {
	0x1ff0, 0x1ff0, 0x0820, 0x0820,
	0x0820, 0x0c60, 0x06c0, 0x0100,
	0x0100, 0x06c0, 0x0c60, 0x0820,
	0x0820, 0x0820, 0x1ff0, 0x1ff0
    };
    int isRGB0 = -1, isRGB;
    int verbose = 0;
    int stoponerr = 0;			/* stop on read error */
    char* filename;
    TIFF* tif = NULL;
    int fg = 0;
    int c;
    int dirnum = -1;
    int order0 = 0, order;
    uint32 diroff = 0;
    uint16 photo0 = (uint16) -1, photo;
    long x, y, xmax, ymax;
    int ix, nix;
    TIFFErrorHandler oerror = TIFFSetErrorHandler(NULL);
    TIFFErrorHandler owarning = TIFFSetWarningHandler(NULL);
    uint32 w, h;
    long wid = -1;

    while ((c = getopt(argc, argv, "d:o:p:cerflmsvw")) != -1)
	switch (c) {
	case 'c':
	    isRGB0 = 0;
	    break;
	case 'd':
	    dirnum = atoi(optarg);
	    break;
	case 'e':
	    oerror = TIFFSetErrorHandler(oerror);
	    break;
	case 'f':
	    fg = 1;
	    break;
	case 'l':
	    order0 = FILLORDER_LSB2MSB;
	    break;
	case 'm':
	    order0 = FILLORDER_MSB2LSB;
	    break;
	case 'o':
	    diroff = strtoul(optarg, NULL, 0);
	    break;
	case 'p':
	    photo0 = photoArg(optarg);
	    break;
	case 'r':
	    isRGB0 = 1;
	    break;
	case 's':
	    stoponerr = 1;
	    break;
	case 'w':
	    owarning = TIFFSetWarningHandler(owarning);
	    break;
	case 'v':
	    verbose = 1;
	    break;
	case '?':
	    usage();
	    /*NOTREACHED*/
	}
    if (argc - optind < 1)
	usage();
    xmax = getgdesc(GD_XPMAX) - XFUDGE;
    ymax = getgdesc(GD_YPMAX) - YFUDGE;
    ix = optind;
    do {
	tif = TIFFOpen(argv[ix], "r");
    } while (tif == NULL && (ix = nextImage(argv, ix, optind, argc, FALSE)));
    if (tif == NULL)
	exit(0);
    if (ix == optind) {
	/*
	 * Set initial directory if user-specified
	 * file was opened successfully.
	 */
	if (dirnum != -1 && !TIFFSetDirectory(tif, dirnum))
	    TIFFError(argv[ix], "Error, seeking to directory %d", dirnum);
	if (diroff != 0 && !TIFFSetSubDirectory(tif, diroff))
	    TIFFError(argv[ix], "Error, setting subdirectory at %#x", diroff);
    }
    isRGB = isRGB0;
    order = order0;
    photo = photo0;
    goto newfile0;
    for (;;) {
	TIFFRGBAImage img;
	char title[1024];			/* window title line */
	const char* cp;
	int isrgb;

	if (order)
	    TIFFSetField(tif, TIFFTAG_FILLORDER, order);
	if (photo != (uint16) -1)
	    TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, photo);
	if (!TIFFRGBAImageBegin(&img, tif, stoponerr, title)) {
	    TIFFError(filename, title);
	    goto bad2;
	}
	/*
	 * Use a full-color window if the image is
	 * full color or a palette image and the
	 * hardware support is present.
	 */
	isrgb = isRGB;
	if (isrgb == -1)
	    isrgb = (img.bitspersample >= 8 &&
		(img.photometric == PHOTOMETRIC_RGB ||
		 img.photometric == PHOTOMETRIC_YCBCR ||
		 img.photometric == PHOTOMETRIC_SEPARATED ||
		 img.photometric == PHOTOMETRIC_PALETTE));
	/*
	 * Check to see if the hardware can display 24-bit RGB.
	 */
	if (isrgb && getgdesc(GD_BITS_NORM_SNG_RED) < img.bitspersample &&
	  !getgdesc(GD_DITHER)) {
	    if (verbose)
		printf("Warning, display is incapable of full RGB,%s\n",
			 " using dithered colormap");
	    isrgb = 0;
	}
	/*
	 * Colormap-based display is done by overriding the put
	 * routine to install a private method that understands
	 * how to convert RGBA values to suitable colormap indices.
	 */
	if (!isrgb)
	    setupColormapSupport(&img);
	/*
	 * Override default ``put routine'' with private
	 * routine that also draws the raster on the display.
	 */
	if (img.put.any == 0) {
	    TIFFError(filename,
		"No \"put\" routine; must not handle image format");
	    goto bad3;
	}
	if (img.isContig) {
	    putContig = img.put.contig;
	    img.put.contig = putContigAndDraw;
	} else {
	    putSeparate = img.put.separate;
	    img.put.separate = putSeparateAndDraw;
	}
	/*
	 * Setup the image raster as required.
	 */
	if ((w = img.width) > xmax)
	    w = xmax;
	if ((h = img.height) > ymax)
	    h = ymax;
	if (w != width || h != height) {
	    if (raster != NULL)
		_TIFFfree(raster), raster = NULL;
	    raster = (uint32*) _TIFFmalloc(w * h * sizeof (uint32));
	    if (raster == 0) {
		width = height = 0;
		TIFFError(filename, "No space for raster buffer");
		goto bad3;
	    }
	    width = w;
	    height = h;
	}
	/*
	 * Create a new window or reconfigure an existing
	 * one to suit the image to be displayed.
	 */
	if (wid < 0) {
	    x = (xmax+XFUDGE-width)/2;
	    y = (ymax+YFUDGE-height)/2;
	    prefposition(x, x+width-1, y, y+height-1);
	    cp = strrchr(filename, '/');
	    sprintf(title, "%s [%u] %s",
		cp == NULL ? filename : cp+1,
		(unsigned int) TIFFCurrentDirectory(tif),
		isrgb ? " rgb" : " cmap");
	    if (fg)
		foreground();
	    wid = winopen(title);
	    if (wid < 0) {
		TIFFError(filename, "Can not create window");
		TIFFRGBAImageEnd(&img);
		break;
	    }
	    curstype(C16X1);
	    defcursor(1, hourglass);
	    qdevice(LEFTMOUSE);
	    qdevice(MIDDLEMOUSE);
	    qdevice(RIGHTMOUSE);
	    qdevice(KEYBD);
	    qdevice(PAGEUPKEY);
	    qdevice(PAGEDOWNKEY);
	    qdevice(HOMEKEY);
	    qdevice(ENDKEY);
	} else {
	    x = (xmax+XFUDGE-width)/2;
	    y = (ymax+YFUDGE-height)/2;
	    winposition(x, x+width-1, y, y+height-1);
	    viewport(0, width-1, 0, height-1);
	    cp = strrchr(filename, '/');
	    sprintf(title, "%s [%u] %s",
		cp == NULL ? filename : cp+1,
		(unsigned int) TIFFCurrentDirectory(tif),
		isrgb ? " rgb" : " cmap");
	    wintitle(title);
	}
	singlebuffer();
	if (isrgb) {
	    RGBmode();
	    gconfig();
	} else {
	    cmode();
	    gconfig();
	}
	/*
	 * Fetch the image.
	 */
	setcursor(1, 0, 0);
	greyi(225);
	clear();
	(void) TIFFRGBAImageGet(&img, raster, width, height);
	setcursor(0, 0, 0);
	/*
	 * Process input.
	 */
	for (;;) {
	    short val;
	    switch (qread(&val)) {
	    case KEYBD:
		switch (val) {
		case 'b':			/* photometric MinIsBlack */
		    photo = PHOTOMETRIC_MINISBLACK;
		    goto newpage;
		case 'l':			/* lsb-to-msb FillOrder */
		    order = FILLORDER_LSB2MSB;
		    goto newpage;
		case 'm':			/* msb-to-lsb FillOrder */
		    order = FILLORDER_MSB2LSB;
		    goto newpage;
		case 'c':			/* colormap visual */
		    isRGB = 0;
		    goto newpage;
		case 'r':			/* RGB visual */
		    isRGB = 1;
		    goto newpage;
		case 'w':			/* photometric MinIsWhite */
		    photo = PHOTOMETRIC_MINISWHITE;
		    goto newpage;
		case 'W':			/* toggle warnings */
		    owarning = TIFFSetWarningHandler(owarning);
		    goto newpage;
		case 'E':			/* toggle errors */
		    oerror = TIFFSetErrorHandler(oerror);
		    goto newpage;
		case 'z':			/* reset to defaults */
		case 'Z':
		    order = order0;
		    photo = photo0;
		    isRGB = isRGB0;
		    if (owarning == NULL)
			owarning = TIFFSetWarningHandler(NULL);
		    if (oerror == NULL)
			oerror = TIFFSetErrorHandler(NULL);
		    goto newpage;
		case 'q':			/* exit */
		case '\033':
		    TIFFRGBAImageEnd(&img);
		    goto done;
		}
		break;
	    case PAGEUPKEY:			/* previous logical image */
		if (val) {
		    if (TIFFCurrentDirectory(tif) > 0) {
			if (TIFFSetDirectory(tif, TIFFCurrentDirectory(tif)-1))
			    goto newpage;
			beep();		/* XXX */
		    } else {
			ix = prevImage(argv, ix, optind, argc, TRUE);
			/* XXX set directory to last image in new file */
			goto newfile;
		    }
		}
		break;
	    case PAGEDOWNKEY:			/* next logical image */
		if (val) {
		    if (!TIFFLastDirectory(tif)) {
			if (TIFFReadDirectory(tif))
			    goto newpage;
			beep();		/* XXX */
		    } else {
			ix = nextImage(argv, ix, optind, argc, TRUE);
			goto newfile;
		    }
		}
		break;
	    case HOMEKEY:			/* 1st image in current file */
		if (val) {
		    if (TIFFSetDirectory(tif, 0))
			goto newpage;
		    beep();
		}
		break;
	    case ENDKEY:			/* last image in current file */
		if (val) {
		    /* XXX */
		    beep();
		}
		break;
	    case RIGHTMOUSE:			/* previous file */
		if (val) {
		    if (nix = prevImage(argv, ix, optind, argc, FALSE)) {
			ix = nix;
			goto newfile;
		    }
		    beep();
		}
		break;
	    case LEFTMOUSE:			/* next file */
		if (val) {
		    if (nix = nextImage(argv, ix, optind, argc, FALSE)) {
			ix = nix;
			goto newfile;
		    }
		    beep();
		}
		break;
	    case MIDDLEMOUSE:			/* first file */
		if (val) {
		    if (nix = nextImage(argv, optind-1, optind, argc, FALSE)) {
			ix = nix;
			goto newfile;
		    }
		    beep();
		}
		break;
	    case REDRAW:
		lrectwrite(0, 0, width-1, height-1, raster);
		break;
	    }
	}
    newfile:
	TIFFRGBAImageEnd(&img);
	if (tif != NULL && argv[ix] != filename)
	    TIFFClose(tif), tif = NULL;
	/* fall thru... */
    newfile0:
	if (argv[ix] == NULL)
	    break;
	filename = argv[ix];
	if (tif == NULL) {
	    tif = TIFFOpen(filename, "r");
	    if (tif == NULL)
		goto bad1;
	    isRGB = isRGB0;
	    order = order0;
	    photo = photo0;
	}
	continue;
    newpage:
	TIFFRGBAImageEnd(&img);
	continue;
    bad3:
	TIFFRGBAImageEnd(&img);
    bad2:
	TIFFClose(tif), tif = NULL;
    bad1:
	argv[ix] = NULL;			/* don't revisit file */
	ix = nextImage(argv, ix, optind, argc, TRUE);
	goto newfile0;
    }
done:
    if (wid >= 0)
	winclose(wid);
    if (raster != NULL)
	_TIFFfree(raster);
    if (tif != NULL)
	TIFFClose(tif);
    return (0);
}

static int
prevImage(char* argv[], int ix, int b, int e, int wrap)
{
    int i;

    for (i = ix-1; i >= b && argv[i] == NULL; i--)
	;
    if (i < b) {
	if (wrap) {
	    for (i = e-1; i > ix && argv[i] == NULL; i--)
		;
	} else
	    i = 0;
    }
    return (i);
}

static int
nextImage(char* argv[], int ix, int b, int e, int wrap)
{
    int i;

    for (i = ix+1; i < e && argv[i] == NULL; i++)
	;
    if (i >= e) {
	if (wrap) {
	    for (i = b; i < ix && argv[i] == NULL; i++)
		;
	} else
	    i = 0;
    }
    return (i);
}

static void
beep(void)
{
    greyi(0);
    clear();
    sginap(5);
    lrectwrite(0, 0, width-1, height-1, raster);
}

char* stuff[] = {
"usage: tiffgt [options] file.tif",
"where options are:",
" -c		use colormap visual",
" -d dirnum	set initial directory (default is 0)",
" -e		enable display of TIFF error messages",
" -f  		run program in the foreground",
" -l  		force lsb-to-msb FillOrder",
" -m  		force msb-to-lsb FillOrder",
" -o offset	set initial directory offset",
" -p photo	override photometric interpretation",
" -r		use fullcolor visual",
" -s  		stop decoding on first error (default is ignore errors)",
" -v		enable verbose mode",
" -w		enable display of TIFF warning messages",
NULL
};

static void
usage(void)
{
    char buf[BUFSIZ];
    int i;

    setbuf(stderr, buf);
    for (i = 0; stuff[i] != NULL; i++)
	fprintf(stderr, "%s\n", stuff[i]);
    exit(-1);
}

static uint16
photoArg(const char* arg)
{
    if (strcmp(arg, "miniswhite") == 0)
	return (PHOTOMETRIC_MINISWHITE);
    else if (strcmp(arg, "minisblack") == 0)
	return (PHOTOMETRIC_MINISBLACK);
    else if (strcmp(arg, "rgb") == 0)
	return (PHOTOMETRIC_RGB);
    else if (strcmp(arg, "palette") == 0)
	return (PHOTOMETRIC_PALETTE);
    else if (strcmp(arg, "mask") == 0)
	return (PHOTOMETRIC_MASK);
    else if (strcmp(arg, "separated") == 0)
	return (PHOTOMETRIC_SEPARATED);
    else if (strcmp(arg, "ycbcr") == 0)
	return (PHOTOMETRIC_YCBCR);
    else if (strcmp(arg, "cielab") == 0)
	return (PHOTOMETRIC_CIELAB);
    else
	return ((uint16) -1);
}

static void
putContigAndDraw(TIFFRGBAImage* img, uint32* raster,
    uint32 x, uint32 y, uint32 w, uint32 h,
    int32 fromskew, int32 toskew,
    unsigned char* cp)
{
    (*putContig)(img, raster, x, y, w, h, fromskew, toskew, cp);
    if (x+w == width) {
	w = width;
	if (img->orientation == ORIENTATION_TOPLEFT)
	    lrectwrite(0, y-(h-1), w-1, y, raster-x-(h-1)*w);
	else
	    lrectwrite(0, y, w-1, y+h-1, raster);
    }
}

static void
putSeparateAndDraw(TIFFRGBAImage* img, uint32* raster,
    uint32 x, uint32 y, uint32 w, uint32 h,
    int32 fromskew, int32 toskew,
    unsigned char* r, unsigned char* g, unsigned char* b, unsigned char* a)
{
    (*putSeparate)(img, raster, x, y, w, h, fromskew, toskew, r, g, b, a);
    if (x+w == width) {
	w = width;
	if (img->orientation == ORIENTATION_TOPLEFT)
	    lrectwrite(x, y-(h-1), w-1, y, raster-x-(h-1)*w);
	else
	    lrectwrite(x, y, w-1, y+h-1, raster);
    }
}

/*
 * {red,green,blue}_inverse are tables in libgutil.a that
 * do an inverse map from (r,g,b) to the closest colormap
 * index in the "standard" GL colormap.  grey_inverse is
 * the equivalent map for mapping greyscale values to
 * colormap indices.  We access these maps directly instead
 * of through the rgbi and greyi functions to avoid the
 * additional overhead of the color calls that they make.
 */
extern	u_char red_inverse[256];
extern	u_char green_inverse[256];
extern	u_char blue_inverse[256];
extern	u_char grey_inverse[256];
#define	greyi(g)	grey_inverse[g]

static u_char
rgbi(u_char r, u_char g, u_char b)
{
    return (r == g && g == b ? grey_inverse[r] :
	red_inverse[r] + green_inverse[g] + blue_inverse[b]);
}

/*
 * The following routines move decoded data returned
 * from the TIFF library into rasters that are suitable
 * for passing to lrecwrite.  They do the necessary
 * conversions for when a colormap drawing mode is used.
 */
#define	REPEAT8(op)	REPEAT4(op); REPEAT4(op)
#define	REPEAT4(op)	REPEAT2(op); REPEAT2(op)
#define	REPEAT2(op)	op; op
#define	CASE8(x,op)			\
    switch (x) {			\
    case 7: op; case 6: op; case 5: op;	\
    case 4: op; case 3: op; case 2: op;	\
    case 1: op;				\
    }
#define	CASE4(x,op)	switch (x) { case 3: op; case 2: op; case 1: op; }
#define	NOP

#define	UNROLL8(w, op1, op2) {		\
    uint32 _x;				\
    for (_x = w; _x >= 8; _x -= 8) {	\
	op1;				\
	REPEAT8(op2);			\
    }					\
    if (_x > 0) {			\
	op1;				\
	CASE8(_x,op2);			\
    }					\
}
#define	UNROLL4(w, op1, op2) {		\
    uint32 _x;				\
    for (_x = w; _x >= 4; _x -= 4) {	\
	op1;				\
	REPEAT4(op2);			\
    }					\
    if (_x > 0) {			\
	op1;				\
	CASE4(_x,op2);			\
    }					\
}
#define	UNROLL2(w, op1, op2) {		\
    uint32 _x;				\
    for (_x = w; _x >= 2; _x -= 2) {	\
	op1;				\
	REPEAT2(op2);			\
    }					\
    if (_x) {				\
	op1;				\
	op2;				\
    }					\
}

#define	SKEW(r,g,b,skew)	{ r += skew; g += skew; b += skew; }

#define	DECLAREContigPutFunc(name) \
static void name(\
    TIFFRGBAImage* img, \
    uint32* cp, \
    uint32 x, uint32 y, \
    uint32 w, uint32 h, \
    int32 fromskew, int32 toskew, \
    u_char* pp \
)

#define	DECLARESepPutFunc(name) \
static void name(\
    TIFFRGBAImage* img,\
    uint32* cp,\
    uint32 x, uint32 y, \
    uint32 w, uint32 h,\
    int32 fromskew, int32 toskew,\
    u_char* r, u_char* g, u_char* b, u_char* a\
)

static	tileContigRoutine libput;

/*
 * 8-bit packed samples => colormap
 */
DECLAREContigPutFunc(putcontig8bittile)
{
    int samplesperpixel = img->samplesperpixel;
    TIFFRGBValue* Map = img->Map;

    (void) y;
    fromskew *= samplesperpixel;
    if (Map) {
	while (h-- > 0) {
	    for (x = w; x-- > 0;) {
		*cp++ = rgbi(Map[pp[0]], Map[pp[1]], Map[pp[2]]);
		pp += samplesperpixel;
	    }
	    cp += toskew;
	    pp += fromskew;
	}
    } else {
	while (h-- > 0) {
	    for (x = w; x-- > 0;) {
		*cp++ = rgbi(pp[0], pp[1], pp[2]);
		pp += samplesperpixel;
	    }
	    cp += toskew;
	    pp += fromskew;
	}
    }
}

/*
 * Convert 8-bit packed samples => colormap
 */
DECLAREContigPutFunc(cvtcontig8bittile)
{
    (*libput)(img, cp, x, y, w, h, fromskew, toskew, pp);
    while (h-- > 0) {
	UNROLL8(w, NOP,
	    cp[0] = rgbi(TIFFGetR(cp[0]),TIFFGetG(cp[0]),TIFFGetB(cp[0])); cp++
	);
	cp += toskew;
    }
}

/*
 * 16-bit packed samples => colormap
 */
DECLAREContigPutFunc(putcontig16bittile)
{
    int samplesperpixel = img->samplesperpixel;
    TIFFRGBValue* Map = img->Map;

    (void) y;
    fromskew *= samplesperpixel;
    if (Map) {
	while (h-- > 0) {
	    for (x = w; x-- > 0;) {
		*cp++ = rgbi(Map[pp[0]], Map[pp[1]], Map[pp[2]]);
		pp += samplesperpixel;
	    }
	    cp += toskew;
	    pp += fromskew;
	}
    } else {
	while (h-- > 0) {
	    for (x = w; x-- > 0;) {
		*cp++ = rgbi(pp[0], pp[1], pp[2]);
		pp += samplesperpixel;
	    }
	    cp += toskew;
	    pp += fromskew;
	}
    }
}

/*
 * 8-bit unpacked samples => colormap
 */
DECLARESepPutFunc(putseparate8bittile)
{
    TIFFRGBValue* Map = img->Map;

    (void) y; (void) a;
    if (Map) {
	while (h-- > 0) {
	    for (x = w; x-- > 0;)
		*cp++ = rgbi(Map[*r++], Map[*g++], Map[*b++]);
	    SKEW(r, g, b, fromskew);
	    cp += toskew;
	}
    } else {
	while (h-- > 0) {
	    for (x = w; x-- > 0;)
		*cp++ = rgbi(*r++, *g++, *b++);
	    SKEW(r, g, b, fromskew);
	    cp += toskew;
	}
    }
}

/*
 * 16-bit unpacked samples => colormap
 */
DECLARESepPutFunc(putseparate16bittile)
{
    TIFFRGBValue* Map = img->Map;

    (void) y; (void) a;
    if (Map) {
	while (h-- > 0) {
	    for (x = 0; x < w; x++)
		*cp++ = rgbi(Map[*r++], Map[*g++], Map[*b++]);
	    SKEW(r, g, b, fromskew);
	    cp += toskew;
	}
    } else {
	while (h-- > 0) {
	    for (x = 0; x < w; x++)
		*cp++ = rgbi(*r++, *g++, *b++);
	    SKEW(r, g, b, fromskew);
	    cp += toskew;
	}
    }
}

/*
 * 8-bit packed CMYK samples => cmap
 *
 * NB: The conversion of CMYK->RGB is *very* crude.
 */
DECLAREContigPutFunc(putcontig8bitCMYKtile)
{
    int samplesperpixel = img->samplesperpixel;
    TIFFRGBValue* Map = img->Map;
    uint16 r, g, b, k;

    (void) y;
    fromskew *= samplesperpixel;
    if (Map) {
	while (h-- > 0) {
	    for (x = w; x-- > 0;) {
		k = 255 - pp[3];
		r = (k*(255-pp[0]))/255;
		g = (k*(255-pp[1]))/255;
		b = (k*(255-pp[2]))/255;
		*cp++ = rgbi(Map[r], Map[g], Map[b]);
		pp += samplesperpixel;
	    }
	    pp += fromskew;
	    cp += toskew;
	}
    } else {
	while (h-- > 0) {
	    UNROLL8(w, NOP,
		k = 255 - pp[3];
		r = (k*(255-pp[0]))/255;
		g = (k*(255-pp[1]))/255;
		b = (k*(255-pp[2]))/255;
		*cp++ = rgbi(r, g, b);
		pp += samplesperpixel);
	    cp += toskew;
	    pp += fromskew;
	}
    }
}

#define	YCbCrtoRGB(dst, yc) {						\
    int Y = (yc);							\
    dst = rgbi(								\
	clamptab[Y+Crrtab[Cr]],						\
	clamptab[Y + (int)((Cbgtab[Cb]+Crgtab[Cr])>>16)],		\
	clamptab[Y+Cbbtab[Cb]]);					\
}
#define	YCbCrSetup							\
    TIFFYCbCrToRGB* ycbcr = img->ycbcr;					\
    int* Crrtab = ycbcr->Cr_r_tab;					\
    int* Cbbtab = ycbcr->Cb_b_tab;					\
    int32* Crgtab = ycbcr->Cr_g_tab;					\
    int32* Cbgtab = ycbcr->Cb_g_tab;					\
    TIFFRGBValue* clamptab = ycbcr->clamptab

/*
 * 8-bit packed YCbCr samples w/ 2,2 subsampling => RGB
 */
DECLAREContigPutFunc(putcontig8bitYCbCr22tile)
{
    YCbCrSetup;
    uint32* cp1 = cp+w+toskew;
    unsigned int incr = 2*toskew+w;

    (void) y;
    /* XXX adjust fromskew */
    for (; h >= 2; h -= 2) {
	x = w>>1;
	do {
	    int Cb = pp[4];
	    int Cr = pp[5];

	    YCbCrtoRGB(cp [0], pp[0]);
	    YCbCrtoRGB(cp [1], pp[1]);
	    YCbCrtoRGB(cp1[0], pp[2]);
	    YCbCrtoRGB(cp1[1], pp[3]);

	    cp += 2, cp1 += 2;
	    pp += 6;
	} while (--x);
	cp += incr, cp1 += incr;
	pp += fromskew;
    }
}
#undef	YCbCrSetup
#undef	YCbCrtoRGB

/*
 * Setup to handle conversion for display in a colormap
 * window.  Many cases are handled by massaging the mapping
 * tables used by the normal library code to convert 32-bit
 * packed RGBA samples into colormap indices.  Other cases
 * are handled with special-case routines that replace the
 * normal ``put routine'' installed by the library.
 */
static void
setupColormapSupport(TIFFRGBAImage* img)
{
    int bitspersample = img->bitspersample;
    int i;

    if (img->BWmap) {
	i = 255;
	do {
	    uint32* p = img->BWmap[i];
	    switch (bitspersample) {
#define	GREY(x)	p[x] = greyi(TIFFGetR(p[x]))
	    case 1: GREY(7); GREY(6); GREY(5); GREY(4);
	    case 2: GREY(3); GREY(2);
	    case 4: GREY(1);
	    case 8: GREY(0);
	    }
#undef	GREY
	} while (i--);
    } else if (img->PALmap) {
	i = 255;
	do {
	    uint32 rgb;
	    uint32* p = img->PALmap[i];
#define	CMAP(x) \
    (rgb = p[x], p[x] = rgbi(TIFFGetR(rgb),TIFFGetG(rgb),TIFFGetB(rgb)))
	    switch (bitspersample) {
	    case 1: CMAP(7); CMAP(6); CMAP(5); CMAP(4);
	    case 2: CMAP(3); CMAP(2);
	    case 4: CMAP(1);
	    case 8: CMAP(0);
	    }
#undef CMAP
	} while (i--);
    } else if (img->isContig) {
	switch (img->photometric) {
	case PHOTOMETRIC_RGB:
		switch (bitspersample) {
		case 8:  img->put.contig = putcontig8bittile; break;
		case 16: img->put.contig = putcontig16bittile; break;
		}
		break;
	case PHOTOMETRIC_SEPARATED:
		switch (bitspersample) {
		case 8:  img->put.contig = putcontig8bitCMYKtile; break;
		}
		break;
	case PHOTOMETRIC_YCBCR:
		if (img->bitspersample == 8) {
		    uint16 hs, vs;
		    TIFFGetFieldDefaulted(img->tif, TIFFTAG_YCBCRSUBSAMPLING,
			&hs, &vs);
		    switch ((hs<<4)|vs) {
		    case 0x22:			/* most common case */
			img->put.contig = putcontig8bitYCbCr22tile;
			break;
		    default:			/* all others cost more */
			libput = img->put.contig;
			img->put.contig = cvtcontig8bittile;
			break;
		    }
		}
		break;
	}
    } else {
	switch (img->photometric) {
	case PHOTOMETRIC_RGB:
	    switch (img->bitspersample) {
	    case 8:  img->put.separate = putseparate8bittile; break;
	    case 16: img->put.separate = putseparate16bittile; break;
	    }
	    break;
	}
    }
}
