/* rletopnm.c - convert Utah Raster Toolkit image to portable anymap
**
** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
**
** 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.
**
** 09/Dec/94: first version
*/
#include "pnm.h"
#include "rle.h"

/*#define DEBUG*/

/* prototypes */
static Header * read_header ARGS((FILE *ifp));
static xel **   read_rle_image ARGS((FILE *ifp, Header *hdr));
static int  do_bytedata ARGS((FILE *ifp, Header *hdr, xel *imagerow, int col, long nbytes, int channel));
static int  do_rundata  ARGS((FILE *ifp, Header *hdr, xel *imagerow, int col, long nbytes, int channel));
static void readerr ARGS((FILE *fp));
static unsigned short   get_short ARGS((FILE *ifp));
static unsigned char    get_byte  ARGS((FILE *ifp));
static void skip_bytes ARGS((FILE *ifp, long nbytes));
static void *   xmalloc ARGS((int bytes));
#define MALLOC(n, type)     ((type *)xmalloc((n) * sizeof(type)))
#define plural(x)   ((x) == 1 ? "" : "s")

static int mode;
#define MODE_COLORMAP       0       /* colormap file (0 channels) */
#define MODE_GRAYSCALE      1       /* 1 channel, no colormap */
#define MODE_PSEUDOCOLOR    2       /* 1 channel with colormap */
#define MODE_TRUECOLOR      3       /* 3 channels with colormap */
#define MODE_DIRECTCOLOR    4       /* 3 channels, no colormap */

static xel bg_xel;

static short verbose = 0;
static pixel *colormap = (pixel *)NULL;
static long colors = 0;

int
main(argc, argv)
    int argc;
    char *argv[];
{
    Header *hdr;
    FILE *ifp;
    xel **image;
    xelval maxval;
    int argn, format;
    char *usage = "[-verbose] [rlefile]";

    pnm_init(&argc, argv);

    argn = 1;
    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
        if( pm_keymatch(argv[argn], "-verbose", 2) )
            verbose++;
        else
        if( pm_keymatch(argv[argn], "-noverbose", 4) )
            verbose = 0;
        else
            pm_usage(usage);
        ++argn;
    }
    if( argn < argc ) {
        ifp = pm_openr( argv[argn] );
        argn++;
    }
    else
        ifp = stdin;
    if( argn != argc )
        pm_usage(usage);

    hdr = read_header(ifp);
    maxval = pm_bitstomaxval(hdr->pixelbits);
    if( mode != MODE_COLORMAP ) {
        image = read_rle_image(ifp, hdr);

        if( mode == MODE_GRAYSCALE ) {
            if( hdr->pixelbits == 1 ) {
                pm_message("writing PBM image");
                format = PBM_TYPE;
            }
            else {
                pm_message("writing PGM image");
                format = PGM_TYPE;
            }
        }
        else {
            pm_message("writing PPM image");
            format = PPM_TYPE;
        }
        pnm_writepnm(stdout, image, hdr->xsize, hdr->ysize, maxval, format, 0);
    }
    else {
        pm_message("writing colormap file (PPM P3 ASCII)");
        ppm_writeppminit(stdout, colors, 1, maxval, 1);
        ppm_writeppmrow(stdout, colormap, colors, maxval, 1);
    }
    pm_close(ifp);
    exit(0);
}


