/*\
 * $Id: ppmtobmp.c,v 1.9 1992/11/24 19:39:33 dws Exp dws $
 *
 * ppmtobmp.c - Converts from a PPM file to a Microsoft Windows or OS/2
 * .BMP file.
 *
 * The current implementation is probably not complete, but it works for
 * me.  I welcome feedback.
 *
 * Copyright (C) 1992 by David W. Sanderson.
 *
 * 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.
 *
 * $Log: ppmtobmp.c,v $
 * Revision 1.9  1992/11/24  19:39:33  dws
 * Added copyright.
 *
 * Revision 1.8  1992/11/17  02:16:52  dws
 * Moved length functions to bmp.h.
 *
 * Revision 1.7  1992/11/11  23:18:16  dws
 * Modified to adjust the bits per pixel to 1, 4, or 8.
 *
 * Revision 1.6  1992/11/11  22:43:39  dws
 * Commented out a superfluous message.
 *
 * Revision 1.5  1992/11/11  05:58:06  dws
 * First version that works.
 *
 * Revision 1.4  1992/11/11  03:40:32  dws
 * Moved calculation of bits per pixel to BMPEncode.
 *
 * Revision 1.3  1992/11/11  03:02:34  dws
 * Added BMPEncode function.
 *
 * Revision 1.2  1992/11/08  01:44:35  dws
 * Added option processing and reading of PPM file.
 *
 * Revision 1.1  1992/11/08  00:46:07  dws
 * Initial revision
\*/

#include        "bmp.h"
#include        "ppm.h"
#include        "ppmcmap.h"
#include        "bitio.h"

#define MAXCOLORS 256

/*
 * Utilities
 */

static char     er_write[] = "stdout: write error";

/* prototypes */
static void PutByte ARGS((FILE *fp, char v));
static void PutShort ARGS((FILE *fp, short v));
static void PutLong ARGS((FILE *fp, long v));
static int BMPwritefileheader ARGS((FILE *fp, int class, unsigned long bitcount,
    unsigned long x, unsigned long y));
static int BMPwriteinfoheader ARGS((FILE *fp, int class, unsigned long bitcount,
    unsigned long x, unsigned long y));
static int BMPwritergb ARGS((FILE *fp, int class, pixval R, pixval G, pixval B));
static int BMPwritergbtable ARGS((FILE *fp, int class, int bpp, int colors,
    pixval *R, pixval *G, pixval *B));
static int BMPwriterow ARGS((FILE *fp, pixel *row, unsigned long cx,
    unsigned short bpp, colorhash_table cht));
static int BMPwritebits ARGS((FILE *fp, unsigned long cx, unsigned long cy,
    unsigned short cBitCount, pixel **pixels, colorhash_table cht));
static int colorstobpp ARGS((int colors));
static void BMPEncode ARGS((FILE *fp, int class, int x, int y, pixel **pixels,
    int colors, colorhash_table cht, pixval *R, pixval *G, pixval *B));
static void
PutByte(FILE *fp, char v)
{
        if (putc(v, fp) == EOF)
        {
                pm_error(er_write);
        }
}

static void
PutShort(FILE *fp, short v)
{
        if (pm_writelittleshort(fp, v) == -1)
        {
                pm_error(er_write);
        }
}

static void
PutLong(fp, v)
        FILE           *fp;
        long            v;
{
        if (pm_writelittlelong(fp, v) == -1)
        {
                pm_error(er_write);
        }
}

