improved shade-lut generation
[gbajam21] / tools / pngdump / image.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <string.h>
4 #include <errno.h>
5 #include <assert.h>
6 #include <png.h>
7 #include "image.h"
8
9 int alloc_image(struct image *img, int x, int y, int bpp)
10 {
11         memset(img, 0, sizeof *img);
12         img->width = x;
13         img->height = y;
14         img->bpp = bpp;
15         img->scansz = img->pitch = x * bpp / 8;
16
17         if(!(img->pixels = malloc(y * img->scansz))) {
18                 fprintf(stderr, "failed to allocate %dx%d (%dbpp) pixel buffer\n", x, y, bpp);
19                 return -1;
20         }
21
22         /* just a guess, assume the user will fill the details, but set up reasonable
23          * defaults just in case...
24          */
25         if(bpp <= 8) {
26                 img->nchan = 1;
27                 img->cmap_ncolors = 1 << bpp;
28         } else if(bpp <= 24) {
29                 img->nchan = 3;
30         } else {
31                 img->nchan = 4;
32         }
33         return 0;
34 }
35
36 int load_image(struct image *img, const char *fname)
37 {
38         int i;
39         FILE *fp;
40         png_struct *png;
41         png_info *info;
42         int chan_bits, color_type;
43         png_uint_32 xsz, ysz;
44         png_color *palette;
45         unsigned char **scanline;
46         unsigned char *dptr;
47
48         if(!(fp = fopen(fname, "rb"))) {
49                 fprintf(stderr, "failed to open: %s: %s\n", fname, strerror(errno));
50                 return -1;
51         }
52
53         if(!(png = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
54                 fclose(fp);
55                 return -1;
56         }
57         if(!(info = png_create_info_struct(png))) {
58                 fclose(fp);
59                 png_destroy_read_struct(&png, 0, 0);
60                 return -1;
61         }
62         if(setjmp(png_jmpbuf(png))) {
63                 fclose(fp);
64                 png_destroy_read_struct(&png, &info, 0);
65                 return -1;
66         }
67
68         png_init_io(png, fp);
69         png_read_png(png, info, 0, 0);
70
71         png_get_IHDR(png, info, &xsz, &ysz, &chan_bits, &color_type, 0, 0, 0);
72         img->width = xsz;
73         img->height = ysz;
74         img->nchan = png_get_channels(png, info);
75         img->bpp = img->nchan * chan_bits;
76         img->scansz = img->pitch = xsz * img->bpp / 8;
77         img->cmap_ncolors = 0;
78
79         if(color_type == PNG_COLOR_TYPE_PALETTE) {
80                 png_get_PLTE(png, info, &palette, &img->cmap_ncolors);
81                 memcpy(img->cmap, palette, img->cmap_ncolors * sizeof *img->cmap);
82         }
83
84         if(!(img->pixels = malloc(ysz * img->scansz))) {
85                 perror("failed to allocate pixel buffer");
86                 fclose(fp);
87                 png_destroy_read_struct(&png, &info, 0);
88                 return -1;
89         }
90         dptr = img->pixels;
91
92         scanline = (unsigned char**)png_get_rows(png, info);
93         for(i=0; i<ysz; i++) {
94                 memcpy(dptr, scanline[i], img->scansz);
95                 dptr += img->pitch;
96         }
97
98         fclose(fp);
99         png_destroy_read_struct(&png, &info, 0);
100         return 0;
101 }
102
103 int save_image(struct image *img, const char *fname)
104 {
105         FILE *fp;
106         int res;
107
108         if(!(fp = fopen(fname, "wb"))) {
109                 fprintf(stderr, "save_image: failed to open: %s: %s\n", fname, strerror(errno));
110                 return -1;
111         }
112         res = save_image_file(img, fp);
113         fclose(fp);
114         return res;
115 }
116
117 int save_image_file(struct image *img, FILE *fp)
118 {
119         int i, chan_bits, coltype;
120         png_struct *png;
121         png_info *info;
122         png_text txt;
123         unsigned char **scanline = 0;
124         unsigned char *pptr;
125
126         if(!(png = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0))) {
127                 fclose(fp);
128                 return -1;
129         }
130         if(!(info = png_create_info_struct(png))) {
131                 png_destroy_write_struct(&png, 0);
132                 fclose(fp);
133                 return -1;
134         }
135
136         txt.compression = PNG_TEXT_COMPRESSION_NONE;
137         txt.key = "Software";
138         txt.text = "pngdump";
139         txt.text_length = 0;
140
141         if(setjmp(png_jmpbuf(png))) {
142                 png_destroy_write_struct(&png, &info);
143                 free(scanline);
144                 fclose(fp);
145                 return -1;
146         }
147
148         switch(img->nchan) {
149         case 1:
150                 if(img->cmap_ncolors > 0) {
151                         coltype = PNG_COLOR_TYPE_PALETTE;
152                 } else {
153                         coltype = PNG_COLOR_TYPE_GRAY;
154                 }
155                 break;
156         case 2:
157                 coltype = PNG_COLOR_TYPE_GRAY_ALPHA;
158                 break;
159         case 3:
160                 coltype = PNG_COLOR_TYPE_RGB;
161                 break;
162         case 4:
163                 coltype = PNG_COLOR_TYPE_RGB_ALPHA;
164                 break;
165         }
166
167         chan_bits = img->bpp / img->nchan;
168         png_set_IHDR(png, info, img->width, img->height, chan_bits, coltype, PNG_INTERLACE_NONE,
169                         PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
170         png_set_text(png, info, &txt, 1);
171
172         if(img->cmap_ncolors > 0) {
173                 png_set_PLTE(png, info, (png_color*)img->cmap, img->cmap_ncolors);
174         }
175
176         if(!(scanline = malloc(img->height * sizeof *scanline))) {
177                 png_destroy_write_struct(&png, &info);
178                 fclose(fp);
179                 return -1;
180         }
181
182         pptr = img->pixels;
183         for(i=0; i<img->height; i++) {
184                 scanline[i] = pptr;
185                 pptr += img->pitch;
186         }
187         png_set_rows(png, info, scanline);
188
189         png_init_io(png, fp);
190         png_write_png(png, info, 0, 0);
191         png_destroy_write_struct(&png, &info);
192         free(scanline);
193         return 0;
194 }
195
196
197 int cmp_image(struct image *a, struct image *b)
198 {
199         int i;
200         unsigned char *aptr = a->pixels;
201         unsigned char *bptr = b->pixels;
202
203         if(a->width != b->width || a->height != b->height || a->bpp != b->bpp || a->nchan != b->nchan) {
204                 return -1;
205         }
206
207         for(i=0; i<a->height; i++) {
208                 if(memcmp(aptr, bptr, a->scansz) != 0) {
209                         return -1;
210                 }
211                 aptr += a->pitch;
212                 bptr += b->pitch;
213         }
214         return 0;
215 }
216
217 void blit(struct image *src, int sx, int sy, int w, int h, struct image *dst, int dx, int dy)
218 {
219         int i;
220         unsigned char *sptr, *dptr;
221
222         assert(src->bpp == dst->bpp);
223         assert(src->nchan == dst->nchan);
224
225         if(sx < 0) { w += sx; sx = 0; }
226         if(sy < 0) { h += sy; sy = 0; }
227         if(dx < 0) { w += dx; sx -= dx; dx = 0; }
228         if(dy < 0) { h += dy; sy -= dy; dy = 0; }
229         if(sx + w >= src->width) w = src->width - sx;
230         if(sy + h >= src->height) h = src->height - sy;
231         if(dx + w >= dst->width) w = dst->width - dx;
232         if(dy + h >= dst->height) h = dst->height - dy;
233
234         if(w <= 0 || h <= 0) return;
235
236         sptr = src->pixels + sy * src->pitch + sx * src->bpp / 8;
237         dptr = dst->pixels + dy * dst->pitch + dx * dst->bpp / 8;
238
239         for(i=0; i<h; i++) {
240                 memcpy(dptr, sptr, w * dst->bpp / 8);
241                 dptr += dst->pitch;
242                 sptr += src->pitch;
243         }
244 }
245
246 unsigned int get_pixel(struct image *img, int x, int y)
247 {
248         unsigned int r, g, b;
249         unsigned char *pptr;
250         unsigned short *pptr16;
251         unsigned int *pptr32;
252
253         switch(img->bpp) {
254         case 4:
255                 pptr = img->pixels + y * img->pitch + x / 2;
256                 return x & 1 ? *pptr & 0xf : *pptr >> 4;
257         case 8:
258                 pptr = img->pixels + y * img->pitch + x;
259                 return *pptr;
260         case 15:
261         case 16:
262                 pptr16 = (unsigned short*)(img->pixels + y * img->pitch + x * 2);
263                 return *pptr16;
264         case 24:
265                 pptr = img->pixels + y * img->pitch + x * 3;
266                 r = pptr[0];
267                 g = pptr[1];
268                 b = pptr[2];
269                 return r | (g << 8) | (b << 16);
270         case 32:
271                 pptr32 = (unsigned int*)(img->pixels + y * img->pitch + x * 4);
272                 return *pptr32;
273
274         default:
275                 fprintf(stderr, "get_pixel not implemented for %d bpp\n", img->bpp);
276         }
277
278         return 0;
279 }
280
281 unsigned int get_pixel_rgb(struct image *img, int x, int y, unsigned int *rgb)
282 {
283         unsigned int pix = get_pixel(img, x, y);
284
285         switch(img->bpp) {
286         case 15:
287                 rgb[0] = (pix & 0x7c00) >> 7;
288                 rgb[1] = (pix & 0x03e0) >> 2;
289                 rgb[2] = (pix & 0x001f) << 3;
290                 rgb[0] |= ((rgb[0] & 8) >> 1) | ((rgb[0] & 8) >> 2) | ((rgb[0] & 8) >> 3);
291                 rgb[1] |= ((rgb[1] & 8) >> 1) | ((rgb[1] & 8) >> 2) | ((rgb[1] & 8) >> 3);
292                 rgb[2] |= ((rgb[2] & 8) >> 1) | ((rgb[2] & 8) >> 2) | ((rgb[2] & 8) >> 3);
293                 break;
294
295         case 16:
296                 rgb[0] = (pix & 0xf800) >> 8;
297                 rgb[1] = (pix & 0x07e0) >> 3;
298                 rgb[2] = (pix & 0x001f) << 3;
299                 rgb[0] |= ((rgb[0] & 8) >> 1) | ((rgb[0] & 8) >> 2) | ((rgb[0] & 8) >> 3);
300                 rgb[1] |= ((rgb[1] & 4) >> 1) | ((rgb[1] & 4) >> 2);
301                 rgb[2] |= ((rgb[2] & 8) >> 1) | ((rgb[2] & 8) >> 2) | ((rgb[2] & 8) >> 3);
302                 break;
303
304         case 24:
305         case 32:
306                 rgb[0] = pix & 0xff;
307                 rgb[1] = (pix >> 8) & 0xff;
308                 rgb[2] = (pix >> 16) & 0xff;
309                 break;
310
311         default:
312                 assert(pix >= 0 && pix < img->cmap_ncolors);
313                 rgb[0] = img->cmap[pix].r;
314                 rgb[1] = img->cmap[pix].g;
315                 rgb[2] = img->cmap[pix].b;
316         }
317
318         return pix;
319 }
320
321 void put_pixel(struct image *img, int x, int y, unsigned int pix)
322 {
323         unsigned char *pptr;
324         unsigned short *pptr16;
325
326         switch(img->bpp) {
327         case 4:
328                 pptr = img->pixels + y * img->pitch + x / 2;
329                 if(x & 1) {
330                         *pptr = (*pptr & 0xf0) | pix;
331                 } else {
332                         *pptr = (*pptr & 0xf) | (pix << 4);
333                 }
334                 break;
335
336         case 8:
337                 pptr = img->pixels + y * img->pitch + x;
338                 *pptr = pix;
339                 break;
340
341         case 16:
342                 pptr16 = (unsigned short*)(img->pixels + y * img->pitch + x * 2);
343                 *pptr16 = pix;
344                 break;
345
346         default:
347                 fprintf(stderr, "put_pixel not implemented for %d bpp\n", img->bpp);
348         }
349 }
350
351 void overlay_key(struct image *src, unsigned int key, struct image *dst)
352 {
353         int i, j;
354         unsigned int pix;
355
356         assert(src->bpp == dst->bpp);
357         assert(src->width == dst->width);
358         assert(src->height == dst->height);
359
360         for(i=0; i<dst->height; i++) {
361                 for(j=0; j<dst->width; j++) {
362                         pix = get_pixel(src, j, i);
363                         if(pix != key) {
364                                 put_pixel(dst, j, i, pix);
365                         }
366                 }
367         }
368 }
369
370 #if 0
371 /* ---- color quantization ---- */
372 struct octnode;
373
374 struct octree {
375         struct octnode *root;
376         struct octnode *levn[8];
377         int ncol, maxcol;
378 };
379
380 struct octnode {
381         struct octree *tree;
382         int r, g, b, nref;
383         int palidx;
384         int nsub;
385         struct octnode *sub[8];
386         struct octnode *next;
387 };
388
389 static void add_color(struct octree *ot, int r, int g, int b);
390 static void reduce_colors(struct octree *ot);
391 static int assign_colors(struct octnode *on, int next, struct cmapent *cmap);
392 static int lookup_color(struct octree *ot, int r, int g, int b);
393 static struct octnode *new_node(struct octree *ot, int lvl);
394 static void del_node(struct octnode *on, int lvl);
395 static void print_tree(struct octnode *n, int lvl);
396 static int count_leaves(struct octnode *n);
397
398 void quantize_image(struct image *img, int maxcol)
399 {
400         int i, j, cidx;
401         unsigned int rgb[3];
402         struct octree ot = {0};
403         struct image newimg = *img;
404
405         if(img->bpp > 8) {
406                 newimg.bpp = 8;
407                 newimg.nchan = 1;
408                 newimg.scansz = newimg.width;
409                 newimg.pitch = 8 * img->pitch / img->bpp;
410         }
411
412         ot.root = new_node(&ot, 0);
413         ot.maxcol = maxcol;
414
415         for(i=0; i<img->height; i++) {
416                 for(j=0; j<img->width; j++) {
417                         get_pixel_rgb(img, j, i, rgb);
418                         add_color(&ot, rgb[0], rgb[1], rgb[2]);
419
420                         while(count_leaves(ot.root) > ot.maxcol) {
421                         //while(ot.ncol > ot.maxcol) {
422                                 reduce_colors(&ot);
423                         }
424                 }
425         }
426
427         /* use created octree to generate the palette */
428         newimg.cmap_ncolors = assign_colors(ot.root, 0, newimg.cmap);
429
430         /* replace image pixels */
431         for(i=0; i<img->height; i++) {
432                 for(j=0; j<img->width; j++) {
433                         get_pixel_rgb(img, j, i, rgb);
434                         cidx = lookup_color(&ot, rgb[0], rgb[1], rgb[2]);
435                         assert(cidx >= 0 && cidx < maxcol);
436                         put_pixel(&newimg, j, i, cidx);
437                 }
438         }
439
440         *img = newimg;
441 }
442
443 static int subidx(int bit, int r, int g, int b)
444 {
445         assert(bit >= 0 && bit < 8);
446         bit = 7 - bit;
447         return ((r >> bit) & 1) | ((g >> (bit - 1)) & 2) | ((b >> (bit - 2)) & 4);
448 }
449
450 static int tree_height(struct octnode *on)
451 {
452         int i, subh, max = 0;
453
454         if(!on) return 0;
455
456         for(i=0; i<8; i++) {
457                 subh = tree_height(on->sub[i]);
458                 if(subh > max) max = subh;
459         }
460         return max + 1;
461 }
462
463 static void add_color(struct octree *ot, int r, int g, int b)
464 {
465         int i, idx;
466         struct octnode *on;
467
468         on = ot->root;
469         for(i=0; i<8; i++) {
470                 idx = subidx(i, r, g, b);
471
472                 if(!on->sub[idx]) {
473                         on->sub[idx] = new_node(ot, i + 1);
474                         if(i == 7) {
475                                 /* this only adds a color if the parent node was previously not
476                                  * a leaf. Otherwise the new one just takes the parent's place
477                                  */
478                                 ot->ncol++;
479                         }
480                         on->nsub++;
481                 }
482
483                 on->r += r;
484                 on->g += g;
485                 on->b += b;
486                 on->nref++;
487
488                 on = on->sub[idx];
489         }
490
491         on->r += r;
492         on->g += g;
493         on->b += b;
494         on->nref++;
495 }
496
497 static int count_nodes(struct octnode *n)
498 {
499         int count = 0;
500         while(n) {
501                 count++;
502                 n = n->next;
503         }
504         return count;
505 }
506
507 static int count_leaves(struct octnode *n)
508 {
509         int i, cnt;
510
511         if(!n) return 0;
512         if(n->nsub <= 0) return 1;
513
514         cnt = 0;
515         for(i=0; i<8; i++) {
516                 cnt += count_leaves(n->sub[i]);
517         }
518         return cnt;
519 }
520
521 static void reduce_colors(struct octree *ot)
522 {
523         int i, lvl, best_nref;
524         struct octnode *n, *best;
525
526         lvl = 8;
527         while(--lvl >= 0) {
528                 best_nref = INT_MAX;
529                 best = 0;
530                 n = ot->levn[lvl];
531
532                 while(n) {
533                         if(n->nref < best_nref && n->nsub) {
534                                 best = n;
535                                 best_nref = n->nref;
536                         }
537                         n = n->next;
538                 }
539
540                 if(best) {
541                         for(i=0; i<8; i++) {
542                                 if(best->sub[i]) {
543                                         del_node(best->sub[i], lvl + 1);
544                                         best->sub[i] = 0;
545                                 }
546                         }
547                         if(best->nsub) {
548                                 /* this wasn't previously a leaf, but now it is */
549                                 ot->ncol++;
550                                 best->nsub = 0;
551                         }
552                         break;
553                 }
554         }
555 }
556
557 static int assign_colors(struct octnode *on, int next, struct cmapent *cmap)
558 {
559         int i;
560
561         if(!on) return next;
562
563         if(on->nsub <= 0) {
564                 assert(next < on->tree->maxcol);
565                 cmap[next].r = on->r / on->nref;
566                 cmap[next].g = on->g / on->nref;
567                 cmap[next].b = on->b / on->nref;
568                 on->palidx = next++;
569         }
570
571         for(i=0; i<8; i++) {
572                 next = assign_colors(on->sub[i], next, cmap);
573         }
574         return next;
575 }
576
577 static int lookup_color(struct octree *ot, int r, int g, int b)
578 {
579         int i, idx;
580         struct octnode *on = ot->root;
581
582         for(i=0; i<8; i++) {
583                 idx = subidx(i, r, g, b);
584                 if(!on->sub[idx]) break;
585                 on = on->sub[idx];
586         }
587
588         return on->palidx;
589 }
590
591 static int have_node(struct octnode *list, struct octnode *n)
592 {
593         while(list) {
594                 if(list == n) return 1;
595                 list = list->next;
596         }
597         return 0;
598 }
599
600 static struct octnode *new_node(struct octree *ot, int lvl)
601 {
602         struct octnode *on;
603
604         if(!(on = calloc(1, sizeof *on))) {
605                 perror("failed to allocate octree node");
606                 abort();
607         }
608
609         on->tree = ot;
610         on->palidx = -1;
611
612         if(lvl < 8) {
613                 if(have_node(ot->levn[lvl], on)) {
614                         fprintf(stderr, "double-insertion!\n");
615                         abort();
616                 }
617                 on->next = ot->levn[lvl];
618                 ot->levn[lvl] = on;
619         }
620         return on;
621 }
622
623 static void del_node(struct octnode *on, int lvl)
624 {
625         int i;
626         struct octree *ot;
627         struct octnode dummy, *prev;
628
629         if(!on) return;
630         ot = on->tree;
631
632         if(!on->nsub) {
633                 ot->ncol--;     /* removing a leaf removes a color */
634         }
635
636         for(i=0; i<8; i++) {
637                 del_node(on->sub[i], lvl + 1);
638         }
639
640         if(lvl < 8) {
641                 dummy.next = ot->levn[lvl];
642                 prev = &dummy;
643
644                 while(prev->next) {
645                         if(prev->next == on) {
646                                 prev->next = on->next;
647                                 break;
648                         }
649                         prev = prev->next;
650                 }
651                 ot->levn[lvl] = dummy.next;
652         }
653
654         free(on);
655 }
656
657 static void print_tree(struct octnode *n, int lvl)
658 {
659         int i;
660
661         if(!n) return;
662
663         for(i=0; i<lvl; i++) {
664                 fputs("|  ", stdout);
665         }
666
667         printf("+-%p: <%d %d %d> #%d", (void*)n, n->r, n->g, n->b, n->nref);
668         if(n->palidx >= 0) printf(" [%d]\n", n->palidx);
669         putchar('\n');
670
671         for(i=0; i<8; i++) {
672                 print_tree(n->sub[i], lvl + 1);
673         }
674 }
675 #endif