/*
** tifftopnm.c - converts a Tagged Image File to a portable anymap
**
** Derived by Jef Poskanzer from tif2ras.c, which is:
**
** Copyright (c) 1990 by Sun Microsystems, Inc.
**
** Author: Patrick J. Naughton
** naughton@wind.sun.com
**
** Permission to use, copy, modify, and distribute this software and its
** documentation for any purpose and without fee is hereby granted,
** provided that the above copyright notice appear in all copies and that
** both that copyright notice and this permission notice appear in
** supporting documentation.
**
** This file is provided AS IS with no warranties of any kind.  The author
** shall have no liability with respect to the infringement of copyrights,
** trade secrets or any patents by this file or any part thereof.  In no
** event will the author be liable for any lost revenue or profits or
** other special, indirect and consequential damages.
*/

/* Implementation note:

  This probably ought to be reimplemented using TIFFRGBAImageGet() 
  from the Tiff library, because that routine automatically decodes
  anything and everything that might be in a Tiff image file now or
  in the future.

  Much of the code below is basically copied from the Tiff library
  because we use the more primitive TIFFReadScanLine() access method.

  -Bryan 00.03.05
*/

#define _BSD_SOURCE 1
    /* Make sure strdup() is in string.h */

#include <string.h>
#include "pnm.h"
#ifdef VMS
#ifdef SYSV
#undef SYSV
#endif
#include <tiffioP.h>
#endif
#include <tiffio.h>

/* The following are in current tiff.h, but so that we can compile against
   older tiff libraries, we define them here.
*/

#ifndef PHOTOMETRIC_LOGL
#define PHOTOMETRIC_LOGL 32844
#endif
#ifndef PHOTOMETRIC_LOGLUV
#define PHOTOMETRIC_LOGLUV 32845
#endif

#define MAXCOLORS 1024
#ifndef PHOTOMETRIC_DEPTH
#define PHOTOMETRIC_DEPTH 32768
#endif

/* The following form a cursor into the raster buffer */
static u_char* inP;
static int bitsleft;

/* The following describe the structure of the raster buffer */
static int maxval;
static unsigned short bps, spp;

/* The NEXTSAMPLE macro effects the reading of a sample from the raster
   buffer via the above cursor into the variable 'sample'.
*/
#define NEXTSAMPLE \
    { \
    if ( bitsleft == 0 ) \
	{ \
	++inP; \
	bitsleft = 8; \
	} \
    bitsleft -= bps; \
    sample = ( *inP >> bitsleft ) & maxval; \
    }


struct cmdline_info {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    char *input_filename;
    int headerdump;
    char *alpha_filename;
    int alpha_stdout;
} cmdline;



static void
parse_command_line(int argc, char ** argv,
                   struct cmdline_info *cmdline_p);
static void
pick_rgba_pixel(xelval * const r_p, xelval * const b_p, xelval * const g_p,
                xelval * const r_a);
static void
pick_cmyk_pixel(xelval * const r_p, xelval * const b_p, xelval * const g_p);