/*
 * BMP writing
 */

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwritefileheader(fp, class, bitcount, x, y)
        FILE           *fp;
        int             class;
        unsigned long   bitcount;
        unsigned long   x;
        unsigned long   y;
{
        PutByte(fp, 'B');
        PutByte(fp, 'M');

        /* cbSize */
        PutLong(fp, BMPlenfile(class, bitcount, -1, x, y));

        /* xHotSpot */
        PutShort(fp, 0);

        /* yHotSpot */
        PutShort(fp, 0);

        /* offBits */
        PutLong(fp, BMPoffbits(class, bitcount, -1));

        return 14;
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwriteinfoheader(fp, class, bitcount, x, y)
        FILE           *fp;
        int             class;
        unsigned long   bitcount;
        unsigned long   x;
        unsigned long   y;
{
        long    cbFix;

        /* cbFix */
        switch (class)
        {
        case C_WIN:
                cbFix = 40;
                PutLong(fp, cbFix);

                /* cx */
                PutLong(fp, x);
                /* cy */
                PutLong(fp, y);
                /* cPlanes */
                PutShort(fp, 1);
                /* cBitCount */
                PutShort(fp, bitcount);

                /*
                 * We've written 16 bytes so far, need to write 24 more
                 * for the required total of 40.
                 */

                PutLong(fp, 0);
                PutLong(fp, 0);
                PutLong(fp, 0);
                PutLong(fp, 0);
                PutLong(fp, 0);
                PutLong(fp, 0);


                break;
        case C_OS2:
                cbFix = 12;
                PutLong(fp, cbFix);

                /* cx */
                PutShort(fp, x);
                /* cy */
                PutShort(fp, y);
                /* cPlanes */
                PutShort(fp, 1);
                /* cBitCount */
                PutShort(fp, bitcount);

                break;
        default:
                pm_error(er_internal, "BMPwriteinfoheader");
        }

        return cbFix;
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwritergb(FILE *fp, int class, pixval R, pixval G, pixval B)
{
        switch (class)
        {
        case C_WIN:
                PutByte(fp, B);
                PutByte(fp, G);
                PutByte(fp, R);
                PutByte(fp, 0);
                return 4;
        case C_OS2:
                PutByte(fp, B);
                PutByte(fp, G);
                PutByte(fp, R);
                return 3;
        default:
                pm_error(er_internal, "BMPwritergb");
        }
        return -1;
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwritergbtable(fp,class,bpp,colors,R,G,B)
        FILE           *fp;
        int             class;
        int             bpp;
        int             colors;
        pixval         *R;
        pixval         *G;
        pixval         *B;
{
        int             nbyte = 0;
        int             i;
        long            ncolors;

        for (i = 0; i < colors; i++)
        {
                nbyte += BMPwritergb(fp,class,R[i],G[i],B[i]);
        }

        ncolors = (1 << bpp);

        for (; i < ncolors; i++)
        {
                nbyte += BMPwritergb(fp,class,0,0,0);
        }

        return nbyte;
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwriterow(FILE *fp, pixel *row, unsigned long cx, unsigned short bpp, 
            colorhash_table cht)
{
        BITSTREAM       b;
        unsigned        nbyte = 0;
        int             rc;
        unsigned        x;

        if ((b = pm_bitinit(fp, "w")) == (BITSTREAM) 0)
        {
                return -1;
        }

        for (x = 0; x < cx; x++, row++)
        {
                if ((rc = pm_bitwrite(b, bpp, ppm_lookupcolor(cht, row))) == -1)
                {
                        return -1;
                }
                nbyte += rc;
        }

        if ((rc = pm_bitfini(b)) == -1)
        {
                return -1;
        }
        nbyte += rc;

        /*
         * Make sure we write a multiple of 4 bytes.
         */
        while (nbyte % 4)
        {
                PutByte(fp, 0);
                nbyte++;
        }

        return nbyte;
}

/*
 * returns the number of bytes written, or -1 on error.
 */
static int
BMPwritebits(FILE *fp, unsigned long cx, unsigned long cy, 
             unsigned short cBitCount, pixel **pixels, colorhash_table cht)
{
        int             nbyte = 0;
        long            y;

        if(cBitCount > 24)
        {
                pm_error("cannot handle cBitCount: %d"
                         ,cBitCount);
        }

        /*
         * The picture is stored bottom line first, top line last
         */

        for (y = cy - 1; y >= 0; y--)
        {
                int rc;
                rc = BMPwriterow(fp, pixels[y], cx, cBitCount, cht);

                if(rc == -1)
                {
                        pm_error("couldn't write row %d"
                                 ,y);
                }
                if(rc%4)
                {
                        pm_error("row had bad number of bytes: %d"
                                 ,rc);
                }
                nbyte += rc;
        }

        return nbyte;
}

/*
 * Return the number of bits per pixel required to represent the
 * given number of colors.
 */

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

        if (colors < 1)
        {
                pm_error("can't have less than one color");
        }

        if ((bpp = pm_maxvaltobits(colors - 1)) > 8)
        {
                pm_error("can't happen");
        }

        return bpp;
}

/*
 * Write a BMP file of the given class.
 *
 * Note that we must have 'colors' in order to know exactly how many
 * colors are in the R, G, B, arrays.  Entries beyond those in the
 * arrays are undefined.
 */
static void
BMPEncode(fp, class, x, y, pixels, colors, cht, R, G, B)
        FILE           *fp;
        int             class;
        int             x;
        int             y;
        pixel         **pixels;
        int             colors; /* number of valid entries in R,G,B */
        colorhash_table cht;
        pixval         *R;
        pixval         *G;
        pixval         *B;
{
        int             bpp;    /* bits per pixel */
        unsigned long   nbyte = 0;

        bpp = colorstobpp(colors);

        /*
         * I have found empirically at least one BMP-displaying program
         * that can't deal with (for instance) using 3 bits per pixel.
         * I have seen no programs that can deal with using 3 bits per
         * pixel.  I have seen programs which can deal with 1, 4, and
         * 8 bits per pixel.
         *
         * Based on this, I adjust actual the number of bits per pixel
         * as follows.  If anyone knows better, PLEASE tell me!
         */
        switch(bpp)
        {
        case 2:
        case 3:
                bpp = 4;
                break;
        case 5:
        case 6:
        case 7:
                bpp = 8;
                break;
        }

        pm_message("Using %d bits per pixel", bpp);

        nbyte += BMPwritefileheader(fp, class, bpp, x, y);
        nbyte += BMPwriteinfoheader(fp, class, bpp, x, y);
        nbyte += BMPwritergbtable(fp, class, bpp, colors, R, G, B);

        if(nbyte !=     ( BMPlenfileheader(class)
                        + BMPleninfoheader(class)
                        + BMPlenrgbtable(class, bpp, -1)))
        {
                pm_error(er_internal, "BMPEncode");
        }

        nbyte += BMPwritebits(fp, x, y, bpp, pixels, cht);
        if(nbyte != BMPlenfile(class, bpp, -1, x, y))
        {
                pm_error(er_internal, "BMPEncode");
        }
}

#if 0
report(class, bitcount, x, y)
        int             class;
        unsigned long   bitcount;
        unsigned long   x;
        unsigned long   y;
{
        char *name;
        switch (class)
        {
        case C_WIN:
                name = "Win";
                break;
        case C_OS2:
                name = "OS/2";
                break;
        default:
                pm_error(er_internal, "report");
                return 0;
        }

        pm_message("For class %s, bitcount %d, x %d, y %d:"
                , name
                , bitcount
                , x
                , y);
        pm_message("\tlenrgbtable: %d"
                , BMPlenrgbtable(class, bitcount));
        pm_message("\tlenline:       %d"
                , BMPlenline(class, bitcount, x));
        pm_message("\tlenbits:       %d"
                , BMPlenbits(class, bitcount, x, y));
        pm_message("\toffbits:       %d"
                , BMPoffbits(class, bitcount));
        pm_message("\tlenfile:       %d"
                , BMPlenfile(class, bitcount, x, y));
}

int
main(ac, av)
    int             ac;
    char          **av;
{
    ppm_init(&ac, av);

    if(ac != 5)
    {
	pm_message("usage: ppmtobmp class bitcount x y");
	exit(1);
    }

    report(atoi(av[1]), atoi(av[2]), atoi(av[3]), atoi(av[4]));

    exit(0);
}
#endif

int
main(argc, argv)
        int             argc;
        char          **argv;
{
        FILE           *ifp = stdin;
        char           *usage = "[-windows] [-os2] [ppmfile]";
        int             class = C_OS2;

        int             argn;
        int             rows;
        int             cols;
        int             colors;
        int             i;
        pixval          maxval;
        colorhist_vector chv;
        pixval          Red[MAXCOLORS];
        pixval          Green[MAXCOLORS];
        pixval          Blue[MAXCOLORS];

        pixel** pixels;
        colorhash_table cht;


        ppm_init(&argc, argv);

        argn = 1;

        while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0')
        {
                if (pm_keymatch(argv[argn], "-windows", 2))
                        class = C_WIN;
                else if (pm_keymatch(argv[argn], "-os2", 2))
                        class = C_OS2;
                else
                        pm_usage(usage);
                ++argn;
        }

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

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

        pixels = ppm_readppm(ifp, &cols, &rows, &maxval);

        pm_close(ifp);

#if 0
        {
                char *name;
                switch (class)
                {
                case C_WIN:
                        name = "a Windows";
                        break;
                case C_OS2:
                        name = "an OS/2";
                        break;
                default:
                        pm_error(er_internal, "report");
                        break;
                }
                pm_message("generating %s BMP file", name);
        }
#endif

        /* Figure out the colormap. */
        pm_message("computing colormap...");
        chv = ppm_computecolorhist(pixels, cols, rows, MAXCOLORS, &colors);
        if (chv == (colorhist_vector) 0)
                pm_error("too many colors - try doing a 'ppmquant %d'"
                        , MAXCOLORS);
        pm_message("%d colors found", colors);

        /*
         * Now turn the ppm colormap into the appropriate GIF
         * colormap.
         */
        if (maxval > 255)
        {
                pm_message("maxval is not 255 - automatically rescaling colors");
        }
        for (i = 0; i < colors; ++i)
        {
                if (maxval == 255)
                {
                        Red[i] = PPM_GETR(chv[i].color);
                        Green[i] = PPM_GETG(chv[i].color);
                        Blue[i] = PPM_GETB(chv[i].color);
                }
                else
                {
                        Red[i] = (pixval) PPM_GETR(chv[i].color) * 255 / maxval;
                        Green[i] = (pixval) PPM_GETG(chv[i].color) * 255 / maxval;
                        Blue[i] = (pixval) PPM_GETB(chv[i].color) * 255 / maxval;
                }
        }

        /* And make a hash table for fast lookup. */
        cht = ppm_colorhisttocolorhash(chv, colors);
        ppm_freecolorhist(chv);

        /* All set, let's do it. */
        BMPEncode(stdout, class
                , cols, rows, pixels, colors, cht
                ,Red, Green, Blue);

        pm_close(stdout);

        exit(0);
}