static Header *
read_header(ifp)
    FILE *ifp;
{
    Header *hdr = MALLOC(1, Header);

    hdr->magic = get_short(ifp);
    if( hdr->magic != RLE_MAGIC )
        pm_error("bad magic number - not an Utah Raster file");

    hdr->xpos       = get_short(ifp);
    hdr->ypos       = get_short(ifp);
    hdr->xsize      = get_short(ifp);
    hdr->ysize      = get_short(ifp);
    hdr->flags      = get_byte(ifp);
    hdr->ncolors    = get_byte(ifp);
    hdr->pixelbits  = get_byte(ifp);
    hdr->ncmap      = get_byte(ifp);
    hdr->cmaplen    = get_byte(ifp);

    /* some sanity checks */
    if( hdr->pixelbits > 8 )
        pm_error("too many bits per pixel: %d (max 8)", hdr->pixelbits);

    if( verbose ) {
        pm_message("dimensions: %d x %d, %d channel%s, %d bit%s/pixel (per channel)",
                    hdr->xsize, hdr->ysize,
                    hdr->ncolors, plural(hdr->ncolors),
                    hdr->pixelbits, plural(hdr->pixelbits));
    }

    switch( hdr->ncolors ) {
        case 0:
            if( hdr->ncmap == 3 )
                mode = MODE_COLORMAP;
            else
                goto fail1;
            break;
        case 1:
            if( hdr->ncmap == 0 )
                mode = MODE_GRAYSCALE;
            else
            if( hdr->ncmap == 3 )
                mode = MODE_PSEUDOCOLOR;
            else
                goto fail1;
            break;
        case 3:
            if( hdr->ncmap == 0 )
                mode = MODE_DIRECTCOLOR;
            else
            if( hdr->ncmap == 3 )
                mode = MODE_TRUECOLOR;
            else
                goto fail1;
            break;
        default:
        fail1:
            pm_error("don't know how to handle %d channels with %d color map channels", hdr->ncolors, hdr->ncmap);
            break;
    }

#ifdef DEBUG
    pm_message("mode = %d", mode);
#endif

    if( hdr->flags & H_NO_BACKGROUND ) {
        /* set bg_xel to black */
        bg_xel = pnm_blackxel(pm_bitstomaxval(hdr->pixelbits), mode == MODE_GRAYSCALE ? PGM_TYPE : PPM_TYPE);
        get_byte(ifp);      /* skip pad byte */
    }
    else {
        unsigned char r, g, b;
        if( hdr->ncolors == 1 ) {
            r = get_byte(ifp);
            PNM_ASSIGN1(bg_xel, r);
        }
        else
        if( hdr->ncolors == 3 ) {
            r = get_byte(ifp);
            g = get_byte(ifp);
            b = get_byte(ifp);
            PPM_ASSIGN(bg_xel, r, g, b);
        }

        if( !odd(hdr->ncolors) )
            (void)get_byte(ifp);    /* skip pad byte */
    }

    if( hdr->ncmap ) {
        long i;

        colors = 1 << hdr->cmaplen;
        colormap = ppm_allocrow(colors);

        /* 3 colormap channels (because of the previous switch) */
        for( i = 0; i < colors; i++ )
            PPM_PUTR(colormap[i], get_short(ifp) & 0xff);
        for( i = 0; i < colors; i++ )
            PPM_PUTG(colormap[i], get_short(ifp) & 0xff);
        for( i = 0; i < colors; i++ )
            PPM_PUTB(colormap[i], get_short(ifp) & 0xff);
    }

    if( hdr->flags & H_COMMENT ) {
        unsigned short length = (unsigned short)get_short(ifp);
        skip_bytes(ifp, (long)length);
        if( odd(length) )
            (void)get_byte(ifp);    /* skip pad byte */
    }

#ifdef DEBUG
    pm_message("file position after reading header: %ld", ftell(ifp));
#endif

    return hdr;
}


static xel **
read_rle_image(ifp, hdr)
    FILE *ifp;
    Header *hdr;
{
    int col, row, cols, rows, rlerow;
    xel **image;
    int opcode;
    unsigned short channel = CHANNEL_RED;
    unsigned short datum;
    long nbytes;

    cols = hdr->xsize;
    rows = hdr->ysize;

    image = pnm_allocarray(cols, rows);

    /* clear image to background color */
    for( row = 0; row < rows; row++ )
        for( col = 0; col < cols; col++ )
            image[row][col] = bg_xel;

    row = rows-1;  col = 0;         /* RLE files have origin in lower left corner */
    rlerow = 0;
    for(;;) {
        opcode = fgetc(ifp);
        if( opcode == EOF || opcode == OP_EOF || opcode == (OP_EOF|OP_LONG_DATUM) )
            return image;
        datum = (unsigned short)get_byte(ifp);
        if( opcode & OP_LONG_DATUM ) {
            datum = get_short(ifp);
            opcode &= ~OP_LONG_DATUM;
        }

        switch( opcode ) {
            case OP_SKIPLINES:
                row -= datum;
                rlerow += datum;
                break;
            case OP_SETCOLOR:
                channel = datum;
                col = 0;
                break;
            case OP_SKIPPIXELS:
                col += datum;
                break;
            case OP_BYTEDATA:
                nbytes = (long)datum + 1;
                if( nbytes > cols-col )
                    pm_error("row %d: %ld bytes ByteData, space left %d", rlerow, nbytes, cols-col);
                if( (hdr->flags & H_ALPHA) && channel == CHANNEL_ALPHA ) {
                    skip_bytes(ifp, nbytes);
                }
                else
                if( channel >= hdr->ncolors )
                    pm_error("** TODO **");
                else
                    col = do_bytedata(ifp, hdr, image[row], col, nbytes, (int)channel);
                if( odd(nbytes) )
                    (void)get_byte(ifp);
                break;
            case OP_RUNDATA:
                nbytes = (long)datum + 1;
                if( nbytes > cols-col )
                    pm_error("row %d: %ld bytes RunData, space left %d", rlerow, nbytes, cols-col);
                if( (hdr->flags & H_ALPHA) && channel == CHANNEL_ALPHA ) {
                    (void)get_short(ifp);
                }
                else
                if( channel >= hdr->ncolors )
                    pm_error("** TODO **");
                else
                    col = do_rundata(ifp, hdr, image[row], col, nbytes, (int)channel);
                break;
            default:
                pm_error("unkown opcode %d @ file pos %ld", opcode, ftell(ifp));
        }
    }
}


