/* ppmtoilbm.c - read a portable pixmap and produce an IFF ILBM file
**
** Copyright (C) 1989 by Jef Poskanzer.
** Modified 20/Jun/93 by Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)
**  - 24bit support (new options -24if, -24force)
**  - HAM8 support (well, anything from HAM3 to HAM(MAXPLANES))
**  - now writes up to 8 (16) planes (new options -maxplanes, -fixplanes)
**  - colormap file (new option -map)
**  - write colormap only (new option -cmaponly)
**  - only writes CAMG chunk if its a HAM-picture
** Modified 29/Aug/93 by Ingo Wilken
**  - operates row-by-row whenever possible
**  - faster colorscaling with lookup-table (~20% faster on HAM pictures)
**  - options -ham8 and -ham6 now imply -hamforce
**
**
**           std   HAM  24bit cmap  direct
**  -------+-----+-----+-----+-----+-----
**  BMHD     yes   yes   yes   yes   yes
**  CMAP     yes   (1)   no    yes   no
**  BODY     yes   yes   yes   no    yes
**  other    -     CAMG  -     -     DCOL
**  nPlanes  1-8   3-8   24    0     3-24   if configured without ILBM_BIGRAW
**  nPlanes  1-16  3-16  24    0     3-48   if configured with ILBM_BIGRAW
**
**  (1): grayscale colormap
**
** 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 "ppm.h"
#include "ppmcmap.h"
#include "ilbm.h"


#define MODE_DIRECT     4   /* direct color ILBM */
#define MODE_CMAP       3   /* write normal file, but colormap only */
#define MODE_24         2   /* write a 24bit (deep) ILBM */
#define MODE_HAM        1   /* write a HAM */
#define MODE_NONE       0   /* write a normal picture */

#define ECS_MAXPLANES   5
#define ECS_HAMPLANES   6
#define AGA_MAXPLANES   8
#define AGA_HAMPLANES   8

#ifdef AMIGA_AGA
#define DEF_MAXPLANES   AGA_MAXPLANES
#define DEF_HAMPLANES   AGA_HAMPLANES
#else
#define DEF_MAXPLANES   ECS_MAXPLANES
#define DEF_HAMPLANES   ECS_HAMPLANES
#endif
#define DEF_DCOLPLANES  5


static int colorstobpp ARGS((int colors));
#define put_fourchars(str)  (void)(fputs(str, stdout))
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 ppm_to_ham ARGS((FILE *fp, int cols, int rows, int maxval, int hambits));
static void ppm_to_24  ARGS((FILE *fp, int cols, int rows, int maxval));
static void ppm_to_direct  ARGS((FILE *fp, int cols, int rows, int maxval, DirectColor *direct));
static void ppm_to_std ARGS((FILE *fp, int cols, int rows, int maxval, colorhist_vector chv, int colors, int nPlanes));
static void ppm_to_cmap ARGS((int maxval, colorhist_vector chv, int colors));
static void write_form_ilbm ARGS((int size));
static void write_bmhd ARGS((int cols, int rows, int nPlanes));
static void write_std_cmap ARGS((colorhist_vector chv, int colors, int maxval));
static void encode_row ARGS((rawtype *row, int cols, int nPlanes));
static int get_int_val ARGS((char *string, char *option, int bot, int top));
static pixel * next_pixrow ARGS((FILE *fp, int row));
static pixval * make_val_table ARGS((pixval oldmaxval, pixval newmaxval));
static void * xmalloc ARGS((int bytes));
static void init_read ARGS((FILE *fp, int *colsP, int *rowsP, pixval *maxvalP, int readall));


static unsigned char *coded_rowbuf;
static pixel **pixels;
static pixel *pixrow;

#define NEWDEPTH(pix, table)   PPM_ASSIGN((pix), (table)[PPM_GETR(pix)], (table)[PPM_GETG(pix)], (table)[PPM_GETB(pix)])

#define MAXCOLORS       (1<<maxplanes)