int
main( argc, argv )
    int argc;
    char* argv[];
    {
    int cols, rows, grayscale, format;
    int numcolors;
    register TIFF* tif;
    FILE *alpha_file, *imageout_file;
    int row, i;
    register int col;
    u_char* buf;
    xel* xelrow;
        /* The ppm-format row of the image row we are presently converting */
    gray* alpharow;
        /* The pgm-format row representing the alpha values for the image 
           row we are presently converting.
           */
    xel colormap[MAXCOLORS];
    register u_char sample;
    unsigned short photomet, planarconfig, inkset;
    unsigned short* redcolormap;
    unsigned short* greencolormap;
    unsigned short* bluecolormap;

    pnm_init( &argc, argv );

    parse_command_line(argc, argv, &cmdline);

    if ( cmdline.input_filename != NULL ) {
        tif = TIFFOpen( cmdline.input_filename, "r" );
        if ( tif == NULL )
            pm_error( "error opening TIFF file %s", cmdline.input_filename );
	} else {
        tif = TIFFFdOpen( 0, "Standard Input", "r" );
        if ( tif == NULL )
            pm_error( "error opening standard input as TIFF file" );
	}

    if (cmdline.alpha_stdout)
        alpha_file = stdout;
    else if (cmdline.alpha_filename == NULL) 
        alpha_file = NULL;
    else {
        alpha_file = pm_openw(cmdline.alpha_filename);
    }

    if (cmdline.alpha_stdout) 
        imageout_file = NULL;
    else
        imageout_file = stdout;


    if ( cmdline.headerdump )
	TIFFPrintDirectory( tif, stderr, TIFFPRINT_NONE );

    if ( ! TIFFGetField( tif, TIFFTAG_BITSPERSAMPLE, &bps ) )
	bps = 1;
    if ( ! TIFFGetField( tif, TIFFTAG_SAMPLESPERPIXEL, &spp ) )
	spp = 1;
    if ( ! TIFFGetField( tif, TIFFTAG_PHOTOMETRIC, &photomet ) )
	pm_error( "error getting photometric" );
    if( spp > 1 ){
	if ( ! TIFFGetField( tif, TIFFTAG_PLANARCONFIG, &planarconfig ) )
	    pm_error( "error getting planarconfig" );
    }else{
	planarconfig = PLANARCONFIG_CONTIG;
    }


    switch ( spp )
	{
	case 1:
	case 3:
	case 4:
	break;

	default:
	pm_error(
	    "can only handle 1-channel gray scale or 1- or 3-channel color" );
	}

    switch( planarconfig )
	{
	case PLANARCONFIG_CONTIG:
	    break;
	case PLANARCONFIG_SEPARATE:
	    if (photomet != PHOTOMETRIC_RGB && photomet != PHOTOMETRIC_SEPARATED)
		pm_error( "can only handle separate planes "
                 "with RGB or SEPARATED data" );
	    break;
	default:
	    pm_error("Unrecongnized PLANARCONFIG tag!\n");
	}

    (void) TIFFGetField( tif, TIFFTAG_IMAGEWIDTH, &cols );
    (void) TIFFGetField( tif, TIFFTAG_IMAGELENGTH, &rows );

    if ( cmdline.headerdump )
	{
	pm_message( "%dx%dx%d image", cols, rows, bps * spp );
	pm_message( "%d bits/sample, %d samples/pixel", bps, spp );
	}

    maxval = ( 1 << bps ) - 1;
    if ( maxval == 1 && spp == 1 ) {
        if ( cmdline.headerdump )
          pm_message("monochrome" );
        grayscale = 1;
	} else {
        /* How come we don't deal with the photometric for the monochrome 
           case (make sure it's one we know)?  -Bryan 00.03.04
           */
        switch ( photomet ) {
          case PHOTOMETRIC_MINISBLACK:
            if ( cmdline.headerdump )
              pm_message( "%d graylevels (min=black)", maxval + 1 );
            grayscale = 1;
            break;

          case PHOTOMETRIC_MINISWHITE:
            if ( cmdline.headerdump )
              pm_message( "%d graylevels (min=white)", maxval + 1 );
            grayscale = 1;
            break;

          case PHOTOMETRIC_PALETTE:
            if ( cmdline.headerdump )
              pm_message( "colormapped" );
            if ( ! TIFFGetField( tif, TIFFTAG_COLORMAP, 
                                &redcolormap, &greencolormap, &bluecolormap ) )
              pm_error( "error getting colormaps" );
            numcolors = maxval + 1;
            if ( numcolors > MAXCOLORS )
              pm_error( "too many colors" );
            maxval = PNM_MAXMAXVAL;
            grayscale = 0;
            for ( i = 0; i < numcolors; ++i ) {
                register xelval r, g, b;
                r = (long) redcolormap[i] * PNM_MAXMAXVAL / 65535L;
                g = (long) greencolormap[i] * PNM_MAXMAXVAL / 65535L;
                b = (long) bluecolormap[i] * PNM_MAXMAXVAL / 65535L;
                PPM_ASSIGN( colormap[i], r, g, b );
            }
            break;

          case PHOTOMETRIC_SEPARATED:
            if ( cmdline.headerdump )
              pm_message( "color separation" );
            if ( TIFFGetField( tif, TIFFTAG_INKNAMES, &inkset ) == 1
                && inkset != INKSET_CMYK)
            if (inkset != INKSET_CMYK) 
               pm_error("This color separation file uses an inkset (%d) ",
                        "we can't handle.  We handle only CMYK.", inkset);
            if (spp != 4) 
              pm_error("This CMYK color separation file is %d samples per "
                       "pixel.  We need 4 samples, though: C, M, Y, and K.  ",
                       spp);
            grayscale = 0;
            break;
            
          case PHOTOMETRIC_RGB:
            if ( cmdline.headerdump )
              pm_message( "RGB truecolor" );
            grayscale = 0;
            break;

          case PHOTOMETRIC_MASK:
            pm_error( "don't know how to handle PHOTOMETRIC_MASK" );

          case PHOTOMETRIC_DEPTH:
            pm_error( "don't know how to handle PHOTOMETRIC_DEPTH" );

          case PHOTOMETRIC_YCBCR:
            pm_error( "don't know how to handle PHOTOMETRIC_YCBCR" );

          case PHOTOMETRIC_CIELAB:
            pm_error( "don't know how to handle PHOTOMETRIC_CIELAB" );

          case PHOTOMETRIC_LOGL:
            pm_error( "don't know how to handle PHOTOMETRIC_LOGL" );

          case PHOTOMETRIC_LOGLUV:
            pm_error( "don't know how to handle PHOTOMETRIC_LOGLUV" );

          default:
            pm_error( "unknown photometric: %d", photomet );
	    }
	}
    if ( maxval > PNM_OVERALLMAXVAL )
        pm_error("bits/sample (%d) in the input image is too large.\n",
                 bps);
    if ( grayscale )
	{
	if ( maxval == 1 )
	    {
	    format = PBM_TYPE;
	    pm_message( "writing PBM file" );
	    }
	else
	    {
	    format = PGM_TYPE;
	    pm_message( "writing PGM file" );
	    }
	}
    else
	{
	format = PPM_TYPE;
	pm_message( "writing PPM file" );
	}

    buf = (u_char*) malloc(TIFFScanlineSize(tif));
    if ( buf == NULL )
	pm_error( "can't allocate memory for scanline buffer" );

    if (imageout_file != NULL) 
        pnm_writepnminit( imageout_file, 
                          cols, rows, (xelval) maxval, format, 0 );
    if (alpha_file != NULL) 
        pgm_writepgminit( alpha_file, cols, rows, (gray) maxval, 0 );

    xelrow = pnm_allocrow( cols );
    alpharow = pgm_allocrow( cols );

    for ( row = 0; row < rows; ++row ) {
        if ( TIFFReadScanline( tif, buf, row, 0 ) < 0 )
          pm_error( "bad data read on line %d", row );

        /* Set readout cursor to beginning of raster buffer */
        inP = buf;
        bitsleft = 8;

        switch ( photomet ) {
          case PHOTOMETRIC_MINISBLACK:
            for ( col = 0; col < cols; ++col ) {
                NEXTSAMPLE
                PNM_ASSIGN1( xelrow[col], sample );
                alpharow[col] = 0;
            }
            break;

          case PHOTOMETRIC_MINISWHITE:
            for ( col = 0; col < cols; ++col ) {
                NEXTSAMPLE
                sample = maxval - sample;
                PNM_ASSIGN1( xelrow[col], sample );
                alpharow[col] = 0;
            }
            break;

          case PHOTOMETRIC_PALETTE:
            for ( col = 0; col < cols; ++col ) {
                NEXTSAMPLE
                xelrow[col] = colormap[sample];
                alpharow[col] = 0;
            }
            break;

          case PHOTOMETRIC_SEPARATED:
            if (planarconfig == PLANARCONFIG_CONTIG) {
                for ( col = 0; col < cols; ++col ) {
                    xelval r, g, b;
                    pick_cmyk_pixel(&r, &b, &g);
            
                    PPM_ASSIGN( xelrow[col], r, g, b );
                    alpharow[col] = 0;
                }
            } else 
                pm_error("Can't handle multiple plane color separation file");
            break;

          case PHOTOMETRIC_RGB:
            if( planarconfig == PLANARCONFIG_CONTIG ) {
                for ( col = 0; col < cols; ++col ) {
                    xelval r, g, b, a;
                    
                    pick_rgba_pixel(&r, &b, &g, &a);
            
                    PPM_ASSIGN( xelrow[col], r, g, b );
                    alpharow[col] = a;
                }
            } else {
                /* The input is in separate planes, so the scan line in the 
                   buffer now is just the red.  We need to read twice more
                   to get the green and blue.
                   */
                
                /* First clear the value and assign the reds */
                for ( col = 0; col < cols; ++col ) {
                    PPM_ASSIGN( xelrow[col], 0, 0, 0 );
                    NEXTSAMPLE
                    PPM_PUTR( xelrow[col], sample );
                }
                
                /* Next the greens */
                if ( TIFFReadScanline( tif, buf, row, 1 ) < 0 )
                  pm_error( "bad data read on green line %d", row );
                inP = buf;
                bitsleft = 8;
                for ( col = 0; col < cols; ++col ) {
                    NEXTSAMPLE
                    PPM_PUTG( xelrow[col], sample );
                }

                /* And finally the blues */
                if ( TIFFReadScanline( tif, buf, row, 2 ) < 0 )
                  pm_error( "bad data read on green line %d", row );
                inP = buf;
                bitsleft = 8;
                for ( col = 0; col < cols; ++col ) {
                    NEXTSAMPLE
                    PPM_PUTB( xelrow[col], sample );
                }
                /* Could there be an alpha plane?  (We assume no.  But if so,
                   here is where to read it) */
                for ( col = 0; col < cols; ++col) 
                    alpharow[col] = 0;
            }		
            break;
            
          default:
            pm_error( "internal error:  unknown photometric in the picking "
                     "routine: %d", photomet );
        }
        if (imageout_file != NULL) 
            pnm_writepnmrow( imageout_file, 
                             xelrow, cols, (xelval) maxval, format, 0 );
        if (alpha_file != NULL) 
            pgm_writepgmrow( alpha_file, alpharow, cols, (gray) maxval, 0);
    }

    if (imageout_file != NULL) 
        pm_close( imageout_file );
    if (alpha_file != NULL)
        pm_close( alpha_file );

    /* If the program failed, it previously aborted with nonzero completion
       code, via various function calls.
    */
    return 0;
    }



