/* png.c - Jon Mayo - PUBLIC DOMAIN - July 29, 2003 */ /* History : * 2003-07-29 : initial version * 2006-08-29 : modified to support transparency chunk 'tRNS' * 2008-03-12 : modified to support gAMA, sBIT, bKGD, and tIME. different crc32 * implementation. cleaned up error handling. detects IEND chunk * is always present at end of file. */ #include #include #include #include #include "byte_order.h" typedef enum { PNG_TYPE_IHDR=BE32(0x49484452), PNG_TYPE_PLTE=BE32(0x504C5445), PNG_TYPE_IDAT=BE32(0x49444154), PNG_TYPE_IEND=BE32(0x49454E44), PNG_TYPE_tRNS=BE32(0x74524E53), PNG_TYPE_sBIT=BE32(0x73424954), PNG_TYPE_gAMA=BE32(0x67414d41), PNG_TYPE_bKGD=BE32(0x624B4744), PNG_TYPE_cHRM=BE32(0x6348524D), PNG_TYPE_pHYs=BE32(0x70485973), PNG_TYPE_hIST=BE32(0x68495354), PNG_TYPE_tIME=BE32(0x74494D45) } png_type_t; typedef enum { PNG_COLOR_GRAYSCALE=0, PNG_COLOR_RGB=2, PNG_COLOR_PALETTE=3, PNG_COLOR_GRAYSCALE_ALPHA=4, PNG_COLOR_RGBA=6 } png_color_t; struct png_info { unsigned width, height; unsigned bit_depth; png_color_t color_type; double gamma; }; /**** CRC32 : does calculate 4 bits at a time for a smaller table ****/ /* ANSI X3.66 crc-32 polynomial: * x^0 + x^1 + x^2 + x^4 + x^5 + x^7 + x^8 + x^10 + * x^11 + x^12 + x^16 + x^22 + x^23 + x^26 + x^32 */ static const uint32_t crc32_tab[16] = { 0x00000000, 0x1db71064, 0x3b6e20c8, 0x26d930ac, 0x76dc4190, 0x6b6b51f4, 0x4db26158, 0x5005713c, 0xedb88320, 0xf00f9344, 0xd6d6a3e8, 0xcb61b38c, 0x9b64c2b0, 0x86d3d2d4, 0xa00ae278, 0xbdbdf21c }; static uint32_t crc_calc(uint32_t crc, size_t len, const void *buf) { size_t n; for(n=0;n> 4) ^ crc32_tab[(crc & 0xf) ^ (((const uint8_t*)buf)[n] & 0xf)]; crc = (crc >> 4) ^ crc32_tab[(crc & 0xf) ^ (((const uint8_t*)buf)[n] >> 4)]; } return crc; } /**** Byte order parsing ***/ /** reads a big-endian 32-bit integer */ static uint32_t get_be_uint32(const void *data, size_t off) { uint32_t ret; const unsigned char *c=(const unsigned char*)data+off; ret=(uint32_t)c[0]<<24|(uint32_t)c[1]<<16|(uint32_t)c[2]<<8|(uint32_t)c[3]; return ret; } /** reads a big-endian 16-bit integer */ static uint16_t get_be_uint16(const void *data, size_t off) { uint16_t ret; const unsigned char *c=(const unsigned char*)data+off; ret=(uint16_t)c[0]<<8|c[1]; return ret; } /** reads an 8-bit unsigned integer **/ static uint8_t get_uint8(const void *data, size_t off) { return ((const uint8_t*)data)[off]; } /**** PNG parsing ****/ static int png_sanity_ihdr(struct png_info *pi) { /* limit the image size to 65536 x 65536 max. * and only 536870912 pixels total. */ if(pi->width < 0x10000 && pi->height<0x10000 && pi->width*pi->height<0x20000000 && (pi->color_type==PNG_COLOR_GRAYSCALE || pi->color_type==PNG_COLOR_RGB || pi->color_type==PNG_COLOR_PALETTE || pi->color_type==PNG_COLOR_GRAYSCALE_ALPHA || pi->color_type==PNG_COLOR_RGBA) ) { return 1; } else { fprintf(stderr, "error: PNG image IHDR doesn't look quite right.\n"); return 0; } } static void png_init_info(struct png_info *pi) { memset(pi, 0, sizeof *pi); } static int png_read_sig(FILE *f, struct png_info *pi) { uint8_t sig[8]; int result; result=fread(sig, sizeof sig, 1, f); if(result<=0) { return 0; } return !memcmp("\211PNG\r\n\032\n", sig, sizeof sig); } static int png_read_chunk(FILE *f, struct png_info *pi, int *length, png_type_t *type, void **data) { struct { uint32_t length; uint32_t type; } chunk_header; int chunk_data_len; uint8_t *chunk_data; uint32_t crc32; int result; uint32_t crc32_c; /* read in the header */ result=fread(&chunk_header, sizeof chunk_header, 1, f); if(result<=0) { return 0; } /* convert the endian of the length field */ chunk_data_len=BE32(chunk_header.length); /* read the data if it's not 0 length. */ if(chunk_data_len) { chunk_data=malloc(chunk_data_len); result=fread(chunk_data, chunk_data_len, 1, f); /* TODO: handle end of file appropriately */ if(result<=0) goto error; } else { chunk_data=NULL; } /* read the CRC32 of the chunk */ result=fread(&crc32, sizeof crc32, 1, f); if(result<=0) goto error; /* check CRC32 of type and data sections of the chunk */ crc32_c=~crc_calc(crc_calc(~0, sizeof chunk_header.type, &chunk_header.type), chunk_data_len, chunk_data); if(crc32_c!=BE32(crc32)) { fprintf(stderr, "CRC checksum failed while reading chunk\n"); goto error; } /* load up the pass by reference values */ if(type) *type=chunk_header.type; if(length) *length=chunk_data_len; if(data) *data=chunk_data; return 1; error: fprintf(stderr, "Error reading chunk data!\n"); free(chunk_data); return 0; } static int png_read_chunk_all(FILE *f, struct png_info *pi) { void *chunk_data; int chunk_data_len; int iend_found=0; /* has the IEND chunk been read? */ png_type_t chunk_type; while(png_read_chunk(f, pi, &chunk_data_len, &chunk_type, &chunk_data)) { if(iend_found) { fprintf(stderr, "IEND. chunks where found trailling IEND\n"); goto error; } switch(chunk_type) { case PNG_TYPE_IHDR: { /* make sure the amount of header data we got is correct */ if(chunk_data_len<13) { fprintf(stderr, "Corrupt IHDR! (less than 13 bytes)\n"); goto error; } pi->width=get_be_uint32(chunk_data, 0); pi->height=get_be_uint32(chunk_data, 4); pi->bit_depth=get_uint8(chunk_data, 8); pi->color_type=get_uint8(chunk_data, 9); printf("IHDR. %ux%u, %ubpp\n", pi->width, pi->height, pi->bit_depth); /* check to see if the values we loaded look okay */ if(!png_sanity_ihdr(pi)) goto error; break; } case PNG_TYPE_PLTE: printf("PLTE. len=%d\n", chunk_data_len); break; case PNG_TYPE_IDAT: if(chunk_data_len==1) { printf("IDAT. empty!\n"); } else { printf("IDAT. len=%d\n", chunk_data_len); } break; case PNG_TYPE_IEND: printf("IEND. len=%d\n", chunk_data_len); iend_found=1; /* after this should be EOF */ break; case PNG_TYPE_tRNS: if(chunk_data_len==1) { printf("tRNS. color=0x%02X\n", get_uint8(chunk_data, 0)); } else { printf("tRNS. len=%d\n", chunk_data_len); } break; case PNG_TYPE_sBIT: if(pi->color_type==PNG_COLOR_GRAYSCALE && chunk_data_len==1) { printf("sBIT. defined\n"); } else if (pi->color_type==PNG_COLOR_RGB && chunk_data_len==3) { printf("sBIT. defined\n"); } else if (pi->color_type==PNG_COLOR_PALETTE && chunk_data_len==3) { printf("sBIT. defined\n"); } else if (pi->color_type==PNG_COLOR_GRAYSCALE_ALPHA && chunk_data_len==2) { printf("sBIT. defined\n"); } else if (pi->color_type==PNG_COLOR_RGBA && chunk_data_len==4) { printf("sBIT. defined\n"); } else { printf("sBIT. invalid color type. chunk len=%d\n", chunk_data_len); } break; case PNG_TYPE_gAMA: if(chunk_data_len==4) { pi->gamma=get_be_uint32(chunk_data, 0)/100000.; printf("gAMA. gamma=%f\n", pi->gamma); } else { printf("gAMA. invalid chunk len=%d\n", chunk_data_len); } break; case PNG_TYPE_bKGD: if(pi->color_type==PNG_COLOR_GRAYSCALE && chunk_data_len==2) { printf("bKGD. background=0x%04x\n", get_be_uint16(chunk_data, 0)); } else if ((pi->color_type==PNG_COLOR_RGB && chunk_data_len==6) || (pi->color_type==PNG_COLOR_RGBA && chunk_data_len==6)) { printf("bKGD. background=R:0x%04x G:0x%04x B:0x%04x\n", get_be_uint16(chunk_data, 0), get_be_uint16(chunk_data, 2), get_be_uint16(chunk_data, 4) ); } else if (pi->color_type==PNG_COLOR_PALETTE && chunk_data_len==1) { printf("bKGD. background=0x%02x\n", get_uint8(chunk_data, 0)); } else if (pi->color_type==PNG_COLOR_GRAYSCALE_ALPHA && chunk_data_len==2) { printf("bKGD. background=0x%04x\n", get_be_uint16(chunk_data, 0)); } else { printf("bKGD. invalid color type. chunk len=%d\n", chunk_data_len); } break; case PNG_TYPE_cHRM: /* Primary chromaticities and white point */ /* TODO: do something with this */ printf("cHRM. chunk len=%d\n", chunk_data_len); break; case PNG_TYPE_pHYs: printf("pHYs: TODO\n"); break; case PNG_TYPE_hIST: printf("hIST: TODO\n"); break; case PNG_TYPE_tIME: printf("tIME: year=%4u month=%u day=%u %02u:%02u:%02u\n", get_be_uint16(chunk_data, 0), get_uint8(chunk_data, 2), get_uint8(chunk_data, 3), get_uint8(chunk_data, 4), get_uint8(chunk_data, 5), get_uint8(chunk_data, 6) ); break; default: fprintf(stderr, "Unknown type=%08X '%c%c%c%c' len=%d\n", BE32(chunk_type), chunk_type&255, chunk_type>>8&255, chunk_type>>16&255, chunk_type>>24&255, chunk_data_len ); } free(chunk_data); } return iend_found; /* only successful if IEND was found */ error: free(chunk_data); return 0; } #ifdef STAND_ALONE int main(int argc, char **argv) { FILE *f; struct png_info pi; int result; int i; for(i=1;i