/* pnmtosgi.c - convert portable anymap to SGI image
**
** Copyright (C) 1994 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
**
** Based on the SGI image description v0.9 by Paul Haeberli (paul@sgi.comp)
** Available via ftp from sgi.com:graphics/SGIIMAGESPEC
**
** 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.
**
** 29Jan94: first version
*/
#include "pnm.h"
#include "sgi.h"

/*#define DEBUG*/

typedef short       ScanElem;
typedef struct {
    ScanElem *  data;
    long        length;
} ScanLine;

/* prototypes */
static void put_big_short ARGS((short s));
static void put_big_long ARGS((long l));
#define put_byte(b)     (void)(putc((unsigned char)(b), stdout))
static void put_short_as_byte ARGS((short s));
static void write_header ARGS((int cols, int rows, xelval maxval, int bpc, int dimensions, int channels, char *imagename));
static void write_table  ARGS((long *table, int tabsize));
static void write_channels ARGS((int cols, int rows, int channels, void (*put) ARGS((short)) ));
static long * build_channels ARGS((FILE *ifp, int cols, int rows, xelval maxval, int format, int bpc, int channels));
static ScanElem *compress ARGS((ScanElem *temp, int row, int rows, int cols, int chan_no, long *table, int bpc));
static int rle_compress ARGS((ScanElem *inbuf, int cols));
static void * xmalloc ARGS((int bytes));
#define MALLOC(n, type)     (type *)xmalloc((n) * sizeof(type))

#define WORSTCOMPR(x)   (2*(x) + 2)


#define MAXVAL_BYTE     255
#define MAXVAL_WORD     65535

static char storage = STORAGE_RLE;
static ScanLine * channel[3];
static ScanElem * rletemp;
static xel * pnmrow;


int
main(argc, argv)
    int argc;
    char *argv[];
{
    FILE *ifp;
    int argn;
    char *usage = "[-verbatim|-rle] [-imagename <name>] [pnmfile]";
    int cols, rows, format;
    xelval maxval;
    char *imagename = "no name";
    int bpc, dimensions, channels;
    long *table = NULL;

    pnm_init(&argc, argv);

    argn = 1;
    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
        if( pm_keymatch(argv[argn], "-verbatim", 2) )
            storage = STORAGE_VERBATIM;
        else
        if( pm_keymatch(argv[argn], "-rle", 2) )
            storage = STORAGE_RLE;
        else
        if( pm_keymatch(argv[argn], "-imagename", 2) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            imagename = argv[argn];
        }
        else
            pm_usage(usage);
        ++argn;
    }

    if( argn < argc ) {
        ifp = pm_openr( argv[argn] );
        argn++;
    }
    else
        ifp = stdin;

    if( argn != argc )
        pm_usage(usage);

    pnm_pbmmaxval = PNM_MAXMAXVAL;  /* use larger value for better results */
    pnm_readpnminit(ifp, &cols, &rows, &maxval, &format);
    pnmrow = pnm_allocrow(cols);

    switch( PNM_FORMAT_TYPE(format) ) {
        case PBM_TYPE:
            pm_message("promoting PBM to PGM");
        case PGM_TYPE:
            dimensions = 2; channels = 1;
            break;
        case PPM_TYPE:
            dimensions = 3; channels = 3;
            break;
        default:
            pm_error("can\'t happen");
    }
    if( maxval <= MAXVAL_BYTE )
        bpc = 1;
    else if( maxval <= MAXVAL_WORD )
        bpc = 2;
    else
        pm_error("maxval too large - try using \"pnmdepth %d\"", MAXVAL_WORD);

    table = build_channels(ifp, cols, rows, maxval, format, bpc, channels);
    pnm_freerow(pnmrow);
    pm_close(ifp);

    write_header(cols, rows, maxval, bpc, dimensions, channels, imagename);
    if( table )
        write_table(table, rows * channels);
    if( bpc == 1 )
        write_channels(cols, rows, channels, put_short_as_byte);
    else
        write_channels(cols, rows, channels, put_big_short);

    exit(0);
}


static void
write_header(cols, rows, maxval, bpc, dimensions, channels, imagename)
    int cols, rows;
    xelval maxval;
    int bpc, dimensions, channels;
    char *imagename;
{
    int i;

#ifdef DEBUG
    pm_message("writing header");
#endif

    put_big_short(SGI_MAGIC);
    put_byte(storage);
    put_byte((char)bpc);
    put_big_short(dimensions);
    put_big_short(cols);
    put_big_short(rows);
    put_big_short(channels);
    put_big_long(0);                /* PIXMIN */
    put_big_long(maxval);           /* PIXMAX */
    for( i = 0; i < 4; i++ )
        put_byte(0);
    for( i = 0; i < 79 && imagename[i] != '\0'; i++ )
        put_byte(imagename[i]);
    for(; i < 80; i++ )
        put_byte(0);
    put_big_long(CMAP_NORMAL);
    for( i = 0; i < 404; i++ )
        put_byte(0);
}


