add missing tools/pngdump to the repo
[gbajam22] / tools / pngdump / main.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <math.h>
5 #include <errno.h>
6 #include <assert.h>
7 #include "image.h"
8
9 enum {
10         MODE_PIXELS,
11         MODE_CMAP,
12         MODE_PNG,
13         MODE_INFO
14 };
15
16 void conv_gba_image(struct image *img);
17 void dump_colormap(struct image *img, int text, FILE *fp);
18 void print_usage(const char *argv0);
19
20 int main(int argc, char **argv)
21 {
22         int i, j, mode = 0;
23         int text = 0;
24         int renibble = 0;
25         char *outfname = 0;
26         char *slut_fname = 0, *cmap_fname = 0;
27         char *infiles[256];
28         int num_infiles = 0;
29         struct image img, tmpimg;
30         FILE *out = stdout;
31         FILE *aux_out;
32         int *shade_lut = 0;
33         int *lutptr;
34         int shade_levels = 8;
35         int maxcol = 0;
36         int lvl;
37         int conv_555 = 0;
38         int gbacolors = 0;
39
40         for(i=1; i<argc; i++) {
41                 if(argv[i][0] == '-') {
42                         if(argv[i][2] == 0) {
43                                 switch(argv[i][1]) {
44                                 case 'P':
45                                         mode = MODE_PNG;
46                                         break;
47
48                                 case 'p':
49                                         mode = MODE_PIXELS;
50                                         break;
51
52                                 case 'c':
53                                         mode = MODE_CMAP;
54                                         break;
55
56                                 case 'i':
57                                         mode = MODE_INFO;
58                                         break;
59
60                                 case 'C':
61                                         if(!argv[++i] || (maxcol = atoi(argv[i])) < 2 || maxcol > 256) {
62                                                 fprintf(stderr, "-C must be followed by the number of colors to reduce down to\n");
63                                                 return 1;
64                                         }
65                                         break;
66
67                                 case 's':
68                                         if(!argv[++i] || (shade_levels = atoi(argv[i])) == 0) {
69                                                 fprintf(stderr, "-s must be followed by the number of shade levels\n");
70                                                 return 1;
71                                         }
72                                         break;
73
74                                 case 't':
75                                         text = 1;
76                                         break;
77
78                                 case 'n':
79                                         renibble = 1;
80                                         break;
81
82                                 case 'g':
83                                         gbacolors = 1;
84                                         break;
85
86                                 case 'o':
87                                         if(!argv[++i]) {
88                                                 fprintf(stderr, "%s must be followed by a filename\n", argv[i - 1]);
89                                                 return 1;
90                                         }
91                                         outfname = argv[i];
92                                         break;
93
94                                 case 'h':
95                                         print_usage(argv[0]);
96                                         return 0;
97
98                                 default:
99                                         fprintf(stderr, "invalid option: %s\n", argv[i]);
100                                         print_usage(argv[0]);
101                                         return 1;
102                                 }
103                         } else {
104                                 if(strcmp(argv[i], "-oc") == 0) {
105                                         if(!argv[++i]) {
106                                                 fprintf(stderr, "-oc must be followed by a filename\n");
107                                                 return 1;
108                                         }
109                                         cmap_fname = argv[i];
110
111                                 } else if(strcmp(argv[i], "-os") == 0) {
112                                         if(!argv[++i]) {
113                                                 fprintf(stderr, "-os must be followed by a filename\n");
114                                                 return 1;
115                                         }
116                                         slut_fname = argv[i];
117
118                                 } else if(strcmp(argv[i], "-555") == 0) {
119                                         conv_555 = 1;
120
121                                 } else {
122                                         fprintf(stderr, "invalid option: %s\n", argv[i]);
123                                         print_usage(argv[0]);
124                                         return 1;
125                                 }
126                         }
127                 } else {
128                         infiles[num_infiles++] = argv[i];
129                 }
130         }
131
132         if(!num_infiles) {
133                 fprintf(stderr, "pass the filename of a PNG file\n");
134                 return 1;
135         }
136         if(load_image(&img, infiles[0]) == -1) {
137                 fprintf(stderr, "failed to load PNG file: %s\n", infiles[0]);
138                 return 1;
139         }
140
141         if(gbacolors) {
142                 conv_gba_image(&img);
143         }
144
145         for(i=1; i<num_infiles; i++) {
146                 if(load_image(&tmpimg, infiles[i]) == -1) {
147                         fprintf(stderr, "failed to load PNG file: %s\n", infiles[i]);
148                         return 1;
149                 }
150                 if(tmpimg.width != img.width || tmpimg.height != img.height) {
151                         fprintf(stderr, "size mismatch: first image (%s) is %dx%d, %s is %dx%d\n",
152                                         infiles[0], img.width, img.height, infiles[i], tmpimg.width, tmpimg.height);
153                         return 1;
154                 }
155                 if(tmpimg.bpp != img.bpp) {
156                         fprintf(stderr, "bpp mismatch: first image (%s) is %d bpp, %s is %d bpp\n",
157                                         infiles[0], img.bpp, infiles[i], img.bpp);
158                         return 1;
159                 }
160
161                 overlay_key(&tmpimg, 0, &img);
162         }
163
164         /* generate shading LUT and quantize image as necessary */
165         if(slut_fname) {
166                 if(img.bpp > 8) {
167                         fprintf(stderr, "shading LUT generation is only supported for indexed color images\n");
168                         return 1;
169                 }
170                 if(!(aux_out = fopen(slut_fname, "wb"))) {
171                         fprintf(stderr, "failed to open shading LUT output file: %s: %s\n", slut_fname, strerror(errno));
172                         return 1;
173                 }
174
175                 if(!maxcol) maxcol = 256;
176
177                 if(!(shade_lut = malloc(maxcol * shade_levels * sizeof *shade_lut))) {
178                         fprintf(stderr, "failed to allocate shading look-up table\n");
179                         return 1;
180                 }
181
182                 gen_shades(&img, shade_levels, maxcol, shade_lut);
183
184                 lutptr = shade_lut;
185                 for(i=0; i<maxcol; i++) {
186                         for(j=0; j<shade_levels; j++) {
187                                 lvl = lutptr[shade_levels - j - 1];
188                                 if(text) {
189                                         fprintf(aux_out, "%d%c", lvl, j < shade_levels - 1 ? ' ' : '\n');
190                                 } else {
191                                         fputc(lvl, aux_out);
192                                 }
193                         }
194                         lutptr += shade_levels;
195                 }
196                 fclose(aux_out);
197
198         } else if(maxcol) {
199                 /* perform any color reductions if requested */
200                 if(img.bpp <= 8 && img.cmap_ncolors <= maxcol) {
201                         fprintf(stderr, "requested reduction to %d colors, but image has %d colors\n", maxcol, img.cmap_ncolors);
202                         return 1;
203                 }
204                 quantize_image(&img, maxcol);
205         }
206
207         if(cmap_fname) {
208                 if(img.bpp > 8) {
209                         fprintf(stderr, "colormap output works only for indexed color images\n");
210                         return 1;
211                 }
212                 if(!(aux_out = fopen(cmap_fname, "wb"))) {
213                         fprintf(stderr, "failed to open colormap output file: %s: %s\n", cmap_fname, strerror(errno));
214                         return 1;
215                 }
216                 dump_colormap(&img, text, aux_out);
217                 fclose(aux_out);
218         }
219
220         if(img.bpp == 4 && renibble) {
221                 unsigned char *ptr = img.pixels;
222                 for(i=0; i<img.width * img.height; i++) {
223                         unsigned char p = *ptr;
224                         *ptr++ = (p << 4) | (p >> 4);
225                 }
226         }
227
228         if(conv_555) {
229                 struct image img555;
230                 unsigned int rgb24[3], rgb15;
231
232                 if(alloc_image(&img555, img.width, img.height, 15) == -1) {
233                         fprintf(stderr, "failed to allocate temporary %dx%d image for 555 conversion\n",
234                                         img.width, img.height);
235                         return 1;
236                 }
237
238                 for(i=0; i<img.height; i++) {
239                         for(j=0; j<img.width; j++) {
240                                 get_pixel_rgb(&img, j, i, rgb24);
241                                 rgb15 = ((rgb24[0] >> 3) & 0x1f) | ((rgb24[1] << 2) & 0x3e0) |
242                                         ((rgb24[2] << 7) & 0x7c00);
243                                 put_pixel(&img555, j, i, rgb15);
244                         }
245                 }
246                 free(img.pixels);
247                 img = img555;
248         }
249
250         if(outfname) {
251                 if(!(out = fopen(outfname, "wb"))) {
252                         fprintf(stderr, "failed to open output file: %s: %s\n", outfname, strerror(errno));
253                         return 1;
254                 }
255         }
256
257         switch(mode) {
258         case MODE_PNG:
259                 save_image_file(&img, out);
260                 break;
261
262         case MODE_PIXELS:
263                 fwrite(img.pixels, 1, img.scansz * img.height, out);
264                 break;
265
266         case MODE_CMAP:
267                 dump_colormap(&img, text, out);
268                 break;
269
270         case MODE_INFO:
271                 printf("size: %dx%d\n", img.width, img.height);
272                 printf("bit depth: %d\n", img.bpp);
273                 printf("scanline size: %d bytes\n", img.scansz);
274                 if(img.cmap_ncolors > 0) {
275                         printf("colormap entries: %d\n", img.cmap_ncolors);
276                 } else {
277                         printf("color channels: %d\n", img.nchan);
278                 }
279                 break;
280         }
281
282         fclose(out);
283         return 0;
284 }
285
286 #define MIN(a, b)                       ((a) < (b) ? (a) : (b))
287 #define MIN3(a, b, c)           ((a) < (b) ? MIN(a, c) : MIN(b, c))
288 #define MAX(a, b)                       ((a) > (b) ? (a) : (b))
289 #define MAX3(a, b, c)           ((a) > (b) ? MAX(a, c) : MAX(b, c))
290
291 void rgb_to_hsv(float *rgb, float *hsv)
292 {
293         float min, max, delta;
294
295         min = MIN3(rgb[0], rgb[1], rgb[2]);
296         max = MAX3(rgb[0], rgb[1], rgb[2]);
297         delta = max - min;
298
299         if(max == 0) {
300                 hsv[0] = hsv[1] = hsv[2] = 0;
301                 return;
302         }
303
304         hsv[2] = max;                   /* value */
305         hsv[1] = delta / max;   /* saturation */
306
307         if(delta == 0.0f) {
308                 hsv[0] = 0.0f;
309         } else if(max == rgb[0]) {
310                 hsv[0] = (rgb[1] - rgb[2]) / delta;
311         } else if(max == rgb[1]) {
312                 hsv[0] = 2.0f + (rgb[2] - rgb[0]) / delta;
313         } else {
314                 hsv[0] = 4.0f + (rgb[0] - rgb[1]) / delta;
315         }
316         /*
317         hsv[0] /= 6.0f;
318
319         if(hsv[0] < 0.0f) hsv[0] += 1.0f;
320         */
321         hsv[0] *= 60.0f;
322         if(hsv[0] < 0) hsv[0] += 360;
323         hsv[0] /= 360.0f;
324 }
325
326 #define RETRGB(r, g, b) \
327         do { \
328                 rgb[0] = r; \
329                 rgb[1] = g; \
330                 rgb[2] = b; \
331                 return; \
332         } while(0)
333
334 void hsv_to_rgb(float *hsv, float *rgb)
335 {
336         float sec, frac, o, p, q;
337         int hidx;
338
339         if(hsv[1] == 0.0f) {
340                 rgb[0] = rgb[1] = rgb[2] = hsv[2];      /* value */
341         }
342
343         sec = floor(hsv[0] * (360.0f / 60.0f));
344         frac = (hsv[0] * (360.0f / 60.0f)) - sec;
345
346         o = hsv[2] * (1.0f - hsv[1]);
347         p = hsv[2] * (1.0f - hsv[1] * frac);
348         q = hsv[2] * (1.0f - hsv[1] * (1.0f - frac));
349
350         hidx = (int)sec;
351         switch(hidx) {
352         default:
353         case 0: RETRGB(hsv[2], q, o);
354         case 1: RETRGB(p, hsv[2], o);
355         case 2: RETRGB(o, hsv[2], q);
356         case 3: RETRGB(o, p, hsv[2]);
357         case 4: RETRGB(q, o, hsv[2]);
358         case 5: RETRGB(hsv[2], o, p);
359         }
360 }
361
362 void gba_color(struct cmapent *color)
363 {
364         float rgb[3], hsv[3];
365
366         rgb[0] = pow((float)color->r / 255.0f, 2.2);
367         rgb[1] = pow((float)color->g / 255.0f, 2.2);
368         rgb[2] = pow((float)color->b / 255.0f, 2.2);
369
370         /* saturate colors */
371         rgb_to_hsv(rgb, hsv);
372         hsv[1] *= 1.2f;
373         hsv[2] *= 2.0f;
374         if(hsv[1] > 1.0f) hsv[1] = 1.0f;
375         if(hsv[2] > 1.0f) hsv[2] = 1.0f;
376         hsv_to_rgb(hsv, rgb);
377
378         rgb[0] = pow(rgb[0], 1.0 / 2.6);
379         rgb[1] = pow(rgb[1], 1.0 / 2.6);
380         rgb[2] = pow(rgb[2], 1.0 / 2.6);
381
382         color->r = (int)(rgb[0] * 255.0f);
383         color->g = (int)(rgb[1] * 255.0f);
384         color->b = (int)(rgb[2] * 255.0f);
385 }
386
387 void conv_gba_image(struct image *img)
388 {
389         int i;
390
391         if(img->cmap_ncolors) {
392                 for(i=0; i<img->cmap_ncolors; i++) {
393                         gba_color(img->cmap + i);
394                 }
395         } else {
396                 /* TODO */
397         }
398 }
399
400 void dump_colormap(struct image *img, int text, FILE *fp)
401 {
402         int i;
403
404         if(text) {
405                 for(i=0; i<img->cmap_ncolors; i++) {
406                         fprintf(fp, "%d %d %d\n", img->cmap[i].r, img->cmap[i].g, img->cmap[i].b);
407                 }
408         } else {
409                 fwrite(img->cmap, sizeof img->cmap[0], 1 << img->bpp, fp);
410         }
411 }
412
413 void print_usage(const char *argv0)
414 {
415         printf("Usage: %s [options] <input file>\n", argv0);
416         printf("Options:\n");
417         printf(" -o <output file>: specify output file (default: stdout)\n");
418         printf(" -oc <cmap file>: output colormap to separate file\n");
419         printf(" -os <lut file>: generate and output shading LUT\n");
420         printf(" -p: dump pixels (default)\n");
421         printf(" -P: output in PNG format\n");
422         printf(" -c: dump colormap (palette) entries\n");
423         printf(" -C <colors>: reduce image down to specified number of colors\n");
424         printf(" -s <shade levels>: used in conjunction with -os (default: 8)\n");
425         printf(" -i: print image information\n");
426         printf(" -t: output as text when possible\n");
427         printf(" -n: swap the order of nibbles (for 4bpp)\n");
428         printf(" -555: convert to BGR555\n");
429         printf(" -h: print usage and exit\n");
430 }