diff options
Diffstat (limited to 'converter/ppm/ppmtowinicon.c')
-rw-r--r-- | converter/ppm/ppmtowinicon.c | 881 |
1 files changed, 881 insertions, 0 deletions
diff --git a/converter/ppm/ppmtowinicon.c b/converter/ppm/ppmtowinicon.c new file mode 100644 index 00000000..bcaa0e08 --- /dev/null +++ b/converter/ppm/ppmtowinicon.c @@ -0,0 +1,881 @@ +/* ppmtowinicon.c - read portable pixmap file(s) and write a MS Windows .ico +** +** Copyright (C) 2000 by Lee Benfield - lee@benf.org +** +** 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 <math.h> +#include <string.h> + +#include "winico.h" +#include "ppm.h" +#include "mallocvar.h" +#include "shhopt.h" +#include "nstring.h" + +#define MAJVERSION 0 +#define MINVERSION 3 + +#define MAXCOLORS 256 + +struct cmdlineInfo { + /* All the information the user supplied in the command line, + in a form easy for the program to use. + */ + unsigned int iconCount; + const char **inputFilespec; /* '-' if stdin; malloc'ed array */ + const char **andpgmFilespec; /* NULL if unspecified; malloc'ed array */ + const char *output; /* '-' if unspecified */ + unsigned int truetransparent; + unsigned int verbose; +}; + + +static bool verbose; + +static int file_offset = 0; /* not actually used, but useful for debug. */ +static FILE * ofp; + +static void +parseCommandLine(int argc, + char ** argv, + struct cmdlineInfo *cmdlineP ) { +/*---------------------------------------------------------------------------- + Parse program command line described in Unix standard form by argc + and argv. Return the information in the options as *cmdlineP. + + If command line is internally inconsistent (invalid options, etc.), + issue error message to stderr and abort program. + + Note that the strings we return are stored in the storage that + was passed to us as the argv array. We also trash *argv. +-----------------------------------------------------------------------------*/ + optEntry *option_def; + /* Instructions to optParseOptions3 on how to parse our options. + */ + optStruct3 opt; + + unsigned int option_def_index; + + unsigned int outputSpec, andpgms; + + MALLOCARRAY_NOFAIL(option_def, 100); + + option_def_index = 0; /* incremented by OPTENT3 */ + OPTENT3(0, "output", OPT_STRING, &cmdlineP->output, + &outputSpec, 0); + OPTENT3(0, "andpgms", OPT_FLAG, NULL, + &andpgms, 0); + OPTENT3(0, "truetransparent", OPT_FLAG, NULL, + &cmdlineP->truetransparent, 0); + OPTENT3(0, "verbose", OPT_STRING, NULL, + &cmdlineP->verbose, 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); + /* Uses and sets argc, argv, and some of *cmdlineP and others. */ + + if (!outputSpec) + cmdlineP->output = "-"; + + + if (!andpgms) { + if (argc-1 == 0) { + cmdlineP->iconCount = 1; + MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount); + cmdlineP->inputFilespec[0] = "-"; + } else { + unsigned int iconIndex; + + cmdlineP->iconCount = argc-1; + MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount); + for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex) + cmdlineP->inputFilespec[iconIndex] = argv[iconIndex+1]; + } + { + unsigned int iconIndex; + MALLOCARRAY_NOFAIL(cmdlineP->andpgmFilespec, cmdlineP->iconCount); + for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex) + cmdlineP->andpgmFilespec[iconIndex] = NULL; + } + } else { + if (argc-1 < 2) + pm_error("with -andpgms, you must specify at least two " + "arguments: image file name and and mask file name. " + "You specified %d", argc-1); + else if ((argc-1)/2*2 != (argc-1)) + pm_error("with -andpgms, you must specify an even number of " + "arguments. You specified %d", argc-1); + else { + unsigned int iconIndex; + cmdlineP->iconCount = (argc-1)/2; + MALLOCARRAY_NOFAIL(cmdlineP->inputFilespec, cmdlineP->iconCount); + MALLOCARRAY_NOFAIL(cmdlineP->andpgmFilespec, cmdlineP->iconCount); + for (iconIndex = 0; iconIndex < cmdlineP->iconCount; ++iconIndex) { + cmdlineP->inputFilespec[iconIndex] = argv[1 + iconIndex*2]; + cmdlineP->andpgmFilespec[iconIndex] = argv[2 + iconIndex*2]; + } + } + } + +} + + + +static void +PutByte(int const v) { + + if (putc(v, ofp) == EOF) + pm_error("Unable to write byte to output file."); +} + + + +static void +PutShort(short const v) { + + if (pm_writelittleshort(ofp, v) == -1) + pm_error("Unable to write short integer to output file"); +} + + + +static void +PutLong(long const v) { + + if (pm_writelittlelong(ofp, v) == -1) + pm_error("Unable to write long integer to output file"); +} + + + +/* + * These have no purpose but to wrapper the Byte, Short & Long + * functions. + */ +static void +writeU1 (u1 const v) { + file_offset++; + PutByte(v); +} + +static void +writeU2 (u2 const v) { + file_offset +=2; + PutShort(v); +} + +static void +writeU4 (u4 const v) { + file_offset += 4; + PutLong(v); +} + +static MS_Ico +createIconFile (void) { + MS_Ico MSIconData; + + MALLOCVAR_NOFAIL(MSIconData); + + MSIconData->reserved = 0; + MSIconData->type = 1; + MSIconData->count = 0; + MSIconData->entries = NULL; + return MSIconData; +} + + +/* create andBitmap from pgm */ + +static ICON_bmp +createAndBitmap (gray ** const ba, int const cols, int const rows, + gray const maxval) { + /* + * How wide should the u1 string for each row be? + * each byte is 8 pixels, but must be a multiple of 4 bytes. + */ + ICON_bmp icBitmap; + int xBytes,y,x; + int wt = cols; + u1 ** rowData; + + MALLOCVAR_NOFAIL(icBitmap); + + wt >>= 3; + if (wt & 3) { + wt = (wt & ~3) + 4; + } + xBytes = wt; + MALLOCARRAY_NOFAIL(rowData, rows); + icBitmap->xBytes = xBytes; + icBitmap->data = rowData; + icBitmap->size = xBytes * rows; + for (y=0;y<rows;y++) { + u1 * row; + int byteOn = 0; + int bitOn = 128; + + MALLOCARRAY_NOFAIL(row, xBytes); + + memset (row, 0, xBytes); + rowData[rows-y-1] = row; + /* + * Check there's a bit array, otherwise we're just faking this... + */ + if (ba) { + for (x=0;x<cols;x++) { + /* Black (bit clear) is transparent in PGM alpha maps, + * in ICO bit *set* is transparent. + */ + if (ba[y][x] <= maxval/2) row[byteOn] |= bitOn; + + if (bitOn == 1) { + byteOn++; + bitOn = 128; + } else { + bitOn >>= 1; + } + } + } + } + return icBitmap; +} + + +/* + * Depending on if the image is stored as 1bpp, 4bpp or 8bpp, the + * encoding mechanism is different. + * + * I didn't re-use the code from ppmtobmp since I need to keep the + * bitmaps in memory till I've loaded all ppms. + * + * 8bpp => 1 byte/palette index. + * 4bpp => High Nibble, Low Nibble + * 1bpp => 1 palette value per bit, high bit 1st. + */ +static ICON_bmp +create1Bitmap (pixel ** const pa, int const cols, int const rows, + colorhash_table const cht) { + /* + * How wide should the u1 string for each row be? + * each byte is 8 pixels, but must be a multiple of 4 bytes. + */ + ICON_bmp icBitmap; + int xBytes,y,x; + int wt = cols; + u1 ** rowData; + + MALLOCVAR_NOFAIL(icBitmap); + + wt >>= 3; + if (wt & 3) { + wt = (wt & ~3) + 4; + } + xBytes = wt; + MALLOCARRAY_NOFAIL(rowData, rows); + icBitmap->xBytes = xBytes; + icBitmap->data = rowData; + icBitmap->size = xBytes * rows; + for (y=0;y<rows;y++) { + u1 * row; + int byteOn = 0; + int bitOn = 128; + int value; + + MALLOCARRAY_NOFAIL(row, xBytes); + memset (row, 0, xBytes); + rowData[rows-y-1] = row; + /* + * Check there's a pixel array, otherwise we're just faking this... + */ + if (pa) { + for (x=0;x<cols;x++) { + /* + * So we've got a colorhash_table with two colors in it. + * Which is black?! + * + * Unless the hashing function changes, 0's black. + */ + value = ppm_lookupcolor(cht, &pa[y][x]); + if (!value) { + /* leave black. */ + } else { + row[byteOn] |= bitOn; + } + if (bitOn == 1) { + byteOn++; + bitOn = 128; + } else { + bitOn >>= 1; + } + } + } + } + return icBitmap; +} + + +static ICON_bmp +create4Bitmap (pixel ** const pa, int const cols, int const rows, + colorhash_table const cht) { + /* + * How wide should the u1 string for each row be? + * each byte is 8 pixels, but must be a multiple of 4 bytes. + */ + ICON_bmp icBitmap; + int xBytes,y,x; + int wt = cols; + u1 ** rowData; + + MALLOCVAR_NOFAIL(icBitmap); + + wt >>= 1; + if (wt & 3) { + wt = (wt & ~3) + 4; + } + xBytes = wt; + MALLOCARRAY_NOFAIL(rowData, rows); + icBitmap->xBytes = xBytes; + icBitmap->data = rowData; + icBitmap->size = xBytes * rows; + + for (y=0;y<rows;y++) { + u1 * row; + int byteOn = 0; + int nibble = 1; /* high nibble = 1, low nibble = 0; */ + int value; + + MALLOCARRAY_NOFAIL(row, xBytes); + + memset (row, 0, xBytes); + rowData[rows-y-1] = row; + /* + * Check there's a pixel array, otherwise we're just faking this... + */ + if (pa) { + for (x=0;x<cols;x++) { + value = ppm_lookupcolor(cht, &pa[y][x]); + /* + * Shift it, if we're putting it in the high nibble. + */ + if (nibble) { + value <<= 4; + } + row[byteOn] |= value; + if (nibble) { + nibble = 0; + } else { + nibble = 1; + byteOn++; + } + } + } + } + return icBitmap; +} + + + +static ICON_bmp +create8Bitmap (pixel ** const pa, int const cols, int const rows, + colorhash_table const cht) { + /* + * How wide should the u1 string for each row be? + * each byte is 8 pixels, but must be a multiple of 4 bytes. + */ + ICON_bmp icBitmap; + int xBytes,y,x; + int wt = cols; + u1 ** rowData; + + MALLOCVAR_NOFAIL(icBitmap); + + if (wt & 3) { + wt = (wt & ~3) + 4; + } + xBytes = wt; + MALLOCARRAY_NOFAIL(rowData, rows); + icBitmap->xBytes = xBytes; + icBitmap->data = rowData; + icBitmap->size = xBytes * rows; + + for (y=0;y<rows;y++) { + u1 * row; + + MALLOCARRAY_NOFAIL(row, xBytes); + memset (row, 0, xBytes); + rowData[rows-y-1] = row; + /* + * Check there's a pixel array, otherwise we're just faking this... + */ + if (pa) { + for (x=0;x<cols;x++) { + row[x] = ppm_lookupcolor(cht, &pa[y][x]); + } + } + } + return icBitmap; +} + + + +static IC_InfoHeader +createInfoHeader(IC_Entry const entry, ICON_bmp const xbmp, + ICON_bmp const abmp) { + IC_InfoHeader ih; + + MALLOCVAR_NOFAIL(ih); + + ih->size = 40; + ih->width = entry->width; + ih->height = entry->height * 2; + ih->planes = 1; + ih->bitcount = entry->bitcount; + ih->compression = 0; + ih->imagesize = entry->width * entry->height * 8 / entry->bitcount; + ih->x_pixels_per_m= 0; + ih->y_pixels_per_m= 0; + ih->colors_used = 1 << entry->bitcount; + ih->colors_important = 0; + return ih; +} + + + +static IC_Palette +createCleanPalette(void) { + IC_Palette palette; + int x; + + MALLOCVAR_NOFAIL(palette); + + MALLOCARRAY_NOFAIL(palette->colors, MAXCOLORS); + for (x=0;x<MAXCOLORS;x++ ){ + palette->colors[x] = NULL; + } + return palette; +} + + + +static void +addColorToPalette(IC_Palette const palette, int const i, + int const r, int const g, int const b) { + + MALLOCVAR_NOFAIL(palette->colors[i]); + + palette->colors[i]->red = r; + palette->colors[i]->green = g; + palette->colors[i]->blue = b; + palette->colors[i]->reserved = 0; +} + + + +static ICON_bmp +createBitmap (int const bpp, pixel ** const pa, + int const cols, int const rows, + colorhash_table const cht) { + + ICON_bmp retval; + const int assumed_bpp = (pa == NULL) ? 1 : bpp; + + switch (assumed_bpp) { + case 1: + retval = create1Bitmap (pa,cols,rows,cht); + break; + case 4: + retval = create4Bitmap (pa,cols,rows,cht); + break; + case 8: + default: + retval = create8Bitmap (pa,cols,rows,cht); + break; + } + return retval; +} + + + +static void +makePalette(pixel ** const xorPPMarray, + int const xorCols, + int const xorRows, + pixval const xorMaxval, + IC_Palette * const paletteP, + colorhash_table * const xorChtP, + int * const colorsP, + const char ** const errorP) { + /* + * Figure out the colormap and turn it into the appropriate GIF + * colormap - this code's pretty much straight from ppmtobpm + */ + colorhist_vector xorChv; + unsigned int i; + int colors; + IC_Palette palette = createCleanPalette(); + + if (verbose) pm_message("computing colormap..."); + xorChv = ppm_computecolorhist(xorPPMarray, xorCols, xorRows, MAXCOLORS, + &colors); + if (xorChv == NULL) + asprintfN(errorP, + "image has too many colors - try doing a 'pnmquant %d'", + MAXCOLORS); + else { + *errorP = NULL; + + if (verbose) pm_message("%d colors found", colors); + + if (verbose && (xorMaxval > 255)) + pm_message("maxval is not 255 - automatically rescaling colors"); + for (i = 0; i < colors; ++i) { + if (xorMaxval == 255) { + addColorToPalette(palette,i, + PPM_GETR(xorChv[i].color), + PPM_GETG(xorChv[i].color), + PPM_GETB(xorChv[i].color)); + } else { + addColorToPalette(palette,i, + PPM_GETR(xorChv[i].color) * 255 / xorMaxval, + PPM_GETG(xorChv[i].color) * 255 / xorMaxval, + PPM_GETB(xorChv[i].color) * 255 / xorMaxval); + } + } + + /* And make a hash table for fast lookup. */ + *xorChtP = ppm_colorhisttocolorhash(xorChv, colors); + + ppm_freecolorhist(xorChv); + + *paletteP = palette; + *colorsP = colors; + } +} + + + +static void +getOrFakeAndMap(const char * const andPgmFname, + int const xorCols, + int const xorRows, + gray *** const andPGMarrayP, + pixval * const andMaxvalP, + colorhash_table * const andChtP, + const char ** const errorP) { + + int andRows, andCols; + + if (!andPgmFname) { + /* He's not supplying a bitmap for 'and'. Fake the bitmap. */ + *andPGMarrayP = NULL; + *andMaxvalP = 1; + *andChtP = NULL; + *errorP = NULL; + } else { + FILE * andfile; + andfile = pm_openr(andPgmFname); + *andPGMarrayP = pgm_readpgm(andfile, &andCols, &andRows, andMaxvalP); + pm_close(andfile); + + if ((andCols != xorCols) || (andRows != xorRows)) { + asprintfN(errorP, + "And mask and image have different dimensions " + "(%d x %d vs %d x %d). Aborting.", + andCols, xorCols, andRows, xorRows); + } else + *errorP = NULL; + } +} + + + +static void +blackenTransparentAreas(pixel ** const xorPPMarray, + int const cols, + int const rows, + gray ** const andPGMarray, + pixval const andMaxval) { + + unsigned int row; + + if (verbose) pm_message("Setting transparent pixels to black"); + + for (row = 0; row < rows; ++row) { + unsigned int col; + for (col = 0; col < cols; ++col) { + if (andPGMarray[row][col] < andMaxval) + /* It's not opaque here; make it black */ + PPM_ASSIGN(xorPPMarray[row][col], 0, 0, 0); + } + } +} + + + +static void +addEntryToIcon(MS_Ico const MSIconData, + const char * const xorPpmFname, + const char * const andPgmFname, + bool const trueTransparent) { + + IC_Entry entry; + FILE * xorfile; + pixel ** xorPPMarray; + gray ** andPGMarray; + ICON_bmp xorBitmap; + ICON_bmp andBitmap; + int rows, cols; + int bpp, colors; + int entry_cols; + IC_Palette palette; + colorhash_table xorCht; + colorhash_table andCht; + const char * error; + + pixval xorMaxval; + gray andMaxval; + + MALLOCVAR_NOFAIL(entry); + + /* + * Read the xor PPM. + */ + xorfile = pm_openr(xorPpmFname); + xorPPMarray = ppm_readppm(xorfile, &cols, &rows, &xorMaxval); + pm_close(xorfile); + /* + * Since the entry uses 1 byte to hold the width and height of the icon, the + * image can't be more than 256 x 256. + */ + if (rows > 255 || cols > 255) { + pm_error("Max size for a icon is 255 x 255 (1 byte fields). " + "%s is %d x %d", xorPpmFname, cols, rows); + } + + if (verbose) pm_message("read PPM: %dw x %dh, maxval = %d", + cols, rows, xorMaxval); + + makePalette(xorPPMarray, cols, rows, xorMaxval, + &palette, &xorCht, &colors, &error); + + if (error) + pm_error("Unable to make palette for '%s'. %s", xorPpmFname, error); + /* + * All the icons I found seemed to pad the palette to the max entries + * for that bitdepth. + * + * The spec indicates this isn't neccessary, but I'll follow this behaviour + * just in case. + */ + if (colors < 3) { + bpp = 1; + entry_cols = 2; + } else if (colors < 17) { + bpp = 4; + entry_cols = 16; + } else { + bpp = 8; + entry_cols = 256; + } + + getOrFakeAndMap(andPgmFname, cols, rows, + &andPGMarray, &andMaxval, &andCht, &error); + if (error) + pm_error("Error in and map for '%s'. %s", xorPpmFname, error); + + if (andPGMarray && trueTransparent) + blackenTransparentAreas(xorPPMarray, cols, rows, + andPGMarray, andMaxval); + + xorBitmap = createBitmap(bpp, xorPPMarray, cols, rows, xorCht); + andBitmap = createAndBitmap(andPGMarray, cols, rows, andMaxval); + /* + * Fill in the entry data fields. + */ + entry->width = cols; + entry->height = rows; + entry->color_count = entry_cols; + entry->reserved = 0; + entry->planes = 1; + /* + * all the icons I looked at ignored this value... + */ + entry->bitcount = bpp; + entry->ih = createInfoHeader(entry, xorBitmap, andBitmap); + entry->colors = palette->colors; + entry->size_in_bytes = + xorBitmap->size + andBitmap->size + 40 + (4 * entry->color_count); + if (verbose) + pm_message("entry->size_in_bytes = %d + %d + %d = %d", + xorBitmap->size ,andBitmap->size, + 40, entry->size_in_bytes ); + /* + * We don't know the offset ATM, set to 0 for now. + * Have to calculate this at the end. + */ + entry->file_offset = 0; + entry->xorBitmapOut = xorBitmap->data; + entry->andBitmapOut = andBitmap->data; + entry->xBytesXor = xorBitmap->xBytes; + entry->xBytesAnd = andBitmap->xBytes; + /* + * Add the entry to the entries array. + */ + ++MSIconData->count; + /* Perhaps I should allocate ahead, and take fewer trips to the well. */ + REALLOCARRAY(MSIconData->entries, MSIconData->count); + MSIconData->entries[MSIconData->count-1] = entry; +} + + + +static void +writeIC_Entry (IC_Entry const entry) { + writeU1(entry->width); + writeU1(entry->height); + writeU1(entry->color_count); /* chops 256->0 on its own.. */ + writeU1(entry->reserved); + writeU2(entry->planes); + writeU2(entry->bitcount); + writeU4(entry->size_in_bytes); + writeU4(entry->file_offset); +} + + + +static void +writeIC_InfoHeader (IC_InfoHeader const ih) { + writeU4(ih->size); + writeU4(ih->width); + writeU4(ih->height); + writeU2(ih->planes); + writeU2(ih->bitcount); + writeU4(ih->compression); + writeU4(ih->imagesize); + writeU4(ih->x_pixels_per_m); + writeU4(ih->y_pixels_per_m); + writeU4(ih->colors_used); + writeU4(ih->colors_important); +} + + + +static void +writeIC_Color (IC_Color const col) { + /* Since the ppm might not have as many colors in it as we'd like, + * (2, 16, 256), stick 0 in the gaps. + * + * This means that we lose palette information, but that can't be + * helped. + */ + if (col == NULL) { + writeU1(0); + writeU1(0); + writeU1(0); + writeU1(0); + } else { + writeU1(col->blue); + writeU1(col->green); + writeU1(col->red); + writeU1(col->reserved); + } +} + + + +static void +writeBitmap(u1 ** const bitmap, int const xBytes, int const height) { + int y; + for (y = 0;y<height;y++) { + fwrite (bitmap[y],1,xBytes,ofp); + file_offset += xBytes; + } +} + + + +static void +writeMS_Ico(MS_Ico const MSIconData, + const char * const outFname) { + int x,y; + + ofp = pm_openw(outFname); + + writeU2(MSIconData->reserved); + writeU2(MSIconData->type); + writeU2(MSIconData->count); + for (x=0;x<MSIconData->count;x++) writeIC_Entry(MSIconData->entries[x]); + for (x=0;x<MSIconData->count;x++) { + writeIC_InfoHeader(MSIconData->entries[x]->ih); + for (y=0;y<(MSIconData->entries[x]->color_count);y++) { + writeIC_Color(MSIconData->entries[x]->colors[y]); + } + if (verbose) pm_message("writing xor bitmap"); + writeBitmap(MSIconData->entries[x]->xorBitmapOut, + MSIconData->entries[x]->xBytesXor, + MSIconData->entries[x]->height); + if (verbose) pm_message("writing and bitmap"); + writeBitmap(MSIconData->entries[x]->andBitmapOut, + MSIconData->entries[x]->xBytesAnd, + MSIconData->entries[x]->height); + } + fclose(ofp); +} + + + +int +main(int argc, char ** argv) { + + struct cmdlineInfo cmdline; + + MS_Ico const MSIconDataP = createIconFile(); + unsigned int iconIndex; + unsigned int offset; + + ppm_init (&argc, argv); + + parseCommandLine(argc, argv, &cmdline); + + verbose = cmdline.verbose; + + for (iconIndex = 0; iconIndex < cmdline.iconCount; ++iconIndex) { + addEntryToIcon(MSIconDataP, cmdline.inputFilespec[iconIndex], + cmdline.andpgmFilespec[iconIndex], + cmdline.truetransparent); + } + /* + * Now we have to go through and calculate the offsets. + * The first infoheader starts at 6 + count*16 bytes. + */ + offset = (MSIconDataP->count * 16) + 6; + for (iconIndex = 0; iconIndex < MSIconDataP->count; ++iconIndex) { + IC_Entry entry = MSIconDataP->entries[iconIndex]; + entry->file_offset = offset; + /* + * Increase the offset by the size of this offset & data. + * this includes the size of the color data. + */ + offset += entry->size_in_bytes; + } + /* + * And now, we have to actually SAVE the .ico! + */ + writeMS_Ico(MSIconDataP, cmdline.output); + + free(cmdline.inputFilespec); + free(cmdline.andpgmFilespec); + + return 0; +} + |