diff options
author | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
---|---|---|
committer | giraffedata <giraffedata@9d0c8265-081b-0410-96cb-a4ca84ce46f8> | 2006-08-19 03:12:28 +0000 |
commit | 1fd361a1ea06e44286c213ca1f814f49306fdc43 (patch) | |
tree | 64c8c96cf54d8718847339a403e5e67b922e8c3f /converter/ppm/ppmtobmp.c | |
download | netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.gz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.tar.xz netpbm-mirror-1fd361a1ea06e44286c213ca1f814f49306fdc43.zip |
Create Subversion repository
git-svn-id: http://svn.code.sf.net/p/netpbm/code/trunk@1 9d0c8265-081b-0410-96cb-a4ca84ce46f8
Diffstat (limited to 'converter/ppm/ppmtobmp.c')
-rw-r--r-- | converter/ppm/ppmtobmp.c | 795 |
1 files changed, 795 insertions, 0 deletions
diff --git a/converter/ppm/ppmtobmp.c b/converter/ppm/ppmtobmp.c new file mode 100644 index 00000000..071f3b12 --- /dev/null +++ b/converter/ppm/ppmtobmp.c @@ -0,0 +1,795 @@ +/* + * 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. + * + */ + +#define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ +#define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ + +#include <assert.h> +#include <string.h> +#include "bmp.h" +#include "ppm.h" +#include "shhopt.h" +#include "bitio.h" + +#define MAXCOLORS 256 + +enum colortype {TRUECOLOR, PALETTE}; + +typedef struct { +/*---------------------------------------------------------------------------- + A color map for a BMP file. +-----------------------------------------------------------------------------*/ + unsigned int count; + /* Number of colors in the map. The first 'count' elements of these + arrays are defined; all others are not. + */ + colorhash_table cht; + + /* Indices in the following arrays are the same as in 'cht', above. */ + unsigned char red[MAXCOLORS]; + unsigned char grn[MAXCOLORS]; + unsigned char blu[MAXCOLORS]; +} colorMap; + + + +static void +freeColorMap(const colorMap * const colorMapP) { + + if (colorMapP->cht) + ppm_freecolorhash(colorMapP->cht); +} + + + +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 * const fp, unsigned char const v) { + if (putc(v, fp) == EOF) + pm_error("Write of a byte to a file failed."); + + /* Note: a Solaris/SPARC user reported on 2003.09.29 that the above + putc() returned EOF when a former version of this code declared + v as "char" instead of "unsigned char". This was apparently due + to a bug in his C library that caused 255 to look like -1 at some + critical internal point. + */ +} + + + +static void +PutShort(FILE * const fp, short const v) { + if (pm_writelittleshort(fp, v) == -1) + pm_error("Write of a halfword to a file failed."); +} + + + +static void +PutLong(FILE * const fp, long const v) { + if (pm_writelittlelong(fp, v) == -1) + pm_error("Write of a word to a file failed."); +} + + + +/* + * BMP writing + */ + +static int +BMPwritefileheader(FILE * const fp, + int const class, + unsigned long const bitcount, + unsigned long const x, + unsigned long const y) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +-----------------------------------------------------------------------------*/ + 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; +} + + + +static int +BMPwriteinfoheader(FILE * const fp, + int const class, + unsigned long const bitcount, + unsigned long const x, + unsigned long const y) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +----------------------------------------------------------------------------*/ + long cbFix; + + switch (class) { + case C_WIN: { + cbFix = 40; + PutLong(fp, cbFix); + + PutLong(fp, x); /* cx */ + PutLong(fp, y); /* cy */ + PutShort(fp, 1); /* cPlanes */ + PutShort(fp, bitcount); /* cBitCount */ + + /* + * We've written 16 bytes so far, need to write 24 more + * for the required total of 40. + */ + + PutLong(fp, 0); /* Compression */ + PutLong(fp, 0); /* ImageSize */ + PutLong(fp, 0); /* XpixelsPerMeter */ + PutLong(fp, 0); /* YpixelsPerMeter */ + PutLong(fp, 0); /* ColorsUsed */ + PutLong(fp, 0); /* ColorsImportant */ + } + break; + case C_OS2: { + cbFix = 12; + PutLong(fp, cbFix); + + PutShort(fp, x); /* cx */ + PutShort(fp, y); /* cy */ + PutShort(fp, 1); /* cPlanes */ + PutShort(fp, bitcount); /* cBitCount */ + } + break; + default: + pm_error(er_internal, "BMPwriteinfoheader"); + } + + return cbFix; +} + + + +static int +BMPwritergb(FILE * const fp, + int const class, + pixval const R, + pixval const G, + pixval const B) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +-----------------------------------------------------------------------------*/ + 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; +} + + + +static int +BMPwritecolormap(FILE * const ifP, + int const class, + int const bpp, + const colorMap * const colorMapP) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +-----------------------------------------------------------------------------*/ + long const ncolors = (1 << bpp); + + unsigned int nbyte; + unsigned int i; + + nbyte = 0; + for (i = 0; i < colorMapP->count; ++i) + nbyte += BMPwritergb(ifP, class, + colorMapP->red[i], + colorMapP->grn[i], + colorMapP->blu[i]); + + for (; i < ncolors; ++i) + nbyte += BMPwritergb(ifP, class, 0, 0, 0); + + return nbyte; +} + + + +static int +BMPwriterow_palette(FILE * const fp, + const pixel * const row, + unsigned long const cx, + unsigned short const bpp, + colorhash_table const cht) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +-----------------------------------------------------------------------------*/ + BITSTREAM b; + int retval; + + b = pm_bitinit(fp, "w"); + if (b == NULL) + retval = -1; + else { + unsigned int nbyte; + unsigned int x; + bool error; + + nbyte = 0; /* initial value */ + error = FALSE; /* initial value */ + + for (x = 0; x < cx && !error; ++x) { + int rc; + rc = pm_bitwrite(b, bpp, ppm_lookupcolor(cht, &row[x])); + if (rc == -1) + error = TRUE; + else + nbyte += rc; + } + if (error) + retval = -1; + else { + int rc; + + rc = pm_bitfini(b); + if (rc == -1) + retval = -1; + else { + nbyte += rc; + + /* Make sure we write a multiple of 4 bytes. */ + while (nbyte % 4 != 0) { + PutByte(fp, 0); + ++nbyte; + } + retval = nbyte; + } + } + } + return retval; +} + + + +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++) { + /* We scale to the BMP maxval, which is always 255. */ + 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; +} + + + +static int +BMPwritebits(FILE * const fp, + unsigned long const cx, + unsigned long const cy, + enum colortype const colortype, + unsigned short const cBitCount, + const pixel ** const pixels, + pixval const maxval, + colorhash_table const cht) { +/*---------------------------------------------------------------------------- + Return the number of bytes written, or -1 on error. +-----------------------------------------------------------------------------*/ + int nbyte; + long y; + + if (cBitCount > 24) + pm_error("cannot handle cBitCount: %d", cBitCount); + + nbyte = 0; /* initial value */ + + /* 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 != 0) + pm_error("row had bad number of bytes: %d", rc); + nbyte += rc; + } + + return nbyte; +} + + + +static void +BMPEncode(FILE * const ifP, + int const class, + enum colortype const colortype, + int const bpp, + int const x, + int const y, + const pixel ** const pixels, + pixval const maxval, + const colorMap * const colorMapP) { +/*---------------------------------------------------------------------------- + Write a BMP file of the given class. +-----------------------------------------------------------------------------*/ + unsigned long nbyte; + + 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 = 0; /* initial value */ + nbyte += BMPwritefileheader(ifP, class, bpp, x, y); + nbyte += BMPwriteinfoheader(ifP, class, bpp, x, y); + if (colortype == PALETTE) + nbyte += BMPwritecolormap(ifP, class, bpp, colorMapP); + + if (nbyte != (BMPlenfileheader(class) + + BMPleninfoheader(class) + + BMPlencolormap(class, bpp, -1))) + pm_error(er_internal, "BMPEncode 1"); + + nbyte += BMPwritebits(ifP, x, y, colortype, bpp, pixels, maxval, + colorMapP->cht); + if (nbyte != BMPlenfile(class, bpp, -1, x, y)) + pm_error(er_internal, "BMPEncode 2"); +} + + + +static void +makeBilevelColorMap(colorMap * const colorMapP) { + + colorMapP->count = 2; + colorMapP->cht = NULL; + colorMapP->red[0] = 0; + colorMapP->grn[0] = 0; + colorMapP->blu[0] = 0; + colorMapP->red[1] = 255; + colorMapP->grn[1] = 255; + colorMapP->blu[1] = 255; +} + + + +static void +BMPEncodePBM(FILE * const ifP, + int const class, + int const cols, + int const rows, + unsigned char ** const bitrow) { +/*---------------------------------------------------------------------------- + Write a bi-level BMP file of the given class. +-----------------------------------------------------------------------------*/ + /* Note: + Only PBM input uses this routine. Color images represented by 1 bpp via + color palette use the general BMPEncode(). + */ + unsigned int const adjustedCols = (cols + 31) / 32 * 32; + unsigned int const packedBytes = adjustedCols / 8; + + unsigned long nbyte; + colorMap bilevelColorMap; + unsigned int row; + + /* colortype == PALETTE */ + pm_message("Writing 1 bit per pixel with a black-white palette"); + + nbyte = 0; /* initial value */ + nbyte += BMPwritefileheader(ifP, class, 1, cols, rows); + nbyte += BMPwriteinfoheader(ifP, class, 1, cols, rows); + + makeBilevelColorMap(&bilevelColorMap); + + nbyte += BMPwritecolormap(ifP, class, 1, &bilevelColorMap); + + if (nbyte != (BMPlenfileheader(class) + + BMPleninfoheader(class) + + BMPlencolormap(class, 1, -1))) + pm_error(er_internal, "BMPEncodePBM 1"); + + for (row = 0; row < rows; ++row){ + size_t bytesWritten; + + bytesWritten = fwrite(bitrow[row], 1, packedBytes, ifP); + if (bytesWritten != packedBytes){ + if (feof(ifP)) + pm_error("End of file writing row %u of BMP raster.", row); + else + pm_error("Error writing BMP raster. Errno=%d (%s)", + errno, strerror(errno)); + } else + nbyte += bytesWritten; + } + + if (nbyte != BMPlenfile(class, 1, -1, cols, rows)) + pm_error(er_internal, "BMPEncodePBM 2"); +} + + + +static void +analyze_colors(const pixel ** const pixels, + int const cols, + int const rows, + pixval const maxval, + int * const minimum_bpp_p, + colorMap * const colorMapP) { +/*---------------------------------------------------------------------------- + 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 *colorMapP. Use only the first *colors_p entries of the + map. + + If there are too many colors for a palette, return colorMapP->cht + == NULL. + + Issue informational messages. +-----------------------------------------------------------------------------*/ + /* Figure out the colormap. */ + colorhist_vector chv; + int colorCount; + + pm_message("analyzing colors..."); + chv = ppm_computecolorhist((pixel**)pixels, cols, rows, MAXCOLORS, + &colorCount); + colorMapP->count = colorCount; + if (chv == NULL) { + pm_message("More than %u colors found", MAXCOLORS); + *minimum_bpp_p = 24; + colorMapP->cht = NULL; + } else { + unsigned int const minbits = pm_maxvaltobits(colorMapP->count - 1); + + unsigned int i; + + pm_message("%u colors found", colorMapP->count); + + /* 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 (minbits == 1) + *minimum_bpp_p = 1; + else if (minbits <= 4) + *minimum_bpp_p = 4; + else if (minbits <= 8) + *minimum_bpp_p = 8; + else + *minimum_bpp_p = 24; + + /* + * Now scale the maxval to 255 as required by BMP format. + */ + for (i = 0; i < colorMapP->count; ++i) { + colorMapP->red[i] = (pixval) PPM_GETR(chv[i].color) * 255 / maxval; + colorMapP->grn[i] = (pixval) PPM_GETG(chv[i].color) * 255 / maxval; + colorMapP->blu[i] = (pixval) PPM_GETB(chv[i].color) * 255 / maxval; + } + + /* And make a hash table for fast lookup. */ + colorMapP->cht = ppm_colorhisttocolorhash(chv, colorMapP->count); + ppm_freecolorhist(chv); + } +} + + + +static void +choose_colortype_bpp(struct cmdline_info const cmdline, + unsigned int const colors, + unsigned int const minimum_bpp, + enum colortype * const colortype_p, + unsigned int * const bits_per_pixel_p) { + + if (!cmdline.bppSpec) { + /* User has no preference as to bits per pixel. Choose the + smallest number possible for this image. + */ + *bits_per_pixel_p = minimum_bpp; + } 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; + } + + assert(*bits_per_pixel_p == 1 || + *bits_per_pixel_p == 4 || + *bits_per_pixel_p == 8 || + *bits_per_pixel_p == 24); + + if (*bits_per_pixel_p > 8) + *colortype_p = TRUECOLOR; + else { + *colortype_p = PALETTE; + } +} + + + +static void +doPbm(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + int const format, + int const class, + FILE * const ofP) { + + /* In the PBM case the raster is read directly from the input by + pbm_readpbmrow_packed. The raster format is almost identical, + except that BMP specifies rows to be zero-filled to 32 bit borders + and that in BMP the bottom row comes first in order. + */ + + int const CHARBITS = (sizeof(unsigned char)*8); + int const colChars = pbm_packed_bytes(cols); + int const adjustedCols = (cols+31) /32 * 32; + int const packedBytes = adjustedCols /8; + + unsigned char ** bitrow; + unsigned int row; + + bitrow = pbm_allocarray_packed(adjustedCols, rows); + + for (row = 0; row < rows; ++row) { + unsigned char * const thisRow = bitrow[rows - row - 1]; + + /* Clear end of each row */ + thisRow[packedBytes-1] = 0x00; + thisRow[packedBytes-2] = 0x00; + thisRow[packedBytes-3] = 0x00; + thisRow[packedBytes-4] = 0x00; + + pbm_readpbmrow_packed(ifP, thisRow, cols, format); + + { + unsigned int i; + for (i = 0; i < colChars; ++i) + thisRow[i] = ~thisRow[i]; /* flip all pixels */ + } + /* This may seem unnecessary, because the color palette + (RGB[] in BMPEncodePBM) can be inverted for the same effect. + However we take this precaution, for there is indication that + some BMP viewers may get confused with that. + */ + + if (cols % 8 >0) { + /* adjust final partial byte */ + thisRow[colChars-1] >>= CHARBITS - cols % CHARBITS; + thisRow[colChars-1] <<= CHARBITS - cols % CHARBITS; + } + } + + BMPEncodePBM(ofP, class, cols, rows, bitrow); +} + + + +static void +doPgmPpm(FILE * const ifP, + unsigned int const cols, + unsigned int const rows, + pixval const maxval, + int const ppmFormat, + int const class, + FILE * const ofP) { + + /* PGM and PPM. The input image is read into a PPM array, scanned + for color analysis and converted to a BMP raster. + Logic works for PBM. + */ + int minimumBpp; + unsigned int bitsPerPixel; + enum colortype colortype; + unsigned int row; + + pixel ** pixels; + colorMap colorMap; + + pixels = ppm_allocarray(cols, rows); + + for (row = 0; row < rows; ++row) + ppm_readppmrow(ifP, pixels[row], cols, maxval, ppmFormat); + + analyze_colors((const pixel**)pixels, cols, rows, maxval, + &minimumBpp, &colorMap); + + choose_colortype_bpp(cmdline, colorMap.count, minimumBpp, &colortype, + &bitsPerPixel); + + BMPEncode(stdout, class, colortype, bitsPerPixel, + cols, rows, (const pixel**)pixels, maxval, &colorMap); + + freeColorMap(&colorMap); +} + + + +int +main(int argc, char **argv) { + + FILE * ifP; + int rows; + int cols; + pixval maxval; + int ppmFormat; + + ppm_init(&argc, argv); + + parse_command_line(argc, argv, &cmdline); + + ifP = pm_openr(cmdline.input_filename); + + ppm_readppminit(ifP, &cols, &rows, &maxval, &ppmFormat); + + if (PPM_FORMAT_TYPE(ppmFormat) == PBM_TYPE) + doPbm(ifP, cols, rows, ppmFormat, cmdline.class, stdout); + else + doPgmPpm(ifP, cols, rows, maxval, ppmFormat, cmdline.class, stdout); + + pm_close(ifP); + pm_close(stdout); + + return 0; +} |