Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 300 lines (254 sloc) 8.54 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
986036f @pornel Palette proto
authored
10 void interpolate_palette_front(int palette[], int dither);
11
5b56b7e @pornel Bam!
authored
12 struct box {
333392f @pornel Switched to doubles
authored
13 double sum, variance;
14 int start, end;
5b56b7e @pornel Bam!
authored
15 };
16
333392f @pornel Switched to doubles
authored
17 double weighted_avg(struct box box, double histogram[])
5b56b7e @pornel Bam!
authored
18 {
333392f @pornel Switched to doubles
authored
19 double weight=0,sum=0;
5b56b7e @pornel Bam!
authored
20 for(int val=box.start; val < box.end; val++) {
21 weight += histogram[val];
22 sum += val*histogram[val];
23 }
24 return weight ? sum/weight : 0.0f;
25 }
26
333392f @pornel Switched to doubles
authored
27 double variance(struct box box, double histogram[])
5b56b7e @pornel Bam!
authored
28 {
333392f @pornel Switched to doubles
authored
29 double avg = weighted_avg(box,histogram);
5b56b7e @pornel Bam!
authored
30
333392f @pornel Switched to doubles
authored
31 double weight=0,sum=0;
5b56b7e @pornel Bam!
authored
32 for(int val=box.start; val < box.end; val++) {
33 weight += histogram[val];
34 sum += (avg-val)*(avg-val)*histogram[val];
35 }
36 return weight ? sum/weight : 0.0f;
37 }
38
e1432f5 @pornel Voronoi iteration
authored
39
40 double palette_mse(double histogram[], int palette[])
41 {
42 int tmp[256];
43 memcpy(tmp, palette, 256*sizeof(palette[0]));
44 interpolate_palette_front(tmp, 0);
45
46 double mse=0, hist_total=0;
47 for (int i=0; i < 256; i++) {
48 int best = tmp[i];
49 mse += (i-best)*(i-best) * histogram[i];
50
51 hist_total += histogram[i];
52 }
53 return mse / hist_total;
54 }
55
091f163 @pornel Extracted palette build
authored
56 void palette_from_boxes(struct box boxes[], int numboxes, double histogram[], int palette[])
57 {
58 memset(palette, 0, 256*sizeof(palette[0]));
59
60 for(int box=0; box < numboxes; box++) {
61 int value = round(weighted_avg(boxes[box],histogram));
62 palette[value] = value;
63 }
64 }
65
5b56b7e @pornel Bam!
authored
66 /*
67 1-dimensional median cut, using variance for "largest" box
68 */
333392f @pornel Switched to doubles
authored
69 void reduce(const int maxcolors, double histogram[], int palette[])
5b56b7e @pornel Bam!
authored
70 {
71 int numboxes=1;
72 struct box boxes[256];
73
a498ea8 @pornel Formatting
authored
74 boxes[0].start=1; // skip first and last entry, as they're always included
75 boxes[0].end=255;
76 boxes[0].sum=0;
77 for(int i=boxes[0].start; i < boxes[0].end; i++) boxes[0].sum += histogram[i];
5b56b7e @pornel Bam!
authored
78 boxes[0].variance = variance(boxes[0], histogram);
79
77a06ed @pornel Off by 1
authored
80 while(numboxes < maxcolors) {
5b56b7e @pornel Bam!
authored
81 int boxtosplit=-1;
82 int largest=0;
83 for(int box=0; box < numboxes; box++) {
84 if (boxes[box].variance > largest && (boxes[box].end-boxes[box].start)>=2) {
85 largest = boxes[box].variance;
86 boxtosplit=box;
87 }
88 }
89 if (boxtosplit < 0) {
90 break;
91 }
333392f @pornel Switched to doubles
authored
92 double sum=0;
5b56b7e @pornel Bam!
authored
93 int val=boxes[boxtosplit].start;
94 for(; val < boxes[boxtosplit].end-1; val++) {
95 sum += histogram[val];
96 if (sum >= boxes[boxtosplit].sum/2.0) {
97 break;
98 }
99 }
100
101 boxes[numboxes].start = boxes[boxtosplit].start;
102 boxes[numboxes].end = val+1;
103 boxes[numboxes].sum = sum;
104 boxes[numboxes].variance = variance(boxes[numboxes], histogram);
105 boxes[boxtosplit].start = val+1;
106 boxes[boxtosplit].sum -= boxes[numboxes].sum;
107 boxes[boxtosplit].variance = variance(boxes[boxtosplit], histogram);
108 numboxes++;
109 }
110
091f163 @pornel Extracted palette build
authored
111 palette_from_boxes(boxes, numboxes, histogram, palette);
5b56b7e @pornel Bam!
authored
112 }
113
7436b18 @pornel Remapping with dithering
authored
114 void remap(read_info img, const int *palette1, const int *palette2)
ccab456 @pornel Extracted remap
authored
115 {
116 for(int i=0; i < img.height; i++) {
7436b18 @pornel Remapping with dithering
authored
117 for(int j=0; j < img.width; j++) {
118 int x = j*4;
119 const int *palette = (i^j)&1 ? palette1 : palette2;
120
ccab456 @pornel Extracted remap
authored
121 int a = palette[img.row_pointers[i][x+3]];
122 if (a) {
123 img.row_pointers[i][x] = palette[img.row_pointers[i][x]];
124 img.row_pointers[i][x+1] = palette[img.row_pointers[i][x+1]];
125 img.row_pointers[i][x+2] = palette[img.row_pointers[i][x+2]];
126 img.row_pointers[i][x+3] = a;
127 } else {
128 // clear "dirty alpha"
129 img.row_pointers[i][x] = 0;
130 img.row_pointers[i][x+1] = 0;
131 img.row_pointers[i][x+2] = 0;
132 img.row_pointers[i][x+3] = 0;
133 }
134 }
135 }
136 }
137
333392f @pornel Switched to doubles
authored
138 void intensity_histogram(read_info img, double histogram[])
7ae4649 @pornel Extracted histogram
authored
139 {
140 for(int i=0; i < img.height; i++) {
141 for(int x=0; x < img.width*4; x+=4) {
333392f @pornel Switched to doubles
authored
142 double a = 1.0-img.row_pointers[i][x+3]/255.0;
7ae4649 @pornel Extracted histogram
authored
143 a = 1.0-a*a;
144
145 // opaque colors get more weight
146 histogram[img.row_pointers[i][x]] += a;
147 histogram[img.row_pointers[i][x+1]] += a;
148 histogram[img.row_pointers[i][x+2]] += a;
149 histogram[img.row_pointers[i][x+3]] += 0.9;
150 }
151 }
152 }
153
986036f @pornel Palette proto
authored
154 void interpolate_palette_front(int palette[], int dither)
7436b18 @pornel Remapping with dithering
authored
155 {
c2edf11 @pornel Split dither function
authored
156 int nextval=0, lastval=0;
c9b503c @pornel Fixed initialisation
authored
157 palette[0]=0;
158 palette[255]=255; // 0 and 255 are always included
7436b18 @pornel Remapping with dithering
authored
159
c2edf11 @pornel Split dither function
authored
160 for(int val=0; val < 256; val++) {
7436b18 @pornel Remapping with dithering
authored
161 if (palette[val]==val) {
162 lastval = val;
163 for(int j=val+1; j < 256; j++) {
164 if (palette[j]==j) {nextval=j; break;}
165 }
166 }
167 if (!dither) {
168 palette[val] = (val - lastval) < (nextval - val) ? lastval : nextval;
169 } else {
170 palette[val] = (val - lastval)/2 < (nextval - val) ? lastval : nextval;
171 }
172 }
c2edf11 @pornel Split dither function
authored
173 }
7436b18 @pornel Remapping with dithering
authored
174
986036f @pornel Palette proto
authored
175 void interpolate_palette_back(int palette2[], int dither)
c2edf11 @pornel Split dither function
authored
176 {
177 int nextval=255, lastval=255;
c9b503c @pornel Fixed initialisation
authored
178 palette2[0]=0;
179 palette2[255]=255; // 0 and 255 are always included
180
c2edf11 @pornel Split dither function
authored
181 for(int val=255; val >=0; val--) {
7436b18 @pornel Remapping with dithering
authored
182 if (palette2[val]==val) {
183 lastval = val;
184 for(int j=val-1; j >= 0; j--) {
185 if (palette2[j]==j) {nextval=j; break;}
186 }
187 }
188 if (!dither) {
189 palette2[val] = (val - lastval) >= (nextval - val) ? lastval : nextval;
190 } else {
191 palette2[val] = (val - lastval)/2 >= (nextval - val) ? lastval : nextval;
192 }
193 }
194 }
195
986036f @pornel Palette proto
authored
196 void dither_palette(int palette[], int palette2[], int dither)
c2edf11 @pornel Split dither function
authored
197 {
198 memcpy(palette2, palette, sizeof(int)*256);
199
200 // front to back. When dithering, it's biased towards nextval
201 interpolate_palette_front(palette, dither);
202
203 // back to front, so dithering bias is the other way.
204 interpolate_palette_back(palette2, dither);
205 }
206
c9be566 @pornel -d argument
authored
207 void usage(const char *exepath)
208 {
209 const char *name = strrchr(exepath, '/');
210 if (name) name++; else name = exepath;
6366310 @pornel Version bump
authored
211 fprintf(stderr, "Median Cut PNG Posterizer 1.2 (2011).\n" \
c9be566 @pornel -d argument
authored
212 "Usage: %s [-d] levels\n\n" \
213 "Specify number of levels 2-255 as an argument. -d enables dithering\n" \
214 "Image is always read from stdin and written to stdout.\n"
215 "%s -d 16 < in.png > out.png\n", name, name);
216 }
217
e1432f5 @pornel Voronoi iteration
authored
218 void voronoi(double histogram[], int palette[])
219 {
220 interpolate_palette_front(palette, 0);
221
222 double counts[256] = {0};
223 double sums[256] = {0};
224
225 // remap palette
226 for (int i=0; i < 256; i++) {
227 int best = palette[i];
228 counts[best] += histogram[i];
229 sums[best] += histogram[i] * (double)i;
230 }
231
232 memset(palette, 0, 256*sizeof(palette[0]));
233
234 // rebuild palette from remapped averages
235 for(int i=0; i < 256; i++) {
236 if (counts[i]) {
237 int value = round(sums[i]/counts[i]);
238 palette[value] = value;
239 }
240 }
241 }
242
5b56b7e @pornel Bam!
authored
243 int main(int argc, char *argv[])
244 {
c9be566 @pornel -d argument
authored
245 int argn=1;
7436b18 @pornel Remapping with dithering
authored
246 int dither=0;
c9be566 @pornel -d argument
authored
247 if (argc==3 && 0==strcmp("-d", argv[1])) {
248 dither=1;
249 argn++;
250 }
251 int maxcolors=0;
252 if (argc==(argn+1)) {
253 maxcolors=atoi(argv[argn]);
254 argn++;
255 }
5b56b7e @pornel Bam!
authored
256
c9be566 @pornel -d argument
authored
257 if (argc != argn || maxcolors < 2 || maxcolors > 255) {
258 usage(argv[0]);
5b56b7e @pornel Bam!
authored
259 return 1;
260 }
261
262 read_info img;
4357b57 @pornel IO error handling
authored
263 pngquant_error retval;
264 if ((retval = rwpng_read_image(stdin, &img))) {
5b56b7e @pornel Bam!
authored
265 fprintf(stderr, "Error: cannot read PNG from stdin\n");
4357b57 @pornel IO error handling
authored
266 return retval;
5b56b7e @pornel Bam!
authored
267 }
268
333392f @pornel Switched to doubles
authored
269 double histogram[256]={0};
7ae4649 @pornel Extracted histogram
authored
270 intensity_histogram(img, histogram);
5b56b7e @pornel Bam!
authored
271
97313d9 @pornel Reserved black and white
authored
272 // reserve colors for black and white
f38c74a @pornel Removed 0 and 255 from histogram
authored
273 // and omit them from histogram to avoid confusing median cut
274 if (histogram[0] && maxcolors>2) {maxcolors--; histogram[0]=0;}
275 if (histogram[255] && maxcolors>2) {maxcolors--; histogram[255]=0;}
97313d9 @pornel Reserved black and white
authored
276
091f163 @pornel Extracted palette build
authored
277 int palette[256], palette2[256];
5b56b7e @pornel Bam!
authored
278 reduce(maxcolors, histogram, palette);
279
e1432f5 @pornel Voronoi iteration
authored
280 double last_mse = INFINITY;
281 for(int j=0; j < 100; j++) {
282 voronoi(histogram, palette);
283 double new_mse = palette_mse(histogram, palette);
284 if (new_mse == last_mse) break;
285 last_mse = new_mse;
286 }
287
7436b18 @pornel Remapping with dithering
authored
288 dither_palette(palette, palette2, dither);
6828c02 @pornel Moved palette code
authored
289
7436b18 @pornel Remapping with dithering
authored
290 remap(img, palette, palette2);
ccab456 @pornel Extracted remap
authored
291
4357b57 @pornel IO error handling
authored
292 if ((retval = rwpng_write_image_init(stdout, &img)) ||
293 (retval = rwpng_write_image_whole(&img))) {
294 fprintf(stderr, "Error: cannot write PNG to stdout\n");
295 return retval;
296 }
5b56b7e @pornel Bam!
authored
297
298 return 0;
299 }
Something went wrong with that request. Please try again.