/*\
 * $Id: bmp.h,v 1.3 1992/11/24 19:39:56 dws Exp dws $
 * 
 * bmp.h - routines to calculate sizes of parts of BMP files
 *
 * Some fields in BMP files contain offsets to other parts
 * of the file.  These routines allow us to calculate these
 * offsets, so that we can read and write BMP files without
 * the need to fseek().
 * 
 * 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: bmp.h,v $
 * Revision 1.3  1992/11/24  19:39:56  dws
 * Added copyright.
 *
 * Revision 1.2  1992/11/17  02:13:37  dws
 * Adjusted a string's name.
 *
 * Revision 1.1  1992/11/16  19:54:44  dws
 * Initial revision
 *
 \*/

/* prototypes */
static unsigned long BMPlenfileheader ARGS((int class));
static unsigned long BMPleninfoheader ARGS((int class));
static unsigned long BMPlenrgbtable ARGS((int class, unsigned long bitcount));
static unsigned long BMPlenline ARGS((int class, unsigned long bitcount, unsigned long x));
static unsigned long BMPlenbits ARGS((int class, unsigned long bitcount, unsigned long x, unsigned long y));
static unsigned long BMPlenfile ARGS((int class, unsigned long bitcount, unsigned long x, unsigned long y));
static unsigned long BMPoffbits ARGS((int class, unsigned long bitcount));

/*
 * Classes of BMP files
 */

#define C_WIN	1
#define C_OS2	2

static char er_internal[] = "%s: internal error!";

static unsigned long BMPlenfileheader(class)
    int class;
{
  switch (class) {
    case C_WIN:
      return 14;
    case C_OS2:
      return 14;
    default:
      pm_error(er_internal, "BMPlenfileheader");
      return 0;
  }
}

static unsigned long BMPleninfoheader(class)
    int class;
{
  switch (class) {
    case C_WIN:
      return 40;
    case C_OS2:
      return 12;
    default:
      pm_error(er_internal, "BMPleninfoheader");
      return 0;
  }
}

static unsigned long BMPlenrgbtable(class, bitcount)
    int class;
    unsigned long bitcount;
{
  unsigned long lenrgb;

  if (bitcount < 1) {
    pm_error(er_internal, "BMPlenrgbtable");
    return 0;
  }
  switch (class) {
    case C_WIN:
      lenrgb = 4;
      break;
    case C_OS2:
      lenrgb = 3;
      break;
    default:
      pm_error(er_internal, "BMPlenrgbtable");
      return 0;
  }

  return (1 << bitcount) * lenrgb;
}

/*
 * length, in bytes, of a line of the image
 * 
 * Evidently each row is padded on the right as needed to make it a
 * multiple of 4 bytes long.  This appears to be true of both
 * OS/2 and Windows BMP files.
 */
static unsigned long BMPlenline(class, bitcount, x)
    int class;
    unsigned long bitcount;
    unsigned long x;
{
  unsigned long bitsperline;

  switch (class) {
    case C_WIN:
      break;
    case C_OS2:
      break;
    default:
      pm_error(er_internal, "BMPlenline");
      return 0;
  }

  bitsperline = x * bitcount;

  /*
   * if bitsperline is not a multiple of 32, then round
   * bitsperline up to the next multiple of 32.
   */
  if ((bitsperline % 32) != 0) {
    bitsperline += (32 - (bitsperline % 32));
  }

  if ((bitsperline % 32) != 0) {
    pm_error(er_internal, "BMPlenline");
    return 0;
  }

  /* number of bytes per line == bitsperline/8 */
  return bitsperline >> 3;
}

/* return the number of bytes used to store the image bits */
static unsigned long BMPlenbits(class, bitcount, x, y)
    int class;
    unsigned long bitcount;
    unsigned long x;
    unsigned long y;
{
  return y * BMPlenline(class, bitcount, x);
}

/* return the offset to the BMP image bits */
static unsigned long BMPoffbits(class, bitcount)
    int class;
    unsigned long bitcount;
{
  return BMPlenfileheader(class)
    + BMPleninfoheader(class)
    + BMPlenrgbtable(class, bitcount);
}

