Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 230 lines (201 sloc) 6.677 kb
5b56b7e @pornel Bam!
authored
1
2 #include <stdio.h>
3 #include <stdlib.h>
c9be566 @pornel -d argument
authored
4 #include <string.h>
5b56b7e @pornel Bam!
authored
5 #include <assert.h>
6 #include <math.h>
7 #include "png.h"
8 #include "rwpng.h"
9
10 struct box {
11 int start,end;
12 float sum;
13 float variance;
14 };
15
16 float weighted_avg(struct box box, float histogram[])
17 {
18 float weight=0,sum=0;
19 for(int val=box.start; val < box.end; val++) {
20 weight += histogram[val];
21 sum += val*histogram[val];
22 }
23 return weight ? sum/weight : 0.0f;
24 }
25
26 float variance(struct box box, float histogram[])
27 {
28 float avg = weighted_avg(box,histogram);
29
30 float weight=0,sum=0;
31 for(int val=box.start; val < box.end; val++) {
32 weight += histogram[val];
33 sum += (avg-val)*(avg-val)*histogram[val];
34 }
35 return weight ? sum/weight : 0.0f;
36 }
37
38 /*
39 1-dimensional median cut, using variance for "largest" box
40 */
41 void reduce(const int maxcolors, float histogram[], int palette[])
42 {
43 int numboxes=1;
44 struct box boxes[256];
45
a498ea8 @pornel Formatting
authored
46 boxes[0].start=1; // skip first and last entry, as they're always included
47 boxes[0].end=255;
48 boxes[0].sum=0;
49 for(int i=boxes[0].start; i < boxes[0].end; i++) boxes[0].sum += histogram[i];
5b56b7e @pornel Bam!
authored
50 boxes[0].variance = variance(boxes[0], histogram);
51
52 while(numboxes < maxcolors-1) {
53 int boxtosplit=-1;
54 int largest=0;
55 for(int box=0; box < numboxes; box++) {
56 if (boxes[box].variance > largest && (boxes[box].end-boxes[box].start)>=2) {
57 largest = boxes[box].variance;
58 boxtosplit=box;
59 }
60 }
61 if (boxtosplit < 0) {
62 break;
63 }
64 float sum=0;
65 int val=boxes[boxtosplit].start;
66 for(; val < boxes[boxtosplit].end-1; val++) {
67 sum += histogram[val];
68 if (sum >= boxes[boxtosplit].sum/2.0) {
69 break;
70 }
71 }
72
73 boxes[numboxes].start = boxes[boxtosplit].start;
74 boxes[numboxes].end = val+1;
75 boxes[numboxes].sum = sum;
76 boxes[numboxes].variance = variance(boxes[numboxes], histogram);
77 boxes[boxtosplit].start = val+1;
78 boxes[boxtosplit].sum -= boxes[numboxes].sum;
79 boxes[boxtosplit].variance = variance(boxes[boxtosplit], histogram);
80 numboxes++;
81 }
82
83 for(int box=0; box < numboxes; box++) {
84 int value = roundf(weighted_avg(boxes[box],histogram));
85 palette[value] = value;
86 }
87 }
88
7436b18 @pornel Remapping with dithering
authored
89 void remap(read_info img, const int *palette1, const int *palette2)
ccab456 @pornel Extracted remap
authored
90 {
91 for(int i=0; i < img.height; i++) {
7436b18 @pornel Remapping with dithering
authored
92 for(int j=0; j < img.width; j++) {
93 int x = j*4;
94 const int *palette = (i^j)&1 ? palette1 : palette2;
95
ccab456 @pornel Extracted remap
authored
96 int a = palette[img.row_pointers[i][x+3]];
97 if (a) {
98 img.row_pointers[i][x] = palette[img.row_pointers[i][x]];
99 img.row_pointers[i][x+1] = palette[img.row_pointers[i][x+1]];
100 img.row_pointers[i][x+2] = palette[img.row_pointers[i][x+2]];
101 img.row_pointers[i][x+3] = a;
102 } else {
103 // clear "dirty alpha"
104 img.row_pointers[i][x] = 0;
105 img.row_pointers[i][x+1] = 0;
106 img.row_pointers[i][x+2] = 0;
107 img.row_pointers[i][x+3] = 0;
108 }
109 }
110 }
111 }
112
7ae4649 @pornel Extracted histogram
authored
113 void intensity_histogram(read_info img, float histogram[])
114 {
115 for(int i=0; i < img.height; i++) {
116 for(int x=0; x < img.width*4; x+=4) {
117 float a = 1.0-img.row_pointers[i][x+3]/255.0;
118 a = 1.0-a*a;
119
120 // opaque colors get more weight
121 histogram[img.row_pointers[i][x]] += a;
122 histogram[img.row_pointers[i][x+1]] += a;
123 histogram[img.row_pointers[i][x+2]] += a;
124 histogram[img.row_pointers[i][x+3]] += 0.9;
125 }
126 }
127 }
128
7436b18 @pornel Remapping with dithering
authored
129 void dither_palette(int* palette, int* palette2, int dither)
130 {
131 int nextval=0;
132 int lastval=0;
133 palette[0]=0;
134 palette[255]=255; // 0 and 255 are always included
135 memcpy(palette2, palette, sizeof(int)*256);
136
137 // front to back. When dithering, it's biased towards nextval
138 for(int val=0; val < 256; val++)
139 {
140 if (palette[val]==val) {
141 lastval = val;
142 for(int j=val+1; j < 256; j++) {
143 if (palette[j]==j) {nextval=j; break;}
144 }
145 }
146 if (!dither) {
147 palette[val] = (val - lastval) < (nextval - val) ? lastval : nextval;
148 } else {
149 palette[val] = (val - lastval)/2 < (nextval - val) ? lastval : nextval;
150 }
151 }
152
153 lastval=255; nextval=255;
154 // back to front, so dithering bias is the other way.
155 for(int val=255; val >=0; val--)
156 {
157 if (palette2[val]==val) {
158 lastval = val;
159 for(int j=val-1; j >= 0; j--) {
160 if (palette2[j]==j) {nextval=j; break;}
161 }
162 }
163 if (!dither) {
164 palette2[val] = (val - lastval) >= (nextval - val) ? lastval : nextval;
165 } else {
166 palette2[val] = (val - lastval)/2 >= (nextval - val) ? lastval : nextval;
167 }
168 }
169 }
170
c9be566 @pornel -d argument
authored
171 void usage(const char *exepath)
172 {
173 const char *name = strrchr(exepath, '/');
174 if (name) name++; else name = exepath;
175 fprintf(stderr, "Median Cut PNG Posterizer 1.1 (2011).\n" \
176 "Usage: %s [-d] levels\n\n" \
177 "Specify number of levels 2-255 as an argument. -d enables dithering\n" \
178 "Image is always read from stdin and written to stdout.\n"
179 "%s -d 16 < in.png > out.png\n", name, name);
180 }
181
5b56b7e @pornel Bam!
authored
182 int main(int argc, char *argv[])
183 {
c9be566 @pornel -d argument
authored
184 int argn=1;
7436b18 @pornel Remapping with dithering
authored
185 int dither=0;
c9be566 @pornel -d argument
authored
186 if (argc==3 && 0==strcmp("-d", argv[1])) {
187 dither=1;
188 argn++;
189 }
190 int maxcolors=0;
191 if (argc==(argn+1)) {
192 maxcolors=atoi(argv[argn]);
193 argn++;
194 }
5b56b7e @pornel Bam!
authored
195
c9be566 @pornel -d argument
authored
196 if (argc != argn || maxcolors < 2 || maxcolors > 255) {
197 usage(argv[0]);
5b56b7e @pornel Bam!
authored
198 return 1;
199 }
200
201 read_info img;
4357b57 @pornel IO error handling
authored
202 pngquant_error retval;
203 if ((retval = rwpng_read_image(stdin, &img))) {
5b56b7e @pornel Bam!
authored
204 fprintf(stderr, "Error: cannot read PNG from stdin\n");
4357b57 @pornel IO error handling
authored
205 return retval;
5b56b7e @pornel Bam!
authored
206 }
207
208 float histogram[256]={0};
7ae4649 @pornel Extracted histogram
authored
209 intensity_histogram(img, histogram);
5b56b7e @pornel Bam!
authored
210
97313d9 @pornel Reserved black and white
authored
211 // reserve colors for black and white
212 if (histogram[0] && maxcolors>2) maxcolors--;
213 if (histogram[255] && maxcolors>2) maxcolors--;
214
7436b18 @pornel Remapping with dithering
authored
215 int palette[256] = {0}, palette2[256];
5b56b7e @pornel Bam!
authored
216 reduce(maxcolors, histogram, palette);
217
7436b18 @pornel Remapping with dithering
authored
218 dither_palette(palette, palette2, dither);
6828c02 @pornel Moved palette code
authored
219
7436b18 @pornel Remapping with dithering
authored
220 remap(img, palette, palette2);
ccab456 @pornel Extracted remap
authored
221
4357b57 @pornel IO error handling
authored
222 if ((retval = rwpng_write_image_init(stdout, &img)) ||
223 (retval = rwpng_write_image_whole(&img))) {
224 fprintf(stderr, "Error: cannot write PNG to stdout\n");
225 return retval;
226 }
5b56b7e @pornel Bam!
authored
227
228 return 0;
229 }
Something went wrong with that request. Please try again.