From: Matthias Kramm <kramm@quiss.org>
Date: Wed, 31 Mar 2010 00:00:05 +0000 (-0700)
Subject: improved png compression
X-Git-Tag: version-0-9-1~63
X-Git-Url: http://git.asbjorn.biz/?a=commitdiff_plain;h=27fe979dab6d4d058d5e608b8057f698804c93bf;p=swftools.git

improved png compression
---

diff --git a/lib/png.c b/lib/png.c
index 1738151..7190553 100644
--- a/lib/png.c
+++ b/lib/png.c
@@ -23,6 +23,7 @@
 #include <math.h>
 #include <fcntl.h>
 #include <zlib.h>
+#include <limits.h>
 
 #ifdef EXPORT
 #undef EXPORT
@@ -830,11 +831,10 @@ static colornum_t* getColors(COL*image, int size, int*num)
     return colors;
 }
 
-static COL* getOptimalPalette(COL*image, int size, int palettesize)
+static void getOptimalPalette(COL*image, int size, int palettesize, COL*palette)
 {
     int num;
-    COL* ret = malloc(sizeof(COL)*palettesize);
-    memset(ret, 0, sizeof(COL)*palettesize);
+    memset(palette, 0, sizeof(COL)*256);
     colornum_t*colors = getColors(image, size, &num);
 
     assert(palettesize<=256);
@@ -846,12 +846,12 @@ static COL* getOptimalPalette(COL*image, int size, int palettesize)
            the image anyway, we are done */
         int t;
         for(t=0;t<num;t++) {
-            ret[t].r = colors[t].color;
-            ret[t].g = colors[t].color>>8;
-            ret[t].b = colors[t].color>>16;
-            ret[t].a = 255;
+            palette[t].r = colors[t].color;
+            palette[t].g = colors[t].color>>8;
+            palette[t].b = colors[t].color>>16;
+            palette[t].a = 255;
         }
-        return ret;
+        return;
     }
 
     if(num>2048) {
@@ -928,22 +928,20 @@ static COL* getOptimalPalette(COL*image, int size, int palettesize)
     free(belongsto);
     free(colors);
     for(t=0;t<palettesize;t++) {
-        ret[t].r = centers[t].color;
-        ret[t].g = centers[t].color>>8;
-        ret[t].b = centers[t].color>>16;
-        ret[t].a = 255;
+        palette[t].r = centers[t].color;
+        palette[t].g = centers[t].color>>8;
+        palette[t].b = centers[t].color>>16;
+        palette[t].a = 255;
     }
     free(centers);
-    return ret;
 }
 
 static int sqr(const int x) {return x*x;}
 
-static void quantizeImage(unsigned char*_image, int size, int numcolors, unsigned char**newimage, COL**palette) 
+static void png_quantize_image(unsigned char*_image, int size, int numcolors, unsigned char**newimage, COL*palette) 
 {
     COL*image = (COL*)_image;
-    COL*pal= getOptimalPalette(image, size, numcolors);
-    *palette = pal;
+    getOptimalPalette(image, size, numcolors, palette);
     *newimage = (unsigned char*)malloc(size);
     int t;
     for(t=0;t<size;t++) {
@@ -952,15 +950,14 @@ static void quantizeImage(unsigned char*_image, int size, int numcolors, unsigne
         int s;
         for(s=0;s<numcolors;s++) {
             int distance = 0;
-            distance += sqr((pal[s].r) - (image[t].r))*5;
-            distance += sqr((pal[s].g) - (image[t].g))*6;
-            distance += sqr((pal[s].b) - (image[t].b))*4;
+            distance += sqr((palette[s].r) - (image[t].r))*5;
+            distance += sqr((palette[s].g) - (image[t].g))*6;
+            distance += sqr((palette[s].b) - (image[t].b))*4;
             if(distance<best) {
                 best = distance;
                 bestcol = s;
             }
         }
-        //image[t] = pal[bestcol];
         (*newimage)[t] = bestcol;
     }
 }
@@ -1146,7 +1143,130 @@ static int finishzlib(z_stream*zs, FILE*fi)
     return size;
 }
 
