/*---------------------------------------------------------------------------- Generates HTML tag with proper WIDTH and HEIGHT from a given .GIF, .PNG, or .JPG file. Chris Giese http://SisAndHappy.com/ChrisGiese This code is public domain (no copyright). You can do whatever you want with it. Modified Feb 24, 2012: - read_le/read_be redone yet again Modified Feb 24, 2011: - "width" and "height" now come before "src" in the tag, and width and height are both printed with a field width of 4 digits; letting you sort the output by width or height Modified March 2, 2007: - Eliminated #if statement that depended on CPU byte order - Some style changes in code Modified March 3, 2007: - Fixed stupid unsigned-char-promoted-to-int bug in read_le/be16/32 routines Only limited error-checking is done on the image. For example, this program produces output for most of the invalid images in Willem van Schaik's PNGSUITE. ----------------------------------------------------------------------------*/ #include /* memcmp() */ /* SEEK_..., FILE, stderr, fprintf(), printf() */ #include /* fopen(), fseek(), fread(), fclose() */ static unsigned long g_wd, g_ht; /****************************************************************************/ static unsigned read_le16(const void *buf0) { const unsigned char *buf = buf0; unsigned rv; rv = buf[1]; rv <<= 8; rv |= buf[0]; return rv; } /****************************************************************************/ static unsigned read_be16(const void *buf0) { const unsigned char *buf = buf0; unsigned rv; rv = buf[0]; /* MSB */ rv <<= 8; rv |= buf[1]; /* LSB */ return rv; } /****************************************************************************/ static unsigned long read_be32(const void *buf0) { const unsigned char *buf = buf0; unsigned long rv; rv = buf[0]; /* MSB */ rv <<= 8; rv |= buf[1]; rv <<= 8; rv |= buf[2]; rv <<= 8; rv |= buf[3]; /* LSB */ return rv; } /***************************************************************************** File Size offset (bytes) Value(s) Meaning ------ ------- ----------------- ------------------------------------ 0 6 "GIF87a"/"GIF89a" Magic values that identify .GIF file 6 2 - "Screen" (bounding box) width 8 2 - "Screen" (bounding box) height 10 1 - Packed fields byte 11 1 - Background color 12 1 - Unused (GIF87) or aspect ratio (GIF89) 13 *****************************************************************************/ static int check_gif(FILE *f) { unsigned char buf[13]; /* read file header (the "screen descriptor") */ fseek(f, 0, SEEK_SET); if(fread(buf, 1, 13, f) != 13) return +1; /* too short; not GIF */ /* validate */ if((memcmp(&buf[0], "GIF87a", 6) && memcmp(&buf[0], "GIF89a", 6))) return +1; g_wd = read_le16(&buf[6]); g_ht = read_le16(&buf[8]); return 0; /* success */ } /***************************************************************************** File Size offset (bytes) Value(s) Meaning ------ ------- -------- ------- 0 8 "\x89""PNG\r\n\x1A\n" Magic values that identify .PNG file 8 4 13 Typical IHDR chunk length 12 4 "IHDR" Other critical chunks: "IEND", "IDAT" 16 4 - Image width 20 4 - Image height 24 1 - Image depth (bits per pixel) 25 1 - Color type: 0=grayscale,2=RGB, 3=palette,4=gray+alpha,6=RGB+alpha 26 1 0 Compression type: 0=flate 27 1 0 Filter scheme: 0=default 28 1 - Interlace type: 0=none, 1=Adam7 29 *****************************************************************************/ static int check_png(FILE *f) { unsigned char buf[24]; /* read 8-byte file header + 8-byte IHDR chunk header + first 8 bytes of the IHDR chunk */ fseek(f, 0, SEEK_SET); if(fread(buf, 1, 24, f) != 24) return +1; /* too short; not PNG */ /* validate */ if(memcmp(&buf[0], "\x89""PNG\r\n\x1A\n", 8)) return +1; /* look for IHDR chunk read_be32(&buf[8]) is the IHDR chunk length */ if(memcmp(&buf[12], "IHDR", 4)) return -1; /* invalid PNG file */ /* get width and height */ g_wd = read_be32(&buf[16]); g_ht = read_be32(&buf[20]); return 0; } /***************************************************************************** File Size offset (bytes) Value(s) Meaning ------ ------- -------- ------- 0 2 0xFF, 0xD8 JFIF Start Of Image (SOI) segment (note: some JPGs have APP1 [Exif] blocks here instead of APP0) (note: some JPGs have other blocks [such as comments] before the APPn block) 2 2 0xFF, 0xE0 JFIF APP0 segment 4 2 16 Length of APP0 segment 6 5 "JFIF\x00" Magic values that identify .JPG file 11 2 - JFIF major, minor version 13 1 - 0=aspect ratio, 1=dots/in, 2=dots/cm 14 2 - Horizontal aspect ratio or resolution 16 2 - Vertical aspect ratio or resolution 18 1 usu. 0 Thumbnail width 19 1 usu. 0 Thumbnail height 20 N 2 0xFF, 0xC0/0xC2 JPEG Start Of Frame (SOF) marker 0xFFC2 marker is for interlaced JPEG N+2 2 8? SOF chunk len N+4 1 usu. 8 8=8-bit color, 12=12-bit color N+5 2 - Image height N+7 2 - Image width N+9 1 usu. 3 1=grayscale, 3=color (YCbCr) N+10 xxx - maybe check that at least one of each of these chunks is present: DQT (quantization table), DHT (Huffman table), SOF (Start Of Frame), SOS (Start Of Scan) xxx - maybe make sure at least one APPn chunk is present xxx - maybe validate all chunks until you get to SOS. *****************************************************************************/ static int check_jpg(FILE *f) { unsigned marker, len; unsigned char buf[6]; fseek(f, 0, SEEK_SET); /* read header and validate */ if(fread(buf, 1, 6, f) != 6) return +1; /* too short; not JPEG */ /* 0xFFD8=SOI; Start Of Image */ marker = read_be16(&buf[0]); if(marker != 0xFFD8) return +1; /* At first, I checked for the APP0 segment here, but then I found a JPEG with the Exif (APP1) segment instead. Then I tested for any segment from APP0-APP15...only to later find a JPEG with a comment block between the SOI and the APP0. Executive summary: check only for SOI to validate a JPEG file. */ while(1) { marker = read_be16(&buf[2]); len = read_be16(&buf[4]); if(len < 2) break; /* 0xFFCn=SOFn (Start Of Frame), except for 0xFFC4=DHT (Define Huffman Table), 0xFFC8=JPG (Extension), and 0xFFCC=DAC (Define Arithmetic Coding) */ if(marker >= 0xFFC0 && marker <= 0xFFCF && marker != 0xFFC4 && marker != 0xFFC8 && marker != 0xFFCC) { /* read the SOF chunk, get image width and height, and return */ if(fread(buf, 1, 6, f) != 6) return -1; g_ht = read_be16(&buf[1]); g_wd = read_be16(&buf[3]); return 0; } /* advance to next chunk */ fseek(f, len - 2, SEEK_CUR); /* read chunk header */ if(fread(&buf[2], 1, 4, f) != 4) break; } return -1; /* EOF before SOF chunk seen */ } /****************************************************************************/ int main(int arg_c, char *arg_v[]) { static int (* const check[])(FILE *) = { check_gif, check_png, check_jpg }; static const char *type[] = { "GIF", "PNG", "JPEG" }; /**/ int i, k = 1; unsigned j; FILE *in; /* check command-line args */ if(arg_c < 2) { printf("Generates HTML tag with proper WIDTH and HEIGHT\n" "from a given image file\n"); return 1; } /* for each arg */ for(i = 1; i < arg_c; i++) { /* open input file */ in = fopen(arg_v[i], "rb"); if(in == NULL) { fprintf(stderr, "Error: can't open input file " "'%s'\n", arg_v[i]); continue; } /* for each image file type we know about... */ for(j = 0; j < sizeof(check) / sizeof(check[0]); j++) { /* ...try to get image width and height from input image file */ k = check[j](in); if(k <= 0) break; } /* done with input file; close it */ fclose(in); /* success! */ if(k == 0) { printf("\n", g_wd, g_ht, arg_v[i]); } /* it's an image file type we recognize, but there's something wrong with the file */ else if(k < 0) { fprintf(stderr, "Error: invalid %s file '%s'\n", type[j], arg_v[i]); } /* unrecognized image file format */ else { fprintf(stderr, "Error: format of image file '%s' " "is unknown\n", arg_v[i]); } } return 0; }