/* ppmtowinicon.c - read portable pixmap file(s) and write a MS Windows .ico ** ** Copyright (C) 2000 by Lee Benfield - lee@recoil.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. */ /* * Changelog * * 2000/05/24 - using colorhash_table instead of a straight array search * Though it's a little slower for files due to the low # of * colors, its neater. LAB. */ #include #include "winico.h" #include "ppm.h" #include "ppmcmap.h" #define MAJVERSION 0 #define MINVERSION 3 #define MAXCOLORS 256 MS_Ico createIconFile ARGS((void)); ICON_bmp create1Bitmap ARGS((pixel ** pa, int cols, int rows,colorhash_table cht)); ICON_bmp create4Bitmap ARGS((pixel ** pa, int cols, int rows,colorhash_table cht)); ICON_bmp create8Bitmap ARGS((pixel ** pa, int cols, int rows,colorhash_table cht)); IC_InfoHeader createInfoHeader ARGS((IC_Entry entry, ICON_bmp xbmp, ICON_bmp abmp)); void addEntryToIcon ARGS((MS_Ico MSIconData, char * xorPPM, char * andPPM)); void writeIC_Entry ARGS((IC_Entry entry)); void writeIC_InfoHeader ARGS((IC_InfoHeader ih)); void writeIC_Color ARGS((IC_Color col)); void writeBitmap ARGS((u1 ** bitmap, int xBytes, int height)); void writeMS_Ico ARGS((MS_Ico MSIconData, char * outFname)); static int verbose = 0; static int readAndMaps = 0; static int file_offset = 0; /* not actually used, but useful for debug. */ static int readFromStdin = 0; static char er_write[] = "%s: write error"; static char * infname = ""; static char * outfname = "-"; static FILE * ofp; static FILE * ifp; void PutByte(v) int v; { if (putc(v, ofp) == EOF) { pm_error(er_write, outfname); } } void PutShort(v) short v; { if (pm_writelittleshort(ofp, v) == -1) { pm_error(er_write, outfname); } } void PutLong(v) long v; { if (pm_writelittlelong(ofp, v) == -1) { pm_error(er_write, outfname); } } /* * These have no purpose but to wrapper the Byte, Short & Long * functions. */ void writeU1 (v) u1 v; { file_offset++; PutByte(v); } void writeU1String (string, length) char * string; int length; { fwrite(string,sizeof(u1),length,ofp); file_offset += length; } void writeU2 (v) u2 v; { file_offset +=2; PutShort(v); } void writeU4 (v) u4 v; { file_offset += 4; PutLong(v); } MS_Ico createIconFile (void) { MS_Ico MSIconData = malloc( sizeof (* MSIconData) ); MSIconData->reserved = 0; MSIconData->type = 1; MSIconData->count = 0; MSIconData->entries = NULL; return MSIconData; } /* * 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. */ ICON_bmp create1Bitmap (pa,cols,rows,cht) pixel ** pa; int cols; int rows; colorhash_table 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 = malloc ( sizeof (* icBitmap) ); int xBytes,y,x; int wt = cols; u1 ** rowData; wt >>= 3; if (wt & 3) { wt = (wt & ~3) + 4; } xBytes = wt; rowData = malloc ( rows * sizeof (char *)); icBitmap->xBytes = xBytes; icBitmap->data = rowData; icBitmap->size = xBytes * rows; for (y=0;y>= 1; } } } } return icBitmap; } ICON_bmp create4Bitmap (pa,cols,rows,cht) pixel ** pa; int cols; int rows; colorhash_table 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 = malloc ( sizeof (* icBitmap) ); int xBytes,y,x; int wt = cols; u1 ** rowData; wt >>= 1; if (wt & 3) { wt = (wt & ~3) + 4; } xBytes = wt; rowData = malloc ( rows * sizeof (char *)); icBitmap->xBytes = xBytes; icBitmap->data = rowData; icBitmap->size = xBytes * rows; for (y=0;yxBytes = xBytes; icBitmap->data = rowData; icBitmap->size = xBytes * rows; for (y=0;ysize = 40; ih->width = entry->width; ih->height = entry->height * 2; ih->planes = 1; ih->bitcount = entry->bitcount; ih->compression = 0; ih->imagesize = 0; /* uncompressed, so 0. */ ih->x_pixels_per_m= 0; ih->y_pixels_per_m= 0; ih->colors_used = 0; ih->colors_important = 0; return ih; } IC_Palette createCleanPalette(void) { IC_Palette palette = malloc ( sizeof (* palette) ); int x; palette->colors = malloc (MAXCOLORS * sizeof(IC_Color *)); for (x=0;xcolors[x] = NULL; } return palette; } void addColorToPalette(palette,i,r,g,b) IC_Palette palette; int i; int r; int g; int b; { palette->colors[i] = malloc ( sizeof (* 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 (const int bpp, pixel ** const pa, const int cols, const int rows, const colorhash_table 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; } void addEntryToIcon (MSIconData,xorPPM,andPPM) MS_Ico MSIconData; char * xorPPM; char * andPPM; { IC_Entry entry = malloc ( sizeof (* entry) ); FILE * inPPM; pixel ** xorPPMarray; pixel ** andPPMarray; ICON_bmp xorBitmap; ICON_bmp andBitmap; int xorRows, xorCols, andRows, andCols; int bpp, colors, i; int entry_cols; IC_Palette palette = createCleanPalette(); colorhist_vector xorChv; colorhash_table xorCht; colorhash_table andCht; pixval xorMaxP, andMaxP; /* * Read the xor PPM. */ inPPM = pm_openr(xorPPM); xorPPMarray = ppm_readppm( inPPM, &xorCols, &xorRows, &xorMaxP ); pm_close(inPPM); /* * 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 (xorRows > 255 || xorCols > 255) { pm_error("Max size for a icon is 255 x 255 (1 byte fields)\n%s is %d x %d",xorPPM,xorCols,xorRows); } /* * Figure out the colormap and turn it into the appropriate GIF * colormap - this code's pretty much straight from ppmtobpm */ if (verbose) pm_message("computing colormap..."); xorChv = ppm_computecolorhist(xorPPMarray, xorCols, xorRows, MAXCOLORS, &colors); if (xorChv == (colorhist_vector) 0) pm_error("%s has too many colors - try doing a 'ppmquant %d'" , xorPPM, MAXCOLORS); if (verbose) pm_message("%d colors found", colors); if (verbose && (xorMaxP > 255)) { pm_message("maxval is not 255 - automatically rescaling colors"); } for (i = 0; i < colors; ++i) { if (xorMaxP == 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 / xorMaxP, PPM_GETG(xorChv[i].color) * 255 / xorMaxP, PPM_GETB(xorChv[i].color) * 255 / xorMaxP); } } /* And make a hash table for fast lookup. */ xorCht = ppm_colorhisttocolorhash(xorChv, colors); ppm_freecolorhist(xorChv); /* * 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; } if (!strlen (andPPM)) { /* * They're not supplying a bitmap for 'and'. * Fake the bitmap. */ andPPMarray = NULL; andCols = xorCols; andRows = xorRows; andMaxP = 1; andCht = NULL; } else { /* to check for 2 colors max */ colorhist_vector tempchv; int colors = 0; inPPM = pm_openr(andPPM); andPPMarray = ppm_readppm( inPPM, &andCols, &andRows, &andMaxP ); pm_close(inPPM); /* * We check that there are only 2 palette entries here. * * This could be fixed by using a pbm, but then I'd have to write them * in winicontoppm, which wouldnt be toppm anymore, etc... * * Since we're only looking at black and not black, there's no need to * scale. */ tempchv = ppm_computecolorhist(andPPMarray, xorCols, xorRows, 2, &colors); if (tempchv == (colorhist_vector) 0) pm_error("%s should have only 2 colors", andPPM); andCht = ppm_colorhisttocolorhash(tempchv, colors); ppm_freecolorhist(tempchv); /* * Need to check and & xor are same height & size. */ if ((andCols != xorCols) || (andRows != xorRows)) { pm_error("%s and %s have different dimensions.\nAborting.\n",xorPPM,andPPM); } } if (verbose) pm_message ("Dimensions of ppms %d, %d\n",xorCols,xorRows); xorBitmap = createBitmap(bpp,xorPPMarray,xorCols,xorRows,xorCht); andBitmap = createBitmap(1, andPPMarray,xorCols,xorRows,andCht); /* * Fill in the entry data fields. */ entry->width = xorCols; entry->height = xorRows; 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\n",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 use something that allocs a decent amount at start... */ MSIconData->entries = realloc (MSIconData->entries, MSIconData->count * sizeof(IC_Entry *)); MSIconData->entries[MSIconData->count-1] = entry; } void writeIC_Entry (entry) IC_Entry 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); } void writeIC_InfoHeader (ih) IC_InfoHeader ih; { writeU4(ih->size); writeU4(ih->width); writeU4(ih->height); writeU2(ih->planes); writeU2(ih->bitcount); writeU4(ih->imagesize); writeU4(ih->compression); writeU4(ih->x_pixels_per_m); writeU4(ih->y_pixels_per_m); writeU4(ih->colors_used); writeU4(ih->colors_important); } void writeIC_Color (col) IC_Color 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); } } void writeBitmap(bitmap,xBytes,height) u1 ** bitmap; int xBytes; int height; { int y; for (y = 0;yreserved); writeU2(MSIconData->type); writeU2(MSIconData->count); for (x=0;xcount;x++) writeIC_Entry(MSIconData->entries[x]); for (x=0;xcount;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\n"); writeBitmap(MSIconData->entries[x]->xorBitmapOut,MSIconData->entries[x]->xBytesXor,MSIconData->entries[x]->height); if (verbose) pm_message("writing and bitmap\n"); writeBitmap(MSIconData->entries[x]->andBitmapOut,MSIconData->entries[x]->xBytesAnd,MSIconData->entries[x]->height); } fclose (ofp); } int main (argc,argv) int argc; char ** argv; { MS_Ico MSIconData = createIconFile(); int iconOn = 1; int argn; int offset; char * usage = "[-andppms] [-output output.ico] [icon1.ppm icon2.ppm ... ]"; /* * usage: * * ppmtowinicon [-andppms] icon1.ppm [icon2.ppm icon3.ppm ... ] output.ico * * Use -a flag if you supply your own and bitmaps. */ ppm_init ( &argc, argv); /* * Parse command line arguments. */ argn = 1; while (argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0') { if (pm_keymatch(argv[argn], "-verbose", 2)) verbose++; else if (pm_keymatch(argv[argn], "-andppms", 2)) { readAndMaps++; } else if (pm_keymatch(argv[argn], "-output", 2)) { if (argc - argn > 1) { outfname = argv[argn+1]; argn++; } else { pm_error ("-output must be supplied a filename"); } } else pm_usage(usage); ++argn; } if (argn < argc) { /* * If there's cmd line args left over, fine. * Use them later. */ } else { ifp = stdin; infname = "noname"; readFromStdin++; } if (readFromStdin) { addEntryToIcon(MSIconData, "-", ""); if (verbose) pm_message ("Added entry from stdin.\n"); } else { /* * If we're not using fake and maps, then we skip 1 each time. */ for ( iconOn = argn; iconOn < argc ; iconOn += (readAndMaps ? 2 : 1) ) { char * xorPPM; char * andPPM; xorPPM = argv[iconOn]; andPPM = (readAndMaps ? argv[iconOn+1] : ""); addEntryToIcon(MSIconData, xorPPM, andPPM); } } /* * Now we have to go through and calculate the offsets. * The first infoheader starts at 6 + count*16 bytes. */ offset = (MSIconData->count * 16) + 6; for ( iconOn = 0; iconOn < MSIconData->count; iconOn++ ) { IC_Entry entry = MSIconData->entries[iconOn]; 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(MSIconData,argv[argc-1]); return 0; }