-static void filter_line8(int filtermode, unsigned char*dest, unsigned char*src, int width)
+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, int width, int 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;t<len;t++) {
+	u32 col32 = *(u32*)&img[t];
+	if(col32 == lastcol32)
+	    continue;
+	
+	if(img[t].a!=255)
+	    *has_alpha=1;
+	int i;
+	
+	u32 hash = color_hash(&img[t])&255;
+
+	int csize = size[hash];
+	u32*cpal = &pal[hash*256];
+	int*ccount = &count[hash*256];
+	for(i=0;i<csize;i++) {
+	    if(col32 == cpal[i]) {
+		ccount[i]++;
+		break;
+	    }
+	}
+	if(i==csize) {
+	    if(palsize==256) {
+		palette_overflow = 1;
+		break;
+	    }
+	    count[size[hash]] = 1;
+	    cpal[size[hash]++] = col32;
+	    palsize++;
+	}
+	lastcol32 = col32;
+    }
+    if(palette_overflow) {
+	free(pal);
+	*has_alpha=1;
+	return width*height;
+    }
+    if(palette) {
+	int i = 0;
+	int occurences[256];
+	for(t=0;t<256;t++) {
+	    int s;
+	    int csize = size[t];
+	    u32* cpal = &pal[t*256];
+	    int* ccount = &count[t*256];
+	    for(s=0;s<csize;s++) {
+		occurences[i] = ccount[s];
+		palette[i++] = *(COL*)(&cpal[s]);
+	    }
+	}
+	assert(i==palsize);
+	int j;
+	for(i=0;i<palsize-1;i++) {
+	    for(j=i+1;j<palsize;j++) {
+		if(occurences[j] < occurences[i]) {
+		    int o = occurences[i];
+		    COL c = palette[i];
+		    occurences[i] = occurences[j];
+		    palette[i] = palette[j];
+		    occurences[j] = o;
+		    palette[j] = c;
+		}
+	    }
+	}
+    }
+    free(pal);
+    free(count);
+    return palsize;
+}
+
+static void png_map_to_palette(COL*src, unsigned char*dest, int size, COL*palette, int palette_size)
+{
+    int t;
+    int palette_hash[1024];
+    memset(palette_hash, 0, sizeof(int)*1024);
+    
+    for(t=0;t<palette_size;t++) {
+	u32 hash = color_hash(&palette[t])&1023;
+	while(palette_hash[hash])
+	    hash = (hash+1)&1023;
+	palette_hash[hash] = t;
+    }
+
+    for(t=0;t<size;t++) {
+	u32 hash = color_hash(&src[t]);
+	int index = 0;
+	while(1) {
+	    hash&=1023;
+	    index = palette_hash[hash];
+	    if(!memcmp(&palette[index], &src[t], sizeof(COL)))
+		break;
+	    hash++;
+	}
+	dest[t] = palette_hash[hash];
+    }
+}
+
+static int png_apply_specific_filter_8(int filtermode, unsigned char*dest, unsigned char*src, int width)
 {
     int pos2 = 0;
     int pos = 0;
@@ -1186,9 +1306,10 @@ static void filter_line8(int filtermode, unsigned char*dest, unsigned char*src,
 	    pos++;
 	}
     }
+    return filtermode;
 }
 
-static void filter_line32(int filtermode, unsigned char*dest, unsigned char*src, int width)
+static int png_apply_specific_filter_32(int filtermode, unsigned char*dest, unsigned char*src, int width)
 {
     int pos2 = 0;
     int pos = 0;
@@ -1254,6 +1375,79 @@ static void filter_line32(int filtermode, unsigned char*dest, unsigned char*src,
 	    pos+=4;
 	}
     }
+    return filtermode;
+}
+    
+static int*num_bits_table = 0;
+
+static void make_num_bits_table()
+{
+    if(num_bits_table) return;
+    num_bits_table = malloc(sizeof(num_bits_table[0])*256);
+    int t;
+    for(t=0;t<256;t++) {
+	int bits=0;
+	int v = t;
+	while(v) {
+	    bits++;
+	    v&=v-1;
+	}
+	num_bits_table[t]=bits;
+    }
+}
+
+static int png_apply_filter(unsigned char*dest, unsigned char*src, int width, int y, int bpp)
+{
+    make_num_bits_table();
+
+    int num_filters = y>0?5:2; //don't apply y-direction filter in first line
+    int f;
+    int best_nr = 0;
+    int best_energy = INT_MAX;
+    int w = width*(bpp/8);
+    unsigned char* pairs = malloc(8192);
+    assert(bpp==8 || bpp==32);
+    for(f=0;f<num_filters;f++) {
+	if(bpp==8)
+	    png_apply_specific_filter_8(f, dest, src, width);
+	else
+	    png_apply_specific_filter_32(f, dest, src, width);
+	int x;
+
+	/* approximation for zlib compressability: test how many different
+	   (byte1,byte2) occur */
+	memset(pairs, 0, 8192);
+	int different_pairs = 0;
+	for(x=0;x<w-1;x++) {
+	    int v = dest[x+1]<<8|dest[x];
+	    int p = v>>3;
+	    int b = 1<<(v&7);
+	    if(!pairs[p]&b) {
+		pairs[p]|=b;
+		different_pairs ++;
+	    }
+	}
+	int energy = different_pairs;
+	if(energy<best_energy) {
+	    best_nr = f;
+	    best_energy = energy;
+	}
+    }
+    if(bpp==8)
+	png_apply_specific_filter_8(best_nr, dest, src, width);
+    else
+	png_apply_specific_filter_32(best_nr, dest, src, width);
+    free(pairs);
+    return best_nr;
+}
+
+int png_apply_filter_8(unsigned char*dest, unsigned char*src, int width, int y)
+{
+    png_apply_filter(dest, src, width, y, 8);
+}
+int png_apply_filter_32(unsigned char*dest, unsigned char*src, int width, int y)
+{
+    png_apply_filter(dest, src, width, y, 32);
 }
 
 EXPORT void savePNG(const char*filename, unsigned char*data, int width, int height, int numcolors)
