/* rasttopnm.c - read a Sun rasterfile and produce a portable anymap
**
** Copyright (C) 1989, 1991 by Jef Poskanzer.
**
** 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 software is provided "as is" without express or
** implied warranty.
*/

#include "pm_c_util.h"
#include "mallocvar.h"
#include "shhopt.h"
#include "pnm.h"
#include "rast.h"



struct cmdlineInfo {
    /* All the information the user supplied in the command line,
       in a form easy for the program to use.
    */
    const char * inputFileName;
    unsigned int index;
};



static void
parseCommandLine(int argc, const char ** argv,
                 struct cmdlineInfo * const cmdlineP) {
/*----------------------------------------------------------------------------
   Note that the file spec array we return is stored in the storage that
   was passed to us as the argv array.
-----------------------------------------------------------------------------*/
    optEntry * option_def;
        /* Instructions to OptParseOptions2 on how to parse our options.
         */
    optStruct3 opt;

    unsigned int option_def_index;

    MALLOCARRAY_NOFAIL(option_def, 100);

    option_def_index = 0;   /* incremented by OPTENT3 */
 
    opt.opt_table = option_def;
    opt.short_allowed = false;  /* We have no short (old-fashioned) options */
    opt.allowNegNum = false;  /* We have no parms that are negative numbers */

    OPTENT3(0,   "index",     OPT_FLAG,   NULL, &cmdlineP->index,   0);

    pm_optParseOptions3(&argc, (char **)argv, opt, sizeof(opt), 0);
        /* Uses and sets argc, argv, and some of *cmdlineP and others. */

    if (argc-1 == 0) 
        cmdlineP->inputFileName = "-";
    else if (argc-1 != 1)
        pm_error("Program takes zero or one argument (filename).  You "
                 "specified %d", argc-1);
    else
        cmdlineP->inputFileName = argv[1];
}



static bool
colorMapIsGrayscale(colormap_t   const colorMap,
                    unsigned int const mapLength) {

    unsigned int i;
    bool grayscale;

    for (i = 0, grayscale = true; i < mapLength / 3; ++i) {
        if (colorMap.map[0][i] != colorMap.map[1][i] ||
            colorMap.map[1][i] != colorMap.map[2][i]) {
            grayscale = false;
        }
    }
    return grayscale;
}



static void
analyzeImage(struct rasterfile const header,
             colormap_t        const colorMap,
             int *             const formatP,
             xelval *          const maxvalP,
             bool *            const grayscaleP,
             xel *             const zeroP,
             xel *             const oneP) {

    bool const grayscale =
        header.ras_maplength == 0 ||
        colorMapIsGrayscale(colorMap, header.ras_maplength);

    switch (header.ras_depth) {
    case 1:
        if (header.ras_maptype == RMT_NONE && header.ras_maplength == 0) {
            *maxvalP = 1;
            *formatP = PBM_TYPE;
            PNM_ASSIGN1(*zeroP, 1);
            PNM_ASSIGN1(*oneP, 0);
        } else if (header.ras_maptype == RMT_EQUAL_RGB &&
                   header.ras_maplength == 6) {
            if (grayscale) {
                *maxvalP = 255;
                *formatP = PGM_TYPE;
                PNM_ASSIGN1(*zeroP, colorMap.map[0][0]);
                PNM_ASSIGN1(*oneP, colorMap.map[0][1]);
            } else {
                *maxvalP = 255;
                *formatP = PPM_TYPE;
                PPM_ASSIGN(
                    *zeroP, colorMap.map[0][0], colorMap.map[1][0],
                    colorMap.map[2][0]);
                PPM_ASSIGN(
                    *oneP, colorMap.map[0][1], colorMap.map[1][1],
                    colorMap.map[2][1]);
            }
        } else
            pm_error(
                "this depth-1 rasterfile has a non-standard colormap - "
                "type %ld length %ld",
                header.ras_maptype, header.ras_maplength);
        break;

    case 8:
        if (grayscale) {
            *maxvalP = 255;
            *formatP = PGM_TYPE;
        } else if (header.ras_maptype == RMT_EQUAL_RGB) {
            *maxvalP = 255;
            *formatP = PPM_TYPE;
        } else
            pm_error(
                "this depth-8 rasterfile has a non-standard colormap - "
                "type %ld length %ld",
                header.ras_maptype, header.ras_maplength);
        break;

    case 24:
    case 32:
        if (header.ras_maptype == RMT_NONE && header.ras_maplength == 0);
        else if (header.ras_maptype == RMT_RAW || header.ras_maplength == 768);
        else
            pm_error(
                "this depth-%ld rasterfile has a non-standard colormap - "
                "type %ld length %ld",
                header.ras_depth, header.ras_maptype, header.ras_maplength);
        *maxvalP = 255;
        *formatP = PPM_TYPE;
        break;

    default:
        pm_error("invalid depth: %ld.  Can handle only depth 1, 8, 24, or 32.",
                 header.ras_depth);
    }
}



