/* pnmtorast.c - read a portable anymap and produce a Sun rasterfile
**
** 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 "pnm.h"
#include "rast.h"
#include "mallocvar.h"

#define MAXCOLORS 256
static colormap_t* make_pr_colormap ARGS(( colorhist_vector chv, int colors ));
static colormap_t* make_gray_pr_colormap ARGS(( void ));
static colormap_t* alloc_pr_colormap ARGS(( void ));

int
main( argc, argv )
    int argc;
    char* argv[];
{
    FILE* ifp;
    xel** xels;
    xel* xelrow;
    xel p;
    register xel* xP;
    colorhist_vector chv;
    colorhash_table cht;
    colormap_t* pr_colormapP;
    int argn, pr_type, rows, cols, format, i;
    int depth, colors, linesize, row;
    register int col, bitcount;
    xelval maxval;
    struct pixrect* pr;
    unsigned char* data;
    register unsigned char* byteP;
    const char* const usage = "[-standard|-rle] [pnmfile]";

    pnm_init( &argc, argv );

    argn = 1;
    pr_type = RT_BYTE_ENCODED;

    while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
    {
        if ( pm_keymatch( argv[argn], "-standard", 2 ) )
            pr_type = RT_STANDARD;
        else if ( pm_keymatch( argv[argn], "-rle", 2 ) )
            pr_type = RT_BYTE_ENCODED;
        else
            pm_usage( usage );
        ++argn;
    }

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

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

    xels = pnm_readpnm( ifp, &cols, &rows, &maxval, &format );

    pm_close( ifp );

    /* Figure out the proper depth and colormap. */
    switch ( PNM_FORMAT_TYPE(format) )
    {
    case PPM_TYPE:
        pm_message( "computing colormap..." );
        chv = ppm_computecolorhist( xels, cols, rows, MAXCOLORS, &colors );
        if ( chv == (colorhist_vector) 0 )
        {
            pm_message(
                "Too many colors - proceeding to write a 24-bit non-mapped" );
            pm_message(
                "rasterfile.  If you want 8 bits, try doing a 'pnmquant %d'.",
                MAXCOLORS );
            depth = 24;
            pr_type = RT_STANDARD;
            pr_colormapP = (colormap_t*) 0;
        }
        else
        {
            pm_message( "%d colors found", colors );

            if ( maxval != 255 )
                for ( i = 0; i < colors; ++i )
                    PPM_DEPTH( chv[i].color, chv[i].color, maxval, 255 );

            /* Force white to slot 0 and black to slot 1, if possible. */
            PPM_ASSIGN( p, 255, 255, 255 );
            ppm_addtocolorhist( chv, &colors, MAXCOLORS, &p, 0, 0 );
            PPM_ASSIGN( p, 0, 0, 0 );
            ppm_addtocolorhist( chv, &colors, MAXCOLORS, &p, 0, 1 );

            if ( colors == 2 )
            {
                /* Monochrome. */
                depth = 1;
                pr_colormapP = (colormap_t*) 0;
            }
            else
            {
                /* Turn the ppm colormap into the appropriate Sun colormap. */
                depth = 8;
                pr_colormapP = make_pr_colormap( chv, colors );
            }
            cht = ppm_colorhisttocolorhash( chv, colors );
            ppm_freecolorhist( chv );
        }

        break;

    case PGM_TYPE:
        depth = 8;
        pr_colormapP = make_gray_pr_colormap( );
        break;

    default:
        depth = 1;
        pr_colormapP = (colormap_t*) 0;
        break;
    }

    if ( maxval > 255 && depth != 1 )
        pm_message(
            "maxval is not 255 - automatically rescaling colors" );
    
    /* Allocate space for the Sun-format image. */
    if ( (pr = mem_create(cols, rows, depth)) == (struct pixrect*) 0 )
        pm_error( "unable to create new pixrect" );
    data = ( (struct mpr_data*) pr->pr_data )->md_image;
    linesize = ( (struct mpr_data*) pr->pr_data )->md_linebytes;

    /* And compute the Sun image.  The variables at this point are:
    **   cht is null or not
    **   depth is 1, 8, or 24
    */
    for ( row = 0; row < rows; ++row )
    {
        xelrow = xels[row];
        byteP = data;
        switch ( depth )
        {
        case 1:
            *byteP = 0;
            bitcount = 7;
            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
            {
                register int color;

                switch ( PNM_FORMAT_TYPE(format) )
                {
                case PPM_TYPE:
                    if ( maxval != 255 )
                        PPM_DEPTH( *xP, *xP, maxval, 255 );
                    color = ppm_lookupcolor( cht, xP );
                    if ( color == -1 )
                        pm_error(
                            "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
                            row, col, PPM_GETR(*xP), PPM_GETG(*xP),
                            PPM_GETB(*xP) );
                    if ( color )
                        *byteP |= 1 << bitcount;
                    break;

                default:
                    color = PNM_GET1( *xP );
                    if ( ! color )
                        *byteP |= 1 << bitcount;
                    break;
                }
                --bitcount;
                if ( bitcount < 0 )
                {
                    ++byteP;
                    *byteP = 0;
                    bitcount = 7;
                }
            }
            break;

        case 8:
            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
            {
                register int color;

                switch ( PNM_FORMAT_TYPE(format) )
                {
                case PPM_TYPE:
                    if ( maxval != 255 )
                        PPM_DEPTH( *xP, *xP, maxval, 255 );
                    color = ppm_lookupcolor( cht, xP );
                    if ( color == -1 )
                        pm_error(
                            "color not found?!?  row=%d col=%d  r=%d g=%d b=%d",
                            row, col, PPM_GETR(*xP), PPM_GETG(*xP),
                            PPM_GETB(*xP) );
                    break;

                case PGM_TYPE:
                    color = PNM_GET1( *xP );
                    if ( maxval != 255 )
                        color = color * 255 / maxval;
                    break;

                default:
                    color = PNM_GET1( *xP );
                }
                *byteP++ = color;
            }
            break;

        case 24:
            /* If depth is 24, we do NOT have a valid cht. */
            for ( col = 0, xP = xelrow; col < cols; ++col, ++xP )
            {
                if ( maxval != 255 )
                    PPM_DEPTH( *xP, *xP, maxval, 255 );
                *byteP++ = PPM_GETB( *xP );
                *byteP++ = PPM_GETG( *xP );
                *byteP++ = PPM_GETR( *xP );
            }
            break;

        default:
            pm_error( "can't happen" );
        }
        data += linesize;
    }
    pnm_freearray( xels, rows );

    /* Finally, write the sucker out. */
    if ( pr_dump( pr, stdout, pr_colormapP, pr_type, 0 ) == PIX_ERR )
        pm_error( "error writing rasterfile" );

    exit( 0 );
}

