/* winicontoppm.c - read a MS Windows .ico file and write portable pixmap(s) ** ** Copyright (C) 2000,2003 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. ** ** Changes: ** ** 03/2003 - Added 24+32 bpp capability. */ #define _DEFAULT_SOURCE 1 /* New name for SVID & BSD source defines */ #define _BSD_SOURCE 1 /* Make sure strdup() is in string.h */ #define _XOPEN_SOURCE 500 /* Make sure strdup() is in string.h */ #include #include #include #include #include "pm_c_util.h" #include "ppm.h" #include "shhopt.h" #include "nstring.h" #include "mallocvar.h" #include "winico.h" #define MAJVERSION 0 #define MINVERSION 4 static int fileOffset = 0; /* not actually used, but useful for debug */ static const char er_read[] = "%s: read error"; static const char * infname; struct cmdlineInfo { /* All the information the user supplied in the command line, in a form easy for the program to use. */ const char * inputFilespec; const char * outputFilespec; unsigned int allicons; unsigned int bestqual; unsigned int writeands; unsigned int multippm; unsigned int verbose; }; static void parseCommandLine (int argc, const 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; optStruct3 opt; unsigned int option_def_index; MALLOCARRAY(option_def, 100); option_def_index = 0; /* incremented by OPTENT3 */ OPTENT3(0, "allicons", OPT_FLAG, NULL, &cmdlineP->allicons, 0 ); OPTENT3(0, "bestqual", OPT_FLAG, NULL, &cmdlineP->bestqual, 0 ); OPTENT3(0, "writeands", OPT_FLAG, NULL, &cmdlineP->writeands, 0 ); OPTENT3(0, "multippm", OPT_FLAG, NULL, &cmdlineP->multippm, 0 ); OPTENT3(0, "verbose", OPT_FLAG, 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 */ pm_optParseOptions4(&argc, argv, opt, sizeof(opt), 0); /* Uses and sets argc, argv, and some of *cmdlineP and others. */ if (argc-1 < 1) cmdlineP->inputFilespec = "-"; else cmdlineP->inputFilespec = argv[1]; if (argc-1 < 2) { cmdlineP->outputFilespec = "-"; if (cmdlineP->writeands || cmdlineP->allicons) pm_error("If you specify the -writeands or -allicons option, " "you must also specify an output file name argument."); } else cmdlineP->outputFilespec = argv[2]; if (argc-1 > 2) pm_error("Too many arguments (%d). Input filespec and " "output filespec are the only possible arguments.", argc-1); } static int GetByte(FILE * const ifP) { int v; v = getc(ifP); if (v == EOF) pm_error(er_read, infname); return v; } static short GetShort(FILE * const ifP) { short v; pm_readlittleshort(ifP, &v); return v; } static long GetLong(FILE * const ifP) { long v; pm_readlittlelong(ifP, &v); return v; } /* * These have no purpose but to wrapper the Byte, Short & Long * functions. */ static u1 readU1(FILE * const ifP) { ++fileOffset; return GetByte(ifP); } static u1 * readU1String (FILE * const ifP, unsigned int const length) { u1 * string; size_t rc; MALLOCARRAY(string, length + 1); if (string == NULL) pm_error("out of memory"); rc = fread(string, sizeof(u1), length, ifP); if (rc < length) { if (feof(ifP)) pm_error("File read failed. Premature end of file"); else pm_error("File read failed. Errno=%d (%s)", errno, strerror(errno)); } string[length] = 0; fileOffset += length * sizeof(u1); return string; } static u2 readU2 (FILE * const ifP) { fileOffset +=2; return GetShort(ifP); } static u4 readU4 (FILE * const ifP) { fileOffset += 4; return GetLong(ifP); } static IC_Entry * readICEntry(FILE * const ifP) { IC_Entry * entryP; /* malloc'ed */ u1 widthFld; /* 0 means 256 */ u1 heightFld; /* 0 means 256 */ u1 colorCtFld; /* 0 means 256 */ MALLOCVAR(entryP); if (!entryP) pm_error("Unable to allocate memory for IC entry"); widthFld = readU1(ifP); heightFld = readU1(ifP); entryP->color_count = readU1(ifP); entryP->reserved = readU1(ifP); /* never referenced (should be 0) */ entryP->planes = readU2(ifP); /* never referenced */ entryP->bitcount = readU2(ifP); /* must be 0, 1, 4, or 8 */ entryP->size_in_bytes = readU4(ifP); /* never referenced */ entryP->file_offset = readU4(ifP); /* never referenced */ entryP->colors = NULL; entryP->ih = NULL; entryP->xorBitmap = NULL; entryP->andBitmap = NULL; entryP->width = widthFld == 0 ? 256 : widthFld; entryP->height = heightFld == 0 ? 256 : heightFld; entryP->color_count = colorCtFld == 0 ? 256 : colorCtFld; if (entryP->width != entryP->height) pm_message("warning: icon is not square: %u x %u", entryP->width, entryP->height); return entryP; } static IC_InfoHeader * readInfoHeader(FILE * const ifP, const IC_Entry * const entryP) { IC_InfoHeader * ihP; /* malloc'ed */ MALLOCVAR(ihP); if (!ihP) pm_error("Unable to allocate memory for info header"); ihP->size = readU4(ifP); /* never referenced */ ihP->width = readU4(ifP); /* must equal entryP->width */ ihP->height = readU4(ifP); /* must be 2 * entryP->height */ ihP->planes = readU2(ifP); /* never referenced */ ihP->bitcount = readU2(ifP); ihP->compression = readU4(ifP); ihP->imagesize = readU4(ifP); /* never referenced */ ihP->x_pixels_per_m = readU4(ifP); /* never referenced */ ihP->y_pixels_per_m = readU4(ifP); /* never referenced */ ihP->colors_used = readU4(ifP); /* checked below, otherwise never referenced */ ihP->colors_important = readU4(ifP); /* checked below, otherwise never referenced */ if ((entryP->width != ihP->width) || (entryP->height != ihP->height / 2)) { pm_error("mismatch in header and image dimensions " "(%u x %u vs. %u x %u)", entryP->width, entryP->height, ihP->width, ihP->height / 2); } else if (ihP->height % 2 != 0) pm_error("invalid image height value %u (cannot be an odd number)", ihP->height); if (ihP->bitcount > 8) pm_error("abnormal bit per pixel value %u", ihP->bitcount); if ((entryP->bitcount != 0) && (entryP->bitcount != ihP->bitcount)) { pm_error("mismatch in header and image bpp value" "(%u vs. %u)", entryP->bitcount, ihP->bitcount); } if (ihP->colors_used > entryP->color_count) pm_error("'colors used' value %u exceeds total colors %u", ihP->colors_used, entryP->color_count); if (ihP->colors_important > entryP->color_count) pm_error("'important colors' value %u exceeds total colors %u", ihP->colors_important, entryP->color_count); if (ihP->compression) { pm_error("Can't handle compressed icons"); } return ihP; } static IC_Color * readICColor(FILE * const ifP) { IC_Color * colorP; /* malloc'ed */ MALLOCVAR(colorP); if (!colorP) pm_error("Unable to allocate memory for color"); /* 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. */ colorP->blue = readU1(ifP); colorP->green = readU1(ifP); colorP->red = readU1(ifP); colorP->reserved = readU1(ifP); return colorP; } static u1 * read1Bitmap (FILE * const ifP, unsigned int const width, unsigned int const height) { unsigned int row; unsigned int xByteCt; u1 * bitmap; unsigned int wt; MALLOCARRAY(bitmap, width * height); if (bitmap == NULL) pm_error("out of memory"); /* 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. */ wt = width >> 3; if ((wt & 0x3) != 0) { wt = (wt & ~0x3) + 4; } xByteCt = wt; for (row = 0; row < height; ++row) { u1 * const imgRow = readU1String(ifP, xByteCt); unsigned int col; unsigned int rowByte; unsigned int xOrVal; for (col = 0, rowByte = 0, xOrVal = 0x80; col < width; ++col) { *(bitmap+((height - row - 1) * width) + col) = (imgRow[rowByte] & xOrVal) / xOrVal; if (xOrVal == 0x01) { xOrVal = 0x80; ++rowByte; } else xOrVal >>= 1; } free(imgRow); } return bitmap; } static u1 * read4Bitmap (FILE * const ifP, unsigned int const width, unsigned int const height) { unsigned int row; u1 * bitmap; unsigned int wt; unsigned int xByteCt; MALLOCARRAY(bitmap, width * height); if (bitmap == NULL) pm_error("out of memory"); wt = width >> 1; if (wt & 0x3) { wt = (wt & ~0x3) + 4; } xByteCt = wt; for (row = 0; row < height; ++row) { u1 * const imgRow = readU1String(ifP, xByteCt); unsigned int rowByte; bool bottom; unsigned int col; for (col = 0, rowByte = 0, bottom = true; col < width; ++col) { /* 2 nibbles, 2 values */ if (bottom) { *(bitmap + ((height - row - 1) * width) + col) = (imgRow[rowByte] & 0xF0) >> 4; } else { *(bitmap + ((height - row -1) * width) + col) = (imgRow[rowByte] & 0xF); ++rowByte; } bottom = !bottom; } free(imgRow); } return bitmap; } static u1 * read8Bitmap (FILE * const ifP, unsigned int const width, unsigned int const height) { unsigned int row; unsigned int xByteCt; unsigned int wt; u1 * bitmap; MALLOCARRAY(bitmap, width * height); if (bitmap == NULL) pm_error("out of memory"); wt = width; if (wt & 0x3) { wt = (wt & ~0x3) + 4; } xByteCt = wt; for (row = 0; row < height; ++row) { u1 * imgRow = readU1String(ifP, xByteCt); unsigned int rowByte; unsigned int col; for (col = 0, rowByte = 0; col < width; ++col) *(bitmap + ((height - row - 1) * width) + col) = imgRow[rowByte++]; free(imgRow); } return bitmap; } static u1 * readXBitmap (FILE * const ifP, unsigned int const width, unsigned int const height, unsigned int const bpp) { /*---------------------------------------------------------------------------- Read a true color bitmap. (24/32 bits) The output routine deplanarizes it for us, we keep it flat here. -----------------------------------------------------------------------------*/ unsigned int const byteCt = bpp >> 3; unsigned int const xByteCt = width * byteCt; u1 * bitmap; /* remember - bmp (dib) stored upside down, so reverse */ MALLOCARRAY(bitmap, byteCt * width * height); if (bitmap == NULL) pm_error("out of memory allocating bitmap array"); { unsigned int i; u1 * bitcurptr; for (i = 0, bitcurptr = &bitmap[byteCt * width * (height-1)]; i < height; ++i, bitcurptr -= xByteCt) { u1 * const row = readU1String(ifP, xByteCt); memcpy(bitcurptr, row, xByteCt); free(row); } } return bitmap; } static MS_Ico * readIconFile(FILE * const ifP, bool const verbose) { unsigned int i; MS_Ico * MSIconDataP; /* malloc'ed */ MALLOCVAR(MSIconDataP); MSIconDataP->reserved = readU2(ifP); /* should be 0 */ MSIconDataP->type = readU2(ifP); /* should be 1 (ICO) or 2 (CUR) */ MSIconDataP->count = readU2(ifP); /* # icons in file */ if (MSIconDataP->reserved != 0) pm_message("Signature 'reserved' field is %u (should be 0)", MSIconDataP->reserved); if (MSIconDataP->type != 1 && MSIconDataP->type != 2) pm_error("Type %u file. Can handle only type 1 or 2.", MSIconDataP->type); if (MSIconDataP->count == 0) pm_error("Invalid image count: 0"); else if (verbose) pm_message("File contains %u images", MSIconDataP->count); MALLOCARRAY(MSIconDataP->entries, MSIconDataP->count); if (MSIconDataP->entries == NULL) pm_error("out of memory"); /* Read in each of the entries */ for (i = 0; i < MSIconDataP->count; ++i) MSIconDataP->entries[i] = readICEntry(ifP); /* Read in the infoheader, color map (if any) and the actual bit/pix maps for the icons. */ if (verbose) pm_message("#\tColors\tBPP\tWidth\tHeight\n"); for (i = 0; i < MSIconDataP->count; ++i) { IC_Entry * const entryP = MSIconDataP->entries[i]; unsigned int bpp; /* bits per pixel */ entryP->ih = readInfoHeader(ifP, MSIconDataP->entries[i]); bpp = entryP->bitcount ? entryP->bitcount : entryP->ih->bitcount; /* Read the palette, if appropriate */ switch (bpp) { case 24: case 32: /* 24/32 bpp icon has no palette */ break; default: { unsigned int j; MALLOCARRAY(entryP->colors, entryP->color_count); if (!entryP->colors) pm_error("Could get memory for %u colors", entryP->color_count); for (j = 0; j < entryP->color_count; ++j) entryP->colors[j] = readICColor(ifP); } } if (verbose) { char colsText[10]; sprintf (colsText, "%d", entryP->color_count); pm_message("%d\t%s\t%d\t%d\t%d\n", i, entryP->color_count ? colsText : "TRUE", bpp, entryP->width, entryP->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. * * 24+32 bit (16 is an abomination, which I'll avoid, and expect * no-one to mind) are stored 1byte/plane with a spare (alpha?) * byte for 32 bit. */ { /* * Read XOR Bitmap */ switch (bpp) { case 1: entryP->xorBitmap = read1Bitmap(ifP, entryP->width, entryP->height); break; case 4: entryP->xorBitmap = read4Bitmap(ifP, entryP->width, entryP->height); break; case 8: entryP->xorBitmap = read8Bitmap(ifP, entryP->width, entryP->height); break; case 24: case 32: entryP->xorBitmap = readXBitmap(ifP, entryP->width, entryP->height,bpp); break; default: pm_error("Uncatered bit depth %u", bpp); } /* * Read AND Bitmap */ entryP->andBitmap = read1Bitmap(ifP, entryP->width, entryP->height); } } return MSIconDataP; } static char * trimmedOutputName(const char inputName[]) { /* * Just trim off the final ".ppm", if there is one, else return as is. * oh, for =~ ... :) */ char * const outFile = strdup(inputName); if (streq(outFile + (strlen(outFile) - 4), ".ppm")) { *(outFile + (strlen(outFile) - 4)) = 0; } return outFile; } static int getBestQualityIcon(MS_Ico * const MSIconDataP) { unsigned int i; unsigned int best; unsigned int bestSize; unsigned int bestBpp; for (i = 0, bestSize = 0, bestBpp = 0; i < MSIconDataP->count; ++i) { IC_Entry * const entryP = MSIconDataP->entries[i]; unsigned int const size = entryP->width * entryP->height; unsigned int const bpp = entryP->bitcount ? entryP->bitcount : entryP->ih->bitcount; if (size > bestSize) { best = i; bestSize = size; } else if (size == bestSize && bpp > bestBpp) { best = i; bestBpp = bpp; } } return best; } static void writeXors(FILE * const multiOutF, char * const outputFileBase, IC_Entry * const entryP, 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 * ofP; pixel ** pixArray; unsigned int row; const char * outputFileName; if (multiOutF) { ofP = multiOutF; outputFileName = strdup(""); } else { if (outputFileBase) { if (multiple) { pm_asprintf(&outputFileName, "%s%s_%d.ppm", outputFileBase,(xor ? "_xor" : ""), entryNum); } else { pm_asprintf(&outputFileName, "%s%s.ppm", outputFileBase,(xor ? "_xor" : "")); } } else outputFileName = strdup("-"); ofP = pm_openw(outputFileName); } /* 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. */ pixArray = ppm_allocarray(entryP->width, entryP->height); for (row = 0; row < entryP->height; ++row) { switch (entryP->bitcount) { case 24: case 32: { unsigned int const pixelSize = entryP->bitcount >> 3; u1 * const xorRow = entryP->xorBitmap + row * entryP->width * pixelSize; unsigned int col; for (col = 0; col < entryP->width * pixelSize; col += pixelSize) PPM_ASSIGN(pixArray[row][col/pixelSize], xorRow[col+2], xorRow[col+1], xorRow[col]); } break; default: { u1 * const xorRow = entryP->xorBitmap + row * entryP->width; unsigned int col; for (col = 0; col < entryP->width; ++col) { unsigned int const colorIndex = xorRow[col]; if (colorIndex >= entryP->color_count) { pm_error("Invalid color index %u (max is %u)", colorIndex, entryP->color_count - 1); } else { IC_Color * const colorP = entryP->colors[colorIndex]; PPM_ASSIGN(pixArray[row][col], colorP->red, colorP->green, colorP->blue); } } } break; } } ppm_writeppm(ofP, pixArray, entryP->width, entryP->height, 255 /* maxval */, false /* text */); ppm_freearray(pixArray, entryP->height); pm_strfree(outputFileName); if (!multiOutF) pm_close(ofP); } static void writeAnds(FILE * const multiOutF, char const outputFileBase[], IC_Entry * const entryP, unsigned int const entryNum, bool const multiple) { /*---------------------------------------------------------------------------- Write the "and" image (i.e. the alpha mask) of the image *entryP 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 * ofP; bit ** bitArray; unsigned int row; if (multiOutF) ofP = multiOutF; else { const char * outputFileName; assert(outputFileBase); if (multiple) pm_asprintf(&outputFileName, "%s_and_%u.pbm", outputFileBase, entryNum); else pm_asprintf(&outputFileName, "%s_and.pbm", outputFileBase); ofP = pm_openw(outputFileName); pm_strfree(outputFileName); } bitArray = pbm_allocarray(entryP->width, entryP->height); for (row = 0; row < entryP->height; ++row) { u1 * const andRow = entryP->andBitmap + row * entryP->width; unsigned int col; for (col = 0; col < entryP->width; ++col) { /* Note: black is transparent in a Netpbm alpha mask */ bitArray[row][col] = andRow[col] ? PBM_BLACK: PBM_WHITE; } } pbm_writepbm(ofP, bitArray, entryP->width, entryP->height, 0); pbm_freearray(bitArray, entryP->height); if (!multiOutF) pm_close(ofP); } static void openMultiXor(char const outputFileBase[], bool const writeands, FILE ** const multiOutFP) { const char * outputFileName; if (outputFileBase) { pm_asprintf(&outputFileName, "%s%s.ppm", outputFileBase, (writeands ? "_xor" : "")); } else outputFileName = strdup("-"); *multiOutFP = pm_openw(outputFileName); pm_strfree(outputFileName); } static void openMultiAnd(char const outputFileBase[], FILE ** const multiAndOutFP) { const char * outputFileName; assert(outputFileBase); pm_asprintf(&outputFileName, "%s_and.pbm", outputFileBase); *multiAndOutFP = pm_openw(outputFileName); pm_strfree(outputFileName); } static void freeIconentry(IC_Entry * const entryP) { if (entryP->colors && entryP->color_count) { unsigned int i; for (i = 0; i color_count; ++i) free(entryP->colors[i]); free(entryP->colors); } if (entryP->andBitmap) free(entryP->andBitmap); if (entryP->xorBitmap) free(entryP->xorBitmap); if (entryP->ih) free(entryP->ih); free(entryP); } static void freeIcondata(MS_Ico * const MSIconDataP) { unsigned int i; for (i = 0; i < MSIconDataP->count; ++i) freeIconentry(MSIconDataP->entries[i]); free(MSIconDataP); } int main(int argc, const char *argv[]) { struct cmdlineInfo cmdline; FILE * ifP; unsigned int startEntry, endEntry; MS_Ico * MSIconDataP; char * outputFileBase; FILE * multiOutF; FILE * multiAndOutF; pm_proginit(&argc, argv); parseCommandLine(argc, argv, &cmdline); if (cmdline.bestqual && cmdline.allicons) pm_message("-bestqual doesn't make sense with -allicons. " "Ignoring -bestqual."); if (streq(cmdline.outputFilespec, "-")) outputFileBase = NULL; else outputFileBase = trimmedOutputName(cmdline.outputFilespec); ifP = pm_openr(cmdline.inputFilespec); infname = cmdline.inputFilespec; MSIconDataP = readIconFile(ifP, cmdline.verbose); /* * 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. */ if (cmdline.bestqual) { startEntry = getBestQualityIcon(MSIconDataP); endEntry = startEntry+1; } else { startEntry = 0; if (cmdline.allicons) endEntry = MSIconDataP->count; else endEntry = 1; } if (cmdline.multippm) openMultiXor(outputFileBase, cmdline.writeands, &multiOutF); else multiOutF = NULL; if (cmdline.writeands && cmdline.multippm) openMultiAnd(outputFileBase, &multiAndOutF); else multiAndOutF = NULL; { unsigned int entryNum; for (entryNum = startEntry; entryNum < endEntry; ++entryNum) { IC_Entry * const entryP = MSIconDataP->entries[entryNum]; writeXors(multiOutF, outputFileBase, entryP, entryNum, cmdline.allicons, cmdline.writeands); if (cmdline.writeands) writeAnds(multiAndOutF, outputFileBase, entryP, entryNum, cmdline.allicons); } } if (multiOutF) pm_close(multiOutF); if (multiAndOutF) pm_close(multiAndOutF); /* free up the image data here. */ freeIcondata(MSIconDataP); return 0; }