/* png.c Copyright (c) 2003/2004/2005 Matthias Kramm This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #include #ifdef EXPORT #undef EXPORT #endif #ifdef PNG_INLINE_EXPORTS #define EXPORT static #else #define EXPORT #include "png.h" #endif typedef unsigned u32; typedef struct _COL { unsigned char a,r,g,b; } COL; static int png_read_chunk(char (*head)[4], int*destlen, unsigned char**destdata, FILE*fi) { unsigned int len; unsigned char blen[4]; if(destlen) *destlen=0; if(destdata) *destdata=0; if(!fread(&blen, 4, 1, fi)) { return 0; } if(!fread(head, 4, 1, fi)) { return 0; } len = blen[0]<<24|blen[1]<<16|blen[2]<<8|blen[3]; if(destlen) *destlen = len; if(destdata) { if(!len) { *destdata = 0; } else { *destdata = (unsigned char*)malloc(len); if(!fread(*destdata, len, 1, fi)) { *destdata = 0; if(destlen) *destlen=0; return 0; } } fseek(fi, 4, SEEK_CUR); } else { fseek(fi, len+4, SEEK_CUR); } return 1; } static unsigned int png_get_dword(FILE*fi) { unsigned int a; unsigned char b[4]; fread(&b,4,1,fi); return b[0]<<24|b[1]<<16|b[2]<<8|b[3]; } struct png_header { unsigned width; unsigned height; int bpp; int mode; }; static int png_read_header(FILE*fi, struct png_header*header) { char id[4]; int len; int ok=0; unsigned char head[8] = {137,80,78,71,13,10,26,10}; unsigned char head2[8]; unsigned char*data; fread(head2,8,1,fi); if(strncmp((const char*)head,(const char*)head2,4)) return 0; // not a png file while(png_read_chunk(&id, &len, &data, fi)) { //printf("Chunk: %c%c%c%c (len:%d)\n", id[0],id[1],id[2],id[3], len); if(!strncmp(id, "IHDR", 4)) { char a,b,c,f,i; if(len < 8) exit(1); header->width = data[0]<<24|data[1]<<16|data[2]<<8|data[3]; header->height = data[4]<<24|data[5]<<16|data[6]<<8|data[7]; a = data[8]; // should be 8 b = data[9]; // should be 3(indexed) or 2(rgb) c = data[10]; // compression mode (0) f = data[11]; // filter mode (0) i = data[12]; // interlace mode (0) if(b!=0 && b!=4 && b!=2 && b!=3 && b!=6) { fprintf(stderr, "Image mode %d not supported!\n", b); return 0; } if(a!=8 && (b==2 || b==6)) { printf("Bpp %d in mode %d not supported!\n", b, a); return 0; } if(c!=0) { printf("Compression mode %d not supported!\n", c); return 0; } if(f!=0) { printf("Filter mode %d not supported!\n", f); return 0; } if(i!=0) { printf("Interlace mode %d not supported!\n", i); return 0; } //printf("%dx%d bpp:%d mode:%d comp:%d filter:%d interlace:%d\n",header->width, header->height, a,b,c,f,i); header->bpp = a; header->mode = b; ok = 1; } free(data); } return ok; } typedef unsigned char byte; #define ABS(a) ((a)>0?(a):(-(a))) static inline byte PaethPredictor (byte a,byte b,byte c) { // a = left, b = above, c = upper left int p = a + b - c; // initial estimate int pa = ABS(p - a); // distances to a, b, c int pb = ABS(p - b); int pc = ABS(p - c); // return nearest of a,b,c, // breaking ties in order a,b,c. if (pa <= pb && pa <= pc) return a; else if (pb <= pc) return b; else return c; } static void applyfilter1(int mode, unsigned char*src, unsigned char*old, unsigned char*dest, unsigned width) { int x; unsigned char last=0; unsigned char upperlast=0; if(mode==0) { for(x=0;x 0xffffffff) return 0; unsigned long imagedatalen = (unsigned long)imagedatalen_64; imagedata = (unsigned char*)malloc(imagedatalen); fseek(fi,8,SEEK_SET); while(!feof(fi)) { if(!png_read_chunk(&tagid, &len, &data, fi)) break; if(!strncmp(tagid, "IEND", 4)) { break; } if(!strncmp(tagid, "PLTE", 4)) { palette = data; palettelen = len/3; data = 0; //don't free data //printf("%d colors in palette\n", palettelen); } if(!strncmp(tagid, "tRNS", 4)) { if(header.mode == 3) { alphapalette = data; alphapalettelen = len; data = 0; //don't free data //printf("found %d alpha colors\n", alphapalettelen); } else if(header.mode == 0 || header.mode == 2) { int t; if(header.mode == 2) { alphacolor[0] = data[1]; alphacolor[1] = data[3]; alphacolor[2] = data[5]; } else { alphacolor[0] = alphacolor[1] = alphacolor[2] = data[1]; } hasalphacolor = 1; } } if(!strncmp(tagid, "IDAT", 4)) { if(!zimagedata) { zimagedatalen = len; zimagedata = (unsigned char*)malloc(len); memcpy(zimagedata,data,len); } else { zimagedata = (unsigned char*)realloc(zimagedata, zimagedatalen+len); memcpy(&zimagedata[zimagedatalen], data, len); zimagedatalen += len; } } if(!strncmp(tagid, "tEXt", 4)) { /*int t; printf("Image Text: "); for(t=0;t=32 && data[t]<128) printf("%c", data[t]); else printf("?"); } printf("\n");*/ } if(data) { free(data); data=0; } } fclose(fi); if(!zimagedata || uncompress(imagedata, &imagedatalen, zimagedata, zimagedatalen) != Z_OK) { printf("Couldn't uncompress %s!\n", sname); if(zimagedata) free(zimagedata); return 0; } free(zimagedata); *destwidth = header.width; *destheight = header.height; data2 = (unsigned char*)malloc(header.width*header.height*4); if(header.mode == 4) { int i,s=0; int x,y; int pos=0; unsigned char* old= (unsigned char*)malloc(header.width*2); memset(old, 0, header.width*2); *destdata = data2; for(y=0;y=0;x--) { unsigned char gray = dest[x*2+0]; unsigned char alpha = dest[x*2+1]; dest[x*4+0] = alpha; dest[x*4+1] = gray; dest[x*4+2] = gray; dest[x*4+3] = gray; } } free(old); free(imagedata); } else if(header.mode == 6 || header.mode == 2) { int i,s=0; int x,y; int pos=0; *destdata = data2; unsigned char* firstline = malloc(header.width*4); memset(firstline,0,header.width*4); for(y=0;y>header.bpp); palettelen = 1<32 bit conversion */ for(i=0;i>(16-header.bpp-(s&7)))&v; s+=header.bpp; } src = tmpline; pos+=(header.width*header.bpp+7)/8; } if(!y) { memset(destline,0,header.width); old = &destline[y*header.width]; } else { old = tmpline; } applyfilter1(mode, src, old, destline, header.width); memcpy(tmpline,destline,header.width); for(x=0;xnum - c1->num; } static colornum_t* getColors(COL*image, int size, int*num) { unsigned char*colexists = malloc((256*256*256)/8); memset(colexists, 0, (256*256*256)/8); int t; int count=0; /* find all different colors in the image */ for(t=0;t= col) max=i; else min=i+1; } assert(colors[i].color==col); colors[i].num++; } free(colexists); *num = count; return colors; } static void getOptimalPalette(COL*image, int size, int palettesize, COL*palette) { int num; memset(palette, 0, sizeof(COL)*256); colornum_t*colors = getColors(image, size, &num); assert(palettesize<=256); qsort(colors, num, sizeof(colornum_t), compare_colors); if(num<=palettesize) { /* if there are not more than palettesize different colors in the image anyway, we are done */ int t; for(t=0;t>8; palette[t].b = colors[t].color>>16; palette[t].a = 255; } return; } if(num>2048) { /* if there are too many different colors, pick the ones that occur most often */ num = 2048; } colornum_t*centers = malloc(sizeof(colornum_t)*palettesize); int t; for(t=0;t= (palettesize+num)*2) { fprintf(stderr, "Warning: didn't find optimal palette\n"); break; } change = 0; int s,t; for(s=0;s>0&0xff) - (colors[t].color>>0&0xff)); distance += abs((centers[s].color>>8&0xff) - (colors[t].color>>8&0xff)); distance += abs((centers[s].color>>16&0xff) - (colors[t].color>>16&0xff)); distance *= colors[t].num; if(distance>0)&0xff)*colors[t].num; g += ((colors[t].color>>8)&0xff)*colors[t].num; b += ((colors[t].color>>16)&0xff)*colors[t].num; count+=colors[t].num; } } if(!count) { int random = rand()%num; centers[s].color = colors[random].color; centers[s].num = 0; change = 1; } else { r /= count; g /= count; b /= count; centers[s].color = r|g<<8|b<<16; centers[s].num = count; } } } free(belongsto); free(colors); for(t=0;t>8; palette[t].b = centers[t].color>>16; palette[t].a = 255; } free(centers); } static int sqr(const int x) {return x*x;} static void png_quantize_image(unsigned char*_image, int size, int numcolors, unsigned char**newimage, COL*palette) { COL*image = (COL*)_image; getOptimalPalette(image, size, numcolors, palette); *newimage = (unsigned char*)malloc(size); int t; for(t=0;t> 1); } crc32_table[t] = c; } } static inline void png_write_byte(FILE*fi, unsigned char byte) { fwrite(&byte,1,1,fi); mycrc32 = crc32_table[(mycrc32 ^ byte) & 0xff] ^ (mycrc32 >> 8); } static long png_start_chunk(FILE*fi, char*type, int len) { unsigned char mytype[4]={0,0,0,0}; unsigned char mylen[4]; long filepos; mylen[0] = len>>24; mylen[1] = len>>16; mylen[2] = len>>8; mylen[3] = len; memcpy(mytype,type,strlen(type)); filepos = ftell(fi); fwrite(&mylen, 4, 1, fi); mycrc32=0xffffffff; png_write_byte(fi,mytype[0]); png_write_byte(fi,mytype[1]); png_write_byte(fi,mytype[2]); png_write_byte(fi,mytype[3]); return filepos; } static void png_patch_len(FILE*fi, int pos, int len) { unsigned char mylen[4]; long filepos; mylen[0] = len>>24; mylen[1] = len>>16; mylen[2] = len>>8; mylen[3] = len; fseek(fi, pos, SEEK_SET); fwrite(&mylen, 4, 1, fi); fseek(fi, 0, SEEK_END); } static void png_write_bytes(FILE*fi, unsigned char*bytes, int len) { int t; for(t=0;t>24); png_write_byte(fi,dword>>16); png_write_byte(fi,dword>>8); png_write_byte(fi,dword); } static void png_end_chunk(FILE*fi) { u32 tmp = mycrc32^0xffffffff; unsigned char tmp2[4]; tmp2[0] = tmp>>24; tmp2[1] = tmp>>16; tmp2[2] = tmp>>8; tmp2[3] = tmp; fwrite(&tmp2,4,1,fi); } #define ZLIB_BUFFER_SIZE 16384 static long compress_line(z_stream*zs, Bytef*line, int len, FILE*fi) { long size = 0; zs->next_in = line; zs->avail_in = len; while(1) { int ret = deflate(zs, Z_NO_FLUSH); if (ret != Z_OK) { fprintf(stderr, "error in deflate(): %s", zs->msg?zs->msg:"unknown"); return 0; } if(zs->avail_out != ZLIB_BUFFER_SIZE) { int consumed = ZLIB_BUFFER_SIZE - zs->avail_out; size += consumed; png_write_bytes(fi, zs->next_out - consumed , consumed); zs->next_out = zs->next_out - consumed; zs->avail_out = ZLIB_BUFFER_SIZE; } if(!zs->avail_in) { break; } } return size; } static int test_line(z_stream*zs_orig, Bytef*line, int linelen) { z_stream zs; int ret = deflateCopy(&zs, zs_orig); if(ret != Z_OK) { fprintf(stderr, "Couldn't copy stream\n"); return 0; } zs.next_in = line; zs.avail_in = linelen; long size = 0; int mode = Z_SYNC_FLUSH; while(1) { int ret = deflate(&zs, mode); if (ret != Z_OK && ret != Z_STREAM_END) { fprintf(stderr, "error in deflate(): %s (mode %s, %d bytes remaining)\n", zs.msg?zs.msg:"unknown", mode==Z_SYNC_FLUSH?"Z_SYNC_FLUSH":"Z_FINISH", zs.avail_in); return 0; } if(zs.avail_out != ZLIB_BUFFER_SIZE) { int consumed = ZLIB_BUFFER_SIZE - zs.avail_out; size += consumed; zs.next_out = zs.next_out - consumed; zs.avail_out = ZLIB_BUFFER_SIZE; } if (ret == Z_STREAM_END) { break; } if(!zs.avail_in) { mode = Z_FINISH; } } ret = deflateEnd(&zs); if (ret != Z_OK) { fprintf(stderr, "error in deflateEnd(): %s\n", zs.msg?zs.msg:"unknown"); return 0; } return size; } static int finishzlib(z_stream*zs, FILE*fi) { int size = 0; int ret; while(1) { ret = deflate(zs, Z_FINISH); if (ret != Z_OK && ret != Z_STREAM_END) { fprintf(stderr, "error in deflate(finish): %s\n", zs->msg?zs->msg:"unknown"); return 0; } if(zs->avail_out != ZLIB_BUFFER_SIZE) { int consumed = ZLIB_BUFFER_SIZE - zs->avail_out; size += consumed; png_write_bytes(fi, zs->next_out - consumed , consumed); zs->next_out = zs->next_out - consumed; zs->avail_out = ZLIB_BUFFER_SIZE; } if (ret == Z_STREAM_END) { break; } } ret = deflateEnd(zs); if (ret != Z_OK) { fprintf(stderr, "error in deflateEnd(): %s\n", zs->msg?zs->msg:"unknown"); return 0; } return size; } static inline u32 color_hash(COL*col) { u32 col32 = *(u32*)col; u32 hash = (col32 >> 17) ^ col32; hash ^= ((hash>>8) + 1) ^ hash; return hash; } static int png_get_number_of_palette_entries(COL*img, unsigned width, unsigned height, COL*palette, char*has_alpha) { int len = width*height; int t; int palsize = 0; int size[256]; int palette_overflow = 0; u32 lastcol32 = 0; memset(size, 0, sizeof(size)); u32*pal = (u32*)malloc(65536*sizeof(u32)); int*count = (int*)malloc(65536*sizeof(int)); assert(sizeof(COL)==sizeof(u32)); assert(width && height); lastcol32 = (*(u32*)&img[0])^0xffffffff; // don't match for(t=0;t0?5:2; //don't apply y-direction filter in first line int bytes_per_pixel = bpp>>3; int w = width*bytes_per_pixel; int back_x = bytes_per_pixel; int back_y = y?width*bytes_per_pixel:0; unsigned char*pairs[5]; pairs[0] = calloc(1, 8192); pairs[1] = calloc(1, 8192); pairs[2] = calloc(1, 8192); pairs[3] = calloc(1, 8192); pairs[4] = calloc(1, 8192); unsigned char old[5]; int l = bytes_per_pixel - 1; old[0] = src[l]; old[1] = src[l]; old[2] = src[l] - src[l-back_y]; old[3] = src[l] - src[l-back_y]; old[4] = src[l] - PaethPredictor(0, src[l-back_y], 0); int different_pairs[5] = {0,0,0,0,0}; int x; for(x=bytes_per_pixel;x>3; int b = 1<<(v&7); if(!pairs[i][p]&b) { pairs[i][p]|=b; different_pairs[i]++; } } memcpy(old, dest, sizeof(old)); } int f; int best_nr = 0; int best_energy = INT_MAX; for(f=0;f0?5:2; //don't apply y-direction filter in first line int f; int best_energy = INT_MAX; int w = width*(bpp/8); unsigned char* pairs = malloc(8192); assert(bpp==8 || bpp==32); for(f=0;f>3; int b = 1<<(v&7); if(!pairs[p]&b) { pairs[p]|=b; different_pairs ++; } } int energy = different_pairs; if(energy256) { bpp = 32; cols = 0; format = 5; } else if(!numcolors) { int num = png_get_number_of_palette_entries((COL*)data, width, height, palette, &has_alpha); if(num<=255) { //printf("image has %d different colors (alpha=%d)\n", num, has_alpha); data2 = malloc(width*height); png_map_to_palette((COL*)data, data2, width*height, palette, num); data = data2; bpp = 8; cols = num; format = 3; } else { bpp = 32; cols = 0; format = 5; } } else { bpp = 8; cols = numcolors; format = 3; png_quantize_image(data, width*height, numcolors, &data, palette); } fi = fopen(filename, "wb"); if(!fi) { perror("open"); return; } fwrite(head,sizeof(head),1,fi); png_start_chunk(fi, "IHDR", 13); png_write_dword(fi,width); png_write_dword(fi,height); png_write_byte(fi,8); if(format == 3) png_write_byte(fi,3); //indexed else if(format == 5 && alpha==0) png_write_byte(fi,2); //rgb else if(format == 5 && alpha==1) png_write_byte(fi,6); //rgba else return; png_write_byte(fi,0); //compression mode png_write_byte(fi,0); //filter mode png_write_byte(fi,0); //interlace mode png_end_chunk(fi); if(format == 3) { png_start_chunk(fi, "PLTE", cols*3); for(t=0;t=2) continue; // don't do y direction filters in the first row line[0]=filtermode; //filter type if(bpp==8) png_apply_specific_filter_8(filtermode, line+1, &data[y*srcwidth], width); else png_apply_specific_filter_32(filtermode, line+1, &data[y*srcwidth], width); int size = test_line(&zs, line, linelen); if(size < bestsize) { memcpy(bestline, line, linelen); bestsize = size; } } idatsize += compress_line(&zs, bestline, linelen, fi); } free(bestline); #else for(y=0;y