static colormap_t*
make_pr_colormap( chv, colors )
    colorhist_vector chv;
    int colors;
{
    colormap_t* pr_colormapP;
    int i;

    pr_colormapP = alloc_pr_colormap( );

    for ( i = 0; i < colors; ++i )
    {
        pr_colormapP->map[0][i] = PPM_GETR( chv[i].color );
        pr_colormapP->map[1][i] = PPM_GETG( chv[i].color );
        pr_colormapP->map[2][i] = PPM_GETB( chv[i].color );
    }
    for ( ; i < MAXCOLORS; ++i )
        pr_colormapP->map[0][i] = pr_colormapP->map[1][i] =
            pr_colormapP->map[2][i] = 0;

    return pr_colormapP;
}

static colormap_t*
make_gray_pr_colormap( )
{
    colormap_t* pr_colormapP;
    int i;

    pr_colormapP = alloc_pr_colormap( );

    for ( i = 0; i < MAXCOLORS; ++i )
    {
        pr_colormapP->map[0][i] = i;
        pr_colormapP->map[1][i] = i;
        pr_colormapP->map[2][i] = i;
    }

    return pr_colormapP;
}

static colormap_t*
alloc_pr_colormap( )
{
    colormap_t* pr_colormapP;

    MALLOCVAR(pr_colormapP);
    if ( pr_colormapP == NULL )
        pm_error( "out of memory" );
    pr_colormapP->type = RMT_EQUAL_RGB;
    pr_colormapP->length = MAXCOLORS;
    MALLOCARRAY(pr_colormapP->map[0], MAXCOLORS);
    MALLOCARRAY(pr_colormapP->map[1], MAXCOLORS);
    MALLOCARRAY(pr_colormapP->map[2], MAXCOLORS);
    if ( pr_colormapP->map[0] == NULL || 
         pr_colormapP->map[1] == NULL ||
         pr_colormapP->map[2] == NULL )
        pm_error( "out of memory" );

    return pr_colormapP;
}