/* return the size of the BMP file in bytes */
static unsigned long BMPlenfile(class, bitcount, x, y)
    int class;
    unsigned long bitcount;
    unsigned long x;
    unsigned long y;
{
  return BMPoffbits(class, bitcount)
    + BMPlenbits(class, bitcount, x, y);
}

/*\
 * $Id: bmptoppm.c,v 1.10 1992/11/24 19:38:17 dws Exp dws $
 * 
 * bmptoppm.c - Converts from a Microsoft Windows or OS/2 .BMP file to a
 * PPM file.
 * 
 * The current implementation is probably not complete, but it works for
 * all the BMP files I have.  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: bmptoppm.c,v $
 * Revision 1.10  1992/11/24  19:38:17  dws
 * Added code to verify that reading occurred at the correct offsets.
 * Added copyright.
 *
 * Revision 1.9  1992/11/17  02:15:24  dws
 * Changed to include bmp.h.
 * Eliminated need for fseek(), and therefore the need for a
 * temporary file.
 *
 * Revision 1.8  1992/11/13  23:48:57  dws
 * Made definition of Seekable() static, to match its prototype.
 *
 * Revision 1.7  1992/11/11  00:17:50  dws
 * Generalized to use bitio routines.
 *
 * Revision 1.6  1992/11/10  23:51:44  dws
 * Enhanced command-line handling.
 *
 * Revision 1.5  1992/11/08  00:38:46  dws
 * Changed some names to help w/ addition of ppmtobmp.
 *
 * Revision 1.4  1992/10/27  06:28:28  dws
 * Corrected stupid typo.
 *
 * Revision 1.3  1992/10/27  06:17:10  dws
 * Removed a magic constant value.
 *
 * Revision 1.2  1992/10/27  06:09:58  dws
 * Made stdin seekable.
 *
 * Revision 1.1  1992/10/27  05:31:41  dws
 * Initial revision
 \*/

#define	MAXCOLORS   	256

static char *ifname;

/*
 * Utilities
 */

static int GetByte ARGS((FILE * fp));
static short GetShort ARGS((FILE * fp));
static long GetLong ARGS((FILE * fp));
static void readto ARGS((FILE * fp, unsigned long *ppos, unsigned long dst));
static void BMPreadfileheader ARGS((FILE * fp, unsigned long *ppos,
				    unsigned long *poffBits));
static void BMPreadinfoheader ARGS((FILE * fp, unsigned long *ppos,
	   unsigned long *pcx, unsigned long *pcy, unsigned short *pcBitCount,
				    int *pclass));
static int BMPreadrgbtable ARGS((FILE * fp, unsigned long *ppos,
    unsigned short cBitCount, int class, pixval * R, pixval * G, pixval * B));
static int BMPreadrow ARGS((FILE * fp, unsigned long *ppos, pixel * row,
			    unsigned long cx, unsigned short cBitCount, pixval * R, pixval * G, pixval * B));
static pixel **BMPreadbits ARGS((FILE * fp, unsigned long *ppos,
		    unsigned long offBits, unsigned long cx, unsigned long cy,
    unsigned short cBitCount, int class, pixval * R, pixval * G, pixval * B));

static char er_read[] = "%s: read error";
static char er_seek[] = "%s: seek error";

static int GetByte(fp)
    FILE *fp;
{
  int v;

  if ((v = getc(fp)) == EOF) {
    pm_error(er_read, ifname);
  }

  return v;
}

static short GetShort(fp)
    FILE *fp;
{
  short v;

  if (pm_readlittleshort(fp, &v) == -1) {
    pm_error(er_read, ifname);
  }

  return v;
}

static long GetLong(fp)
    FILE *fp;
{
  long v;

  if (pm_readlittlelong(fp, &v) == -1) {
    pm_error(er_read, ifname);
  }

  return v;
}

/*
 * readto - read as many bytes as necessary to position the
 * file at the desired offset.
 */

