ncurses 6.0 - patch 20170603
[ncurses.git] / test / picsmap.c
1 /****************************************************************************
2  * Copyright (c) 2017 Free Software Foundation, Inc.                        *
3  *                                                                          *
4  * Permission is hereby granted, free of charge, to any person obtaining a  *
5  * copy of this software and associated documentation files (the            *
6  * "Software"), to deal in the Software without restriction, including      *
7  * without limitation the rights to use, copy, modify, merge, publish,      *
8  * distribute, distribute with modifications, sublicense, and/or sell       *
9  * copies of the Software, and to permit persons to whom the Software is    *
10  * furnished to do so, subject to the following conditions:                 *
11  *                                                                          *
12  * The above copyright notice and this permission notice shall be included  *
13  * in all copies or substantial portions of the Software.                   *
14  *                                                                          *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
16  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
17  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
18  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
19  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
20  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
21  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
22  *                                                                          *
23  * Except as contained in this notice, the name(s) of the above copyright   *
24  * holders shall not be used in advertising or otherwise to promote the     *
25  * sale, use or other dealings in this Software without prior written       *
26  * authorization.                                                           *
27  ****************************************************************************/
28 /*
29  * $Id: picsmap.c,v 1.35 2017/06/04 00:20:15 tom Exp $
30  *
31  * Author: Thomas E. Dickey
32  *
33  * A little more interesting than "dots", read a simple image into memory and
34  * measure the time taken to paint it normally vs randomly.
35  *
36  * TODO write cells/second to stderr (or log)
37  * TODO write picture left-to-right/top-to-bottom
38  * TODO write picture randomly
39  * TODO add one-shot option vs repeat-count before exiting
40  * TODO add option for init_color
41  * TODO add option for init_color vs init_extended_color
42  * TODO add option for init_pair vs alloc_pair
43  * TODO use pad to allow pictures larger than screen
44  * TODO improve load of image-file's color-table using tsearch.
45  */
46 #include <test.priv.h>
47
48 #include <sys/types.h>
49 #include <sys/stat.h>
50
51 #undef CUR                      /* use only the curses interface */
52
53 #define  L_BLOCK '['
54 #define  R_BLOCK ']'
55
56 #define  L_CURLY '{'
57 #define  R_CURLY '}'
58
59 #define okCOLOR(n) ((n) >= 0 && (n) < COLORS)
60 #define okRGB(n)   ((n) >= 0 && (n) <= 1000)
61 #define Scaled256(n) (NCURSES_COLOR_T) (int)(((n) * 1000.0) / 256)
62 #define ScaledColor(n) (NCURSES_COLOR_T) (int)(((n) * 1000.0) / scale)
63
64 typedef struct {
65     int ch;                     /* nominal character to display */
66     int fg;                     /* foreground color */
67 } PICS_CELL;
68
69 typedef struct {
70     int fg;
71     int bg;
72 } PICS_PAIR;
73
74 typedef struct {
75     char *name;
76     int high;
77     int wide;
78     int colors;
79     PICS_PAIR *pairs;
80     PICS_CELL *cells;
81 } PICS_HEAD;
82
83 typedef struct {
84     const char *name;
85     int value;
86 } RGB_NAME;
87
88 typedef struct {
89     short red;
90     short green;
91     short blue;
92 } RGB_DATA;
93
94 static void giveup(const char *fmt,...) GCC_PRINTFLIKE(1, 2);
95
96 static bool in_curses = FALSE;
97 static RGB_NAME *rgb_table;
98 static RGB_DATA *all_colors;
99
100 static void
101 free_data(char **data)
102 {
103     free(data[0]);
104     free(data);
105 }
106
107 static void
108 free_pics_head(PICS_HEAD * pics)
109 {
110     free(pics->pairs);
111     free(pics->cells);
112     free(pics);
113 }
114
115 /*
116  * Simplify reading xbm/xpm files by first making an array of lines.  Blank
117  * lines are filtered out.
118  */
119 static char **
120 read_file(const char *filename)
121 {
122     char **result = 0;
123     struct stat sb;
124
125     if (in_curses)
126         endwin();
127
128     printf("** %s\n", filename);
129
130     if (stat(filename, &sb) == 0
131         && (sb.st_mode & S_IFMT) == S_IFREG
132         && sb.st_size != 0) {
133         size_t size = (size_t) sb.st_size;
134         char *blob = typeMalloc(char, size + 1);
135         bool had_line = TRUE;
136         unsigned j;
137         unsigned k = 0;
138
139         result = typeCalloc(char *, size + 1);
140         if (blob != 0 && result != 0) {
141             FILE *fp = fopen(filename, "r");
142             if (fp != 0) {
143                 if (fread(blob, sizeof(char), size, fp) == size) {
144                     for (j = 0; (size_t) j < size; ++j) {
145                         if (blob[j] == '\n') {
146                             blob[j] = '\0';
147                             had_line = TRUE;
148                         } else if (had_line) {
149                             had_line = FALSE;
150                             result[k++] = blob + j;
151                         }
152                     }
153                     result[k] = 0;
154                 }
155                 fclose(fp);
156             }
157         }
158         if (k == 0) {
159             free(blob);
160             free(result);
161             result = 0;
162         }
163     }
164     return result;
165 }
166
167 static void
168 usage(void)
169 {
170     static const char *msg[] =
171     {
172         "Usage: picsmap [options] [imagefile [...]]",
173         "Read/display one or more xbm/xpm files (possibly use \"convert\")",
174         "",
175         "Options:",
176         "  -p palette",
177         "  -r rgb-path"
178     };
179     size_t n;
180
181     if (in_curses)
182         endwin();
183
184     fflush(stdout);
185     for (n = 0; n < SIZEOF(msg); n++)
186         fprintf(stderr, "%s\n", msg[n]);
187     ExitProgram(EXIT_FAILURE);
188 }
189
190 static void
191 giveup(const char *fmt,...)
192 {
193     va_list ap;
194     if (in_curses)
195         endwin();
196     fflush(stdout);
197     va_start(ap, fmt);
198     vfprintf(stderr, fmt, ap);
199     fputc('\n', stderr);
200     va_end(ap);
201     usage();
202 }
203
204 static void
205 init_palette(const char *palette_file)
206 {
207     if (palette_file != 0) {
208         int cp;
209
210         all_colors = typeMalloc(RGB_DATA, (unsigned) COLORS);
211         for (cp = 0; cp < COLORS; ++cp) {
212             color_content(cp,
213                           &all_colors[cp].red,
214                           &all_colors[cp].green,
215                           &all_colors[cp].blue);
216             if (palette_file != 0) {
217                 char **data = read_file(palette_file);
218                 if (data != 0) {
219                     int n;
220                     int red, green, blue;
221                     int scale = 1000;
222                     int c;
223                     for (n = 0; data[n] != 0; ++n) {
224                         if (sscanf(data[n], "scale:%d", &c) == 1) {
225                             scale = c;
226                         } else if (sscanf(data[n], "%d:%d %d %d",
227                                           &c,
228                                           &red,
229                                           &green,
230                                           &blue) == 4
231                                    && okCOLOR(c)
232                                    && okRGB(red)
233                                    && okRGB(green)
234                                    && okRGB(blue)) {
235                             /* *INDENT-EQLS* */
236                             all_colors[c].red   = ScaledColor(red);
237                             all_colors[c].green = ScaledColor(green);
238                             all_colors[c].blue  = ScaledColor(blue);
239                         }
240                     }
241                     free_data(data);
242                 }
243             }
244         }
245     } else if (COLORS   > 1) {
246         /* *INDENT-EQLS* */
247         int power2 = 1;
248         int shift = 0;
249
250         while (power2 < COLORS) {
251             ++shift;
252             power2 <<= 1;
253         }
254
255         if (power2 != COLORS || (shift % 3) != 0) {
256             giveup("With %d colors, you need a palette-file", COLORS);
257         }
258     }
259 }
260
261 /*
262  * Map the 24-bit RGB value to a color index if using a palette, otherwise to a
263  * direct color value.
264  */
265 static int
266 map_color(int value)
267 {
268     int result = value;
269
270     if (result < 0) {
271         result = -1;
272     } else {
273         /* *INDENT-EQLS* */
274         int red   = (value & 0xff0000) >> 16;
275         int green = (value & 0x00ff00) >> 8;
276         int blue  = (value & 0x0000ff) >> 0;
277
278         if (all_colors != 0) {
279 #define Diff2(n,m) ((m) - all_colors[n].m) * ((m) - all_colors[n].m)
280 #define Diff2S(n) Diff2(n,red) + Diff2(n,green) + Diff2(n,blue)
281             int d2 = Diff2S(0);
282             int n;
283
284             /* *INDENT-EQLS* */
285             red   = Scaled256(red);
286             green = Scaled256(green);
287             blue  = Scaled256(blue);
288
289             for (result = 0, n = 1; n < COLORS; ++n) {
290                 int d = Diff2(n, red) + Diff2(n, green) + Diff2(n, blue);
291                 if (d < d2) {
292                     d2 = d;
293                     result = n;
294                 }
295             }
296         } else {                /* direct color */
297             int power2 = 1;
298             int shifts = 8;
299
300             while (power2 < COLORS) {
301                 power2 <<= 3;
302                 shifts--;
303             }
304
305             if (shifts > 0) {
306                 /* TODO: round up */
307                 red >>= shifts;
308                 green >>= shifts;
309                 blue >>= shifts;
310                 result = ((red << (2 * (8 - shifts)))
311                           + (green << (8 - shifts))
312                           + blue);
313             }
314         }
315     }
316     return result;
317 }
318
319 static int
320 bytes_of(int value)
321 {
322     if (value & 7) {
323         value |= 7;
324         value++;
325     }
326     return value;
327 }
328
329 static int match_c(const char *, const char *,...) GCC_SCANFLIKE(2,3);
330
331 static char *
332 skip_s(char *s)
333 {
334     while (isspace(UChar(*s)))
335         s++;
336     return s;
337 }
338
339 static const char *
340 skip_cs(const char *s)
341 {
342     while (isspace(UChar(*s)))
343         s++;
344     return s;
345 }
346
347 static int
348 match_c(const char *source, const char *pattern,...)
349 {
350     int limit = (int) strlen(source);
351     const char *last_s = source + limit;
352     va_list ap;
353     int ch;
354     int *ip;
355     char *cp;
356     long lv;
357
358     va_start(ap, pattern);
359
360     limit = -1;
361     while (*pattern != '\0') {
362         ch = UChar(*pattern++);
363         /* blank in the pattern matches zero-or-more blanks in source */
364         if (isspace(ch)) {
365             source = skip_cs(source);
366             continue;
367         }
368         /* %c, %d, %s are like sscanf except for special treatment of blanks */
369         if (ch == '%' && *pattern != '\0' && strchr("cdnsx", *pattern)) {
370             bool found = FALSE;
371             ch = *pattern++;
372             switch (ch) {
373             case 'c':
374                 cp = va_arg(ap, char *);
375                 do {
376                     *cp++ = *source++;
377                 } while (--limit > 0);
378                 break;
379             case 'd':
380             case 'x':
381                 limit = -1;
382                 ip = va_arg(ap, int *);
383                 lv = strtol(source, &cp, ch == 'd' ? 10 : 16);
384                 if (cp != 0 && cp != source) {
385                     *ip = (int) lv;
386                     source = cp;
387                 } else {
388                     goto finish;
389                 }
390                 break;
391             case 'n':
392                 /* not really sscanf... */
393                 limit = *va_arg(ap, int *);
394                 break;
395             case 's':
396                 limit = -1;
397                 cp = va_arg(ap, char *);
398                 while (*source != '\0') {
399                     ch = UChar(*source);
400                     if (isspace(ch)) {
401                         break;
402                     } else if (found && (ch == *skip_cs(pattern))) {
403                         break;
404                     } else {
405                         *cp++ = *source++;
406                         found = TRUE;
407                     }
408                 }
409                 *cp = '\0';
410                 break;
411             }
412             continue;
413         }
414         /* other characters are matched literally */
415         if (*source++ != ch) {
416             break;
417         }
418     }
419   finish:
420
421     va_end(ap);
422     if (source > last_s)
423         source = last_s;
424     return (*source || *pattern) ? 0 : 1;
425 }
426
427 static int
428 match_colors(const char *source, int cpp, char *arg1, char *arg2, char *arg3)
429 {
430     int result = 0;
431
432     /* most files use a quasi-fixed format */
433     if (match_c(source, " \"%n%c %s %s \" , ", &cpp, arg1, arg2, arg3)) {
434         arg1[cpp] = '\0';
435         result = 1;
436     } else {
437         char *t;
438         const char *s = skip_cs(source);
439         size_t have = strlen(source);
440
441         if (*s++ == '"' && have > ((size_t) cpp + 2)) {
442             memcpy(arg1, s, (size_t) cpp);
443             s += cpp;
444             while (*s++ == '\t') {
445                 for (t = arg2; (*s != '\0') && strchr("\t\"", *s) == 0;) {
446                     if (*s == ' ') {
447                         s = skip_cs(s);
448                         break;
449                     }
450                     *t++ = *s++;
451                     *t = '\0';
452                 }
453                 for (t = arg3; (*s != '\0') && strchr("\t\"", *s) == 0;) {
454                     *t++ = *s++;
455                     *t = '\0';
456                 }
457                 if (!strcmp(arg2, "c")) {
458                     result = 1;
459                     break;
460                 }
461             }
462         }
463     }
464     return result;
465 }
466
467 static RGB_NAME *
468 parse_rgb(char **data)
469 {
470     char buf[BUFSIZ];
471     int n;
472     unsigned long r, g, b;
473     char *s, *t;
474     size_t item = 0;
475     size_t need;
476     RGB_NAME *result = 0;
477
478     for (need = 0; data[need] != 0; ++need) ;
479     result = typeCalloc(RGB_NAME, need + 2);
480
481     for (n = 0; data[n] != 0; ++n) {
482         if (strlen(t = data[n]) >= sizeof(buf) - 1)
483             continue;
484         if (*(s = skip_s(t)) == '!')
485             continue;
486
487         r = strtoul(s, &t, 10);
488         s = skip_s(t);
489         g = strtoul(s, &t, 10);
490         s = skip_s(t);
491         b = strtoul(s, &t, 10);
492         s = skip_s(t);
493
494         result[item].name = s;
495         t = s + strlen(s);
496         while (t-- != s && isspace(UChar(*t))) {
497             *t = '\0';
498         }
499         result[item].value = (int) ((r & 0xff) << 16 | (g & 0xff) << 8 | (b
500                                                                           & 0xff));
501         ++item;
502     }
503
504     result[item].name = "none";
505     result[item].value = -1;
506
507     return result;
508 }
509
510 static RGB_NAME *
511 lookup_rgb(const char *name)
512 {
513     RGB_NAME *result = 0;
514     if (rgb_table != 0) {
515         int n;
516         for (n = 0; rgb_table[n].name != 0; ++n) {
517             if (!strcasecmp(name, rgb_table[n].name)) {
518                 result = &rgb_table[n];
519                 break;
520             }
521         }
522     }
523     return result;
524 }
525
526 static PICS_HEAD *
527 parse_xbm(char **data)
528 {
529     int n;
530     int state = 0;
531     char buf[BUFSIZ];
532     int num;
533     char ch;
534     char *s;
535     char *t;
536     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
537     size_t which = 0;
538     size_t cells = 0;
539
540     for (n = 0; data[n] != 0; ++n) {
541         if (strlen(s = data[n]) >= sizeof(buf) - 1)
542             continue;
543         switch (state) {
544         case 0:
545         case 1:
546         case 2:
547             if (sscanf(s, "#define %s %d%c", buf, &num, &ch) >= 2) {
548                 if ((t = strstr(buf, "_width")) != 0) {
549                     state |= 1;
550                     result->wide = bytes_of(num);
551                 } else if ((t = strstr(buf, "_height")) != 0) {
552                     state |= 2;
553                     result->high = num;
554                 }
555                 *t = '\0';
556                 if (result->name) {
557                     if (strcmp(result->name, buf)) {
558                         goto finish;
559                     }
560                 } else {
561                     result->name = strdup(buf);
562                 }
563             }
564             break;
565         case 3:
566             if (sscanf(s, "static char %[^_ ]_bits[]%c", buf, &ch) >= 1) {
567                 if (strcmp(result->name, buf)) {
568                     goto finish;
569                 }
570                 state = 4;
571                 cells = (size_t) (result->wide * result->high);
572                 result->cells = typeCalloc(PICS_CELL, cells);
573                 if ((s = strchr(s, L_CURLY)) == 0)
574                     break;
575                 ++s;
576             } else {
577                 break;
578             }
579         case 4:
580             while (*s != '\0') {
581                 while (isspace(UChar(*s))) {
582                     ++s;
583                 }
584                 if (isdigit(UChar(*s))) {
585                     long value = strtol(s, &t, 0);
586                     int b;
587                     if (t != s || value > 255 || value < 0) {
588                         s = t;
589                     } else {
590                         state = -1;
591                         goto finish;
592                     }
593                     /* TODO: which order? */
594                     for (b = 0; b < 8; ++b) {
595                         if (((1L << b) & value) != 0) {
596                             result->cells[which].ch = '*';
597                             result->cells[which].fg = 1;
598                         } else {
599                             result->cells[which].ch = ' ';
600                             result->cells[which].fg = 0;
601                         }
602                         if (++which > cells) {
603                             state = -1;
604                             goto finish;
605                         }
606                     }
607                 }
608                 if (*s == R_CURLY) {
609                     state = 5;
610                     goto finish;
611                 } else if (*s == ',') {
612                     ++s;
613                 }
614             }
615             break;
616         default:
617             break;
618         }
619     }
620   finish:
621     if (state < 4) {
622         if (result) {
623             free_pics_head(result);
624             result = 0;
625         }
626     } else {
627         result->colors = 2;
628         result->pairs = typeCalloc(PICS_PAIR, 2);
629         result->pairs[1].fg = 0xffffff;
630     }
631     return result;
632 }
633
634 static PICS_HEAD *
635 parse_xpm(char **data)
636 {
637     int state = 0;
638     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
639     RGB_NAME *by_name;
640     int n;
641     int cells = 0;
642     int color = 0;
643     int cpp = 1;                /* chars per pixel */
644     int num[6];
645     int which = 0;
646     char ch;
647     const char *cs;
648     char *s;
649     char buf[BUFSIZ];
650     char arg1[BUFSIZ];
651     char arg2[BUFSIZ];
652     char arg3[BUFSIZ];
653     char **list = 0;
654
655     for (n = 0; data[n] != 0; ++n) {
656         if (strlen(s = data[n]) >= sizeof(buf) - 1)
657             continue;
658         switch (state) {
659         case 0:
660             if (match_c(s, " /* XPM */ ")) {
661                 state = 1;
662             }
663             break;
664         case 1:
665             if (match_c(s, " static char * %s [] = %c ", arg1, &ch) &&
666                 ch == L_CURLY) {
667                 result->name = strdup(arg1);
668                 state = 2;
669             }
670             break;
671         case 2:
672             if (match_c(s, " \" %d %d %d %d \" , ",
673                         num + 0, num + 1, num + 2, num + 3) ||
674                 match_c(s, " \" %d %d %d %d %d %d \" , ",
675                         num + 0, num + 1, num + 2, num + 3, num + 4, num + 5)) {
676                 result->wide = num[0];
677                 result->high = num[1];
678                 result->colors = num[2];
679                 result->pairs = typeCalloc(PICS_PAIR, result->colors);
680                 cells = (size_t) (result->wide * result->high);
681                 result->cells = typeCalloc(PICS_CELL, cells);
682                 list = typeCalloc(char *, result->colors);
683                 cpp = num[3];
684                 state = 3;
685             }
686             break;
687         case 3:
688             if (!match_colors(s, cpp, arg1, arg2, arg3)) {
689                 break;
690             }
691             list[color] = strdup(arg1);
692             if ((by_name = lookup_rgb(arg3)) != 0) {
693                 result->pairs[color].fg = by_name->value;
694             } else if (*arg3 == '#') {
695                 char *rgb = arg3 + 1;
696                 unsigned long value = strtoul(rgb, &s, 16);
697                 switch ((int) strlen(rgb)) {
698                 case 6:
699                     break;
700                 case 12:
701                     value = (((value >> 24) & 0xff0000L)
702                              | ((value >> 16) & 0xff00L)
703                              | ((value >> 8) & 0xffL));
704                     break;
705                 default:
706                     printf("unexpected rgb value %s\n", rgb);
707                     break;
708                 }
709                 result->pairs[color].fg = (int) value;
710             } else {
711                 result->pairs[color].fg = 0;    /* actually an error */
712             }
713             if (++color >= result->colors)
714                 state = 4;
715             break;
716         case 4:
717             if (*(cs = skip_cs(s)) == '"') {
718                 ++cs;
719                 while (*cs != '\0' && *cs != '"') {
720                     int c;
721
722                     for (c = 0; c < result->colors; ++c) {
723                         if (!strncmp(cs, list[c], (size_t) cpp)) {
724                             result->cells[which].ch = list[c][0];
725                             result->cells[which].fg = c;
726                             break;
727                         }
728                     }
729
730                     if (result->cells[which].ch == 0) {
731                         result->cells[which].ch = '?';
732                         result->cells[which].fg = 0;
733                     }
734
735                     if (++which >= cells) {
736                         state = 5;
737                         break;
738                     }
739                     for (c = cpp; c > 0; --c, ++cs) ;
740                 }
741             }
742             break;
743         }
744     }
745
746     if (result && list) {
747         for (n = 0; n < result->colors; ++n)
748             free(list[n]);
749         free(list);
750     }
751
752     if (state < 5) {
753         free_pics_head(result);
754         result = 0;
755     }
756
757     return result;
758 }
759
760 /*
761  * The obscurely-named "convert" is provided by ImageMagick
762  */
763 static PICS_HEAD *
764 parse_img(const char *filename)
765 {
766     char *cmd = malloc(strlen(filename) + 80);
767     FILE *pp;
768     char buffer[BUFSIZ];
769     bool failed = FALSE;
770     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
771
772     sprintf(cmd, "convert -thumbnail %dx \"%s\" txt:-", COLS, filename);
773     if ((pp = popen(cmd, "r")) != 0) {
774         int count = 0;
775         int col = 0;
776         int row = 0;
777         int len = 0;
778         while (fgets(buffer, sizeof(buffer), pp) != 0) {
779             if (strlen(buffer) > 160) {         /* 80 columns would be enough */
780                 failed = TRUE;
781                 break;
782             }
783             if (count++ == 0) {
784                 if (match_c(buffer,
785                             "# ImageMagick pixel enumeration: %d,%d,%d,srgba ",
786                             &col, &row, &len)) {
787                     result->name = strdup(filename);
788                     result->wide = col;
789                     result->high = row;
790                     result->colors = 256;
791                     result->pairs = typeCalloc(PICS_PAIR, result->colors);
792                     result->cells = typeCalloc(PICS_CELL, (size_t) (col * row));
793                 } else {
794                     failed = TRUE;
795                     break;
796                 }
797             } else {
798                 /* subsequent lines begin "col,row: (r,g,b,a) #RGB" */
799                 int r, g, b;
800                 unsigned check;
801                 int which, c;
802                 char *s = strchr(buffer, '#');
803                 if (s != 0) {
804                     /* after the "#RGB", there are differences - just ignore */
805                     while (*s != '\0' && !isspace(UChar(*s)))
806                         ++s;
807                     *++s = '\0';
808                 }
809                 if (match_c(buffer,
810                             "%d,%d: (%d,%d,%d,255) #%x ",
811                             &col, &row,
812                             &r, &g, &b,
813                             &check)) {
814                     if (r > 255 ||
815                         g > 255 ||
816                         b > 255 ||
817                         check != (unsigned) ((r << 16) | (g << 8) | b)) {
818                         failed = TRUE;
819                         break;
820                     }
821                     for (c = 0; c < result->colors; ++c) {
822                         if (result->pairs[c].fg == (int) check) {
823                             break;
824                         } else if (result->pairs[c].fg == 0) {
825                             result->pairs[c].fg = (int) check;
826                             break;
827                         }
828                     }
829                     if (c >= result->colors) {
830                         int more = (result->colors * 3) / 2;
831                         PICS_PAIR *p = typeRealloc(PICS_PAIR, more, result->pairs);
832                         if (p != 0) {
833                             result->colors = more;
834                             result->pairs = p;
835                             result->pairs[c].fg = (int) check;
836                             result->pairs[c].bg = 0;
837                             while (++c < more) {
838                                 result->pairs[c].fg = 0;
839                                 result->pairs[c].bg = 0;
840                             }
841                         }
842                     }
843                     which = col + (row * result->wide);
844                     result->cells[which].ch = '#';      /* TODO: space? */
845                     result->cells[which].fg = (c < result->colors) ? c : -1;
846                 } else {
847                     failed = TRUE;
848                     break;
849                 }
850             }
851         }
852         pclose(pp);
853         if (!failed) {
854             for (len = result->colors; len > 3; len--) {
855                 if (result->pairs[len - 1].fg == 0) {
856                     result->colors = len - 1;
857                 } else {
858                     break;
859                 }
860             }
861         }
862     }
863     free(cmd);
864
865     if (failed) {
866         free_pics_head(result);
867         result = 0;
868     }
869
870     return result;
871 }
872
873 static PICS_HEAD *
874 read_picture(const char *filename, char **data)
875 {
876     PICS_HEAD *pics;
877     if ((pics = parse_xbm(data)) == 0) {
878         if ((pics = parse_xpm(data)) == 0) {
879             if ((pics = parse_img(filename)) == 0) {
880                 free_data(data);
881                 giveup("unexpected file-format for \"%s\"", filename);
882             }
883         }
884     }
885     return pics;
886 }
887
888 static void
889 dump_picture(PICS_HEAD * pics)
890 {
891     int y, x;
892
893     printf("Name %s\n", pics->name);
894     printf("Size %dx%d\n", pics->high, pics->wide);
895     printf("Color\n");
896     for (y = 0; y < pics->colors; ++y) {
897         if (pics->pairs[y].fg < 0) {
898             printf(" %3d: %d\n", y, pics->pairs[y].fg);
899         } else {
900             printf(" %3d: #%06x\n", y, pics->pairs[y].fg);
901         }
902     }
903     for (y = 0; y < pics->high; ++y) {
904         for (x = 0; x < pics->wide; ++x) {
905             putchar(pics->cells[y * pics->wide + x].ch);
906         }
907         putchar('\n');
908     }
909 }
910
911 static void
912 show_picture(PICS_HEAD * pics)
913 {
914     int y, x;
915     int n;
916
917     if (has_colors()) {
918         for (n = 0; n < pics->colors; ++n) {
919             init_pair((short) (n + 1),
920                       (short) map_color(pics->pairs[n].fg),
921                       COLOR_BLACK);
922         }
923         attrset(COLOR_PAIR(1));
924         erase();
925     }
926     for (y = 0; y < pics->high; ++y) {
927         if (y >= LINES)
928             break;
929         move(y, 0);
930         for (x = 0; x < pics->wide; ++x) {
931             if (x >= COLS)
932                 break;
933             n = (y * pics->wide + x);
934             attrset(COLOR_PAIR(pics->cells[n].fg + 1));
935             addch((chtype) pics->cells[n].ch);
936         }
937     }
938     mvgetch(0, 0);
939     endwin();
940 }
941
942 int
943 main(int argc, char *argv[])
944 {
945     int n;
946     const char *palette_path = 0;
947     const char *rgb_path = "/etc/X11/rgb.txt";
948
949     while ((n = getopt(argc, argv, "p:r:")) != -1) {
950         switch (n) {
951         case 'p':
952             palette_path = optarg;
953             break;
954         case 'r':
955             rgb_path = optarg;
956             break;
957         default:
958             usage();
959             break;
960         }
961     }
962
963     if (optind < argc) {
964         char **rgb_data = read_file(rgb_path);
965
966         if (rgb_data)
967             rgb_table = parse_rgb(rgb_data);
968
969         if (isatty(fileno(stdout))) {
970             in_curses = TRUE;
971             initscr();
972             cbreak();
973             noecho();
974             if (has_colors()) {
975                 start_color();
976                 init_palette(palette_path);
977             }
978             scrollok(stdscr, FALSE);
979             endwin();
980         }
981         if (optind >= argc)
982             giveup("expected at least one image filename");
983
984         for (n = optind; n < argc; ++n) {
985             PICS_HEAD *pics;
986             char **data = read_file(argv[n]);
987
988             if (data == 0) {
989                 giveup("cannot read \"%s\"", argv[n]);
990             }
991             pics = read_picture(argv[n], data);
992             if (in_curses) {
993                 show_picture(pics);
994             } else {
995                 dump_picture(pics);
996             }
997             free_data(data);
998             free_pics_head(pics);
999         }
1000         free_data(rgb_data);
1001         free(rgb_table);
1002     } else {
1003         usage();
1004     }
1005     ExitProgram(EXIT_SUCCESS);
1006 }