/* winicontoppm.c - read a MS Windows .ico file and write portable pixmap(s) ** ** 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. */ #include #include "ppm.h" #include "winico.h" #define MAJVERSION 0 #define MINVERSION 3 u1 readU1 ARGS((void)); u1 * readU1String ARGS((int length)); u2 readU2 ARGS((void)); u4 readU4 ARGS((void)); static int GetByte ARGS((void)); static short GetShort ARGS((void)); static long GetLong ARGS((void)); IC_Entry readICEntry ARGS((void)); IC_InfoHeader readInfoHeader ARGS((void)); IC_Color readICColor ARGS((void)); u1 * read1Bitmap ARGS((int width, int height)); u1 * read4Bitmap ARGS((int width, int height)); u1 * read8Bitmap ARGS((int width, int height)); MS_Ico readIconFile ARGS((void)); char * trimOutputName ARGS((char * inputName)); int getBestQualityIcon ARGS((MS_Ico MSIconData)); static int writeToFile = 0; static int verbose = 0; static int allicons = 0; static int writeands = 0; static int bestqual = 0; static int multippm = 0; static int file_offset = 0; /* not actually used, but useful for debug */ static char er_read[] = "%s: read error"; static char * infname; static char * outfname; static FILE * ifp; /* * These have no purpose but to wrapper the Byte, Short & Long * functions. */ u1 readU1 (void) { file_offset++; return GetByte(); } u1 * readU1String (length) int length; { u1 * string = malloc (sizeof (u1) * (length+1)); fread(string,sizeof(u1),length,ifp); string[length] = 0; file_offset += length; return string; } u2 readU2 (void) { file_offset +=2; return GetShort(); } u4 readU4 (void) { file_offset += 4; return GetLong(); } static int GetByte(void) { int v; if ((v = getc(ifp)) == EOF) { pm_error(er_read, infname); } return v; } static short GetShort(void) { short v; if (pm_readlittleshort(ifp, &v) == -1) { pm_error(er_read, infname); } return v; } static long GetLong(void) { long v; if (pm_readlittlelong(ifp, &v) == -1) { pm_error(er_read, infname); } return v; } IC_Entry readICEntry (void) { IC_Entry entry = malloc ( sizeof (* entry) ); entry->width = readU1(); entry->height = readU1(); entry->color_count = readU1(); entry->reserved = readU1(); entry->planes = readU2(); entry->bitcount = readU2(); entry->size_in_bytes = readU4(); entry->file_offset = readU4(); /* * Spec says 0 color count is really 256. */ if (entry->color_count == 0) entry->color_count = 256; return entry; } IC_InfoHeader readInfoHeader (void) { IC_InfoHeader ih = malloc ( sizeof (* ih) ); ih->size = readU4(); ih->width = readU4(); ih->height = readU4(); ih->planes = readU2(); ih->bitcount = readU2(); ih->compression = readU4(); ih->imagesize = readU4(); ih->x_pixels_per_m = readU4(); ih->y_pixels_per_m = readU4(); ih->colors_used = readU4(); ih->colors_important = readU4(); return ih; } /* * I don't know why this isn't the same as the spec, it just isn't * The colors honestly seem to be stored BGR. Bizarre. * * I've checked this in the BMP code for bmptoppm and the gimp. Guess the * spec I have is just plain wrong. */ IC_Color readICColor (void) { IC_Color col = malloc ( sizeof (* col) ); col->blue = readU1(); col->green = readU1(); col->red = readU1(); col->reserved = readU1(); return col; } /* * Depending on if the image is stored as 1bpp, 4bpp or 8bpp, the * encoding mechanism is different. * * 8bpp => 1 byte/palette index. * 4bpp => High Nibble, Low Nibble * 1bpp => 1 palette value per bit, high bit 1st. */ u1 * read1Bitmap (width, height) int width; int height; { int tmp; int xBytes; u1 * bitmap = malloc ( (width * height ) * (sizeof (u1)) ); int wt = width; wt >>= 3; if (wt & 3) { wt = (wt & ~3) + 4; } xBytes = wt; for (tmp = 0; tmp>= 1; } } } return bitmap; } u1 * read4Bitmap (width, height) int width; int height; { int tmp; u1 * bitmap = malloc ( (width * height ) * (sizeof (u1)) ); int wt = width; int xBytes; wt >>= 1; if (wt & 3) { wt = (wt & ~3) + 4; } xBytes = wt; for (tmp = 0; tmp> 4; } else { *(bitmap+((height-tmp-1)*width) + (x)) = (row[rowByte] & 0xF); rowByte++; } bottom = !bottom; } } return bitmap; } u1 * read8Bitmap (width, height) int width; int height; { int tmp; unsigned int xBytes; unsigned int wt = width; u1 * bitmap = malloc ( (width * height ) * (sizeof (u1)) ); if (wt & 3) { wt = (wt & ~3) + 4; } xBytes = wt; for (tmp = 0; tmpreserved = readU2(); /* * Type - should equal 1 */ MSIconData->type = readU2(); /* * count - no of icons in file.. */ MSIconData->count = readU2(); /* * Allocate "count" array of entries. */ if (verbose) fprintf (stderr,"Icon file contains %d icons.\n",MSIconData->count); MSIconData->entries = malloc (MSIconData->count * sizeof(IC_Entry *)); /* * Read in each of the entries */ for (iter = 0;iter < MSIconData->count ; iter++ ) { MSIconData->entries[iter] = readICEntry(); } /* * After that, we have to read in the infoheader, color map (if any) and the * actual bit/pix maps for the icons. */ if (verbose) fprintf (stderr,"#\tColors\tBPP\tWidth\tHeight\n"); for (iter = 0;iter < MSIconData->count ; iter++ ) { int bpp; MSIconData->entries[iter]->ih = readInfoHeader (); MSIconData->entries[iter]->colors = malloc (MSIconData->entries[iter]->color_count * sizeof(IC_Color *)); for (iter2 = 0;iter2 < MSIconData->entries[iter]->color_count ; iter2++ ) { MSIconData->entries[iter]->colors[iter2] = readICColor(); } /* * What's the bits per pixel? * Bit confusing, since there's a field in entry, and in the infoheader. * I'll use both but let the entry field take precedence. */ bpp = MSIconData->entries[iter]->bitcount ? MSIconData->entries[iter]->bitcount : MSIconData->entries[iter]->ih->bitcount; if (verbose) fprintf (stderr,"%d\t%d\t%d\t%d\t%d\n",iter,MSIconData->entries[iter]->color_count,bpp,MSIconData->entries[iter]->width,MSIconData->entries[iter]->height); /* * Pixels are stored bottom-up, left-to-right. Pixel lines are padded with zeros * to end on a 32bit (4byte) boundary. Every line will have the same number of * bytes. Color indices are zero based, meaning a pixel color of 0 represents the * first color table entry, a pixel color of 255 (if there are that many) represents * the 256th entry. */ { /* * Read XOR Bitmap */ switch (bpp) { case 1: MSIconData->entries[iter]->xorBitmap = read1Bitmap(MSIconData->entries[iter]->width,MSIconData->entries[iter]->height); break; case 4: MSIconData->entries[iter]->xorBitmap = read4Bitmap(MSIconData->entries[iter]->width,MSIconData->entries[iter]->height); break; case 8: MSIconData->entries[iter]->xorBitmap = read8Bitmap(MSIconData->entries[iter]->width,MSIconData->entries[iter]->height); break; default: pm_error("Uncatered bit depth %d\n",bpp); } /* * Read AND Bitmap */ MSIconData->entries[iter]->andBitmap = read1Bitmap(MSIconData->entries[iter]->width,MSIconData->entries[iter]->height); } } return MSIconData; } char * trimOutputName (inputName) char * inputName; { /* * Just trim off the final ".ppm", if there is one, else return as is. * oh, for =~ ... :) */ char * outFile = malloc ( sizeof (char) * (strlen (inputName) + 1)); strcpy(outFile, inputName); if (!strcmp (outFile + (strlen (outFile) - 4), ".ppm")) { *(outFile + (strlen (outFile) - 4)) = 0; } return outFile; } int getBestQualityIcon(MSIconData) MS_Ico MSIconData; { int x,best,best_size,best_bpp,bpp,size; IC_Entry entry; best_size = best_bpp = 0; for (x = 0; x < MSIconData->count; x++) { entry = MSIconData->entries[x]; size = entry->width * entry->height; bpp = entry->bitcount ? entry->bitcount : entry->ih->bitcount; if (size > best_size) { best = x; best_size = size; } else if (size == best_size && bpp > best_bpp) { best = x; best_bpp = bpp; } } return best; } static void writeXors(bool const writeToFile, FILE * const multiOutF, char outputFileBase[], IC_Entry const entry, int const entryNum, bool const multiple, bool const xor) { /*---------------------------------------------------------------------------- Write an "xor" image (i.e. the main image) out. 'multiple' means this is one of multiple images that are being written. 'entryNum' is the sequence number within the winicon file of the image we are writing. 'xor' means to include "xor" in the output file name. if 'multiOutF' is non-null, it is the stream descriptor of an open stream to which we are to write the image. If it is null, we are to open a file using outputFileBase[] and 'entryNum' and 'xor' to derive its name, and close it afterward. -----------------------------------------------------------------------------*/ FILE * outF; pixel ** ppm_array; int row; char *outputFile; outputFile = malloc(sizeof(char) * (strlen (outputFileBase) + 20)); if (multiOutF) outF = multiOutF; else { if (writeToFile) { if (multiple) { sprintf(outputFile, "%s%s_%d.ppm", outputFileBase,(xor ? "_xor" : ""), entryNum); } else { sprintf(outputFile, "%s%s.ppm", outputFileBase,(xor ? "_xor" : "")); } } else strcpy(outputFile, "-"); outF = pm_openw(outputFile); } /* * allocate an array to save the bmp data into. * note that entry->height will be 1/2 entry->ih->height, * as the latter adds "and" and "xor" height. */ ppm_array = ppm_allocarray(entry->width, entry->height); for (row=0; row < entry->height; row++) { u1 * xorRow; int col; xorRow = entry->xorBitmap + row * entry->width; for (col=0; col < entry->width; col++) { int colorIndex; IC_Color color; colorIndex = xorRow[col]; color = entry->colors[colorIndex]; PPM_ASSIGN(ppm_array[row][col], color->red,color->green,color->blue); } } ppm_writeppm(outF,ppm_array,entry->width, entry->height, (pixval) 255, 0); ppm_freearray(ppm_array,entry->height); if (!multiOutF) pm_close(outF); } static void writeAnds(FILE * const multiOutF, char outputFileBase[], IC_Entry const entry, int const entryNum, bool multiple) { /*---------------------------------------------------------------------------- Write an "and" image (i.e. the alpha mask) out. 'multiple' means this is one of multiple images that are being written. 'entryNum' is the sequence number within the winicon file of the image we are writing. if 'multiOutF' is non-null, it is the stream descriptor of an open stream to which we are to write the image. If it is null, we are to open a file using outputFileBase[] and 'entryNum' and 'xor' to derive its name, and close it afterward. -----------------------------------------------------------------------------*/ FILE * outF; bit ** pbm_array; u1 * andRow; int row; if (multiOutF) outF = multiOutF; else { char *outputFile; outputFile = malloc ( sizeof (char) * (strlen (outputFileBase) + 20)); if (allicons) sprintf(outputFile, "%s_and_%d.pbm", outputFileBase, entryNum); else sprintf(outputFile, "%s_and.pbm", outputFileBase); outF = pm_openw(outputFile); free(outputFile); } pbm_array = pbm_allocarray(entry->width, entry->height); for (row=0; row < entry->height; row++) { int col; andRow = entry->andBitmap + row * entry->width; for (col=0; col < entry->width; col++) { /* Note: black is transparent in a Netpbm alpha mask */ pbm_array[row][col] = andRow[col] ? PBM_BLACK: PBM_WHITE; } } pbm_writepbm(outF, pbm_array, entry->width, entry->height, 0); pbm_freearray(pbm_array, entry->height); if (!multiOutF) pm_close (outF); } static void openMultiXor(char outputFileBase[], FILE ** const multiOutFP) { char *outputFile; outputFile = malloc ( sizeof (char) * (strlen (outputFileBase) + 20)); /* * Open the output file now, it'll stay open the whole time. */ if (writeToFile) { sprintf(outputFile, "%s%s.ppm", outputFileBase, (writeands ? "_xor" : "")); } else { sprintf(outputFile,"-"); } *multiOutFP = pm_openw(outputFile); free(outputFile); } static void openMultiAnd(char outputFileBase[], FILE ** const multiAndOutFP) { char *outputFile; outputFile = malloc ( sizeof (char) * (strlen (outputFileBase) + 20)); sprintf(outputFile, "%s_and.pbm", outputFileBase); *multiAndOutFP = pm_openw(outputFile); free(outputFile); } int main (int argc, char *argv[]) { int argn; int startEntry, endEntry; char * usage = "[-writeands] [-allicons|-bestqual] [-multippm] " "[-verbose] [iconfile] [ppmdestfile]"; MS_Ico MSIconData; char * outputFileBase; char * outputFile; FILE * multiOutF; FILE * multiAndOutF; 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], "-allicons", 2)) allicons++; else if (pm_keymatch(argv[argn], "-bestqual", 2)) bestqual++; else if (pm_keymatch(argv[argn], "-writeands", 2)) writeands++; else if (pm_keymatch(argv[argn], "-multippm", 2)) multippm++; else pm_usage(usage); ++argn; } if (bestqual && allicons) { pm_message ("bestqual flag ignored."); bestqual--; } if (argn < argc) { ifp = pm_openr(argv[argn]); infname = argv[argn]; ++argn; } else { ifp = stdin; infname = "noname"; } if (argn < argc && strcmp(argv[argn],"-")) { outputFileBase = trimOutputName(argv[argn]); outfname = argv[argn]; outputFile = malloc ( sizeof (char) * (strlen (outputFileBase) + 20)); writeToFile++; ++argn; } else { outfname = "noname"; if (allicons || writeands) { pm_error("When using -allicons or -writeands, " "please supply an output filename.\n"); } if (argn < argc) argn++; } if (argn != argc) pm_usage(usage); MSIconData = readIconFile (); /* * Now we've read the icon file in (Hopefully! :) * Go through each of the entries, and write out files of the * form * * fname_0_xor.ppm * fname_0_and.ppm * * (or to stdout, depending on args parsing above). */ /* * If allicons is set, we want everything, if not, just go through once. */ startEntry = 0; if (allicons) { endEntry = MSIconData->count; } else { endEntry = 1; } /* * If bestqual is set, find the icon with highest size & bpp. */ if (bestqual) { startEntry = getBestQualityIcon(MSIconData); endEntry = startEntry+1; } if (multippm) openMultiXor(outputFileBase, &multiOutF); else multiOutF = NULL; if (writeands && multippm) openMultiAnd(outputFileBase, &multiAndOutF); else multiAndOutF = NULL; { int entryNum; for (entryNum = startEntry ; entryNum < endEntry ; entryNum++ ) { IC_Entry const entry = MSIconData->entries[entryNum]; writeXors(writeToFile, multiOutF, outputFileBase, entry, entryNum, allicons, writeands); if (writeands) writeAnds(multiAndOutF, outputFileBase, entry, entryNum, allicons); } } if (multiOutF) pm_close (multiOutF); if (multiAndOutF) pm_close(multiAndOutF); return 0; }