static void readto(fp, ppos, dst)
    FILE *fp;
    unsigned long *ppos;					/* pointer to number of bytes read from fp */
    unsigned long dst;
{
  unsigned long pos;

  if (!fp || !ppos)
    return;

  pos = *ppos;

  if (pos > dst)
    pm_error("%s: internal error in readto()", ifname);

  for (; pos < dst; pos++) {
    if (getc(fp) == EOF) {
      pm_error(er_read, ifname);
    }
  }

  *ppos = pos;
}

/*
 * BMP reading routines
 */

static void BMPreadfileheader(fp, ppos, poffBits)
    FILE *fp;
    unsigned long *ppos;					/* number of bytes read from fp */
    unsigned long *poffBits;
{
  unsigned long cbSize;
  unsigned short xHotSpot;
  unsigned short yHotSpot;
  unsigned long offBits;

  if (GetByte(fp) != 'B') {
    pm_error("%s is not a BMP file", ifname);
  }
  if (GetByte(fp) != 'M') {
    pm_error("%s is not a BMP file", ifname);
  }

  cbSize = GetLong(fp);
  xHotSpot = GetShort(fp);
  yHotSpot = GetShort(fp);
  offBits = GetLong(fp);

  *poffBits = offBits;

  *ppos += 14;
}

static void BMPreadinfoheader(fp, ppos, pcx, pcy, pcBitCount, pclass)
    FILE *fp;
    unsigned long *ppos;					/* number of bytes read from fp */
    unsigned long *pcx;
    unsigned long *pcy;
    unsigned short *pcBitCount;
    int *pclass;
{
  unsigned long cbFix;
  unsigned short cPlanes;

  unsigned long cx;
  unsigned long cy;
  unsigned short cBitCount;
  int class;

  cbFix = GetLong(fp);

  switch (cbFix) {
    case 12:
      class = C_OS2;

      cx = GetShort(fp);
      cy = GetShort(fp);
      cPlanes = GetShort(fp);
      cBitCount = GetShort(fp);

      break;
    case 40:
      class = C_WIN;

      cx = GetLong(fp);
      cy = GetLong(fp);
      cPlanes = GetShort(fp);
      cBitCount = GetShort(fp);

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

      GetLong(fp);
      GetLong(fp);
      GetLong(fp);
      GetLong(fp);
      GetLong(fp);
      GetLong(fp);

      break;
    default:
      pm_error("%s: unknown cbFix: %d", ifname, cbFix);
      break;
  }

  if (cPlanes != 1) {
    pm_error("%s: don't know how to handle cPlanes = %d"
	     ,ifname
	     ,cPlanes);
  }

  switch (class) {
    case C_WIN:
      pm_message("Windows BMP, %dx%dx%d"
		 ,cx
		 ,cy
		 ,cBitCount);
      break;
    case C_OS2:
      pm_message("OS/2 BMP, %dx%dx%d"
		 ,cx
		 ,cy
		 ,cBitCount);
      break;
  }

  *pcx = cx;
  *pcy = cy;
  *pcBitCount = cBitCount;
  *pclass = class;

  *ppos += cbFix;
}

/*
 * returns the number of bytes read, or -1 on error.
 */
static int BMPreadrgbtable(fp, ppos, cBitCount, class, R, G, B)
    FILE *fp;
    unsigned long *ppos;					/* number of bytes read from fp */
    unsigned short cBitCount;
    int class;
    pixval *R;
    pixval *G;
    pixval *B;
{
  int i;
  int nbyte = 0;

  long ncolors = (1 << cBitCount);

  for (i = 0; i < ncolors; i++) {
    B[i] = (pixval) GetByte(fp);
    G[i] = (pixval) GetByte(fp);
    R[i] = (pixval) GetByte(fp);
    nbyte += 3;

    if (class == C_WIN) {
      (void)GetByte(fp);
      nbyte++;
    }
  }

  *ppos += nbyte;
  return nbyte;
}

/*
 * returns the number of bytes read, or -1 on error.
 */
