/* pnmtorle.c - convert portable anymap to Utah Raster Toolkit image
**
** 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"

/* prototypes */
static void write_rle_header ARGS((FILE *ofp, int cols, int rows, int channels, int pixelbits));
static void pnm_to_1_channel ARGS((FILE *ofp, xel **image, int cols, int rows));
static void pnm_to_3_channels ARGS((FILE *ofp, xel **image, int cols, int rows));
static void write_channel ARGS((FILE *ofp, int channel, unsigned char *rawrow, int cols));
static void write_bytedata ARGS((FILE *ofp, unsigned char *rawrow, int nbytes));
static void write_rundata  ARGS((FILE *ofp, unsigned char *rawrow, int nbytes));
static void put_byte ARGS((FILE *fp, int byte));
static void put_short ARGS((FILE *fp, int word));
#define plural(x)   ((x) == 1 ? "" : "s")


int
main(argc, argv)
    int argc;
    char *argv[];
{
    int argn, cols, rows, format, channels, pixelbits;
    xel **image;
    xelval maxval;
    FILE *ifp;
    char *usage = "[-8bit] [pnmfile]";
    short scale, force8;

    pnm_init(&argc, argv);
    force8 = 0;

    argn = 1;
    /* empty loop for easy option adding */
    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
        if( pm_keymatch(argv[argn], "-8bit", 2) )
            force8 = 1;
        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 = 1;      /* PBM -> 1 bit per pixel */
    image = pnm_readpnm(ifp, &cols, &rows, &maxval, &format);

    if( maxval > 255 ) {
        pm_message("maxval too large - scaling pixel values");
        scale = 1;
    }
    else
    if( force8 && maxval < 255 ) {
        pm_message("scaling pixels to 8 bit");
        scale = 1;
    }
    else
        scale = 0;

    if( scale ) {
        int newformat = format;
        if( PNM_FORMAT_TYPE(format) == PBM_TYPE ) {
            pm_message("promoting PBM to PGM");
            newformat = PGM_TYPE;
        }
        pnm_promoteformat(image, cols, rows, maxval, format, 255, newformat);
        maxval = 255;
        format = newformat;
    }

    pixelbits = pm_maxvaltobits(maxval);
    if( PNM_FORMAT_TYPE(format) == PPM_TYPE )
        channels = 3;
    else
        channels = 1;

    pm_message("writing RLE image: %d channel%s, %d bit%s/pixel (per channel)",
                channels, plural(channels), pixelbits, plural(pixelbits));

    write_rle_header(stdout, cols, rows, channels, pixelbits);
    if( channels == 1 )
        pnm_to_1_channel(stdout, image, cols, rows);
    else
        pnm_to_3_channels(stdout, image, cols, rows);
    put_byte(stdout, OP_EOF);
    put_byte(stdout, 0);    /* dummy datum, in case someone reads opcode+datum as a word */
    exit(0);
}


static void
write_rle_header(ofp, cols, rows, channels, pixelbits)
    FILE *ofp;
    int cols, rows, channels, pixelbits;
{
    put_short(ofp, RLE_MAGIC);
    put_short(ofp, 0);                  /* xpos */
    put_short(ofp, 0);                  /* ypos */
    put_short(ofp, cols);               /* xsize */
    put_short(ofp, rows);               /* ysize */
    put_byte(ofp, H_NO_BACKGROUND);     /* flags */
    put_byte(ofp, channels);            /* ncolors */
    put_byte(ofp, pixelbits);           /* pixelbits */
    put_byte(ofp, 0);                   /* ncmap */
    put_byte(ofp, 0);                   /* cmaplen */

    put_byte(ofp, 0);                   /* fill byte for H_NO_BACKGROUND */
}


