/*\ * $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. * * 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 _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ #include #include "bmp.h" #include "ppm.h" #include "shhopt.h" #include "bitio.h" #define MAXCOLORS 256 enum colortype {TRUECOLOR, PALETTE}; /* * Utilities */ static char er_write[] = "stdout: write error"; static struct cmdline_info { /* All the information the user supplied in the command line, in a form easy for the program to use. */ char *input_filename; int class; /* C_WIN or C_OS2 */ unsigned int bppSpec; unsigned int bpp; } cmdline; static void parse_command_line(int argc, char ** argv, struct cmdline_info *cmdline_p) { /*---------------------------------------------------------------------------- Note that many of the strings that this function returns in the *cmdline_p structure are actually in the supplied argv array. And sometimes, one of these strings is actually just a suffix of an entry in argv! -----------------------------------------------------------------------------*/ optEntry *option_def = malloc(100*sizeof(optEntry)); /* Instructions to OptParseOptions3 on how to parse our options. */ optStruct3 opt; unsigned int windowsSpec, os2Spec; unsigned int option_def_index; option_def_index = 0; /* incremented by OPTENTRY */ OPTENT3('w', "windows", OPT_FLAG, NULL, &windowsSpec, 0); OPTENT3('o', "os2", OPT_FLAG, NULL, &os2Spec, 0); OPTENT3(0, "bpp", OPT_UINT, &cmdline_p->bpp, &cmdline_p->bppSpec, 0); opt.opt_table = option_def; opt.short_allowed = FALSE; /* We have no short (old-fashioned) options */ opt.allowNegNum = FALSE; /* We have no parms that are negative numbers */ optParseOptions3(&argc, argv, opt, sizeof(opt), 0); if (windowsSpec && os2Spec) pm_error("Can't specify both -windows and -os2 options."); else if (windowsSpec) cmdline_p->class = C_WIN; else if (os2Spec) cmdline_p->class = C_OS2; else cmdline_p->class = C_WIN; if (cmdline_p->bppSpec) { if (cmdline_p->bpp != 1 && cmdline_p->bpp != 4 && cmdline_p->bpp != 8 && cmdline_p->bpp != 24) pm_error("Invalid -bpp value specified: %u. The only values valid\n" "in the BMP format are 1, 4, 8, and 24 bits per pixel", cmdline_p->bpp); } if (argc - 1 == 0) cmdline_p->input_filename = strdup("-"); /* he wants stdin */ else if (argc - 1 == 1) cmdline_p->input_filename = strdup(argv[1]); else pm_error("Too many arguments. The only argument accepted\n" "is the input file specificaton"); } static void PutByte(FILE *fp, unsigned 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 BMPwritecolormap(FILE *fp, const int class, const int bpp, const int colors, const unsigned char R[], const unsigned char G[], const unsigned char 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_palette(FILE *fp, const pixel * const row, const unsigned long cx, const unsigned short bpp, const 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++) { if ((rc = pm_bitwrite(b, bpp, ppm_lookupcolor(cht, &row[x]))) == -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 BMPwriterow_truecolor(FILE * const fp, const pixel * const row, unsigned long const cols, pixval const maxval) { /*---------------------------------------------------------------------------- Write a row of a truecolor BMP image to the file 'fp'. The row is 'row', which is 'cols' columns long. Return the number of bytes written. On error, issue error message and exit program. -----------------------------------------------------------------------------*/ /* This works only for 24 bits per pixel. To implement this for the general case (which is only hypothetical -- this program doesn't write any truecolor images except 24 bit and apparently no one else does either), you would move this function into BMPwriterow_palette, which writes arbitrary bit strings. But that would be a lot slower and less robust. */ int nbyte; /* Number of bytes we have written to file so far */ int col; nbyte = 0; /* initial value */ for (col = 0; col < cols; col++) { PutByte(fp, PPM_GETB(row[col]) * 255 / maxval); PutByte(fp, PPM_GETG(row[col]) * 255 / maxval); PutByte(fp, PPM_GETR(row[col]) * 255 / maxval); nbyte += 3; } /* * 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, const unsigned long cx, const unsigned long cy, const enum colortype colortype, const unsigned short cBitCount, const pixel ** const pixels, const pixval maxval, const 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; if (colortype == PALETTE) rc = BMPwriterow_palette(fp, pixels[y], cx, cBitCount, cht); else rc = BMPwriterow_truecolor(fp, pixels[y], cx, maxval); if(rc == -1) { pm_error("couldn't write row %ld", y); } if(rc%4) { pm_error("row had bad number of bytes: %d" ,rc); } nbyte += rc; } return nbyte; } /* * 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(FILE *fp, const int class, const enum colortype colortype, const int bpp, const int x, const int y, const pixel ** const pixels, const pixval maxval, const int colors, const colorhash_table cht, const unsigned char R[], const unsigned char G[], const unsigned char B[]) { unsigned long nbyte = 0; if (colortype == PALETTE) pm_message("Writing %d bits per pixel with a color palette", bpp); else pm_message("Writing %d bits per pixel truecolor (no palette)", bpp); nbyte += BMPwritefileheader(fp, class, bpp, x, y); nbyte += BMPwriteinfoheader(fp, class, bpp, x, y); if (colortype == PALETTE) nbyte += BMPwritecolormap(fp, class, bpp, colors, R, G, B); if(nbyte != ( BMPlenfileheader(class) + BMPleninfoheader(class) + BMPlencolormap(class, bpp, -1))) { pm_error(er_internal, "BMPEncode 1"); } nbyte += BMPwritebits(fp, x, y, colortype, bpp, pixels, maxval, cht); if(nbyte != BMPlenfile(class, bpp, -1, x, y)) { pm_error(er_internal, "BMPEncode 2"); } } static void analyze_colors(const pixel ** const pixels, const int cols, const int rows, const pixval maxval, int * const colors_p, int * const minimum_bpp_p, colorhash_table *cht_p, unsigned char Red[], unsigned char Green[], unsigned char Blue[]) { /*---------------------------------------------------------------------------- Look at the colors in the image 'pixels' and compute values to use in representing those colors in a BMP image. First of all, count the distinct colors. Return as *minimum_bpp_p the minimum number of bits per pixel it will take to represent all the colors in BMP format. If there are few enough colors to represent with a palette, also return the number of colors as *colors_p and a suitable palette (colormap) and a hash table in which to look up indexes into that palette as Red[], Green[], Blue[], and *cht_p. Use only the first *colors_p entries of the Red[], etc. array. If there are too many colors for a palette, return *cht_p == NULL. Issue informational messages. -----------------------------------------------------------------------------*/ /* Figure out the colormap. */ colorhist_vector chv; int i; pm_message("analyzing colors..."); chv = ppm_computecolorhist((pixel**)pixels, cols, rows, MAXCOLORS, colors_p); if (chv == NULL) { pm_message("More than %d colors found", MAXCOLORS); *minimum_bpp_p = 24; *cht_p = NULL; } else { *minimum_bpp_p = pm_maxvaltobits(*colors_p-1); pm_message("%d colors found", *colors_p); /* * Now scale the maxval to 255 as required by BMP format. */ for (i = 0; i < *colors_p; ++i) { 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_p = ppm_colorhisttocolorhash(chv, *colors_p); ppm_freecolorhist(chv); } } static void choose_colortype_bpp(const struct cmdline_info cmdline, const int colors, const int minimum_bpp, enum colortype * const colortype_p, int * const bits_per_pixel_p) { if (!cmdline.bppSpec) { /* User has no preference as to bits per pixel. Note that only 1, 4, 8, and 24 are defined in the BMP spec we implement and other bpp's have in fact been seen to confuse viewers. There is an extended BMP format that has 16 bpp too, but this program doesn't know how to generate that (see Bmptopnm.c, though). */ if (minimum_bpp == 1) *bits_per_pixel_p = 1; else if (minimum_bpp <= 4) *bits_per_pixel_p = 4; else if (minimum_bpp <= 8) *bits_per_pixel_p = 8; else *bits_per_pixel_p = 24; } else { if (cmdline.bpp < minimum_bpp) pm_error("There are too many colors in the image to " "represent in the\n" "number of bits per pixel you requested: %d.\n" "You may use Ppmquant to reduce the number of " "colors in the image.", cmdline.bpp); else *bits_per_pixel_p = cmdline.bpp; } if (*bits_per_pixel_p > 8) *colortype_p = TRUECOLOR; else *colortype_p = PALETTE; } int main(int argc, char **argv) { FILE *ifp; int rows; int cols; int colors; pixval maxval; int minimum_bpp; int bits_per_pixel; enum colortype colortype; pixel** pixels; colorhash_table cht; /* 'Red', 'Green', and 'Blue' are the color map for the BMP file, with indices the same as in 'cht', above. */ unsigned char Red[MAXCOLORS], Green[MAXCOLORS], Blue[MAXCOLORS]; ppm_init(&argc, argv); parse_command_line(argc, argv, &cmdline); ifp = pm_openr(cmdline.input_filename); pixels = ppm_readppm(ifp, &cols, &rows, &maxval); pm_close(ifp); analyze_colors((const pixel**)pixels, cols, rows, maxval, &colors, &minimum_bpp, &cht, Red, Green, Blue); choose_colortype_bpp(cmdline, colors, minimum_bpp, &colortype, &bits_per_pixel); BMPEncode(stdout, cmdline.class, colortype, bits_per_pixel, cols, rows, (const pixel**)pixels, maxval, colors, cht, Red, Green, Blue); if (cht) ppm_freecolorhash(cht); pm_close(stdout); exit(0); }