static int BMPreadrow(fp, ppos, row, cx, cBitCount, R, G, B)
    FILE *fp;
    unsigned long *ppos;					/* number of bytes read from fp */
    pixel *row;
    unsigned long cx;
    unsigned short cBitCount;
    pixval *R;
    pixval *G;
    pixval *B;
{
  BITSTREAM b;
  unsigned nbyte = 0;
  int rc;
  unsigned x;

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

  for (x = 0; x < cx; x++, row++) {
    unsigned long v;

    if ((rc = pm_bitread(b, cBitCount, &v)) == -1) {
      return -1;
    }
    nbyte += rc;

    PPM_ASSIGN(*row, R[v], G[v], B[v]);
  }

  if ((rc = pm_bitfini(b)) != 0) {
    return -1;
  }

  /*
   * Make sure we read a multiple of 4 bytes.
   */
  while (nbyte % 4) {
    GetByte(fp);
    nbyte++;
  }

  *ppos += nbyte;
  return nbyte;
}

static pixel **
  BMPreadbits(fp, ppos, offBits, cx, cy, cBitCount, class, R, G, B)
    FILE *fp;
    unsigned long *ppos;					/* number of bytes read from fp */
    unsigned long offBits;
    unsigned long cx;
    unsigned long cy;
    unsigned short cBitCount;
    int class;
    pixval *R;
    pixval *G;
    pixval *B;
{
  pixel **pixels;						/* output */
  long y;

  readto(fp, ppos, offBits);

  pixels = ppm_allocarray(cx, cy);

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

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

  for (y = cy - 1; y >= 0; y--) {
    int rc;

    rc = BMPreadrow(fp, ppos, pixels[y], cx, cBitCount, R, G, B);

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

  return pixels;
}

int main(argc, argv)
    int argc;
    char **argv;
{
  FILE *ifp = stdin;
  char *usage = "[bmpfile]";
  int argn;

  int rc;
  unsigned long pos = 0;

  unsigned long offBits;

  unsigned long cx;
  unsigned long cy;
  unsigned short cBitCount;
  int class;

  pixval R[MAXCOLORS];						/* reds */
  pixval G[MAXCOLORS];						/* greens */
  pixval B[MAXCOLORS];						/* blues */

  pixel **pixels;

  ppm_init(&argc, argv);

  /*
   * Since this command takes no flags, produce an error message
   * if the user tries to give any.
   * This is friendlier than if the command were to say
   * 'no such file: -help'.
   */

  argn = 1;
  while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') {
    pm_usage(usage);
    ++argn;
  }

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

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

  BMPreadfileheader(ifp, &pos, &offBits);
  BMPreadinfoheader(ifp, &pos, &cx, &cy, &cBitCount, &class);

  if (offBits != BMPoffbits(class, cBitCount)) {
    pm_message("warning: offBits is %d, expected %d"
	       ,pos
	       ,BMPoffbits(class, cBitCount));
  }

  rc = BMPreadrgbtable(ifp, &pos, cBitCount, class, R, G, B);

  if (rc != BMPlenrgbtable(class, cBitCount)) {
    pm_message("warning: %d-byte RGB table, expected %d bytes"
	       ,rc
	       ,BMPlenrgbtable(class, cBitCount));
  }

  pixels = BMPreadbits(ifp, &pos, offBits, cx, cy
		       ,cBitCount, class, R, G, B);

  if (pos != BMPlenfile(class, cBitCount, cx, cy)) {
    pm_message("warning: read %d bytes, expected to read %d bytes"
	       ,pos
	       ,BMPlenfile(class, cBitCount, cx, cy));
  }

  pm_close(ifp);
  ppm_writeppm(stdout, pixels, cx, cy, (pixval) (MAXCOLORS - 1), 0);
  pm_close(stdout);

  exit(0);
}

/*\
 * $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
 \*/

#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(fp, v)
    FILE *fp;
    char v;
{
  if (putc(v, fp) == EOF) {
    pm_error(er_write);
  }
}

static void PutShort(fp, v)
    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, x, y));

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

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

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

  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(fp, class, R, G, B)
    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(fp, row, cx, bpp, cht)
    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(fp, cx, cy, cBitCount, pixels, cht)
    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))) {
    pm_error(er_internal, "BMPEncode");
  }

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

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

  /* 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);
}
