Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

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