static int
do_bytedata(ifp, hdr, imagerow, col, nbytes, channel)
    FILE *ifp;
    Header *hdr;
    xel *imagerow;
    int col;
    long nbytes;
    int channel;
{
    long i;
    int byte;

    for( i = 0; i < nbytes; i++ ) {
        byte = get_byte(ifp);
        switch( mode ) {
            case MODE_GRAYSCALE:
                PNM_ASSIGN1(imagerow[col], byte);
                break;
            case MODE_PSEUDOCOLOR:
                byte = get_byte(ifp);
                imagerow[col] = colormap[byte];
                break;
            case MODE_TRUECOLOR:
                switch( channel ) {
                    case CHANNEL_RED:
                        PPM_PUTR(imagerow[col], PPM_GETR(colormap[byte]));
                        break;
                    case CHANNEL_GREEN:
                        PPM_PUTG(imagerow[col], PPM_GETG(colormap[byte]));
                        break;
                    case CHANNEL_BLUE:
                        PPM_PUTB(imagerow[col], PPM_GETB(colormap[byte]));
                        break;
                }
                break;
            case MODE_DIRECTCOLOR:
                switch( channel ) {
                    case CHANNEL_RED:
                        PPM_PUTR(imagerow[col], byte);
                        break;
                    case CHANNEL_GREEN:
                        PPM_PUTG(imagerow[col], byte);
                        break;
                    case CHANNEL_BLUE:
                        PPM_PUTB(imagerow[col], byte);
                        break;
                }
                break;
        }
        ++col;
    }
    return col;
}


static int
do_rundata(ifp, hdr, imagerow, col, nbytes, channel)
    FILE *ifp;
    Header *hdr;
    xel *imagerow;
    int col;
    long nbytes;
    int channel;
{
    long i;
    int byte;

    byte = get_short(ifp) & 0xff;

    for( i = 0; i < nbytes; i++ ) {
        switch( mode ) {
            case MODE_GRAYSCALE:
                PNM_ASSIGN1(imagerow[col], byte);
                break;
            case MODE_PSEUDOCOLOR:
                byte = get_byte(ifp);
                imagerow[col] = colormap[byte];
                break;
            case MODE_TRUECOLOR:
                switch( channel ) {
                    case CHANNEL_RED:
                        PPM_PUTR(imagerow[col], PPM_GETR(colormap[byte]));
                        break;
                    case CHANNEL_GREEN:
                        PPM_PUTG(imagerow[col], PPM_GETG(colormap[byte]));
                        break;
                    case CHANNEL_BLUE:
                        PPM_PUTB(imagerow[col], PPM_GETB(colormap[byte]));
                        break;
                }
                break;
            case MODE_DIRECTCOLOR:
                switch( channel ) {
                    case CHANNEL_RED:
                        PPM_PUTR(imagerow[col], byte);
                        break;
                    case CHANNEL_GREEN:
                        PPM_PUTG(imagerow[col], byte);
                        break;
                    case CHANNEL_BLUE:
                        PPM_PUTB(imagerow[col], byte);
                        break;
                }
                break;
        }
        ++col;
    }
    return col;
}


static void
readerr(fp)
    FILE *fp;
{
    if( ferror(fp) )
        pm_error("read error");
    else
        pm_error("premature EOF");
}


static unsigned short
get_short(ifp)
    FILE *ifp;
{
    short s;

    if( pm_readlittleshort(ifp, &s) == -1 )
        readerr(ifp);
    return s;
}


static unsigned char
get_byte(ifp)
    FILE *ifp;
{
    int i;

    i = fgetc(ifp);
    if( i == EOF )
        readerr(ifp);
    return (unsigned char)i;
}


static void
skip_bytes(ifp, nbytes)
    FILE *ifp;
    long nbytes;
{
    while( nbytes-- )
        (void)get_byte(ifp);
}


static void *
xmalloc(bytes)
    int bytes;
{
    void *mem;

    if( bytes == 0 )
        return NULL;

    mem = malloc(bytes);
    if( mem == NULL )
        pm_error("out of memory allocating %d bytes", bytes);
    return mem;
}