static void
reportOutputType(int const format) {

    switch (PNM_FORMAT_TYPE(format)) {
    case PBM_TYPE:
        pm_message("writing PBM file");
        break;
    case PGM_TYPE:
        pm_message("writing PGM file");
        break;
    case PPM_TYPE:
        pm_message("writing PPM file");
        break;
    default:
        abort();
    }
}



static void
writePnm(FILE *                 const ofP,
         const struct pixrect * const pixRectP,
         unsigned int           const cols,
         unsigned int           const rows,
         xelval                 const maxval,
         int                    const format,
         unsigned int           const depth,
         long                   const rastType,
         bool                   const grayscale,
         bool                   const colorMapped,
         colormap_t             const colorMap,
         xel                    const zeroXel,
         xel                    const oneXel,
         bool                   const useIndexForColor) {

    unsigned int const lineSize =
        ((struct mpr_data*) pixRectP->pr_data)->md_linebytes;
    unsigned char * const data =
        ((struct mpr_data*) pixRectP->pr_data)->md_image;

    xel * xelrow;
    unsigned int row;
    unsigned char * lineStart;

    pnm_writepnminit(ofP, cols, rows, maxval, format, 0);

    xelrow = pnm_allocrow(cols);

    reportOutputType(format);

    for (row = 0, lineStart = data; row < rows; ++row, lineStart += lineSize) {
        unsigned char * byteP;

        byteP = lineStart; /* initial value */

        switch (depth) {
        case 1: {
            unsigned int col;
            unsigned char mask;
            for (col = 0, mask = 0x80; col < cols; ++col) {
                if (mask == 0x00) {
                    ++byteP;
                    mask = 0x80;
                }
                xelrow[col] = (*byteP & mask) ? oneXel : zeroXel;
                mask = mask >> 1;
            }
        } break;
        case 8: {
            unsigned int col;
            for (col = 0; col < cols; ++col) {
                if (colorMapped && !useIndexForColor)
                    if (grayscale)
                        PNM_ASSIGN1(xelrow[col], colorMap.map[0][*byteP]);
                    else
                        PPM_ASSIGN(xelrow[col],
                                   colorMap.map[0][*byteP],
                                   colorMap.map[1][*byteP],
                                   colorMap.map[2][*byteP]);
                else
                    PNM_ASSIGN1(xelrow[col], *byteP);
                ++byteP;
            }
        } break;
        case 24:
        case 32: {
            unsigned int col;
            for (col = 0; col < cols; ++col) {
                xelval r, g, b;

                if (depth == 32)
                    ++byteP;
                if (rastType == RT_FORMAT_RGB) {
                    r = *byteP++;
                    g = *byteP++;
                    b = *byteP++;
                } else {
                    b = *byteP++;
                    g = *byteP++;
                    r = *byteP++;
                }
                if (colorMapped && !useIndexForColor)
                    PPM_ASSIGN(xelrow[col],
                               colorMap.map[0][r],
                               colorMap.map[1][g],
                               colorMap.map[2][b]);
                else
                    PPM_ASSIGN(xelrow[col], r, g, b);
            }
        } break;
        default:
            pm_error("Invalid depth value: %u", depth);
        }
        pnm_writepnmrow(ofP, xelrow, cols, maxval, format, 0);
    }
}



int
main(int argc, const char ** const argv) {

    struct cmdlineInfo cmdline;
    FILE * ifP;
    struct rasterfile header;
    colormap_t colorMap;
    bool grayscale;
    struct pixrect * pr;
    int format;
    xelval maxval;
    xel zero, one;
    int rc;

    pm_proginit(&argc, argv);

    parseCommandLine(argc, argv, &cmdline);

    ifP = pm_openr(cmdline.inputFileName);

    rc = pr_load_header(ifP, &header);
    if (rc != 0 )
        pm_error("unable to read in rasterfile header");

    if (header.ras_maplength != 0) {
        int rc;
        
        rc = pr_load_colormap(ifP, &header, &colorMap);
        
        if (rc != 0 )
            pm_error("unable to read colormap from RAST file");
    }

    analyzeImage(header, colorMap, &format, &maxval, &grayscale, &zero, &one);

    pr = pr_load_image(ifP, &header, NULL);
    if (pr == NULL )
        pm_error("unable to read in the image from the rasterfile" );

    if (cmdline.index && header.ras_maplength == 0)
        pm_error("You requested to use color map indices as colors (-index), "
                 "but this is not a color mapped image");

    writePnm(stdout, pr, header.ras_width, header.ras_height, maxval, format,
             header.ras_depth, header.ras_type, grayscale, 
             header.ras_maplength > 0, colorMap, zero, one, cmdline.index);

    pm_close(ifP);
    pm_close(stdout);

    return 0;
}