/*--------------------------------------------------------------------------- ** icoutilz.c ** ** Utility program for examining and manipulating BMP and ICO files. ** http://www.high-speed-software.com/icoutilz/ ** ** Copyright (C), 2005, High Speed Software. All rights reserved. ** ** v1.5 2005-02-20 03:56:00-05 sunshine@sunshineco.com ** Fixed bug. bmp_info_print() was leaking a FILE*. ** ** v1.4 2005-02-18 22:17:00-05 zarnuk@high-speed-software.com ** Fixed bug. Was not handling BMP files that used less than the ** maximum allowed number of palette entries. ** ** v1.3 2001-11-09 14:44:34-05 zarnuk@high-speed-software.com ** Suppressed Borland compiler warnings. ** Dynamically allocates the image and palette buffers, since the GNU ** compiler was putting them into the executable even though they were not ** declared static. ** ** v1.2 2001-11-01 10:34:00-05 zarnuk@high-speed-software.com ** Fixed bug. Was not seeking to start of image when loading bmp mask ** files. Added seek() routine so that fseek() failures generate an error ** message. ** Added -brief option. Implemented str_case_cmp(). ** ** TODO: ** Import TIFF files. **-------------------------------------------------------------------------*/ #ifdef __cplusplus extern "C" { #endif #include #include #include #ifndef __BORLANDC__ #include #endif #include #include #ifdef __cplusplus } #endif #ifdef __BORLANDC__ #include #endif #define ICOUTILZ_VERSION "1.5" /* The GNU compiler sometimes cannot tell that a variable has been ** initialized on every path through the code, and complains about ** the variable possibly being used unintialized. The Borland ** compiler does a better job of code analysis and complains that ** the variable is assigned a value that is never used. **/ #ifdef __GNUC__ #define GCC_INIT = 0 #else #define GCC_INIT #endif typedef unsigned char uint8; typedef short int int16; typedef long int int32; typedef struct t_ICO_Prefix { int16 reserved; /* Always 0 */ int16 type; /* 1=ICO 2=CUR */ int16 num_icons; } ICO_Prefix; /* followed by: * ICO_DirEntry dir[ num_icons ]; * ICO_Image image[ num_icons ]; */ typedef struct t_ICO_DirEntry { uint8 width; /* in pixels */ uint8 height; /* in pixels */ uint8 num_colors; /* 0=256 or more */ uint8 reserved; /* Not used, always 0 */ int16 num_planes; /* Not used, always 0 */ int16 bits_per_pixel; /* Not used, always 0 */ int32 size; /* in bytes (combined image and mask) */ int32 offset; /* byte offset to icon from start of file. */ } ICO_DirEntry; typedef struct t_ICO_Header { ICO_Prefix pfx; ICO_DirEntry* dir; } ICO_Header; typedef struct t_BMP_Prefix { char signature[2]; /* "BM" */ int32 filesize; int32 reserved; int32 offset; /* prefix + header + palette */ } BMP_Prefix; int const BMP_Prefix_Size = 14; /* Packed size */ typedef struct t_BMP_Header /* *1* fields used in ICO files */ { int32 header_size; /* *1* Always 40 */ int32 width; /* *1* in pixels */ int32 height; /* *1* in pixels NOTE *HEIGHT* */ int16 num_planes; /* *1* Always 1 */ int16 bits_per_pixel; /* *1* 1, 4, 8 => palette | 24 => true color */ int32 compression; /* 0:none 1/2: rle 4/8 bits/pixel. */ int32 image_size; /* *1* in bytes */ int32 horz_res; /* horizontal resolution pixels/meter */ int32 vert_res; /* vertical resolution pixels/meter */ int32 colors_used; /* # color palette values used 0=all */ int32 colors_important; /* # colors that are "important" 0=all */ } BMP_Header; int const BMP_Header_Size = 40; /* Packed size */ /* Followed by: * BMP_ColorPaletteEntry palette[ n ] * where 'n' is determined from bits_per_pixel. */ typedef struct t_BMP_ColorPaletteEntry { uint8 blue; uint8 green; uint8 red; uint8 reserved; /* Must be 0. */ } BMP_ColorPaletteEntry; #define BMP_ColorPaletteEntry_Size 4 /* NOTE *HEIGHT* ** This value includes the height of the image itself ** plus the height of the transparency bit-mask, thus ** it is twice the value of height in the ICO_DirEntry ** record. */ typedef enum { ARG_OPAQUE, /* No transparency mask. */ ARG_ICO, /* ICO file with a transparency mask. */ ARG_BMP, /* BMP file is transparency mask. */ ARG_BORDER, /* Most frequent color from border is transparent. */ ARG_PIXEL, /* Color of pixel at (x,y) is transparent. */ ARG_PALETTE, /* nth palette entry is transparent. */ ARG_RGB, /* RGB color is transparent. */ ARG_OUTPUT /* The output file. */ } ArgType; typedef struct t_Arg { ArgType type; char const* filename; FILE* file; union { int32 palette_index; int32 num_images; /* for ICO files. */ struct { uint8 r; uint8 g; uint8 b; } rgb; struct { int32 x; int32 y; } pixel; struct { char const* filename; FILE* file; } bmp; } u; } Arg; static char const* ARGV0 = 0; static int NUM_ARGS = 0; static Arg* ARGS = 0; static Arg* OUTPUT = 0; static FILE* OUTPUT_FILE = 0; static char const* OUTPUT_NAME = ""; static unsigned long int OUTPUT_OFFSET = 0; static int NUM_IMAGES = 0; static int CURRENT_IMAGE = 0; /* ** The GNU compiler put these into the executable even though they ** were not declared static, so I allocate them dynamically now. ** ** BMP_ColorPaletteEntry PALETTE_BUFF[ 256 ]; ** BMP_ColorPaletteEntry IMAGE_BUFF[ 256 * 256 ]; ** BMP_ColorPaletteEntry BORDER_BUFF[ 256 * 4 ]; */ static BMP_ColorPaletteEntry* PALETTE_BUFF = 0; static BMP_ColorPaletteEntry* IMAGE_BUFF = 0; static BMP_ColorPaletteEntry* BORDER_BUFF = 0; /*--------------------------------------------------------------------------- ** no_mem **-------------------------------------------------------------------------*/ static int no_mem( void ) { int err = errno; if (err == 0) err = ENOMEM; fprintf( stderr, "%s: memory allocation failed: %d: %s\n", ARGV0, err, strerror(err) ); return err; } /*--------------------------------------------------------------------------- ** allocate_buffers ** These are the maximum possible sizes for each of the ** buffers, so they can handle any requirement. **-------------------------------------------------------------------------*/ static int allocate_buffers( void ) { int rc = 0; PALETTE_BUFF = (BMP_ColorPaletteEntry*) malloc( 256 * sizeof(BMP_ColorPaletteEntry) ); IMAGE_BUFF = (BMP_ColorPaletteEntry*) malloc( 256 * 256 * sizeof(BMP_ColorPaletteEntry) ); BORDER_BUFF = (BMP_ColorPaletteEntry*) malloc( 4 * 256 * sizeof(BMP_ColorPaletteEntry) ); if (PALETTE_BUFF == 0 || IMAGE_BUFF == 0 || BORDER_BUFF == 0) rc = no_mem(); return rc; } /*--------------------------------------------------------------------------- ** deallocate_buffers **-------------------------------------------------------------------------*/ static void deallocate_buffers( void ) { if (PALETTE_BUFF != 0) free( PALETTE_BUFF ); if (IMAGE_BUFF != 0) free( IMAGE_BUFF ); if (BORDER_BUFF != 0) free( BORDER_BUFF ); } /*--------------------------------------------------------------------------- ** str_case_cmp **-------------------------------------------------------------------------*/ static int str_case_cmp( char const* x, char const* y ) { int n = 0; while (n == 0 && *x != '\0' && *y != '\0') { unsigned int cx = (unsigned char) *x++; unsigned int cy = (unsigned char) *y++; if (cx < 0x7f && islower( cx )) cx = toupper( cx ); if (cy < 0x7f && islower( cy )) cy = toupper( cy ); if (cx < cy) n = -1; else if (cx > cy) n = 1; } if (n == 0) { if (*x != '\0') n = 1; else if (*y != '\0') n = -1; } return n; } /*--------------------------------------------------------------------------- ** open_file **-------------------------------------------------------------------------*/ static int open_file( FILE** pf, char const* filename, char const* mode ) { int rc = 0; FILE* const f = fopen( filename, mode ); if (f == 0) { rc = errno; fprintf( stderr, "%s: fopen(%s): %d: %s\n", ARGV0, filename, rc, strerror( rc ) ); } *pf = f; return rc; } /*--------------------------------------------------------------------------- ** seek **-------------------------------------------------------------------------*/ static int seek( FILE* file, size_t offset, char const* filename ) { int const rc = fseek( file, offset, SEEK_SET ); if (rc != 0) { int err = errno; fprintf( stderr, "%s: seek(%s) failed: %d: %s\n", ARGV0, filename, err, strerror(err) ); } return rc; } /*--------------------------------------------------------------------------- ** write_error **-------------------------------------------------------------------------*/ static int write_error( void ) { int rc = errno; fprintf( stderr, "%s: error writing to output file: %d: %s\n", ARGV0, rc, strerror(rc) ); if (rc == 0) rc = 1; return rc; } /*--------------------------------------------------------------------------- ** put_bytes **-------------------------------------------------------------------------*/ static int put_bytes( char const* buff, size_t n ) { int rc = 0; if (fwrite( buff, 1, n, OUTPUT_FILE ) != n) rc = write_error(); else OUTPUT_OFFSET += n; return rc; } /*--------------------------------------------------------------------------- ** put_byte **-------------------------------------------------------------------------*/ static int put_byte( int x ) { int rc = 0; if (putc( x, OUTPUT_FILE ) != x) rc = write_error(); else ++OUTPUT_OFFSET; return rc; } /*--------------------------------------------------------------------------- ** put_uint8 **-------------------------------------------------------------------------*/ static int put_uint8( uint8 x ) { return put_byte( x ); } /*--------------------------------------------------------------------------- ** put_int16 **-------------------------------------------------------------------------*/ static int put_int16( int16 x ) { int rc; int const c0 = (x & 0x0ff); int const c1 = ((x >> 8) & 0x0ff); if ((rc = put_byte( c0 )) == 0) rc = put_byte( c1 ); return rc; } /*--------------------------------------------------------------------------- ** put_int32 **-------------------------------------------------------------------------*/ static int put_int32( int32 x ) { int rc; int const c0 = (x & 0x0ff); int const c1 = ((x >> 8) & 0x0ff); int const c2 = ((x >> 16) & 0x0ff); int const c3 = ((x >> 24) & 0x0ff); if ((rc = put_byte( c0 )) == 0 && (rc = put_byte( c1 )) == 0 && (rc = put_byte( c2 )) == 0) rc = put_byte( c3 ); return rc; } /*--------------------------------------------------------------------------- ** adjust_output_offset ** Pad the output with additional bytes to arrive at an even-word ** boundary for subsequent output. **-------------------------------------------------------------------------*/ static int adjust_output_offset( void ) { int rc = 0; unsigned long int x = OUTPUT_OFFSET & (sizeof(long int) - 1); if (x != 0) { int const n = sizeof(long int) - x; int i; for (i = 0; rc == 0 && i < n; ++i) rc = put_byte( 0 ); } return rc; } /*--------------------------------------------------------------------------- ** read_error **-------------------------------------------------------------------------*/ static int read_error( char const* filename ) { int rc = errno; if (rc != 0) { fprintf( stderr, "%s: error reading file: %s: %d: %s.\n", ARGV0, filename, rc, strerror(rc) ); } else { fprintf( stderr, "%s: unexpected end-of-file: %s\n", ARGV0, filename ); rc = ESRCH; } return rc; } /*--------------------------------------------------------------------------- ** get_bytes **-------------------------------------------------------------------------*/ static int get_bytes( char* buff, size_t n, FILE* file, char const* filename ) { int rc = 0; int m; if ((m = fread( buff, 1, n, file )) != n) rc = read_error( filename ); return rc; } /*--------------------------------------------------------------------------- ** get_byte **-------------------------------------------------------------------------*/ static int get_byte( int* px, FILE* file, char const* filename ) { int rc = 0; int const c = getc( file ); if (c == EOF) rc = read_error( filename ); *px = (c & 0x0ff); return rc; } /*--------------------------------------------------------------------------- ** get_uint8 **-------------------------------------------------------------------------*/ static int get_uint8( uint8* x, FILE* file, char const* filename ) { int c; int const rc = get_byte( &c, file, filename ); *x = c; return rc; } /*--------------------------------------------------------------------------- ** get_int16 **-------------------------------------------------------------------------*/ static int get_int16( int16* x, FILE* file, char const* filename ) { int rc, c0, c1; if ((rc = get_byte( &c0, file, filename )) == 0 && (rc = get_byte( &c1, file, filename )) == 0) *x = (c1 << 8) | c0; return rc; } /*--------------------------------------------------------------------------- ** get_int32 **-------------------------------------------------------------------------*/ static int get_int32( int32* x, FILE* file, char const* filename ) { int rc, c0, c1, c2, c3; if ((rc = get_byte( &c0, file, filename )) == 0 && (rc = get_byte( &c1, file, filename )) == 0 && (rc = get_byte( &c2, file, filename )) == 0 && (rc = get_byte( &c3, file, filename )) == 0) *x = (c3 << 24) | (c2 << 16) | (c1 << 8) | c0; return rc; } /*--------------------------------------------------------------------------- ** put_bmp_header **-------------------------------------------------------------------------*/ static int put_bmp_header( BMP_Header const* hdr ) { int rc; if ((rc = put_int32( hdr->header_size )) == 0 && (rc = put_int32( hdr->width )) == 0 && (rc = put_int32( hdr->height )) == 0 && (rc = put_int16( hdr->num_planes )) == 0 && (rc = put_int16( hdr->bits_per_pixel )) == 0 && (rc = put_int32( hdr->compression )) == 0 && (rc = put_int32( hdr->image_size )) == 0 && (rc = put_int32( hdr->horz_res )) == 0 && (rc = put_int32( hdr->vert_res )) == 0 && (rc = put_int32( hdr->colors_used )) == 0) rc = put_int32( hdr->colors_important ); return rc; } /*--------------------------------------------------------------------------- ** get_bmp_header **-------------------------------------------------------------------------*/ static int get_bmp_header( BMP_Header* hdr, FILE* file, char const* filename ) { int rc; if ((rc = get_int32( &(hdr->header_size), file, filename )) == 0 && (rc = get_int32( &(hdr->width), file, filename )) == 0 && (rc = get_int32( &(hdr->height), file, filename )) == 0 && (rc = get_int16( &(hdr->num_planes), file, filename )) == 0 && (rc = get_int16( &(hdr->bits_per_pixel), file, filename )) == 0 && (rc = get_int32( &(hdr->compression), file, filename )) == 0 && (rc = get_int32( &(hdr->image_size), file, filename )) == 0 && (rc = get_int32( &(hdr->horz_res), file, filename )) == 0 && (rc = get_int32( &(hdr->vert_res), file, filename )) == 0 && (rc = get_int32( &(hdr->colors_used), file, filename )) == 0) rc = get_int32( &(hdr->colors_important), file, filename ); return rc; } /*--------------------------------------------------------------------------- ** put_bmp_prefix **-------------------------------------------------------------------------*/ static int put_bmp_prefix( BMP_Prefix const* pfx ) { int rc; if ((rc = put_byte( pfx->signature[0] )) == 0 && (rc = put_byte( pfx->signature[1] )) == 0 && (rc = put_int32( pfx->filesize )) == 0 && (rc = put_int32( pfx->reserved )) == 0) rc = put_int32( pfx->offset ); return rc; } /*--------------------------------------------------------------------------- ** get_bmp_prefix **-------------------------------------------------------------------------*/ static int get_bmp_prefix( BMP_Prefix* pfx, FILE* file, char const* filename ) { int rc, b, m; if ((rc = get_byte( &b, file, filename )) == 0 && (rc = get_byte( &m, file, filename )) == 0 && (rc = get_int32( &(pfx->filesize), file, filename )) == 0 && (rc = get_int32( &(pfx->reserved), file, filename )) == 0 && (rc = get_int32( &(pfx->offset), file, filename )) == 0) { pfx->signature[0] = b; pfx->signature[1] = m; if (b != 'B' || m != 'M') { fprintf( stderr, "%s: malformed BMP file: %s\n", ARGV0, filename ); rc = 1; } } return rc; } /*--------------------------------------------------------------------------- ** put_palette_entry **-------------------------------------------------------------------------*/ static int put_palette_entry( BMP_ColorPaletteEntry const* rec ) { int rc; if ((rc = put_byte( rec->blue )) == 0 && (rc = put_byte( rec->green )) == 0 && (rc = put_byte( rec->red )) == 0) rc = put_byte( 0 ); /* rec->reserved. */ return rc; } /*--------------------------------------------------------------------------- ** get_palette_entry **-------------------------------------------------------------------------*/ static int get_palette_entry( BMP_ColorPaletteEntry* rec, FILE* file, char const* filename ) { int rc, r, g, b, a; if ((rc = get_byte( &b, file, filename )) == 0 && (rc = get_byte( &g, file, filename )) == 0 && (rc = get_byte( &r, file, filename )) == 0 && (rc = get_byte( &a, file, filename )) == 0) { rec->red = r; rec->green = g; rec->blue = b; rec->reserved = a; } rec->reserved = 0; return rc; } /*--------------------------------------------------------------------------- ** put_ico_prefix **-------------------------------------------------------------------------*/ static int put_ico_prefix( ICO_Prefix const* pfx ) { int rc; if ((rc = put_int16( pfx->reserved )) == 0 && (rc = put_int16( pfx->type )) == 0) rc = put_int16( pfx->num_icons ); return rc; } /*--------------------------------------------------------------------------- ** put_ico_direntry **-------------------------------------------------------------------------*/ static int put_ico_direntry( ICO_DirEntry const* d ) { int rc; if ((rc = put_uint8( d->width )) == 0 && (rc = put_uint8( d->height )) == 0 && (rc = put_uint8( d->num_colors )) == 0 && (rc = put_uint8( d->reserved )) == 0 && (rc = put_int16( d->num_planes )) == 0 && (rc = put_int16( d->bits_per_pixel )) == 0 && (rc = put_int32( d->size )) == 0) rc = put_int32( d->offset ); return rc; } /*--------------------------------------------------------------------------- ** get_ico_prefix **-------------------------------------------------------------------------*/ static int get_ico_prefix( ICO_Prefix* pfx, FILE* f, char const* filename ) { int rc; if ((rc = get_int16( &(pfx->reserved), f, filename )) == 0 && (rc = get_int16( &(pfx->type), f, filename )) == 0 && (rc = get_int16( &(pfx->num_icons), f, filename )) == 0) { int const x = pfx->num_icons; if (pfx->reserved != 0 || (pfx->type != 1 && pfx->type != 2) || (x < 0 || 256 < x)) { rc = 1; fprintf( stderr, "%s: malformed ICO file: %s\n", ARGV0, filename ); } } return rc; } /*--------------------------------------------------------------------------- ** get_ico_direntry **-------------------------------------------------------------------------*/ static int get_ico_direntry( ICO_DirEntry* d, FILE* f, char const* filename ) { int rc; if ((rc = get_uint8( &(d->width), f, filename )) == 0 && (rc = get_uint8( &(d->height), f, filename )) == 0 && (rc = get_uint8( &(d->num_colors), f, filename )) == 0 && (rc = get_uint8( &(d->reserved), f, filename )) == 0 && (rc = get_int16( &(d->num_planes), f, filename )) == 0 && (rc = get_int16( &(d->bits_per_pixel), f, filename )) == 0 && (rc = get_int32( &(d->size), f, filename )) == 0) rc = get_int32( &(d->offset), f, filename ); return rc; } /*--------------------------------------------------------------------------- ** init_output_header **-------------------------------------------------------------------------*/ static int init_output_header( ICO_Header* hdr ) { int rc; int i; int const n = NUM_IMAGES; hdr->pfx.reserved = 0; hdr->pfx.type = 1; hdr->pfx.num_icons = n; hdr->dir = (ICO_DirEntry*) malloc( n * sizeof(ICO_DirEntry) ); if (hdr->dir != 0) { for (i = 0; i < n; ++i) { ICO_DirEntry* d = &(hdr->dir[i]); d->width = 0; d->height = 0; d->num_colors = 0; d->reserved = 0; d->num_planes = 0; d->bits_per_pixel = 0; d->size = 0; d->offset = 0; } if ((rc = put_ico_prefix( &(hdr->pfx) )) == 0) for (i = 0; rc == 0 && i < n; ++i) rc = put_ico_direntry( &(hdr->dir[i]) ); } else { rc = no_mem(); } return rc; } /*--------------------------------------------------------------------------- ** transfer_bytes **-------------------------------------------------------------------------*/ static int transfer_bytes( size_t nbytes, FILE* file, char const* filename ) { int rc = 0; char buff[ 256 ]; while (rc == 0 && nbytes > 0) { size_t n = nbytes; if (n > sizeof(buff)) n = sizeof(buff); nbytes -= n; if ((rc = get_bytes( buff, n, file, filename )) == 0) rc = put_bytes( buff, n ); } return rc; } /*--------------------------------------------------------------------------- ** transfer_ico_file **-------------------------------------------------------------------------*/ static int transfer_ico_file( Arg* p, ICO_Header* hdr ) { int rc = 0; int j0 = CURRENT_IMAGE; int j, i; int const n = p->u.num_images; for (i = 0, j = j0; rc == 0 && i < n; ++i) rc = get_ico_direntry( &(hdr->dir[ j++ ]), p->file, p->filename ); for (i = 0, j = j0; rc == 0 && i < n; ++i) { ICO_DirEntry* d = &(hdr->dir[ j++ ]); if ((rc = seek( p->file, d->offset, p->filename )) == 0 && (rc = adjust_output_offset()) == 0) { d->offset = OUTPUT_OFFSET; rc = transfer_bytes( d->size, p->file, p->filename ); } } CURRENT_IMAGE += n; return rc; } /*--------------------------------------------------------------------------- ** load_palette **-------------------------------------------------------------------------*/ static int load_palette( int ncolors, FILE* f, char const* filename ) { int rc = 0; int i; for (i = 0; rc == 0 && i < ncolors; ++i) rc = get_palette_entry( &(PALETTE_BUFF[i]), f, filename ); return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_1 bits-per-pixel == 1 **-------------------------------------------------------------------------*/ static int load_bmp_image_1( BMP_Header const* bmp, FILE* file, char const* filename, int as_mask ) { int rc = 0; int row, col; int const w = bmp->width; int const h = bmp->height; int const bytes_per_row = (w + 7) / 8; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int x; /* one-byte input buffer. */ int b = 0; /* bit index. */ for (col = 0; rc == 0 && col < w; ++col) { if (b == 0) { b = (1 << 7); rc = get_byte( &x, file, filename ); } if (rc == 0) { if (as_mask) img->reserved = ((b & x) != 0 ? 1 : 0); else img->red = ((b & x) != 0 ? 1 : 0); ++img; b = (b >> 1); } } /* advance to next 4-byte boundary. */ for (col = 0; rc == 0 && col < pad_bytes; ++col) rc = get_byte( &x, file, filename ); } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_4_compressed bits-per-pixel == 4 **-------------------------------------------------------------------------*/ static int load_bmp_image_4_compressed( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 0; int i, col, row; int const w = bmp->width; int const h = bmp->height; int const N = w * h; for (i = 0; i < N; ++i) IMAGE_BUFF[i].red = 0; /* Init entire image to zero. */ row = 0; col = 0; while (rc == 0) { int x, y; if ((rc = get_byte( &x, file, filename )) == 0 && (rc = get_byte( &y, file, filename )) == 0) { if (x != 0) { int const hi = ((y >> 4) & 0x0f); int const lo = (y & 0x0f); int even = 1; for (i = 0; i < x; ++i) { IMAGE_BUFF[ row * w + col ].red = (even ? hi : lo); even = !even; ++col; } } else if (y == 0) /* && x == 0 end-of-line */ { ++row; col = 0; } else if (y == 1) /* && x == 0 end-of-image */ { break; /* *** BREAK *** */ } else if (y == 2) /* && x == 0 */ { if ((rc = get_byte( &x, file, filename )) == 0 && (rc = get_byte( &y, file, filename )) == 0) { col += x; row += y; } } else /* y >= 3 && x == 0 */ { int nbytes = y >> 1; for (i = 0; rc == 0 && i < nbytes; ++i) if ((rc = get_byte( &x, file, filename )) == 0) { int hi = ((x >> 4) & 0x0f); int lo = (x & 0x0f); IMAGE_BUFF[ row * w + col ].red = hi; ++col; IMAGE_BUFF[ row * w + col ].red = lo; ++col; } if (rc == 0 && (y & 1) != 0) { ++nbytes; rc = get_byte( &x, file, filename ); if (rc == 0) { int hi = ((x >> 4) & 0x0f); IMAGE_BUFF[ row * w + col ].red = hi; ++col; } } if (rc == 0 && (nbytes & 1) != 0) rc = get_byte( &x, file, filename ); } } } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_4 bits-per-pixel == 4 **-------------------------------------------------------------------------*/ static int load_bmp_image_4( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 0; int row, col; int const w = bmp->width; int const h = bmp->height; int const bytes_per_row = (w + 1) / 2; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int x; /* one-byte input buffer. */ int even = 1; /* nibble index. */ for (col = 0; rc == 0 && col < w; ++col) { if (even) rc = get_byte( &x, file, filename ); if (rc == 0) { img->red = ((even ? (x >> 4) : x) & 0x0f); ++img; even = !even; } } /* advance to next 4-byte boundary. */ for (col = 0; rc == 0 && col < pad_bytes; ++col) rc = get_byte( &x, file, filename ); } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_8_compressed bits-per-pixel=8, RLE **-------------------------------------------------------------------------*/ static int load_bmp_image_8_compressed( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 0; int i, row, col; int const w = bmp->width; int const h = bmp->height; int const N = w * h; for (i = 0; i < N; ++i) IMAGE_BUFF[i].red = 0; /* Init entire image to zero. */ row = 0; col = 0; while (rc == 0) { int x, y; if ((rc = get_byte( &x, file, filename )) == 0 && (rc = get_byte( &y, file, filename )) == 0) { if (x != 0) { for (i = 0; i < x; ++i) { IMAGE_BUFF[ row * w + col ].red = y; ++col; } } else if (y == 0) /* && (x == 0) end-of-line */ { ++row; col = 0; } else if (y == 1) /* && (x == 0) end-of-image */ { break; /* **** BREAK **** */ } else if (y == 2) /* && (x == 0) delta */ { if ((rc = get_byte( &x, file, filename )) == 0 && (rc = get_byte( &y, file, filename )) == 0) { col += x; row += y; } } else /* (y >= 3) && (x == 0) absolute */ { for (i = 0; rc == 0 && i < y; ++i) if ((rc = get_byte( &x, file, filename )) == 0) { IMAGE_BUFF[ row * w + col ].red = x; ++col; } if (rc == 0 && (y & 1) != 0) /* padding */ rc = get_byte( &x, file, filename ); } } } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_8 bits-per-pixel == 8 **-------------------------------------------------------------------------*/ static int load_bmp_image_8( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 0; int row, col; int const w = bmp->width; int const h = bmp->height; int const bytes_per_row = w; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int x; /* one-byte input buffer. */ for (col = 0; rc == 0 && col < w; ++col) if ((rc = get_byte( &x, file, filename )) == 0) { img->red = (x & 0x0ff); ++img; } /* advance to next 4-byte boundary. */ for (col = 0; rc == 0 && col < pad_bytes; ++col) rc = get_byte( &x, file, filename ); } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image_24 bits-per-pixel == 24 **-------------------------------------------------------------------------*/ static int load_bmp_image_24( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 0; int row, col; int const w = bmp->width; int const h = bmp->height; int const bytes_per_row = w * 3; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int r, g, b; for (col = 0; rc == 0 && col < w; ++col) { if ((rc = get_byte( &r, file, filename )) == 0 && (rc = get_byte( &g, file, filename )) == 0 && (rc = get_byte( &b, file, filename )) == 0) { img->red = r; img->green = g; img->blue = b; img->reserved = 0; ++img; } } /* advance to next 4-byte boundary. */ for (col = 0; rc == 0 && col < pad_bytes; ++col) rc = get_byte( &r, file, filename ); } return rc; } /*--------------------------------------------------------------------------- ** load_bmp_image **-------------------------------------------------------------------------*/ static int load_bmp_image( BMP_Header const* bmp, FILE* file, char const* filename ) { int rc = 1; int const bpp = bmp->bits_per_pixel; if (bmp->compression == 1) { if (bpp == 8) rc = load_bmp_image_8_compressed( bmp, file, filename ); else { fprintf( stderr, "%s: %s: compression=%ld vs bits-per-pixel=%d mismatch.\n", ARGV0, filename, bmp->compression, bpp ); } } else if (bmp->compression == 2) { if (bpp == 4) rc = load_bmp_image_4_compressed( bmp, file, filename ); else { fprintf( stderr, "%s: %s: compression=%ld vs bits-per-pixel=%d mismatch.\n", ARGV0, filename, bmp->compression, bpp ); } } else if (bmp->compression != 0) { fprintf( stderr, "%s: %s: Unknown compression mode:%ld\n", ARGV0, filename, bmp->compression ); } else if (bpp == 1) rc = load_bmp_image_1( bmp, file, filename, 0 ); else if (bpp == 4) rc = load_bmp_image_4( bmp, file, filename ); else if (bpp == 8) rc = load_bmp_image_8( bmp, file, filename ); else if (bpp == 24) rc = load_bmp_image_24( bmp, file, filename ); else { fprintf( stderr, "%s: %s: unsupported bits-per-pixel value: %d.\n", ARGV0, filename, bpp ); } return rc; } /*--------------------------------------------------------------------------- ** compute_mask_from_bmp **-------------------------------------------------------------------------*/ static int compute_mask_from_bmp( BMP_Header const* img_bmp, Arg* p ) { int rc; FILE* const f = p->u.bmp.file; char const* const name = p->u.bmp.filename; BMP_Prefix mask_pfx; BMP_Header mask_bmp; if ((rc = get_bmp_prefix( &mask_pfx, f, name )) == 0 && (rc = get_bmp_header( &mask_bmp, f, name )) == 0) { rc = 1; if (mask_bmp.width != img_bmp->width || mask_bmp.height != img_bmp->height) { fprintf( stderr, "%s: size of %s does not match size of %s.\n", ARGV0, name, p->filename ); } else if (mask_bmp.bits_per_pixel != 1) { fprintf( stderr, "%s: %s is not a monochrome image.\n", ARGV0, name ); } else if (mask_bmp.compression != 0) { fprintf( stderr, "%s: %s: can't handle mask files with compression.\n", ARGV0, name ); } else if ((rc = seek( f, mask_pfx.offset, name )) == 0) rc = load_bmp_image_1( &mask_bmp, f, name, 1 ); } return rc; } /*--------------------------------------------------------------------------- ** compute_mask_from_rgb ** We store the transparency mask in the "reserved" byte of the ** BMP_ColorPaletteEntry cells of the IMAGE_BUFF[] array. **-------------------------------------------------------------------------*/ static void compute_mask_from_rgb( int r, int g, int b, int N ) { BMP_ColorPaletteEntry* i = IMAGE_BUFF; BMP_ColorPaletteEntry* const iN = i + N; for ( ; i < iN; ++i) i->reserved = ((i->red == r && i->green == g && i->blue == b) ? 1 : 0); } /*--------------------------------------------------------------------------- ** compute_mask_from_palette ** We store the transparency mask in the "reserved" byte of the ** BMP_ColorPaletteEntry cells of the IMAGE_BUFF[] array. ** The palette index values are stored in the "red" byte. **-------------------------------------------------------------------------*/ static void compute_mask_from_palette( int x, int N ) { BMP_ColorPaletteEntry* i = IMAGE_BUFF; BMP_ColorPaletteEntry* const iN = i + N; for ( ; i < iN; ++i) i->reserved = ((i->red == x) ? 1 : 0); } /*--------------------------------------------------------------------------- ** opaque_mask **-------------------------------------------------------------------------*/ static void opaque_mask( int N ) { BMP_ColorPaletteEntry* i = IMAGE_BUFF; BMP_ColorPaletteEntry* const iN = i + N; for ( ; i < iN; ++i) i->reserved = 0; } /*--------------------------------------------------------------------------- ** take_most_frequent_border_color **-------------------------------------------------------------------------*/ static void take_most_frequent_border_color( int num_colors, BMP_ColorPaletteEntry* x ) { BMP_ColorPaletteEntry* p = BORDER_BUFF; BMP_ColorPaletteEntry* const pN = p + num_colors; int best = p->reserved; *x = *p; for ( ; p < pN; ++p) if (best < p->reserved) { best = p->reserved; *x = *p; } x->reserved = 0; } /*--------------------------------------------------------------------------- ** store_border_color **-------------------------------------------------------------------------*/ static void store_border_color( int* num_colors, BMP_ColorPaletteEntry const* x ) { BMP_ColorPaletteEntry* p = BORDER_BUFF; BMP_ColorPaletteEntry* const pN = p + *num_colors; for ( ; p < pN; ++p) if (p->red == x->red && p->green == x->green && p->blue == x->blue) { ++p->reserved; return; /* **** RETURN **** */ } *p = *x; p->reserved = 1; ++(*num_colors); } /*--------------------------------------------------------------------------- ** find_most_frequent_border_rgb_color **-------------------------------------------------------------------------*/ static void find_most_frequent_border_rgb_color( BMP_ColorPaletteEntry* e, int width, int height ) { int r, c; int offset; int num_colors = 0; for (c = 0; c < width; ++c) store_border_color( &num_colors, &(IMAGE_BUFF[ c ]) ); for (r = 1; r < height - 1; ++r) { store_border_color( &num_colors, &(IMAGE_BUFF[ r * width ]) ); store_border_color( &num_colors, &(IMAGE_BUFF[ r * width + width - 1 ]) ); } offset = r * (height - 1); for (c = 0; c < width; ++c) store_border_color( &num_colors, &(IMAGE_BUFF[ offset + c ]) ); take_most_frequent_border_color( num_colors, e ); } /*--------------------------------------------------------------------------- ** find_most_frequent_border_palette_color **-------------------------------------------------------------------------*/ static void find_most_frequent_border_palette_color( BMP_ColorPaletteEntry* e, int width, int height ) { int r, c; int num_colors = 0; int offset; BMP_ColorPaletteEntry x; x.red = 0; x.green = 0; x.blue = 0; x.reserved = 0; for (c = 0; c < width; ++c) { x.red = IMAGE_BUFF[c].red; store_border_color( &num_colors, &x ); } for (r = 1; r < height - 1; ++r) { x.red = IMAGE_BUFF[ r * width ].red; store_border_color( &num_colors, &x ); x.red = IMAGE_BUFF[ r * width + width - 1 ].red; store_border_color( &num_colors, &x ); } offset = r * (height - 1); for (c = 0; c < width; ++c) { x.red = IMAGE_BUFF[ offset + c ].red; store_border_color( &num_colors, &x ); } take_most_frequent_border_color( num_colors, e ); } /*--------------------------------------------------------------------------- ** lookup_rgb_in_palette **-------------------------------------------------------------------------*/ static int lookup_rgb_in_palette( int r, int g, int b, int bpp ) { int i; int const N = (1 << bpp); for (i = 0; i < N; ++i) { BMP_ColorPaletteEntry const* e = &(PALETTE_BUFF[i]); if (e->red == r && e->green == g && e->blue == b) return i; } return -1; } /*--------------------------------------------------------------------------- ** compute_bmp_mask ** Compute the transparency mask for this BMP image. **-------------------------------------------------------------------------*/ static int compute_bmp_mask( BMP_Header const* bmp, Arg* p ) { int rc = 0; int const bpp = bmp->bits_per_pixel; int const w = bmp->width; int const h = bmp->height; int const N = w * h; switch (p->type) { case ARG_PALETTE: { if (bpp > 8) { rc = 1; fprintf( stderr, "%s: %s: is not a palette-based image.\n", ARGV0, p->filename ); } else if (p->u.palette_index >= (1 << bpp)) { rc = 1; fprintf( stderr, "%s: %s: palette index out of range.\n", ARGV0, p->filename ); } else compute_mask_from_palette( p->u.palette_index, N ); } break; case ARG_ICO: case ARG_OUTPUT: case ARG_OPAQUE: opaque_mask( N ); break; case ARG_BORDER: { BMP_ColorPaletteEntry e; if (bpp <= 8) { find_most_frequent_border_palette_color( &e, w, h ); compute_mask_from_palette( e.red, N ); } else { find_most_frequent_border_rgb_color( &e, w, h ); compute_mask_from_rgb( e.red, e.green, e.blue, N ); } } break; case ARG_RGB: { int const r = p->u.rgb.r; int const g = p->u.rgb.g; int const b = p->u.rgb.b; if (bpp <= 8) { int const i = lookup_rgb_in_palette( r, g, b, bpp ); if (i < 0) { rc = 1; fprintf( stderr, "%s: %s: color not found in palette.\n", ARGV0, p->filename ); } else compute_mask_from_palette( i, N ); } else compute_mask_from_rgb( r, g, b, N ); } break; case ARG_PIXEL: { int const x = p->u.pixel.x; int const y = p->u.pixel.y; if (x < 0 || w <= x || y < 0 || h <= y) { rc = 1; fprintf( stderr, "%s: %s: pixel coordinates out of range.\n", ARGV0, p->filename ); } else { int const n = y * w + x; BMP_ColorPaletteEntry* e = &(IMAGE_BUFF[n]); if (bpp <= 8) compute_mask_from_palette( e->red, N ); else compute_mask_from_rgb( e->red, e->green, e->blue, N ); } } break; case ARG_BMP: rc = compute_mask_from_bmp( bmp, p ); break; } return rc; } /*--------------------------------------------------------------------------- ** calc_bytes_per_row **-------------------------------------------------------------------------*/ static int calc_bytes_per_row( int bits_per_pixel, int width ) { int n; if (bits_per_pixel == 1) n = ((width + 31) / 32) * 4; else if (bits_per_pixel == 4) n = ((width + 7) / 8) * 4; else if (bits_per_pixel == 8) n = ((width + 3) / 4) * 4; else if (bits_per_pixel == 24) n = (((width * 3) + 3) / 4) * 4; else n = -1; return n; } /*--------------------------------------------------------------------------- ** calc_image_size ** Size of image plus the size of the transparency mask. **-------------------------------------------------------------------------*/ static int calc_image_size( BMP_Header const* bmp, int with_mask ) { int n = calc_bytes_per_row( bmp->bits_per_pixel, bmp->width ); if (with_mask) n += calc_bytes_per_row( 1, bmp->width ); return n * bmp->height; } /*--------------------------------------------------------------------------- ** store_bmp_palette **-------------------------------------------------------------------------*/ static int store_bmp_palette( int bits_per_pixel, int num_colors ) { int rc = 0; if (bits_per_pixel <= 8) { int i; for (i = 0; rc == 0 && i < num_colors; ++i) rc = put_palette_entry( &(PALETTE_BUFF[i]) ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image_data_1 **-------------------------------------------------------------------------*/ static int store_bmp_image_data_1( BMP_Header const* bmp, int for_mask ) { int rc = 0; int i, row, col; int const h = bmp->height; int const w = bmp->width; int const bytes_per_row = (w + 7) / 8; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int x = 0; /* 1-byte buffer. */ int b = (1 << 7); /* bit index. */ for (col = 0; rc == 0 && col < w; ++col) { if ((for_mask && img->reserved != 0) || (!for_mask && img->red != 0 && img->reserved == 0)) x |= b; ++img; b = (b >> 1); if (b == 0) { b = (1 << 7); rc = put_byte( x ); x = 0; } } if (rc == 0 && b != (1 << 7)) rc = put_byte( x ); for (i = 0; rc == 0 && i < pad_bytes; ++i) rc = put_byte( 0 ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image_data_4 **-------------------------------------------------------------------------*/ static int store_bmp_image_data_4( BMP_Header const* bmp ) { int rc = 0; int i, row, col; int const h = bmp->height; int const w = bmp->width; int const bytes_per_row = (w + 1) / 2; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { int x = 0; /* 1-byte buffer. */ int b = 2; /* nibble index. */ for (col = 0; rc == 0 && col < w; ++col) { int y = (img->reserved == 0 ? (img->red & 0x0f) : 0); ++img; if (b == 2) x = (y << 4); else /* assert( b == 1 ) */ x |= y; if (--b == 0) { b = 2; rc = put_byte( x ); x = 0; } } if (rc == 0 && b != 2) rc = put_byte( x ); for (i = 0; rc == 0 && i < pad_bytes; ++i) rc = put_byte( 0 ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image_data_8 **-------------------------------------------------------------------------*/ static int store_bmp_image_data_8( BMP_Header const* bmp ) { int rc = 0; int i, row, col; int const h = bmp->height; int const w = bmp->width; int const bytes_per_row = w; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { for (col = 0; rc == 0 && col < w; ++col) { int x = (img->reserved == 0 ? img->red : 0); ++img; rc = put_byte( x ); } for (i = 0; rc == 0 && i < pad_bytes; ++i) rc = put_byte( 0 ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image_data_24 **-------------------------------------------------------------------------*/ static int store_bmp_image_data_24( BMP_Header const* bmp ) { int rc = 0; int i, row, col; int const h = bmp->height; int const w = bmp->width; int const bytes_per_row = w * 3; int const m = (bytes_per_row & 0x03); int const pad_bytes = (m == 0 ? 0 : 4 - m); BMP_ColorPaletteEntry* img = IMAGE_BUFF; for (row = 0; rc == 0 && row < h; ++row) { for (col = 0; rc == 0 && col < w; ++col) { int r = img->red; int g = img->green; int b = img->blue; if (img->reserved != 0) { r = 0; /* mask-off transparent parts. */ g = 0; b = 0; } ++img; if ((rc = put_byte( r )) == 0 && (rc = put_byte( g )) == 0) rc = put_byte( b ); } for (i = 0; rc == 0 && i < pad_bytes; ++i) rc = put_byte( 0 ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image_data **-------------------------------------------------------------------------*/ static int store_bmp_image_data( BMP_Header const* bmp ) { int rc; int const bpp = bmp->bits_per_pixel; if (bpp == 1) rc = store_bmp_image_data_1( bmp, 0 ); else if (bpp == 4) rc = store_bmp_image_data_4( bmp ); else if (bpp == 8) rc = store_bmp_image_data_8( bmp ); else if (bpp == 24) rc = store_bmp_image_data_24( bmp ); else { rc = 1; fprintf( stderr, "%s: unsupported bits-per-pixel value: %d.\n", ARGV0, bpp ); } return rc; } /*--------------------------------------------------------------------------- ** store_bmp_image **-------------------------------------------------------------------------*/ static int store_bmp_image( BMP_Header* bmp, int with_mask ) { int rc; int h; bmp->compression = 0; bmp->image_size = calc_image_size( bmp, with_mask ); h = bmp->height; if (with_mask) bmp->height = 2 * h; /* NOTE *HEIGHT* */ rc = put_bmp_header( bmp ); bmp->height = h; if (rc == 0 && (rc = store_bmp_palette( bmp->bits_per_pixel,bmp->colors_used )) == 0 && (rc = store_bmp_image_data( bmp )) == 0 && with_mask) rc = store_bmp_image_data_1( bmp, 1 ); return rc; } /*--------------------------------------------------------------------------- ** transfer_bmp_file **-------------------------------------------------------------------------*/ static int transfer_bmp_file( Arg* p, ICO_Header const* hdr ) { int rc; BMP_Prefix pfx; BMP_Header bmp; if ((rc = get_bmp_prefix( &pfx, p->file, p->filename )) == 0 && (rc = get_bmp_header( &bmp, p->file, p->filename )) == 0) { int const w = bmp.width; int const h = bmp.height; int const bpp = bmp.bits_per_pixel; if (bpp != 1 && bpp != 4 && bpp != 8 && bpp != 24) { rc = 1; fprintf( stderr, "%s: %s: corrupt bits-per-pixel: %d\n", ARGV0, p->filename, bpp ); } else if (w < 0 || h < 0) { rc = 1; fprintf( stderr, "%s: %s: image size is negative.\n", ARGV0, p->filename ); } else if (w > 255 || h > 255) { rc = 1; fprintf( stderr, "%s: %s: image is too big. The limit is 255x255.\n", ARGV0, p->filename ); } else { int ncolors = bmp.colors_used; ICO_DirEntry* d = &(hdr->dir[ CURRENT_IMAGE++ ]); d->width = w; d->height = h; if (ncolors == 0) ncolors = (1 << bpp); if (bpp <= 8) d->num_colors = ncolors; else d->num_colors = 0; d->num_planes = 0; d->bits_per_pixel = 0; d->size = 0; d->offset = OUTPUT_OFFSET; if (bpp <= 8) rc = load_palette( bmp.colors_used, p->file, p->filename ); if (rc == 0 && (rc = seek( p->file, pfx.offset, p->filename )) == 0 && (rc = load_bmp_image( &bmp, p->file, p->filename )) == 0 && (rc = compute_bmp_mask( &bmp, p )) == 0 && (rc = store_bmp_image( &bmp, 1 )) == 0) { d->size = OUTPUT_OFFSET - d->offset; } } } return rc; } /*--------------------------------------------------------------------------- ** transfer_images **-------------------------------------------------------------------------*/ static int transfer_images( ICO_Header* hdr ) { int rc = 0; Arg* p = ARGS; Arg* const pN = p + NUM_ARGS; for (; rc == 0 && p < pN; ++p) if (p->type == ARG_ICO) rc = transfer_ico_file( p, hdr ); else if (p->type != ARG_OUTPUT) rc = transfer_bmp_file( p, hdr ); return rc; } /*--------------------------------------------------------------------------- ** backpatch_output_header **-------------------------------------------------------------------------*/ static int backpatch_output_header( ICO_Header const* hdr ) { int rc; if ((rc = seek( OUTPUT_FILE, 0, OUTPUT_NAME )) == 0 && (rc = put_ico_prefix( &(hdr->pfx) )) == 0) { int i; int n = hdr->pfx.num_icons; for (i = 0; rc == 0 && i < n; ++i) rc = put_ico_direntry( &(hdr->dir[i]) ); } return rc; } /*--------------------------------------------------------------------------- ** count_images ** We need to examine the file prefix of ICO files to determine ** the number of images contained in the file. BMP files only ** contain a single image. **-------------------------------------------------------------------------*/ static int count_images( void ) { int rc = 0; int n = 0; Arg* p = ARGS; Arg* const pN = p + NUM_ARGS; for (; rc == 0 && p < pN; ++p) if (p->type == ARG_ICO) { ICO_Prefix pfx; if ((rc = get_ico_prefix( &pfx, p->file, p->filename )) == 0) { int const x = pfx.num_icons; p->u.num_images = x; n += x; } } else if (p->type != ARG_OUTPUT) { ++n; /* BMP files have a single image. */ } NUM_IMAGES = n; return rc; } /*--------------------------------------------------------------------------- ** usage **-------------------------------------------------------------------------*/ static void usage( void ) { puts( "This program examines and manipulates ICO files. The \"-merge\" command\n" "packs multiple BMP and ICO files into a single ICO file. The \"-split\"\n" "command extracts the images from an ICO file into separate BMP files.\n" "The \"-info\" command prints basic information about the images in an\n" "ICO file.\n" "\n" "usage:\n" " icoutilz -merge {file.ico | file.bmp mode}... -output icofile\n" " icoutilz -split -bmp file.ico...\n" " icoutilz -split -ico file.ico...\n" " icoutilz -info [-brief] {file.ico | file.bmp}...\n" "\n" "where mode is one of:\n" " -opaque No transparency mask.\n" " -border The most frequent color from the border of the image is\n" " used as the transparent color.\n" " -lowerleft The color of the pixel in the lower left corner is\n" " used as the transparent color.\n" " -pixel x y The color of the pixel at (x,y) is the transparent color.\n" " (0,0) is the lower-left corner.\n" " -palette n The nth color in the palette is used as the transparent\n" " color. The file.bmp must use a color palette.\n" " Numbering starts at 0, so the first color is 0.\n" " -rgb #rrggbb The transparent color in hex.\n" " -rgb r g b The transparent color in decimal, 0-255.\n" " -bmp file 'File' is a 2-color BMP file that is used as the\n" " transparency mask.\n" "\n" "The \"-split -bmp\" command extracts the images into files named:\n" "filename_wxhxb.BMP and filename_wxhxb_mask.BMP\n" "where: w=width, h=height, b=bits-per-pixel.\n" "The \"_mask\" file is the transparency mask for the image.\n" "A counter is appended to the filename if there is more than\n" "one image with the same width, height and bits-per-pixel.\n" "\n" "The \"-split -ico\" works like the \"-split -bmp\" command except it\n" "extracts the images into ICO files so there are no \"_mask\" files.\n" "\n" "The \"-info\" command prints information from the image files.\n" "\n" ); } /*--------------------------------------------------------------------------- ** is_help **-------------------------------------------------------------------------*/ static int is_help( char const* s ) { while (*s == '-' || *s == '/') ++s; return strstr( " h help H HELP Help ? ", s ) != 0; } /*--------------------------------------------------------------------------- ** has_extension **-------------------------------------------------------------------------*/ static int has_extension( char const* filename, char const* pattern ) { char const* const dot = strrchr( filename, '.' ); return dot != 0 && str_case_cmp( dot + 1, pattern ) == 0; } /*--------------------------------------------------------------------------- ** hexval **-------------------------------------------------------------------------*/ static int hexval( char c ) { if ('0' <= c && c <= '9') return c - '0'; else if ('a' <= c && c <= 'f') return c - 'a' + 10; else if ('A' <= c && c <= 'F') return c - 'A' + 10; return -1; } /*--------------------------------------------------------------------------- ** get_hex **-------------------------------------------------------------------------*/ static int get_hex( char const* s ) { int x = hexval( s[0] ); if (x >= 0) { int y = hexval( s[1] ); if (y >= 0) x = (x << 4) | y; else x = -1; } return x; } /*--------------------------------------------------------------------------- ** parse_arg **-------------------------------------------------------------------------*/ static int parse_arg( int* pi, int argc, char const* const* argv ) { int rc = 0; int i = *pi; char const* err_msg = 0; char const* err_arg = 0; Arg* rec = &(ARGS[ NUM_ARGS++ ]); rec->type = ARG_OUTPUT; rec->filename = 0; rec->file = 0; if (strcmp( argv[i], "-output" ) == 0) { ++i; if (i < argc) { rec->type = ARG_OUTPUT; rec->filename = argv[i]; ++i; } else err_msg = "-output must be followed by a filename."; } else if (has_extension( argv[i], "ico" ) || has_extension( argv[i], "cur" )) { rec->type = ARG_ICO; rec->filename = argv[i]; ++i; } else if (has_extension( argv[i], "bmp" )) { char const* mode; rec->filename = argv[i]; ++i; mode = argv[i]; ++i; if (str_case_cmp( "-opaque", mode ) == 0) { rec->type = ARG_OPAQUE; } else if (str_case_cmp( "-border", mode ) == 0) { rec->type = ARG_BORDER; } else if (str_case_cmp( "-lowerleft", mode ) == 0) { rec->type = ARG_PIXEL; rec->u.pixel.x = 0; rec->u.pixel.y = 0; } else if (str_case_cmp( "-pixel", mode ) == 0) { int x GCC_INIT; int y GCC_INIT; rec->type = ARG_PIXEL; if (i < argc && (x = atoi( argv[i] )) >= 0 && ++i < argc && (y = atoi( argv[i] )) >= 0) { ++i; rec->u.pixel.x = x; rec->u.pixel.y = y; } else { err_msg = "expected pixel coordinate, found '%s'."; err_arg = argv[i]; } } else if (str_case_cmp( "-palette", mode ) == 0) { int n; rec->type = ARG_PALETTE; if (i < argc && (n = atoi( argv[i] )) >= 0) if (n <= 255) { ++i; rec->u.palette_index = n; } else err_msg = "palette index must be between 0 and 255."; else err_msg = "expected palette index."; } else if (str_case_cmp( "-rgb", mode ) == 0) { rec->type = ARG_RGB; if (i < argc) { int r; int g GCC_INIT; int b GCC_INIT; char const* s = argv[i]; if (*s == '#') { ++s; if ((r = get_hex( s )) >= 0 && (g = get_hex( s + 2 )) >= 0 && (b = get_hex( s + 4 )) >= 0) { ++i; rec->u.rgb.r = r; rec->u.rgb.g = g; rec->u.rgb.b = b; } else { err_msg = "invalid rgb value, '#%s'"; err_arg = s; } } else if (i < argc && (r = atoi( argv[i] )) >= 0 && ++i < argc && (g = atoi( argv[i] )) >= 0 && ++i < argc && (b = atoi( argv[i] )) >= 0) { if (r <= 255 && g <= 255 && b <= 255) { ++i; rec->u.rgb.r = r; rec->u.rgb.g = g; rec->u.rgb.b = b; } else err_msg = "rgb values must be between 0 and 255."; } else { err_msg = "expected rgb value, found '%s'"; err_arg = argv[i]; } } else err_msg = "expected rgb value."; } else if (str_case_cmp( "-bmp", mode ) == 0) { rec->type = ARG_BMP; rec->u.bmp.file = 0; if (i < argc) { if (has_extension( argv[i], "bmp" )) { rec->u.bmp.filename = argv[i]; ++i; } else { err_msg = "expected bmp filename, found '%s'."; err_arg = argv[i]; } } else err_msg = "expected bmp filename."; } else { --i; err_msg = "expected mode, found '%s'"; err_arg = mode; } } else { err_msg = "expected BMP/ICO filename, found '%s'."; err_arg = argv[i]; } *pi = i; if (err_msg != 0) { --NUM_ARGS; fprintf( stderr, "%s: ", ARGV0 ); fprintf( stderr, err_msg, err_arg ); fprintf( stderr, "\n" ); rc = 1; usage(); } return rc; } /*--------------------------------------------------------------------------- ** review_args ** Apply global constraints to command-line arguments. Currently the ** only global constraint is that there be exactly one -output ** argument. **-------------------------------------------------------------------------*/ static int review_args( void ) { int rc = 0; int n = 0; Arg* p = ARGS; Arg* const pN = p + NUM_ARGS; for (; p < pN; ++p) if (p->type == ARG_OUTPUT) { ++n; OUTPUT = p; } if (n != 1) { rc = 1; fprintf( stderr, "%s: There must be exactly one \"-output\" argument.\n", ARGV0 ); usage(); } return rc; } /*--------------------------------------------------------------------------- ** parse_command_line **-------------------------------------------------------------------------*/ static int parse_command_line( int argc, char const* const* argv ) { int rc = 0; if (argc < 2 || is_help( argv[1] )) { usage(); rc = 1; } else { int i = 2; /* Skip "-merge" */ while (i < argc && (rc = parse_arg( &i, argc, argv )) == 0) /* empty */; if (rc == 0) rc = review_args(); } return rc; } /*--------------------------------------------------------------------------- ** open_files **-------------------------------------------------------------------------*/ static int open_files( void ) { int rc = 0; Arg* p = ARGS; Arg* const pN = p + NUM_ARGS; for (; rc == 0 && p < pN; ++p) if (p->type == ARG_OUTPUT) { rc = open_file( &(p->file), p->filename, "w+b" ); OUTPUT_FILE = p->file; OUTPUT_NAME = p->filename; } else { rc = open_file( &(p->file), p->filename, "rb" ); if (rc == 0 && p->type == ARG_BMP) rc = open_file( &(p->u.bmp.file), p->u.bmp.filename, "rb" ); } return rc; } /*--------------------------------------------------------------------------- ** close_files **-------------------------------------------------------------------------*/ static void close_files( void ) { Arg* p = ARGS; Arg* const pN = p + NUM_ARGS; for (; p < pN; ++p) { if (p->file != 0) fclose( p->file ); if (p->type == ARG_BMP && p->u.bmp.file != 0) fclose( p->u.bmp.file ); } } /*--------------------------------------------------------------------------- ** ico_merge **-------------------------------------------------------------------------*/ static int ico_merge( int argc, char const* const* argv ) { int rc; ARGS = (Arg*) malloc( argc * sizeof(Arg) ); /* Upper limit. */ if (ARGS != 0) { ICO_Header hdr; if ((rc = parse_command_line( argc, argv )) == 0 && (rc = allocate_buffers()) == 0 && (rc = open_files()) == 0 && (rc = count_images()) == 0 && (rc = init_output_header( &hdr )) == 0 && (rc = transfer_images( &hdr )) == 0) rc = backpatch_output_header( &hdr ); close_files(); deallocate_buffers(); free( ARGS ); } else { rc = no_mem(); } return rc; } /*--------------------------------------------------------------------------- ** dump_bmp_header **-------------------------------------------------------------------------*/ static void dump_bmp_header( int is_ico, int brief, BMP_Header const* hdr ) { if (!brief) fprintf( stdout, " header size: %ld bytes\n", hdr->header_size ); if (!brief || !is_ico) { fprintf( stdout, " width: %ld pixels\n", hdr->width ); fprintf( stdout, " height: %ld pixels\n", hdr->height ); } fprintf( stdout, " bits per pixel: %d\n", hdr->bits_per_pixel ); if (!brief) { fprintf( stdout, " # planes: %d\n", hdr->num_planes ); fprintf( stdout, " compression: %ld\n", hdr->compression ); fprintf( stdout, " image size: %ld\n", hdr->image_size ); fprintf( stdout, "horz resolution: %ld\n", hdr->horz_res ); fprintf( stdout, "vert resolution: %ld\n", hdr->vert_res ); fprintf( stdout, " # colors used: %ld\n", hdr->colors_used ); fprintf( stdout, " # important: %ld\n", hdr->colors_important ); } } /*--------------------------------------------------------------------------- ** ico_print_dir **-------------------------------------------------------------------------*/ static int ico_print_dir( int brief, int i, ICO_DirEntry const* dir, FILE* f, char const* filename ) { int rc = 1; BMP_Header bmp; if (seek( f, dir->offset, filename ) == 0 && (rc = get_bmp_header( &bmp, f, filename )) == 0) { fprintf( stdout, "\n" ); fprintf( stdout, " image: %d\n", i + 1 ); if (!brief) fprintf( stdout, " DIR fields\n" ); fprintf( stdout, " width: %d pixels\n", dir->width ); fprintf( stdout, " height: %d pixels\n", dir->height ); if (!brief) { fprintf( stdout, " # colors: %d\n", dir->num_colors ); fprintf( stdout, " reserved: %d\n", dir->reserved ); fprintf( stdout, " # planes: %d\n", dir->num_planes ); fprintf( stdout, " bits per pixel: %d\n", dir->bits_per_pixel ); fprintf( stdout, " size: %ld bytes\n", dir->size ); fprintf( stdout, " offset: %ld bytes\n", dir->offset ); fprintf( stdout, " BMP fields\n" ); } dump_bmp_header( 1, brief, &bmp ); } return rc; } /*--------------------------------------------------------------------------- ** ico_info_print **-------------------------------------------------------------------------*/ static int ico_info_print( int brief, char const* filename ) { int rc; FILE* f; if ((rc = open_file( &f, filename, "rb" )) == 0) { ICO_Header hdr; if ((rc = get_ico_prefix( &(hdr.pfx), f, filename )) == 0) { int const n = hdr.pfx.num_icons; if (!brief) fprintf( stdout, "=======================================\n" ); else fprintf( stdout, "\n" ); fprintf( stdout, "%s:\n", filename ); if (!brief) { fprintf( stdout, " ICO fields\n"); fprintf( stdout, " reserved: %d\n", hdr.pfx.reserved ); fprintf( stdout, " type: %d\n", hdr.pfx.type ); } fprintf( stdout, " # images: %d\n", hdr.pfx.num_icons ); if (n > 0) { hdr.dir = (ICO_DirEntry*) malloc( n * sizeof(ICO_DirEntry) ); if (hdr.dir != 0) { int i; for (i = 0; rc == 0 && i < n; ++i) rc = get_ico_direntry( &(hdr.dir[i]), f, filename ); for (i = 0; rc == 0 && i < n; ++i) rc = ico_print_dir( brief, i, &(hdr.dir[i]), f, filename ); fprintf( stdout, "\n" ); free( hdr.dir ); } else { rc = no_mem(); } } else { rc = 1; fprintf( stderr, "%s: %s: file contains no images.\n", ARGV0, filename ); } } fclose( f ); } return rc; } /*--------------------------------------------------------------------------- ** bmp_info_print **-------------------------------------------------------------------------*/ static int bmp_info_print( int brief, char const* filename ) { int rc; BMP_Prefix pfx; BMP_Header hdr; FILE* f = 0; if ((rc = open_file( &f, filename, "rb" )) == 0 && (rc = get_bmp_prefix( &pfx, f, filename )) == 0 && (rc = get_bmp_header( &hdr, f, filename )) == 0) { if (!brief) fprintf( stdout, "=======================================\n" ); fprintf( stdout, "%s:\n", filename ); if (!brief) { fprintf( stdout, " signature: '%.2s'\n", pfx.signature ); fprintf( stdout, " filesize: %ld\n", pfx.filesize ); fprintf( stdout, " reserved: %ld\n", pfx.reserved ); fprintf( stdout, " image offset: %ld bytes\n", pfx.offset ); } dump_bmp_header( 0, brief, &hdr ); fprintf( stdout, "\n" ); } if (f != 0) fclose( f ); return rc; } /*--------------------------------------------------------------------------- ** ico_info **-------------------------------------------------------------------------*/ static int ico_info( int argc, char const* const* argv ) { int rc = 0; if (argc < 3 || is_help( argv[2] )) usage(); else { int i = 2; int brief = 0; if (i < argc && str_case_cmp( argv[2], "-brief" ) == 0) { ++i; brief = 1; } for (; i < argc; ++i) { char const* const filename = argv[i]; if (has_extension( filename, "ico" )) ico_info_print( brief, filename ); else if (has_extension( filename, "cur" )) ico_info_print( brief, filename ); else if (has_extension( filename, "bmp" )) bmp_info_print( brief, filename ); else { rc = 1; fprintf( stderr, "%s: Uknown file type: '%s'\n", ARGV0, filename ); } } } return rc; } /*--------------------------------------------------------------------------- ** ico_split_filename **-------------------------------------------------------------------------*/ static char* ico_split_filename( char* buff, char const* i_name, char const* suffix, ICO_DirEntry const* dir ) { char tmp[ 64 ]; char* p; strcpy( buff, i_name ); p = strrchr( buff, '.' ); if (p != 0) *p = '\0'; sprintf( tmp, "_%dx%dx%d", dir->width, dir->height, dir->bits_per_pixel ); strcat( buff, tmp ); if (dir->num_planes != 0) { sprintf( tmp, "_%d", dir->num_planes ); strcat( buff, tmp ); } strcat( buff, suffix ); return buff; } /*--------------------------------------------------------------------------- ** ico_split_to_ico **-------------------------------------------------------------------------*/ static int ico_split_to_ico( ICO_DirEntry const* dir, FILE* i_file, char const* i_name, FILE* o_file, char const* o_name ) { int rc; ICO_Prefix pfx; ICO_DirEntry x; OUTPUT_FILE = o_file; OUTPUT_NAME = o_name; OUTPUT_OFFSET = 0; pfx.reserved = 0; pfx.type = 1; pfx.num_icons = 1; x.width = dir->width; x.height = dir->height; x.num_colors = dir->num_colors; x.reserved = 0; x.num_planes = 0; x.bits_per_pixel = 0; x.size = dir->size; x.offset = 0; /* Need to backpatch this. */ if ((rc = put_ico_prefix( &pfx )) == 0 && (rc = put_ico_direntry( &x )) == 0 && (rc = adjust_output_offset()) == 0) { x.offset = OUTPUT_OFFSET; if ((rc = seek( i_file, dir->offset, i_name )) == 0 && (rc = transfer_bytes( x.size, i_file, i_name )) == 0 && (rc = seek( o_file, 0, o_name )) == 0 && (rc = put_ico_prefix( &pfx )) == 0) rc = put_ico_direntry( &x ); /* backpatch offset. */ } return rc; } /*--------------------------------------------------------------------------- ** ico_split_to_bmp **-------------------------------------------------------------------------*/ static int ico_split_to_bmp( BMP_Header* bmp, ICO_DirEntry const* dir, FILE* i_file, char const* i_name, FILE* o_file, char const* o_name ) { int rc; int image_size; int palette_size; int bpp; int ncolors; int offset; BMP_Prefix pfx; bmp->height /= 2; /* NOTE *HEIGHT* */ image_size = calc_image_size( bmp, 0 ); palette_size = 0; bpp = bmp->bits_per_pixel; ncolors = (1 << bpp); if (bpp <= 8) palette_size = ncolors * BMP_ColorPaletteEntry_Size; OUTPUT_FILE = o_file; OUTPUT_NAME = o_name; OUTPUT_OFFSET = 0; pfx.signature[0] = 'B'; pfx.signature[1] = 'M'; pfx.reserved = 0; pfx.offset = BMP_Prefix_Size + BMP_Header_Size + palette_size; pfx.filesize = image_size + pfx.offset; bmp->header_size = BMP_Header_Size; bmp->num_planes = 1; bmp->compression = 0; bmp->image_size = image_size; bmp->horz_res = 0; bmp->vert_res = 0; bmp->colors_used = 0; bmp->colors_important= 0; offset = dir->offset + BMP_Header_Size; if ((rc = put_bmp_prefix( &pfx )) == 0 && (rc = put_bmp_header( bmp )) == 0 && (rc = seek( i_file, offset, i_name )) == 0 && (rc = transfer_bytes( palette_size, i_file, i_name )) == 0 && (rc = transfer_bytes( image_size, i_file, i_name )) == 0) { char mask_name[ FILENAME_MAX ]; FILE* mask_file; ico_split_filename( mask_name, i_name, "_mask.bmp", dir ); if ((rc = open_file( &mask_file, mask_name, "wb" )) == 0) { char buff[ 2 * BMP_ColorPaletteEntry_Size ]; palette_size = sizeof(buff); bmp->bits_per_pixel = 1; image_size = calc_image_size( bmp, 0 ); bmp->image_size = image_size; pfx.offset = BMP_Prefix_Size + BMP_Header_Size + palette_size; pfx.filesize = image_size + pfx.offset; buff[0] = 0; /* blue; black */ buff[1] = 0; /* green; */ buff[2] = 0; /* red; */ buff[3] = 0; /* reserved; */ buff[4] = (uint8)255; /* blue; white */ buff[5] = (uint8)255; /* green; */ buff[6] = (uint8)255; /* red; */ buff[7] = 0; /* reserved; */ OUTPUT_FILE = mask_file; OUTPUT_NAME = mask_name; if ((rc = put_bmp_prefix( &pfx )) == 0 && (rc = put_bmp_header( bmp )) == 0 && (rc = put_bytes( buff, sizeof(buff) )) == 0) rc = transfer_bytes( image_size, i_file, i_name ); fclose( mask_file ); } } return rc; } /*--------------------------------------------------------------------------- ** set_ico_split_counter ** Find out if there are multiple images with the same width, height, ** and bits-per-pixel values. If so, then set the counter for this ** image. **-------------------------------------------------------------------------*/ static void set_ico_split_counter( int i, ICO_Header* hdr, int num_images ) { int j; int n_before = 0; int n_after = 0; ICO_DirEntry* d = &(hdr->dir[i]); int const w = d->width; int const h = d->height; int const b = d->bits_per_pixel; for (j = 0; j < num_images; ++j) { ICO_DirEntry const* t = &(hdr->dir[j]); if (t->width == w && t->height == h && t->bits_per_pixel == b) { if (j < i) ++n_before; else if (j > i) ++n_after; } } if (n_before > 0 || n_after > 0) d->num_planes = n_before + 1; else d->num_planes = 0; } /*--------------------------------------------------------------------------- ** do_ico_split **-------------------------------------------------------------------------*/ static int do_ico_split( int is_ico, int num_images, ICO_Header* hdr, BMP_Header* bmp, FILE* f, char const* filename ) { int rc = 0; int i; for (i = 0; rc == 0 && i < num_images; ++i) rc = get_ico_direntry( &(hdr->dir[i]), f, filename ); for (i = 0; rc == 0 && i < num_images; ++i) if ((rc = seek( f, hdr->dir[i].offset, filename )) == 0) rc = get_bmp_header( &(bmp[i]), f, filename ); if (rc == 0) { for (i = 0; i < num_images; ++i) hdr->dir[i].bits_per_pixel = bmp[i].bits_per_pixel; for (i = 0; i < num_images; ++i) set_ico_split_counter( i, hdr, num_images ); for (i = 0; i < num_images; ++i) { char o_name[ FILENAME_MAX ]; ICO_DirEntry* d = &(hdr->dir[i]); FILE* o_file; ico_split_filename( o_name, filename, (is_ico ? ".ico" : ".bmp"), d ); if ((rc = open_file( &o_file, o_name, "wb" )) == 0) { if (is_ico) rc = ico_split_to_ico( d, f, filename, o_file, o_name ); else rc = ico_split_to_bmp( &(bmp[i]), d, f, filename, o_file, o_name ); fclose( o_file ); } } } return rc; } /*--------------------------------------------------------------------------- ** ico_split_one **-------------------------------------------------------------------------*/ static int ico_split_one( int is_ico, char const* filename ) { int rc; FILE* f; if ((rc = open_file( &f, filename, "rb" )) == 0) { ICO_Header hdr; if ((rc = get_ico_prefix( &(hdr.pfx), f, filename )) == 0) { int const n = hdr.pfx.num_icons; if (n > 0) { hdr.dir = (ICO_DirEntry*) malloc( n * sizeof(ICO_DirEntry) ); if (hdr.dir != 0) { BMP_Header* bmp = (BMP_Header*) malloc( n * sizeof(BMP_Header) ); if (bmp != 0) { do_ico_split( is_ico, n, &hdr, bmp, f, filename ); free( bmp ); } else { rc = no_mem(); } free( hdr.dir ); } else { rc = no_mem(); } } else { rc = 1; fprintf( stderr, "%s: %s: file contains no images.\n", ARGV0, filename ); } } fclose( f ); } return rc; } /*--------------------------------------------------------------------------- ** ico_split_list **-------------------------------------------------------------------------*/ static int ico_split_list( int is_ico, int argc, char const* const* argv ) { int const rc = allocate_buffers(); if (rc == 0) { int i; for (i = 3; i < argc; ++i) ico_split_one( is_ico, argv[i] ); } deallocate_buffers(); return rc; } /*--------------------------------------------------------------------------- ** ico_split_cmd **-------------------------------------------------------------------------*/ static int ico_split_cmd( int argc, char const* const* argv ) { int rc = 1; if (argc < 3 || is_help( argv[2] )) usage(); else if (str_case_cmp( "-ico", argv[2] ) == 0) rc = ico_split_list( 1, argc, argv ); else if (str_case_cmp( "-bmp", argv[2] ) == 0) rc = ico_split_list( 0, argc, argv ); else { fprintf( stderr, "%s: \"-split\" must be followed by " "\"-ico\" or \"-bmp\"\n", ARGV0 ); usage(); } return rc; } /*--------------------------------------------------------------------------- ** main **-------------------------------------------------------------------------*/ int main( int argc, char const* const* argv ) { int rc = 1; ARGV0 = argv[0]; fprintf( stderr, "icoutilz v" ICOUTILZ_VERSION " Copyright (C) 2005, High Speed Software\n" "by Paul McCarthy \n" "http://www.high-speed-software.com/icoutilz/\n\n" ); fflush( stderr ); if (argc < 2 || is_help( argv[1] )) usage(); else if (str_case_cmp( argv[1], "-merge" ) == 0) rc = ico_merge( argc, argv ); else if (str_case_cmp( argv[1], "-split" ) == 0) rc = ico_split_cmd( argc, argv ); else if (str_case_cmp( argv[1], "-info" ) == 0) rc = ico_info( argc, argv ); else { usage(); fprintf( stderr, "%s: Expected \"-merge\", \"-split\", or \"-info\".\n", ARGV0 ); } return rc; }