static void
pick_rgba_pixel(xelval * const r_p, xelval * const b_p, xelval * const g_p,
                xelval * const a_p) {
/*----------------------------------------------------------------------------
   Pull the next pixel from the TIFF input, assuming it is in rgb or
   rgba format (as determined by the global value 'spp', the number of
   samples per pixel).

   Return the individual components of the pixel.  If there is no alpha
   component, return 0 as the alpha component.
-----------------------------------------------------------------------------*/

    register u_char sample;

    NEXTSAMPLE
    *r_p = sample;
    NEXTSAMPLE
    *g_p = sample;
    NEXTSAMPLE
    *b_p = sample;
    if ( spp == 4 ) {
        NEXTSAMPLE 
        *a_p = sample;
    } else 
        *a_p = 0;
}



static void
pick_cmyk_pixel(xelval * const r_p, xelval * const b_p, xelval * const g_p) {

    register u_char sample;
    register u_char c, y, m, k;
    

    NEXTSAMPLE
    c = sample;
    NEXTSAMPLE
    y = sample;
    NEXTSAMPLE
    m = sample;
    NEXTSAMPLE
    k = sample;

    /* I don't know why this works.  It seems to me that yellow pigment should
       translate into both red and green light, not just green as you see here.
       But this is the formula used by TIFFRGBAImageGet() in the Tiff library.
       */
    
    *r_p = ((255-k)*(255-c))/255;
    *g_p = ((255-k)*(255-y))/255;
    *b_p = ((255-k)*(255-m))/255;
}