static void
pnm_to_1_channel(ofp, image, cols, rows)
    FILE *ofp;
    xel **image;
    int cols, rows;
{
    int row, col;
    unsigned char *rawrow = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));

    for( row = rows-1; row >= 0; row-- ) {
        for( col = 0; col < cols; col++ )
            rawrow[col] = PNM_GET1(image[row][col]);
        write_channel(ofp, 0, rawrow, cols);
        put_byte(ofp, OP_SKIPLINES); put_byte(ofp, 1);
    }
}


static void
pnm_to_3_channels(ofp, image, cols, rows)
    FILE *ofp;
    xel **image;
    int cols, rows;
{
    int row, col;
    unsigned char *redrow, *greenrow, *bluerow;

    redrow   = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
    greenrow = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));
    bluerow  = (unsigned char *)pm_allocrow(cols, sizeof(unsigned char));

    for( row = rows-1; row >= 0; row-- ) {
        for( col = 0; col < cols; col++ ) {
            redrow[col]   = PPM_GETR(image[row][col]);
            greenrow[col] = PPM_GETG(image[row][col]);
            bluerow[col]  = PPM_GETB(image[row][col]);
        }
        write_channel(ofp, CHANNEL_RED,   redrow, cols);
        write_channel(ofp, CHANNEL_GREEN, greenrow, cols);
        write_channel(ofp, CHANNEL_BLUE,  bluerow, cols);
        put_byte(ofp, OP_SKIPLINES); put_byte(ofp, 1);
    }
}


static void
write_channel(ofp, channel, src, cols)
    FILE *ofp;
    int channel;
    unsigned char *src;
    int cols;
{
    unsigned char *end, *lit, *rep;
#define MINLEN  5

    put_byte(ofp, OP_SETCOLOR); put_byte(ofp, channel);
#if 0
    write_bytedata(ofp, src, cols);
#else

    end = src + cols;
    lit = src;
    while( src < end ) {
        /* find longest literal run */
        do
            if( ++src == end )
                goto Done;
        while( *src != *(src-1) );

        /* find longest replicate run */
        rep = src-1;
        do
            if( ++src == end )
                break;
        while( *src == *(src-1) );

        /* if the replicate run is at least MINLEN bytes, save the literal
           run and the replicate run, else merge the replicate run into the
           literal run */
        if( src-rep >= MINLEN ) {
            if( rep > lit )
                write_bytedata(ofp, lit, rep-lit);
            write_rundata(ofp, rep, src-rep);
            lit = src;
        }
    }

Done:
    if( src > lit )
        write_bytedata(ofp, lit, src-lit);
#endif
}


static void
write_bytedata(ofp, rawrow, nbytes)
    FILE *ofp;
    unsigned char *rawrow;
    int nbytes;
{
    if( nbytes <= 256 ) {
        put_byte(ofp, OP_BYTEDATA);  put_byte(ofp, nbytes-1);
    }
    else {
        put_byte(ofp, OP_BYTEDATA|OP_LONG_DATUM); put_byte(ofp, 0);
        put_short(ofp, nbytes-1);
    }
    if( fwrite(rawrow, 1, nbytes, ofp) != nbytes )
        pm_error("write error");
    if( odd(nbytes) )
        put_byte(ofp, 0);
}


static void
write_rundata(ofp, rawrow, nbytes)
    FILE *ofp;
    unsigned char *rawrow;
    int nbytes;
{
    if( nbytes <= 256 ) {
        put_byte(ofp, OP_RUNDATA); put_byte(ofp, nbytes-1);
    }
    else {
        put_byte(ofp, OP_RUNDATA|OP_LONG_DATUM); put_byte(ofp, 0);
        put_short(ofp, nbytes-1);
    }
    put_short(ofp, *rawrow);
}


static void
put_byte(fp, byte)
    FILE *fp;
    int byte;
{
    if( fputc((unsigned char)byte, fp) == EOF )
        pm_error("write error");
}


static void
put_short(fp, word)
    FILE *fp;
    int word;
{
    if( pm_writelittleshort(fp, (unsigned short)word) == -1 )
        pm_error("write error");
}