static void
write_table(table, tabsize)
    long *table;
    int tabsize;
{
    int i;
    long offset;

#ifdef DEBUG
    pm_message("writing table");
#endif

    offset = HeaderSize + tabsize * 8;
    for( i = 0; i < tabsize; i++ ) {
        put_big_long(offset);
        offset += table[i];
    }
    for( i = 0; i < tabsize; i++ )
        put_big_long(table[i]);
}


static void
write_channels(cols, rows, channels, put)
    int cols, rows, channels;
    void (*put) ARGS((short));
{
    int i, row, col;

#ifdef DEBUG
    pm_message("writing image data");
#endif

    for( i = 0; i < channels; i++ ) {
        for( row = 0; row < rows; row++ ) {
            for( col = 0; col < channel[i][row].length; col++ ) {
                (*put)(channel[i][row].data[col]);
            }
        }
    }
}

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;
}

static void
put_big_short(s)
    short s;
{
    if ( pm_writebigshort( stdout, s ) == -1 )
        pm_error( "write error" );
}


static void
put_big_long(l)
    long l;
{
    if ( pm_writebiglong( stdout, l ) == -1 )
        pm_error( "write error" );
}


static void
put_short_as_byte(s)
    short s;
{
    put_byte((unsigned char)s);
}


static long *
build_channels(ifp, cols, rows, maxval, format, bpc, channels)
    FILE *ifp;
    int cols, rows;
    xelval maxval;
    int format, bpc, channels;
{
    int i, row, col, sgirow;
    long *table = NULL;
    ScanElem *temp;

#ifdef DEBUG
    pm_message("building channels");
#endif

    if( storage != STORAGE_VERBATIM ) {
        table = MALLOC(channels * rows, long);
        rletemp = MALLOC(WORSTCOMPR(cols), ScanElem);
    }
    temp = MALLOC(cols, ScanElem);

    for( i = 0; i < channels; i++ )
        channel[i] = MALLOC(rows, ScanLine);

    for( row = 0, sgirow = rows-1; row < rows; row++, sgirow-- ) {
        pnm_readpnmrow(ifp, pnmrow, cols, maxval, format);
        if( channels == 1 ) {
            for( col = 0; col < cols; col++ )
                temp[col] = (ScanElem)PNM_GET1(pnmrow[col]);
            temp = compress(temp, sgirow, rows, cols, 0, table, bpc);
        }
        else {
            for( col = 0; col < cols; col++ )
                temp[col] = (ScanElem)PPM_GETR(pnmrow[col]);
            temp = compress(temp, sgirow, rows, cols, 0, table, bpc);
            for( col = 0; col < cols; col++ )
                temp[col] = (ScanElem)PPM_GETG(pnmrow[col]);
            temp = compress(temp, sgirow, rows, cols, 1, table, bpc);
            for( col = 0; col < cols; col++ )
                temp[col] = (ScanElem)PPM_GETB(pnmrow[col]);
            temp = compress(temp, sgirow, rows, cols, 2, table, bpc);
        }
    }

    free(temp);
    if( table )
        free(rletemp);
    return table;
}


static ScanElem *
compress(temp, row, rows, cols, chan_no, table, bpc)
    ScanElem *temp;
    int row, rows, cols, chan_no;
    long *table;
    int bpc;
{
    int len, i, tabrow;
    ScanElem *p;

    switch( storage ) {
        case STORAGE_VERBATIM:
            channel[chan_no][row].length = cols;
            channel[chan_no][row].data = temp;
            temp = MALLOC(cols, ScanElem);
            break;
        case STORAGE_RLE:
            tabrow = chan_no * rows + row;
            len = rle_compress(temp, cols);     /* writes result into rletemp */
            channel[chan_no][row].length = len;
            channel[chan_no][row].data = p = MALLOC(len, ScanElem);
            for( i = 0; i < len; i++, p++ )
                *p = rletemp[i];
            table[tabrow] = len * bpc;
            break;
        default:
            pm_error("unknown storage type - can\'t happen");
    }
    return temp;
}


/*
slightly modified RLE algorithm from ppmtoilbm.c
written by Robert A. Knop (rknop@mop.caltech.edu)
*/
static int
rle_compress(inbuf, size)
    ScanElem *inbuf;
    int size;
{
    int in, out, hold, count;
    ScanElem *outbuf = rletemp;

    in=out=0;
    while( in<size ) {
        if( (in<size-1) && (inbuf[in]==inbuf[in+1]) ) {     /*Begin replicate run*/
            for( count=0,hold=in; in<size && inbuf[in]==inbuf[hold] && count<127; in++,count++)
                ;
            outbuf[out++]=(ScanElem)(count);
            outbuf[out++]=inbuf[hold];
        }
        else {  /*Do a literal run*/
            hold=out; out++; count=0;
            while( ((in>=size-2)&&(in<size)) || ((in<size-2) && ((inbuf[in]!=inbuf[in+1])||(inbuf[in]!=inbuf[in+2]))) ) {
                outbuf[out++]=inbuf[in++];
                if( ++count>=127 )
                    break;
            }
            outbuf[hold]=(ScanElem)(count | 0x80);
        }
    }
    outbuf[out++] = (ScanElem)0;     /* terminator */
    return(out);
}