static void
parse_command_line(int argc, char ** argv,
                   struct cmdline_info *cmdline_p) {
/*----------------------------------------------------------------------------
   Note that many of the strings that this function returns in the
   *cmdline_p structure are actually in the supplied argv array.  And
   sometimes, one of these strings is actually just a suffix of an entry
   in argv!
-----------------------------------------------------------------------------*/
    optStruct *option_def = malloc(100*sizeof(optStruct));
        /* Instructions to OptParseOptions on how to parse our options.
         */
    unsigned int option_def_index;

    option_def_index = 0;   /* incremented by OPTENTRY */
    OPTENTRY('h', "headerdump", OPT_FLAG,   &cmdline_p->headerdump,     0);
    OPTENTRY(0,   "alphaout",   OPT_STRING, &cmdline_p->alpha_filename, 0);

    /* Set the defaults */
    cmdline_p->headerdump = 0;
    cmdline_p->alpha_filename = NULL;

    optParseOptions(&argc, argv, option_def, 0);
        /* Uses and sets argc, argv, and all of *cmdline_p. */

    if (argc - 1 == 0)
        cmdline_p->input_filename = NULL;  /* he wants stdin */
    else if (argc - 1 == 1)
        cmdline_p->input_filename = strdup(argv[1]);
    else 
        pm_error("Too many arguments.  The only argument accepted\n"
                 "is the input file specificaton");

    if (cmdline_p->alpha_filename && 
        strcmp(cmdline_p->alpha_filename, "-") == 0 )
        cmdline_p->alpha_stdout = 1;
    else 
        cmdline_p->alpha_stdout = 0;
}

