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