add missing tools/pngdump to the repo
[gbajam22] / 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 == 15 ? 16 : 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 15:
342         case 16:
343                 pptr16 = (unsigned short*)(img->pixels + y * img->pitch + x * 2);
344                 *pptr16 = pix;
345                 break;
346
347         default:
348                 fprintf(stderr, "put_pixel not implemented for %d bpp\n", img->bpp);
349         }
350 }
351
352 void overlay_key(struct image *src, unsigned int key, struct image *dst)
353 {
354         int i, j;
355         unsigned int pix;
356
357         assert(src->bpp == dst->bpp);
358         assert(src->width == dst->width);
359         assert(src->height == dst->height);
360
361         for(i=0; i<dst->height; i++) {
362                 for(j=0; j<dst->width; j++) {
363                         pix = get_pixel(src, j, i);
364                         if(pix != key) {
365                                 put_pixel(dst, j, i, pix);
366                         }
367                 }
368         }
369 }
370
371 #if 0
372 /* ---- color quantization ---- */
373 struct octnode;
374
375 struct octree {
376         struct octnode *root;
377         struct octnode *levn[8];
378         int ncol, maxcol;
379 };
380
381 struct octnode {
382         struct octree *tree;
383         int r, g, b, nref;
384         int palidx;
385         int nsub;
386         struct octnode *sub[8];
387         struct octnode *next;
388 };
389
390 static void add_color(struct octree *ot, int r, int g, int b);
391 static void reduce_colors(struct octree *ot);
392 static int assign_colors(struct octnode *on, int next, struct cmapent *cmap);
393 static int lookup_color(struct octree *ot, int r, int g, int b);
394 static struct octnode *new_node(struct octree *ot, int lvl);
395 static void del_node(struct octnode *on, int lvl);
396 static void print_tree(struct octnode *n, int lvl);
397 static int count_leaves(struct octnode *n);
398
399 void quantize_image(struct image *img, int maxcol)
400 {
401         int i, j, cidx;
402         unsigned int rgb[3];
403         struct octree ot = {0};
404         struct image newimg = *img;
405
406         if(img->bpp > 8) {
407                 newimg.bpp = 8;
408                 newimg.nchan = 1;
409                 newimg.scansz = newimg.width;
410                 newimg.pitch = 8 * img->pitch / img->bpp;
411         }
412
413         ot.root = new_node(&ot, 0);
414         ot.maxcol = maxcol;
415
416         for(i=0; i<img->height; i++) {
417                 for(j=0; j<img->width; j++) {
418                         get_pixel_rgb(img, j, i, rgb);
419                         add_color(&ot, rgb[0], rgb[1], rgb[2]);
420
421                         while(count_leaves(ot.root) > ot.maxcol) {
422                         //while(ot.ncol > ot.maxcol) {
423                                 reduce_colors(&ot);
424                         }
425                 }
426         }
427
428         /* use created octree to generate the palette */
429         newimg.cmap_ncolors = assign_colors(ot.root, 0, newimg.cmap);
430
431         /* replace image pixels */
432         for(i=0; i<img->height; i++) {
433                 for(j=0; j<img->width; j++) {
434                         get_pixel_rgb(img, j, i, rgb);
435                         cidx = lookup_color(&ot, rgb[0], rgb[1], rgb[2]);
436                         assert(cidx >= 0 && cidx < maxcol);
437                         put_pixel(&newimg, j, i, cidx);
438                 }
439         }
440
441         *img = newimg;
442 }
443
444 static int subidx(int bit, int r, int g, int b)
445 {
446         assert(bit >= 0 && bit < 8);
447         bit = 7 - bit;
448         return ((r >> bit) & 1) | ((g >> (bit - 1)) & 2) | ((b >> (bit - 2)) & 4);
449 }
450
451 static int tree_height(struct octnode *on)
452 {
453         int i, subh, max = 0;
454
455         if(!on) return 0;
456
457         for(i=0; i<8; i++) {
458                 subh = tree_height(on->sub[i]);
459                 if(subh > max) max = subh;
460         }
461         return max + 1;
462 }
463
464 static void add_color(struct octree *ot, int r, int g, int b)
465 {
466         int i, idx;
467         struct octnode *on;
468
469         on = ot->root;
470         for(i=0; i<8; i++) {
471                 idx = subidx(i, r, g, b);
472
473                 if(!on->sub[idx]) {
474                         on->sub[idx] = new_node(ot, i + 1);
475                         if(i == 7) {
476                                 /* this only adds a color if the parent node was previously not
477                                  * a leaf. Otherwise the new one just takes the parent's place
478                                  */
479                                 ot->ncol++;
480                         }
481                         on->nsub++;
482                 }
483
484                 on->r += r;
485                 on->g += g;
486                 on->b += b;
487                 on->nref++;
488
489                 on = on->sub[idx];
490         }
491
492         on->r += r;
493         on->g += g;
494         on->b += b;
495         on->nref++;
496 }
497
498 static int count_nodes(struct octnode *n)
499 {
500         int count = 0;
501         while(n) {
502                 count++;
503                 n = n->next;
504         }
505         return count;
506 }
507
508 static int count_leaves(struct octnode *n)
509 {
510         int i, cnt;
511
512         if(!n) return 0;
513         if(n->nsub <= 0) return 1;
514
515         cnt = 0;
516         for(i=0; i<8; i++) {
517                 cnt += count_leaves(n->sub[i]);
518         }
519         return cnt;
520 }
521
522 static void reduce_colors(struct octree *ot)
523 {
524         int i, lvl, best_nref;
525         struct octnode *n, *best;
526
527         lvl = 8;
528         while(--lvl >= 0) {
529                 best_nref = INT_MAX;
530                 best = 0;
531                 n = ot->levn[lvl];
532
533                 while(n) {
534                         if(n->nref < best_nref && n->nsub) {
535                                 best = n;
536                                 best_nref = n->nref;
537                         }
538                         n = n->next;
539                 }
540
541                 if(best) {
542                         for(i=0; i<8; i++) {
543                                 if(best->sub[i]) {
544                                         del_node(best->sub[i], lvl + 1);
545                                         best->sub[i] = 0;
546                                 }
547                         }
548                         if(best->nsub) {
549                                 /* this wasn't previously a leaf, but now it is */
550                                 ot->ncol++;
551                                 best->nsub = 0;
552                         }
553                         break;
554                 }
555         }
556 }
557
558 static int assign_colors(struct octnode *on, int next, struct cmapent *cmap)
559 {
560         int i;
561
562         if(!on) return next;
563
564         if(on->nsub <= 0) {
565                 assert(next < on->tree->maxcol);
566                 cmap[next].r = on->r / on->nref;
567                 cmap[next].g = on->g / on->nref;
568                 cmap[next].b = on->b / on->nref;
569                 on->palidx = next++;
570         }
571
572         for(i=0; i<8; i++) {
573                 next = assign_colors(on->sub[i], next, cmap);
574         }
575         return next;
576 }
577
578 static int lookup_color(struct octree *ot, int r, int g, int b)
579 {
580         int i, idx;
581         struct octnode *on = ot->root;
582
583         for(i=0; i<8; i++) {
584                 idx = subidx(i, r, g, b);
585                 if(!on->sub[idx]) break;
586                 on = on->sub[idx];
587         }
588
589         return on->palidx;
590 }
591
592 static int have_node(struct octnode *list, struct octnode *n)
593 {
594         while(list) {
595                 if(list == n) return 1;
596                 list = list->next;
597         }
598         return 0;
599 }
600
601 static struct octnode *new_node(struct octree *ot, int lvl)
602 {
603         struct octnode *on;
604
605         if(!(on = calloc(1, sizeof *on))) {
606                 perror("failed to allocate octree node");
607                 abort();
608         }
609
610         on->tree = ot;
611         on->palidx = -1;
612
613         if(lvl < 8) {
614                 if(have_node(ot->levn[lvl], on)) {
615                         fprintf(stderr, "double-insertion!\n");
616                         abort();
617                 }
618                 on->next = ot->levn[lvl];
619                 ot->levn[lvl] = on;
620         }
621         return on;
622 }
623
624 static void del_node(struct octnode *on, int lvl)
625 {
626         int i;
627         struct octree *ot;
628         struct octnode dummy, *prev;
629
630         if(!on) return;
631         ot = on->tree;
632
633         if(!on->nsub) {
634                 ot->ncol--;     /* removing a leaf removes a color */
635         }
636
637         for(i=0; i<8; i++) {
638                 del_node(on->sub[i], lvl + 1);
639         }
640
641         if(lvl < 8) {
642                 dummy.next = ot->levn[lvl];
643                 prev = &dummy;
644
645                 while(prev->next) {
646                         if(prev->next == on) {
647                                 prev->next = on->next;
648                                 break;
649                         }
650                         prev = prev->next;
651                 }
652                 ot->levn[lvl] = dummy.next;
653         }
654
655         free(on);
656 }
657
658 static void print_tree(struct octnode *n, int lvl)
659 {
660         int i;
661
662         if(!n) return;
663
664         for(i=0; i<lvl; i++) {
665                 fputs("|  ", stdout);
666         }
667
668         printf("+-%p: <%d %d %d> #%d", (void*)n, n->r, n->g, n->b, n->nref);
669         if(n->palidx >= 0) printf(" [%d]\n", n->palidx);
670         putchar('\n');
671
672         for(i=0; i<8; i++) {
673                 print_tree(n->sub[i], lvl + 1);
674         }
675 }
676 #endif