@@ -1274,20 +1468,32 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
     u32 tmp32;
     int bpp;
     int ret;
+    char has_alpha=0;
     z_stream zs;
-    COL*palette=0;
+    COL palette[256];
 
     make_crc32_table();
 
     if(!numcolors) {
-        bpp = 32;
-        cols = 0;
-        format = 5;
+	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;
-        quantizeImage(data, width*height, numcolors, &data, &palette);
+        png_quantize_image(data, width*height, numcolors, &data, palette);
     }
 
     datalen = (width*height*bpp/8+cols*8);
@@ -1317,14 +1523,23 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
     png_end_chunk(fi);
 
     if(format == 3) {
-	png_start_chunk(fi, "PLTE", 768);
-	 for(t=0;t<cols;t++) {
-	     png_write_byte(fi,palette[t].r);
-	     png_write_byte(fi,palette[t].g);
-	     png_write_byte(fi,palette[t].b);
-	 }
+	png_start_chunk(fi, "PLTE", cols*3);
+	for(t=0;t<cols;t++) {
+	    png_write_byte(fi,palette[t].r);
+	    png_write_byte(fi,palette[t].g);
+	    png_write_byte(fi,palette[t].b);
+	}
 	png_end_chunk(fi);
+
+	if(has_alpha) {
+	    png_start_chunk(fi, "tRNS", cols);
+	    for(t=0;t<cols;t++) {
+		png_write_byte(fi,palette[t].a);
+	    }
+	    png_end_chunk(fi);
+	}
     }
+
     long idatpos = png_start_chunk(fi, "IDAT", 0);
     
     memset(&zs,0,sizeof(z_stream));
@@ -1334,7 +1549,7 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
     zs.opaque = Z_NULL;
     zs.next_out = writebuf;
     zs.avail_out = ZLIB_BUFFER_SIZE;
-    ret = deflateInit(&zs, 9);
+    ret = deflateInit(&zs, Z_BEST_COMPRESSION);
     if (ret != Z_OK) {
 	fprintf(stderr, "error in deflateInit(): %s", zs.msg?zs.msg:"unknown");
 	return;
@@ -1353,21 +1568,23 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
         else if(bypp==4) 
             linelen = 1 + ((srcwidth+3)&~3);
 	unsigned char* line = (unsigned char*)malloc(linelen);
-	unsigned char* bestline = (unsigned char*)malloc(linelen);
 	memset(line, 0, linelen);
+#if 0
+	unsigned char* bestline = (unsigned char*)malloc(linelen);
+	memset(bestline, 0, linelen);
 	for(y=0;y<height;y++)
 	{
 	    int filtermode;
 	    int bestsize = 0x7fffffff;
-	    for(filtermode=0;filtermode<=0;filtermode++) {
+	    for(filtermode=0;filtermode<=4;filtermode++) {
 		if(!y && filtermode>=2)
 		    continue; // don't do y direction filters in the first row
 		
 		line[0]=filtermode; //filter type
                 if(bpp==8)
-		    filter_line8(filtermode, line+1, &data[y*srcwidth], width);
+		    png_apply_specific_filter_8(filtermode, line+1, &data[y*srcwidth], width);
                 else
-		    filter_line32(filtermode, line+1, &data[y*srcwidth], width);
+		    png_apply_specific_filter_32(filtermode, line+1, &data[y*srcwidth], width);
 
 		int size = test_line(&zs, line, linelen);
 		if(size < bestsize) {
@@ -1377,7 +1594,18 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
 	    }
 	    idatsize += compress_line(&zs, bestline, linelen, fi);
 	}
-	free(line);free(bestline);
+	free(bestline);
+#else
+	for(y=0;y<height;y++) {
+            if(bpp==8)
+		line[0] = png_apply_filter_8(line+1, &data[y*srcwidth], width, y);
+            else
+		line[0] = png_apply_filter_32(line+1, &data[y*srcwidth], width, y);
+
+	    idatsize += compress_line(&zs, line, linelen, fi);
+	}
+#endif
+	free(line);
     }
     idatsize += finishzlib(&zs, fi);
     png_patch_len(fi, idatpos, idatsize);
@@ -1387,7 +1615,8 @@ EXPORT void savePNG(const char*filename, unsigned char*data, int width, int heig
     png_end_chunk(fi);
 
     free(writebuf);
-    free(data2);
+    if(data2)
+	free(data2);
     fclose(fi);
 }