int
main(argc, argv)
    int argc;
    char *argv[];
{
    FILE *ifp;
    int argn, rows, cols, colors, nPlanes;
    int ifmode, forcemode, maxplanes, fixplanes, hambits, mode;
    pixval maxval;
    colorhist_vector chv;
    DirectColor dcol;
    char *mapfile;
    char *usage =
"[-ecs|-aga] [-ham6|-ham8] [-maxplanes|-mp n] [-fixplanes|-fp n] \
[-normal|-hamif|-hamforce|-24if|-24force|-dcif|-dcforce|-cmaponly] \
[-hambits|-hamplanes n] [-dcbits|-dcplanes r g b] \
[-map ppmfile] [ppmfile]";

    ppm_init(&argc, argv);

    ifmode = MODE_NONE; forcemode = MODE_NONE;
    maxplanes = DEF_MAXPLANES; fixplanes = 0;
    hambits = DEF_HAMPLANES;
    mapfile = NULL;
    dcol.r = dcol.g = dcol.b = DEF_DCOLPLANES;

    argn = 1;
    while( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' ) {
        if( pm_keymatch(argv[argn], "-maxplanes", 4) || pm_keymatch(argv[argn], "-mp", 3) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            maxplanes = get_int_val(argv[argn], argv[argn-1], 1, MAXPLANES);
            fixplanes = 0;
        }
        else
        if( pm_keymatch(argv[argn], "-fixplanes", 4) || pm_keymatch(argv[argn], "-fp", 3) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            fixplanes = get_int_val(argv[argn], argv[argn-1], 1, MAXPLANES);
            maxplanes = fixplanes;
        }
        else
        if( pm_keymatch(argv[argn], "-map", 4) ) {
            if( ++argn >= argc )
                pm_usage(usage);
            mapfile = argv[argn];
        }
        else
        if( pm_keymatch(argv[argn], "-cmaponly", 3) ) {
            forcemode = MODE_CMAP;
        }
        else
        if( pm_keymatch(argv[argn], "-hambits", 5) || pm_keymatch(argv[argn], "-hamplanes", 5) ) {
            if( ++argn > argc )
                pm_usage(usage);
            hambits = get_int_val(argv[argn], argv[argn-1], 3, MAXPLANES);
        }
        else
        if( pm_keymatch(argv[argn], "-ham6", 5) ) {
            hambits = ECS_HAMPLANES;
            forcemode = MODE_HAM;
        }
        else
        if( pm_keymatch(argv[argn], "-ham8", 5) ) {
            hambits = AGA_HAMPLANES;
            forcemode = MODE_HAM;
        }
        else
        if( pm_keymatch(argv[argn], "-ecs", 2) ) {
            maxplanes = ECS_MAXPLANES;
            hambits = ECS_HAMPLANES;
        }
        else
        if( pm_keymatch(argv[argn], "-aga", 2) ) {
            maxplanes = AGA_MAXPLANES;
            hambits = AGA_HAMPLANES;
        }
        else
        if( pm_keymatch(argv[argn], "-hamif", 5) )
            ifmode = MODE_HAM;
        else
        if( pm_keymatch(argv[argn], "-nohamif", 7) ) {
            if( ifmode == MODE_HAM )
                ifmode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-hamforce", 5) )
            forcemode = MODE_HAM;
        else
        if( pm_keymatch(argv[argn], "-nohamforce", 7) ) {
            if( forcemode == MODE_HAM )
                forcemode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-24if", 4) )
            ifmode = MODE_24;
        else
        if( pm_keymatch(argv[argn], "-no24if", 6) ) {
            if( ifmode == MODE_24 )
                ifmode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-24force", 4) )
            forcemode = MODE_24;
        else
        if( pm_keymatch(argv[argn], "-no24force", 6) ) {
            if( forcemode == MODE_24 )
                forcemode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-dcif", 4) ) {
            ifmode = MODE_DIRECT;
        }
        else
        if( pm_keymatch(argv[argn], "-nodcif", 6) ) {
            if( ifmode == MODE_DIRECT )
                ifmode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-dcforce", 4) ) {
            forcemode = MODE_DIRECT;
        }
        else
        if( pm_keymatch(argv[argn], "-nodcforce", 6) ) {
            if( forcemode == MODE_DIRECT )
                forcemode = MODE_NONE;
        }
        else
        if( pm_keymatch(argv[argn], "-dcbits", 4) || pm_keymatch(argv[argn], "-dcplanes", 4) ) {
            char *option = argv[argn];

            if( ++argn >= argc )
                pm_usage(usage);
            dcol.r = (unsigned char) get_int_val(argv[argn], option, 1, MAXPLANES);
            if( ++argn >= argc )
                pm_usage(usage);
            dcol.g = (unsigned char) get_int_val(argv[argn], option, 1, MAXPLANES);
            if( ++argn >= argc )
                pm_usage(usage);
            dcol.b = (unsigned char) get_int_val(argv[argn], option, 1, MAXPLANES);
        }
        else
        if( pm_keymatch(argv[argn], "-normal", 4) )
            ifmode = forcemode = MODE_NONE;
        else
            pm_usage(usage);
        ++argn;
    }

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

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

    if( forcemode != MODE_NONE && mapfile != NULL )
        pm_message("warning - mapfile only used for normal ILBMs");

    mode = forcemode;
    switch( forcemode ) {
        case MODE_HAM:
            /* grayscale colormap for now - we don't need to read the whole
               file into memory and can use row-by-row operation */
            init_read(ifp, &cols, &rows, &maxval, 0);
            pm_message("hamforce option used - proceeding to write a HAM%d file", hambits);
            break;
        case MODE_24:
            init_read(ifp, &cols, &rows, &maxval, 0);
            pm_message("24force option used - proceeding to write a 24bit file");
            break;
        case MODE_DIRECT:
            init_read(ifp, &cols, &rows, &maxval, 0);
            pm_message("dcforce option used - proceeding to write a %d:%d:%d direct color file",
                        dcol.r, dcol.g, dcol.b);
            break;
        case MODE_CMAP:
            /* must read the whole file into memory */
            init_read(ifp, &cols, &rows, &maxval, 1);

            /* Figure out the colormap. */
            pm_message("computing colormap...");
            chv = ppm_computecolorhist(pixels, cols, rows, MAXCMAPCOLORS, &colors);
            if( chv == (colorhist_vector)NULL )
                pm_error("too many colors - try doing a 'ppmquant %d'", MAXCMAPCOLORS);
            pm_message("%d colors found", colors);
            break;
        default:
            /* must read the whole file into memory */
            init_read(ifp, &cols, &rows, &maxval, 1);

            /* Figure out the colormap. */
            if( mapfile ) {
                int mapcols, maprows, row, col;
                pixel **mappixels, *pP;
                pixval mapmaxval;

                pm_message("reading colormap file...");
                ifp = pm_openr(mapfile);
                mappixels = ppm_readppm(ifp, &mapcols, &maprows, &mapmaxval);
                pm_close(ifp);
                if( mapcols == 0 || maprows == 0 )
                    pm_error("null colormap??");

                /* if the maxvals of the ppmfile and the mapfile are the same,
                 * then the scaling to MAXCOLVAL (if necessary) will be done by
                 * the write_std_cmap() function.
                 * Otherwise scale them both to MAXCOLVAL.
                 */
                if( maxval != mapmaxval ) {
                    if( mapmaxval != MAXCOLVAL ) {
                        pixval *table;
                        pm_message("colormap maxval is not %d - rescaling colormap...", MAXCOLVAL);
                        table = make_val_table(mapmaxval, MAXCOLVAL);
                        for( row = 0; row < maprows; ++row )
                            for( col = 0, pP = mappixels[row]; col < mapcols; ++col, ++pP )
                                NEWDEPTH(*pP, table);   /* was PPM_DEPTH( *pP, *pP, mapmaxval, MAXCOLVAL ); */
                        mapmaxval = MAXCOLVAL;
                        free(table);
                    }

                    if( maxval != mapmaxval ) {
                        pixval *table;
                        pm_message("rescaling colors of picture...");
                        table = make_val_table(maxval, mapmaxval);
                        for( row = 0; row < rows; ++row )
                            for( col = 0, pP = pixels[row]; col < cols; ++col, ++pP )
                                NEWDEPTH(*pP, table);   /* was PPM_DEPTH( *pP, *pP, maxval, mapmaxval ); */
                        maxval = mapmaxval;
                        free(table);
                    }
                }

                pm_message("computing colormap...");
                chv = ppm_computecolorhist(mappixels, mapcols, maprows, MAXCMAPCOLORS, &colors);
                ppm_freearray(mappixels, maprows);
                if( chv == (colorhist_vector)NULL )
                    pm_error("too many colors in colormap!");
                pm_message("%d colors found in colormap", colors);

                nPlanes = fixplanes = maxplanes = colorstobpp(colors);
            }
            else {
                pm_message("computing colormap...");
                chv = ppm_computecolorhist( pixels, cols, rows, MAXCOLORS, &colors);
                if( chv == (colorhist_vector)0 ) {
                    /* too many colors */
                    mode = ifmode;
                    switch( ifmode ) {
                        case MODE_HAM:
                            pm_message("too many colors - proceeding to write a HAM%d file", hambits);
                            pm_message("if you want a non-HAM file, try doing a 'ppmquant %d'", MAXCOLORS);
                            break;
                        case MODE_24:
                            pm_message("too many colors - proceeding to write a 24bit file" );
                            pm_message("if you want a non-24bit file, try doing a 'ppmquant %d'", MAXCOLORS);
                            break;
                        case MODE_DIRECT:
                            pm_message("too many colors - proceeding to write a %d:%d:%d direct color file",
                                        dcol.r, dcol.g, dcol.b);
                            pm_message("if you want a non-direct-color file, try doing a 'ppmquant %d'", MAXCOLORS);
                            break;
                        default:
                            pm_message( "too many colors for %d planes", maxplanes );
                            pm_message( "either use -hamif/-hamforce/-24if/-24force/-dcif/-dcforce/-maxplanes,");
                            pm_error( "or try doing a 'ppmquant %d'", MAXCOLORS );
                            break;
                    }
                }
                else {
                    pm_message("%d colors found", colors);
                    nPlanes = colorstobpp(colors);
                    if( fixplanes > nPlanes )
                        nPlanes = fixplanes;
                }
            }
            break;
    }

    if( mode != MODE_CMAP )
        coded_rowbuf= (unsigned char *)xmalloc(RowBytes(cols));

    switch( mode ) {
        case MODE_HAM:
            ppm_to_ham(ifp, cols, rows, maxval, hambits);
            break;
        case MODE_24:
            ppm_to_24(ifp, cols, rows, maxval);
            break;
        case MODE_DIRECT:
            ppm_to_direct(ifp, cols, rows, maxval, &dcol);
            break;
        case MODE_CMAP:
            ppm_to_cmap(maxval, chv, colors);
            break;
        default:
            ppm_to_std(ifp, cols, rows, maxval, chv, colors, nPlanes);
            break;
    }
    pm_close(ifp);
    exit(0);
}


static void
ppm_to_ham(fp, cols, rows, maxval, hambits)
    FILE *fp;
    int cols, rows, maxval, hambits;
{
    int colors, colbits, nPlanes, formsize, cmapsize, i, hammaxval;
    pixel *pP;
    register int row, col;
    pixval *table = NULL;
    rawtype *raw_rowbuf;

    raw_rowbuf = (rawtype *)xmalloc(cols * sizeof(rawtype));
    colbits = hambits-2;
    colors = 1 << colbits;
    hammaxval = pm_bitstomaxval(colbits);
    nPlanes = hambits;
    cmapsize = colors * 3;
    if( odd(cmapsize) )
        cmapsize++;     /* pad CMAP to a word */

    formsize =
        4 +                                         /* ILBM */
        4 + 4 + 20 +                                /* BMHD size header */
        4 + 4 + 4 +                                 /* CAMG size val */
        4 + 4 + cmapsize +                          /* CMAP size colormap */
        4 + 4 + rows * nPlanes * RowBytes(cols);    /* BODY size data */

    write_form_ilbm(formsize);
    write_bmhd(cols, rows, nPlanes);

    /* write camg */
    put_fourchars("CAMG");
    put_big_long(4);
    put_big_long(vmHAM);

    /* write grayscale colormap */
    put_fourchars("CMAP");
    put_big_long(colors * 3);
    table = make_val_table(hammaxval, MAXCOLVAL);
    for( i = 0; i < colors; i++ ) {
        put_byte( table[i] );   /* red */
        put_byte( table[i] );   /* green */
        put_byte( table[i] );   /* blue */
    }
    free(table); table = NULL;
    if( odd(colors * 3) )
        put_byte(0);

    /* write body */
    put_fourchars("BODY");
    put_big_long(rows * nPlanes * RowBytes(cols));

    if( hammaxval != maxval )
        table = make_val_table(maxval, hammaxval);

    for( row = 0; row < rows; row++ ) {
        register int noprev, pr, pg, pb, r, g, b, l;

        noprev = 1;
        for( col = 0, pP = next_pixrow(fp, row); col < cols; col++, pP++ ) {
            r = PPM_GETR( *pP );
            g = PPM_GETG( *pP );
            b = PPM_GETB( *pP );
            l = (int)(PPM_LUMIN(*pP) + 0.5); /* -IUW added '+ 0.5' */
            if( table ) {
                r = table[r];
                g = table[g];
                b = table[b];
                l = table[l];
            }

            if( noprev ) {
                /* No previous pixels, gotta use the gray option. */
                raw_rowbuf[col] = l /* + (HAMCODE_CMAP << colbits) */;
                pr = pg = pb = l;
                noprev = 0;
            }
            else {
                register int dred, dgreen, dblue, dgray;
                /* Compute distances for the four options. */
                dred = abs( g - pg ) + abs( b - pb );
                dgreen = abs( r - pr ) + abs( b - pb );
                dblue = abs( r - pr ) + abs( g - pg );
                dgray = abs( r - l ) + abs( g - l ) + abs( b - l );

                if( dgray <= dred && dgray <= dgreen && dgray <= dblue ) {      /* -IUW  '<=' was '<'  */
                    raw_rowbuf[col] = l /* + (HAMCODE_CMAP << colbits) */;
                    pr = pg = pb = l;
                }
                else
                if( dblue <= dred && dblue <= dgreen ) {
                    raw_rowbuf[col] = b + (HAMCODE_BLUE << colbits);
                    pb = b;
                }
                else
                if( dred <= dgreen ) {
                    raw_rowbuf[col] = r + (HAMCODE_RED << colbits);
                    pr = r;
                }
                else {
                    raw_rowbuf[col] = g + (HAMCODE_GREEN << colbits);
                    pg = g;
                }
            }
        }
        encode_row(raw_rowbuf, cols, nPlanes);
    }
    if( table )
        free(table);
    free(raw_rowbuf);
}


static void
ppm_to_24(fp, cols, rows, maxval)
    FILE *fp;
    int cols, rows, maxval;
{
    int formsize, nPlanes;
    register int row, col;
    pixel *pP;
    pixval *table = NULL;
    rawtype *redbuf, *greenbuf, *bluebuf;

    redbuf   = (rawtype *)xmalloc(cols * sizeof(rawtype));
    greenbuf = (rawtype *)xmalloc(cols * sizeof(rawtype));
    bluebuf  = (rawtype *)xmalloc(cols * sizeof(rawtype));

    nPlanes = 24;

    formsize =
        4 +                                         /* ILBM */
        4 + 4 + 20 +                                /* BMHD size header */
        4 + 4 + rows * nPlanes * RowBytes(cols);    /* BODY size data */

    write_form_ilbm(formsize);
    write_bmhd(cols, rows, nPlanes);

    /* write body */
        put_fourchars("BODY");
    put_big_long(rows * nPlanes * RowBytes(cols));

    if( maxval != MAXCOLVAL ) {
        pm_message("maxval is not %d - automatically rescaling colors", MAXCOLVAL);
        table = make_val_table(maxval, MAXCOLVAL);
    }
    for( row = 0; row < rows; row++ ) {
        pP = next_pixrow(fp, row);
        if( table ) {
            for( col = 0; col < cols; col++, pP++ ) {
                redbuf[col]     = table[PPM_GETR(*pP)];
                greenbuf[col]   = table[PPM_GETG(*pP)];
                bluebuf[col]    = table[PPM_GETB(*pP)];
            }
        }
        else {
            for( col = 0; col < cols; col++, pP++ ) {
                redbuf[col]     = PPM_GETR(*pP);
                greenbuf[col]   = PPM_GETG(*pP);
                bluebuf[col]    = PPM_GETB(*pP);
            }
        }
        encode_row(redbuf, cols, 8);
        encode_row(greenbuf, cols, 8);
        encode_row(bluebuf, cols, 8);
    }
    if( table )
        free(table);
    free(redbuf);
    free(greenbuf);
    free(bluebuf);
}


static void
ppm_to_direct(fp, cols, rows, maxval, dcol)
    FILE *fp;
    int cols, rows, maxval;
    DirectColor *dcol;
{
    int formsize, nPlanes;
    register int row, col;
    pixel *pP;
    pixval *redtable = NULL, *greentable = NULL, *bluetable = NULL;
    pixval redmaxval, greenmaxval, bluemaxval;
    rawtype *redbuf, *greenbuf, *bluebuf;

    redbuf   = (rawtype *)xmalloc(cols * sizeof(rawtype));
    greenbuf = (rawtype *)xmalloc(cols * sizeof(rawtype));
    bluebuf  = (rawtype *)xmalloc(cols * sizeof(rawtype));

    nPlanes = dcol->r + dcol->g + dcol->b;
    redmaxval   = pm_bitstomaxval(dcol->r);
    if( redmaxval != maxval ) {
        pm_message("rescaling reds to %d bits", dcol->r);
        redtable = make_val_table(maxval, redmaxval);
    }
    greenmaxval = pm_bitstomaxval(dcol->g);
    if( greenmaxval != maxval ) {
        pm_message("rescaling greens to %d bits", dcol->g);
        greentable = make_val_table(maxval, greenmaxval);
    }
    bluemaxval  = pm_bitstomaxval(dcol->b);
    if( bluemaxval != maxval ) {
        pm_message("rescaling blues to %d bits", dcol->b);
        bluetable = make_val_table(maxval, bluemaxval);
    }

    formsize =
        4 +                                         /* ILBM */
        4 + 4 + 20 +                                /* BMHD size header */
        4 + 4 + 4 +                                 /* DCOL size description */
        4 + 4 + rows * nPlanes * RowBytes(cols);    /* BODY size data */

    write_form_ilbm(formsize);
    write_bmhd(cols, rows, nPlanes);

    /* write DCOL */
    put_fourchars("DCOL");
    put_big_long(4);
    put_byte(dcol->r);
    put_byte(dcol->g);
    put_byte(dcol->b);
    put_byte(0);    /* pad */

    /* write body */
    put_fourchars("BODY");
    put_big_long(rows * nPlanes * RowBytes(cols));

    for( row = 0; row < rows; row++ ) {
        pP = next_pixrow(fp, row);
        for( col = 0; col < cols; col++, pP++ ) {
            register pixval r, g, b;

            r = PPM_GETR(*pP); if( redtable ) r = redtable[r];
            g = PPM_GETG(*pP); if( greentable ) g = greentable[g];
            b = PPM_GETB(*pP); if( bluetable ) b = bluetable[b];

            redbuf[col] = r;
            greenbuf[col] = g;
            bluebuf[col] = b;
        }
        encode_row(redbuf, cols, dcol->r);
        encode_row(greenbuf, cols, dcol->g);
        encode_row(bluebuf, cols, dcol->b);
    }
    if( redtable )
        free(redtable);
    if( greentable )
        free(greentable);
    if( bluetable )
        free(bluetable);
    free(redbuf);
    free(greenbuf);
    free(bluebuf);
}


static void
ppm_to_cmap(maxval, chv, colors)
    int maxval;
    colorhist_vector chv;
    int colors;
{
    int formsize, cmapsize;

    cmapsize = colors * 3;
    if( odd(cmapsize) )
        cmapsize++;     /* pad to a word */

    formsize =
        4 +                                         /* ILBM */
        4 + 4 + 20 +                                /* BMHD size header */
        4 + 4 + cmapsize;                           /* CMAP size colormap */

    write_form_ilbm(formsize);
    write_bmhd(0, 0, 0);
    write_std_cmap(chv, colors, maxval);
}


static void
ppm_to_std(fp, cols, rows, maxval, chv, colors, nPlanes)
    FILE *fp;
    int cols, rows, maxval;
    colorhist_vector chv;
    int colors, nPlanes;
{
    int formsize, cmapsize;
    colorhash_table cht;
    register int row, col;
    pixel *pP;
    rawtype *raw_rowbuf;

    raw_rowbuf = (rawtype *)xmalloc(cols * sizeof(rawtype));

    /* Make a hash table for fast color lookup. */
    cht = ppm_colorhisttocolorhash(chv, colors);

    cmapsize = colors * 3;
    if( odd(cmapsize) )
        cmapsize++;     /* pad CMAP to a word */

    formsize =
        4 +                                         /* ILBM */
        4 + 4 + 20 +                                /* BMHD size header */
        4 + 4 + cmapsize +                          /* CMAP size colormap */
        4 + 4 + rows * nPlanes * RowBytes(cols);    /* BODY size data */

    write_form_ilbm(formsize);
    write_bmhd(cols, rows, nPlanes);
    write_std_cmap(chv, colors, maxval);

    /* write body */
    put_fourchars("BODY");
    put_big_long(rows * nPlanes * RowBytes(cols));

    for( row = 0; row < rows; row++ ) {
        for( col = 0, pP = next_pixrow(fp, row); col < cols; col++, pP++ ) {
            int ind;

            /* Check hash table to see if we have already matched this color. */
            ind = ppm_lookupcolor(cht, pP);
            if( ind == -1 ) {
                /* No; search colormap for closest match. */
                /* algorithm taken from ppmquant.c   -IUW */
                register int i, r1, g1, b1, r2, g2, b2;
                register long dist, newdist;

                r1 = PPM_GETR(*pP);
                g1 = PPM_GETG(*pP);
                b1 = PPM_GETB(*pP);
                dist = 2000000000;
                for( i = 0; i < colors; ++i ) {
                    r2 = PPM_GETR(chv[i].color);
                    g2 = PPM_GETG(chv[i].color);
                    b2 = PPM_GETB(chv[i].color);
                    newdist = ( r1 - r2 ) * ( r1 - r2 ) +
                              ( g1 - g2 ) * ( g1 - g2 ) +
                              ( b1 - b2 ) * ( b1 - b2 );
                    if( newdist < dist ) {
                        ind = i;
                        dist = newdist;
                    }
                }
            }
            raw_rowbuf[col] = ind;
        }
        encode_row(raw_rowbuf, cols, nPlanes);
    }
}


static void
write_std_cmap(chv, colors, maxval)
    colorhist_vector chv;
    int colors, maxval;
{
    int cmapsize, i;

    cmapsize = 3 * colors;

    /* write colormap */
    put_fourchars("CMAP");
    put_big_long(cmapsize);
    if( maxval != MAXCOLVAL ) {
        pixval *table;
        pm_message("maxval is not %d - automatically rescaling colors", MAXCOLVAL);
        table = make_val_table(maxval, MAXCOLVAL);
        for( i = 0; i < colors; i++ ) {
            put_byte((char)(table[PPM_GETR(chv[i].color)]));
            put_byte((char)(table[PPM_GETG(chv[i].color)]));
            put_byte((char)(table[PPM_GETB(chv[i].color)]));
        }
        free(table);
    }
    else {
        for( i = 0; i < colors; i++ ) {
            put_byte(PPM_GETR(chv[i].color));
            put_byte(PPM_GETG(chv[i].color));
            put_byte(PPM_GETB(chv[i].color));
        }
    }
    if( odd(cmapsize) )
        put_byte(0);
}


static void
write_form_ilbm(size)
    int size;
{
    put_fourchars("FORM");
    put_big_long(size);
    put_fourchars("ILBM");
}


static void
write_bmhd(cols, rows, nPlanes)
    int cols, rows, nPlanes;
{
    put_fourchars("BMHD");
    put_big_long(20);

    put_big_short(cols);
    put_big_short(rows);
    put_big_short(0);       /* x */
    put_big_short(0);       /* y */
    put_byte(nPlanes);
    put_byte(mskNone);      /* masking type */
    put_byte(cmpNone);      /* compression type */
    put_byte(0);            /* pad1 */
    put_big_short(0);       /* tranparentColor */
    put_byte(10);           /* xAsp */
    put_byte(10);           /* yAsp */
    put_big_short(cols);    /* pageWidth */
    put_big_short(rows);    /* pageHeight */
}


/* encode algorithm by Johan Widen (jw@jwdata.se) */
const unsigned char bit_mask[] = {1, 2, 4, 8, 16, 32, 64, 128};

static void
encode_row(row, cols, nPlanes)
    rawtype *row;
    int cols, nPlanes;
{
    register int plane, col;
    int bytes;

    bytes = RowBytes(cols);

    /* Encode and write raw bytes in plane-interleaved form. */
    for( plane = 0; plane < nPlanes; plane++ ) {
        int mask, cbit, wr;
        unsigned char *cp;
        rawtype *rp;

        mask = 1 << plane;
        cbit = -1;
        cp = coded_rowbuf-1;
        rp = row;
        for( col = 0; col < cols; col++, cbit--, rp++ ) {
            if( cbit < 0 ) {
                cbit = 7;
                *++cp = 0;
            }
            if( *rp & mask )
                *cp |= bit_mask[cbit];
        }
        wr = fwrite(coded_rowbuf, 1, bytes, stdout);
        if( wr != bytes )
            pm_error("write error");
    }
}


static int
colorstobpp(colors)
    int colors;
{
    int i;

    for( i = 1; i <= MAXPLANES; i++ ) {
        if( colors <= (1 << i) )
            return i;
    }
    pm_error("too many planes (max %d)", MAXPLANES);
}


#if 0
static void
put_fourchars(str)
    char* str;
{
    fputs( str, stdout );
}
#endif


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


#if 0
static void
put_byte(b)
    unsigned char b;
{
    (void) putc( b, stdout );
}
#endif


static int
get_int_val(string, option, bot, top)
    char *string, *option;
    int bot, top;
{
    int val;

    if( sscanf(string, "%d", &val) != 1 )
        pm_error("option \"%s\" needs integer argument", option);

    if( val < bot || val > top )
        pm_error("option \"%s\" argument value out of range (%d..%d)", option, bot, top);

    return val;
}




static pixval *
make_val_table(oldmaxval, newmaxval)
    pixval oldmaxval, newmaxval;
{
    int i;
    pixval *table;

    table = xmalloc((oldmaxval + 1) * sizeof(pixval));
    for(i = 0; i <= oldmaxval; i++ )
        table[i] = (i * newmaxval + oldmaxval/2) / oldmaxval;

    return table;
}


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

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


static int  gFormat;
static int  gCols;
static int  gMaxval;

static void
init_read(fp, colsP, rowsP, maxvalP, readall)
    FILE *fp;
    int *colsP, *rowsP;
    pixval *maxvalP;
    int readall;
{
    if( readall ) {
        pixels = ppm_readppm(fp, colsP, rowsP, maxvalP);
    }
    else {
        ppm_readppminit(fp, colsP, rowsP, maxvalP, &gFormat);
        pixrow = ppm_allocrow(*colsP);
    }
    gCols = *colsP;
    gMaxval = *maxvalP;
}


static pixel *
next_pixrow(fp, row)
    FILE *fp;
    int row;
{
    if( pixels )
        pixrow = pixels[row];
    else {
        static int rowcnt;
        if( row != rowcnt )
            pm_error("big mistake");
        rowcnt++;
        ppm_readppmrow(fp, pixrow, gCols, gMaxval, gFormat);
    }
    return pixrow;
}

