ncurses 6.0 - patch 20170610
[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.50 2017/06/11 00:37:27 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 "-xc" for init_color vs init_extended_color
41  * TODO add option "-xa" for init_pair vs alloc_pair
42  * TODO use pad to allow pictures larger than screen
43  * TODO improve load of image-file's color-table using tsearch.
44  * TODO add option to just use convert (which can scale) vs builtin xbm/xpm.
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 #if HAVE_ALLOC_PAIR && HAVE_INIT_EXTENDED_COLOR
101 #define USE_EXTENDED_COLORS 1
102 static bool use_extended_pairs = FALSE;
103 static bool use_extended_colors = FALSE;
104 #else
105 #define USE_EXTENDED_COLORS 0
106 #endif
107
108 static void
109 free_data(char **data)
110 {
111     if (data != 0) {
112         free(data[0]);
113         free(data);
114     }
115 }
116
117 static void
118 free_pics_head(PICS_HEAD * pics)
119 {
120     if (pics != 0) {
121         free(pics->pairs);
122         free(pics->cells);
123         free(pics);
124     }
125 }
126
127 /*
128  * Simplify reading xbm/xpm files by first making an array of lines.  Blank
129  * lines are filtered out.
130  */
131 static char **
132 read_file(const char *filename)
133 {
134     char **result = 0;
135     struct stat sb;
136
137     if (in_curses)
138         endwin();
139
140     printf("** %s\n", filename);
141
142     if (stat(filename, &sb) == 0
143         && (sb.st_mode & S_IFMT) == S_IFREG
144         && sb.st_size != 0) {
145         size_t size = (size_t) sb.st_size;
146         char *blob = typeMalloc(char, size + 1);
147         bool had_line = TRUE;
148         unsigned j;
149         unsigned k = 0;
150
151         result = typeCalloc(char *, size + 1);
152         if (blob != 0 && result != 0) {
153             FILE *fp = fopen(filename, "r");
154             if (fp != 0) {
155                 if (fread(blob, sizeof(char), size, fp) == size) {
156                     for (j = 0; (size_t) j < size; ++j) {
157                         if (blob[j] == '\n') {
158                             blob[j] = '\0';
159                             had_line = TRUE;
160                         } else if (had_line) {
161                             had_line = FALSE;
162                             result[k++] = blob + j;
163                         }
164                     }
165                     result[k] = 0;
166                 }
167                 fclose(fp);
168             }
169         }
170         if (k == 0) {
171             free(blob);
172             free(result);
173             result = 0;
174         }
175     }
176     return result;
177 }
178
179 static void
180 usage(void)
181 {
182     static const char *msg[] =
183     {
184         "Usage: picsmap [options] [imagefile [...]]"
185         ,"Read/display one or more xbm/xpm files (possibly use \"convert\")"
186         ,""
187         ,"Options:"
188         ,"  -p palette"
189         ,"  -r rgb-path"
190 #if USE_EXTENDED_COLORS
191         ,"  -x [p]   use extension (p=init_extended_pair)"
192 #endif
193     };
194     size_t n;
195
196     if (in_curses)
197         endwin();
198
199     fflush(stdout);
200     for (n = 0; n < SIZEOF(msg); n++)
201         fprintf(stderr, "%s\n", msg[n]);
202     ExitProgram(EXIT_FAILURE);
203 }
204
205 static void
206 giveup(const char *fmt,...)
207 {
208     va_list ap;
209     if (in_curses)
210         endwin();
211     fflush(stdout);
212     va_start(ap, fmt);
213     vfprintf(stderr, fmt, ap);
214     fputc('\n', stderr);
215     va_end(ap);
216     usage();
217 }
218
219 static void
220 init_palette(const char *palette_file)
221 {
222     if (palette_file != 0) {
223         char **data = read_file(palette_file);
224         int cp;
225
226         all_colors = typeMalloc(RGB_DATA, (unsigned) COLORS);
227         for (cp = 0; cp < COLORS; ++cp) {
228             color_content((short) cp,
229                           &all_colors[cp].red,
230                           &all_colors[cp].green,
231                           &all_colors[cp].blue);
232         }
233         if (palette_file != 0 && data != 0) {
234             int n;
235             int red, green, blue;
236             int scale = 1000;
237             int c;
238             for (n = 0; data[n] != 0; ++n) {
239                 if (sscanf(data[n], "scale:%d", &c) == 1) {
240                     scale = c;
241                 } else if (sscanf(data[n], "%d:%d %d %d",
242                                   &c,
243                                   &red,
244                                   &green,
245                                   &blue) == 4
246                            && okCOLOR(c)
247                            && okRGB(red)
248                            && okRGB(green)
249                            && okRGB(blue)) {
250                     /* *INDENT-EQLS* */
251                     all_colors[c].red   = ScaledColor(red);
252                     all_colors[c].green = ScaledColor(green);
253                     all_colors[c].blue  = ScaledColor(blue);
254                 }
255             }
256         }
257         free_data(data);
258     } else if (COLORS   > 1) {
259         /* *INDENT-EQLS* */
260         int power2 = 1;
261         int shift = 0;
262
263         while (power2 < COLORS) {
264             ++shift;
265             power2 <<= 1;
266         }
267
268         if ((power2 != COLORS) || ((shift % 3) != 0)) {
269             giveup("With %d colors, you need a palette-file", COLORS);
270         }
271     }
272 }
273
274 /*
275  * Map the 24-bit RGB value to a color index if using a palette, otherwise to a
276  * direct color value.
277  */
278 static int
279 map_color(int value)
280 {
281     int result = value;
282
283     if (result < 0) {
284         result = -1;
285     } else {
286         /* *INDENT-EQLS* */
287         int red   = (value & 0xff0000) >> 16;
288         int green = (value & 0x00ff00) >> 8;
289         int blue  = (value & 0x0000ff) >> 0;
290
291         if (all_colors != 0) {
292 #define Diff2(n,m) ((m) - all_colors[n].m) * ((m) - all_colors[n].m)
293 #define Diff2S(n) Diff2(n,red) + Diff2(n,green) + Diff2(n,blue)
294             int d2 = Diff2S(0);
295             int n;
296
297             /* *INDENT-EQLS* */
298             red   = Scaled256(red);
299             green = Scaled256(green);
300             blue  = Scaled256(blue);
301
302             for (result = 0, n = 1; n < COLORS; ++n) {
303                 int d = Diff2(n, red) + Diff2(n, green) + Diff2(n, blue);
304                 if (d < d2) {
305                     d2 = d;
306                     result = n;
307                 }
308             }
309         } else {                /* direct color */
310             int power2 = 1;
311             int shifts = 8;
312
313             while (power2 < COLORS) {
314                 power2 <<= 3;
315                 shifts--;
316             }
317
318             if (shifts > 0) {
319                 /* TODO: round up */
320                 red >>= shifts;
321                 green >>= shifts;
322                 blue >>= shifts;
323                 result = ((red << (2 * (8 - shifts)))
324                           + (green << (8 - shifts))
325                           + blue);
326             }
327         }
328     }
329     return result;
330 }
331
332 static int
333 bytes_of(int value)
334 {
335     if (value & 7) {
336         value |= 7;
337         value++;
338     }
339     return value;
340 }
341
342 static int match_c(const char *, const char *,...) GCC_SCANFLIKE(2,3);
343
344 static char *
345 skip_s(char *s)
346 {
347     while (isspace(UChar(*s)))
348         s++;
349     return s;
350 }
351
352 static const char *
353 skip_cs(const char *s)
354 {
355     while (isspace(UChar(*s)))
356         s++;
357     return s;
358 }
359
360 static char *
361 skip_word(char *s)
362 {
363     s = skip_s(s);
364     while (isgraph(UChar(*s)))
365         s++;
366     return s;
367 }
368
369 static int
370 match_c(const char *source, const char *pattern,...)
371 {
372     int limit = (int) strlen(source);
373     const char *last_s = source + limit;
374     va_list ap;
375     int ch;
376     int *ip;
377     char *cp;
378     long lv;
379
380     va_start(ap, pattern);
381
382     limit = -1;
383     while (*pattern != '\0') {
384         ch = UChar(*pattern++);
385         /* blank in the pattern matches zero-or-more blanks in source */
386         if (isspace(ch)) {
387             source = skip_cs(source);
388             continue;
389         }
390         /* %c, %d, %s are like sscanf except for special treatment of blanks */
391         if (ch == '%' && *pattern != '\0' && strchr("cdnsx", *pattern)) {
392             bool found = FALSE;
393             ch = *pattern++;
394             switch (ch) {
395             case 'c':
396                 cp = va_arg(ap, char *);
397                 do {
398                     *cp++ = *source++;
399                 } while (--limit > 0);
400                 break;
401             case 'd':
402             case 'x':
403                 limit = -1;
404                 ip = va_arg(ap, int *);
405                 lv = strtol(source, &cp, ch == 'd' ? 10 : 16);
406                 if (cp != 0 && cp != source) {
407                     *ip = (int) lv;
408                     source = cp;
409                 } else {
410                     goto finish;
411                 }
412                 break;
413             case 'n':
414                 /* not really sscanf... */
415                 limit = *va_arg(ap, int *);
416                 break;
417             case 's':
418                 limit = -1;
419                 cp = va_arg(ap, char *);
420                 while (*source != '\0') {
421                     ch = UChar(*source);
422                     if (isspace(ch)) {
423                         break;
424                     } else if (found && (ch == *skip_cs(pattern))) {
425                         break;
426                     } else {
427                         *cp++ = *source++;
428                         found = TRUE;
429                     }
430                 }
431                 *cp = '\0';
432                 break;
433             }
434             continue;
435         }
436         /* other characters are matched literally */
437         if (*source++ != ch) {
438             break;
439         }
440     }
441   finish:
442
443     va_end(ap);
444     if (source > last_s)
445         source = last_s;
446     return (*source || *pattern) ? 0 : 1;
447 }
448
449 static int
450 match_colors(const char *source, int cpp, char *arg1, char *arg2, char *arg3)
451 {
452     int result = 0;
453
454     /* most files use a quasi-fixed format */
455     if (match_c(source, " \"%n%c %s %s \" , ", &cpp, arg1, arg2, arg3)) {
456         arg1[cpp] = '\0';
457         result = 1;
458     } else {
459         char *t;
460         const char *s = skip_cs(source);
461         size_t have = strlen(source);
462
463         if (*s++ == '"' && have > ((size_t) cpp + 2)) {
464             memcpy(arg1, s, (size_t) cpp);
465             s += cpp;
466             while (*s++ == '\t') {
467                 for (t = arg2; (*s != '\0') && strchr("\t\"", *s) == 0;) {
468                     if (*s == ' ') {
469                         s = skip_cs(s);
470                         break;
471                     }
472                     *t++ = *s++;
473                     *t = '\0';
474                 }
475                 for (t = arg3; (*s != '\0') && strchr("\t\"", *s) == 0;) {
476                     *t++ = *s++;
477                     *t = '\0';
478                 }
479                 if (!strcmp(arg2, "c")) {
480                     result = 1;
481                     break;
482                 }
483             }
484         }
485     }
486     return result;
487 }
488
489 static RGB_NAME *
490 parse_rgb(char **data)
491 {
492     char buf[BUFSIZ];
493     int n;
494     unsigned long r, g, b;
495     char *s, *t;
496     size_t item = 0;
497     size_t need;
498     RGB_NAME *result = 0;
499
500     for (need = 0; data[need] != 0; ++need) ;
501     result = typeCalloc(RGB_NAME, need + 2);
502
503     for (n = 0; data[n] != 0; ++n) {
504         if (strlen(t = data[n]) >= sizeof(buf) - 1)
505             continue;
506         if (*(s = skip_s(t)) == '!')
507             continue;
508
509         r = strtoul(s, &t, 10);
510         s = skip_s(t);
511         g = strtoul(s, &t, 10);
512         s = skip_s(t);
513         b = strtoul(s, &t, 10);
514         s = skip_s(t);
515
516         result[item].name = s;
517         t = s + strlen(s);
518         while (t-- != s && isspace(UChar(*t))) {
519             *t = '\0';
520         }
521         result[item].value = (int) ((r & 0xff) << 16 | (g & 0xff) << 8 | (b
522                                                                           & 0xff));
523         ++item;
524     }
525
526     result[item].name = "none";
527     result[item].value = -1;
528
529     return result;
530 }
531
532 static RGB_NAME *
533 lookup_rgb(const char *name)
534 {
535     RGB_NAME *result = 0;
536     if (rgb_table != 0) {
537         int n;
538         for (n = 0; rgb_table[n].name != 0; ++n) {
539             if (!strcasecmp(name, rgb_table[n].name)) {
540                 result = &rgb_table[n];
541                 break;
542             }
543         }
544     }
545     return result;
546 }
547
548 static PICS_HEAD *
549 parse_xbm(char **data)
550 {
551     int n;
552     int state = 0;
553     char buf[BUFSIZ];
554     int num;
555     char ch;
556     char *s;
557     char *t;
558     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
559     size_t which = 0;
560     size_t cells = 0;
561
562     for (n = 0; data[n] != 0; ++n) {
563         if (strlen(s = data[n]) >= sizeof(buf) - 1)
564             continue;
565         switch (state) {
566         case 0:
567         case 1:
568         case 2:
569             if (sscanf(s, "#define %s %d%c", buf, &num, &ch) >= 2) {
570                 if ((t = strstr(buf, "_width")) != 0) {
571                     state |= 1;
572                     result->wide = bytes_of(num);
573                 } else if ((t = strstr(buf, "_height")) != 0) {
574                     state |= 2;
575                     result->high = num;
576                 }
577                 *t = '\0';
578                 if (result->name) {
579                     if (strcmp(result->name, buf)) {
580                         goto finish;
581                     }
582                 } else {
583                     result->name = strdup(buf);
584                 }
585             }
586             break;
587         case 3:
588             if (sscanf(s, "static char %[^_ ]_bits[]%c", buf, &ch) >= 1) {
589                 if (strcmp(result->name, buf)) {
590                     goto finish;
591                 }
592                 state = 4;
593                 cells = (size_t) (result->wide * result->high);
594                 result->cells = typeCalloc(PICS_CELL, cells);
595                 if ((s = strchr(s, L_CURLY)) == 0)
596                     break;
597                 ++s;
598             } else {
599                 break;
600             }
601         case 4:
602             while (*s != '\0') {
603                 while (isspace(UChar(*s))) {
604                     ++s;
605                 }
606                 if (isdigit(UChar(*s))) {
607                     long value = strtol(s, &t, 0);
608                     int b;
609                     if (t != s || value > 255 || value < 0) {
610                         s = t;
611                     } else {
612                         state = -1;
613                         goto finish;
614                     }
615                     /* TODO: which order? */
616                     for (b = 0; b < 8; ++b) {
617                         if (((1L << b) & value) != 0) {
618                             result->cells[which].ch = '*';
619                             result->cells[which].fg = 1;
620                         } else {
621                             result->cells[which].ch = ' ';
622                             result->cells[which].fg = 0;
623                         }
624                         if (++which > cells) {
625                             state = -1;
626                             goto finish;
627                         }
628                     }
629                 }
630                 if (*s == R_CURLY) {
631                     state = 5;
632                     goto finish;
633                 } else if (*s == ',') {
634                     ++s;
635                 }
636             }
637             break;
638         default:
639             break;
640         }
641     }
642   finish:
643     if (state < 4) {
644         if (result) {
645             free_pics_head(result);
646             result = 0;
647         }
648     } else {
649         result->colors = 2;
650         result->pairs = typeCalloc(PICS_PAIR, 2);
651         result->pairs[1].fg = 0xffffff;
652     }
653     return result;
654 }
655
656 static PICS_HEAD *
657 parse_xpm(char **data)
658 {
659     int state = 0;
660     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
661     RGB_NAME *by_name;
662     int n;
663     int cells = 0;
664     int color = 0;
665     int cpp = 1;                /* chars per pixel */
666     int num[6];
667     int which = 0;
668     char ch;
669     const char *cs;
670     char *s;
671     char buf[BUFSIZ];
672     char arg1[BUFSIZ];
673     char arg2[BUFSIZ];
674     char arg3[BUFSIZ];
675     char **list = 0;
676
677     for (n = 0; data[n] != 0; ++n) {
678         if (strlen(s = data[n]) >= sizeof(buf) - 1)
679             continue;
680         switch (state) {
681         case 0:
682             if (match_c(s, " /* XPM */ ")) {
683                 state = 1;
684             }
685             break;
686         case 1:
687             if (match_c(s, " static char * %s [] = %c ", arg1, &ch) &&
688                 ch == L_CURLY) {
689                 result->name = strdup(arg1);
690                 state = 2;
691             }
692             break;
693         case 2:
694             if (match_c(s, " \" %d %d %d %d \" , ",
695                         num + 0, num + 1, num + 2, num + 3) ||
696                 match_c(s, " \" %d %d %d %d %d %d \" , ",
697                         num + 0, num + 1, num + 2, num + 3, num + 4, num + 5)) {
698                 result->wide = num[0];
699                 result->high = num[1];
700                 result->colors = num[2];
701                 result->pairs = typeCalloc(PICS_PAIR, result->colors);
702                 cells = (result->wide * result->high);
703                 result->cells = typeCalloc(PICS_CELL, cells);
704                 list = typeCalloc(char *, result->colors);
705                 cpp = num[3];
706                 state = 3;
707             }
708             break;
709         case 3:
710             if (!match_colors(s, cpp, arg1, arg2, arg3)) {
711                 break;
712             }
713             list[color] = strdup(arg1);
714             if ((by_name = lookup_rgb(arg3)) != 0) {
715                 result->pairs[color].fg = by_name->value;
716             } else if (*arg3 == '#') {
717                 char *rgb = arg3 + 1;
718                 unsigned long value = strtoul(rgb, &s, 16);
719                 switch ((int) strlen(rgb)) {
720                 case 6:
721                     break;
722                 case 12:
723                     value = (((value >> 24) & 0xff0000L)
724                              | ((value >> 16) & 0xff00L)
725                              | ((value >> 8) & 0xffL));
726                     break;
727                 default:
728                     printf("unexpected rgb value %s\n", rgb);
729                     break;
730                 }
731                 result->pairs[color].fg = (int) value;
732             } else {
733                 result->pairs[color].fg = 0;    /* actually an error */
734             }
735             if (++color >= result->colors)
736                 state = 4;
737             break;
738         case 4:
739             if (*(cs = skip_cs(s)) == '"') {
740                 ++cs;
741                 while (*cs != '\0' && *cs != '"') {
742                     int c;
743
744                     for (c = 0; c < result->colors; ++c) {
745                         if (!strncmp(cs, list[c], (size_t) cpp)) {
746                             result->cells[which].ch = list[c][0];
747                             result->cells[which].fg = c;
748                             break;
749                         }
750                     }
751
752                     if (result->cells[which].ch == 0) {
753                         result->cells[which].ch = '?';
754                         result->cells[which].fg = 0;
755                     }
756
757                     if (++which >= cells) {
758                         state = 5;
759                         break;
760                     }
761                     for (c = cpp; c > 0; --c, ++cs) ;
762                 }
763             }
764             break;
765         }
766     }
767
768     if (result && list) {
769         for (n = 0; n < result->colors; ++n)
770             free(list[n]);
771         free(list);
772     }
773
774     if (state < 5) {
775         free_pics_head(result);
776         result = 0;
777     }
778
779     return result;
780 }
781
782 /*
783  * The obscurely-named "convert" is provided by ImageMagick
784  */
785 static PICS_HEAD *
786 parse_img(const char *filename)
787 {
788     char *cmd = malloc(strlen(filename) + 256);
789     FILE *pp;
790     char buffer[BUFSIZ];
791     bool failed = FALSE;
792     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
793     int pic_x = 0;
794     int pic_y = 0;
795     int width = in_curses ? COLS : 80;
796
797     sprintf(cmd, "identify \"%s\"", filename);
798
799     if ((pp = popen(cmd, "r")) != 0) {
800         if (fgets(buffer, sizeof(buffer), pp) != 0) {
801             size_t n = strlen(filename);
802             if (strlen(buffer) > n &&
803                 !strncmp(buffer, filename, n) &&
804                 isspace(UChar(buffer[n])) &&
805                 sscanf(skip_word(buffer + n), " %dx%d ", &pic_x, &pic_y) == 2) {
806                 /* distort image to make it show normally on terminal */
807                 pic_x = (166 * pic_x) / 100;
808             } else {
809                 pic_x = pic_y = 0;
810             }
811         }
812         pclose(pp);
813     }
814     if (pic_x <= 0 || pic_y <= 0)
815         goto finish;
816
817     sprintf(cmd, "convert -resize %dx%d\\! -thumbnail %dx \"%s\" txt:-",
818             pic_x, pic_y, width, filename);
819
820     if ((pp = popen(cmd, "r")) != 0) {
821         int count = 0;
822         int col = 0;
823         int row = 0;
824         int len = 0;
825         while (fgets(buffer, sizeof(buffer), pp) != 0) {
826             if (strlen(buffer) > 160) {         /* 80 columns would be enough */
827                 failed = TRUE;
828                 break;
829             }
830             if (count++ == 0) {
831                 if (match_c(buffer,
832                             "# ImageMagick pixel enumeration: %d,%d,%d,srgba ",
833                             &col, &row, &len)) {
834                     result->name = strdup(filename);
835                     result->wide = col;
836                     result->high = row;
837                     result->colors = 256;
838                     result->pairs = typeCalloc(PICS_PAIR, result->colors);
839                     result->cells = typeCalloc(PICS_CELL, (size_t) (col * row));
840                 } else {
841                     failed = TRUE;
842                     break;
843                 }
844             } else {
845                 /* subsequent lines begin "col,row: (r,g,b,a) #RGB" */
846                 int r, g, b;
847                 unsigned check;
848                 int which, c;
849                 char *s = strchr(buffer, '#');
850                 if (s != 0) {
851                     /* after the "#RGB", there are differences - just ignore */
852                     while (*s != '\0' && !isspace(UChar(*s)))
853                         ++s;
854                     *++s = '\0';
855                 }
856                 if (match_c(buffer,
857                             "%d,%d: (%d,%d,%d,255) #%x ",
858                             &col, &row,
859                             &r, &g, &b,
860                             &check)) {
861                     if (r > 255 ||
862                         g > 255 ||
863                         b > 255 ||
864                         check != (unsigned) ((r << 16) | (g << 8) | b)) {
865                         failed = TRUE;
866                         break;
867                     }
868                     for (c = 0; c < result->colors; ++c) {
869                         if (result->pairs[c].fg == (int) check) {
870                             break;
871                         } else if (result->pairs[c].fg == 0) {
872                             result->pairs[c].fg = (int) check;
873                             break;
874                         }
875                     }
876                     if (c >= result->colors) {
877                         int more = (result->colors * 3) / 2;
878                         PICS_PAIR *p = typeRealloc(PICS_PAIR, more, result->pairs);
879                         if (p != 0) {
880                             result->colors = more;
881                             result->pairs = p;
882                             result->pairs[c].fg = (int) check;
883                             result->pairs[c].bg = 0;
884                             while (++c < more) {
885                                 result->pairs[c].fg = 0;
886                                 result->pairs[c].bg = 0;
887                             }
888                         }
889                     }
890                     which = col + (row * result->wide);
891                     result->cells[which].ch = ((in_curses ||
892                                                 check == 0xffffff)
893                                                ? ' '
894                                                : '#');
895                     result->cells[which].fg = (c < result->colors) ? c : -1;
896                 } else {
897                     failed = TRUE;
898                     break;
899                 }
900             }
901         }
902         pclose(pp);
903         if (!failed) {
904             for (len = result->colors; len > 3; len--) {
905                 if (result->pairs[len - 1].fg == 0) {
906                     result->colors = len - 1;
907                 } else {
908                     break;
909                 }
910             }
911         }
912     }
913   finish:
914     free(cmd);
915
916     if (failed) {
917         free_pics_head(result);
918         result = 0;
919     }
920
921     return result;
922 }
923
924 static PICS_HEAD *
925 read_picture(const char *filename, char **data)
926 {
927     PICS_HEAD *pics;
928     if ((pics = parse_xbm(data)) == 0) {
929         if ((pics = parse_xpm(data)) == 0) {
930             if ((pics = parse_img(filename)) == 0) {
931                 free_data(data);
932                 giveup("unexpected file-format for \"%s\"", filename);
933             }
934         }
935     }
936     return pics;
937 }
938
939 static void
940 dump_picture(PICS_HEAD * pics)
941 {
942     int y, x;
943
944     printf("Name %s\n", pics->name);
945     printf("Size %dx%d\n", pics->high, pics->wide);
946     printf("Color\n");
947     for (y = 0; y < pics->colors; ++y) {
948         if (pics->pairs[y].fg < 0) {
949             printf(" %3d: %d\n", y, pics->pairs[y].fg);
950         } else {
951             printf(" %3d: #%06x\n", y, pics->pairs[y].fg);
952         }
953     }
954     for (y = 0; y < pics->high; ++y) {
955         for (x = 0; x < pics->wide; ++x) {
956             putchar(pics->cells[y * pics->wide + x].ch);
957         }
958         putchar('\n');
959     }
960 }
961
962 static void
963 show_picture(PICS_HEAD * pics)
964 {
965     int y, x;
966     int n;
967     int my_pair, my_color;
968
969     if (has_colors()) {
970         for (n = 0; n < pics->colors; ++n) {
971             my_pair = (n + 1);
972             my_color = map_color(pics->pairs[n].fg);
973 #if USE_EXTENDED_COLORS
974             if (use_extended_pairs) {
975                 init_extended_pair(my_pair, my_color, my_color);
976             } else
977 #endif
978             {
979                 my_pair &= 0x7fff;
980                 my_color &= 0x7fff;
981                 init_pair((short) my_pair, (short) my_color, (short) my_color);
982             }
983         }
984         attrset(COLOR_PAIR(1));
985         erase();
986     }
987     for (y = 0; y < pics->high; ++y) {
988         if (y >= LINES)
989             break;
990         move(y, 0);
991         for (x = 0; x < pics->wide; ++x) {
992             if (x >= COLS)
993                 break;
994             n = (y * pics->wide + x);
995             my_pair = pics->cells[n].fg + 1;
996 #if USE_EXTENDED_COLORS
997             if (use_extended_pairs) {
998                 cchar_t temp;
999                 wchar_t wch[2];
1000                 wch[0] = (wchar_t) pics->cells[n].ch;
1001                 wch[1] = 0;
1002                 setcchar(&temp, wch, A_NORMAL, (short) my_pair, &my_pair);
1003                 add_wch(&temp);
1004             } else
1005 #endif
1006             {
1007                 attrset(COLOR_PAIR(my_pair));
1008                 addch((chtype) pics->cells[n].ch);
1009             }
1010         }
1011     }
1012     mvgetch(0, 0);
1013     endwin();
1014 }
1015
1016 int
1017 main(int argc, char *argv[])
1018 {
1019     int n;
1020     const char *palette_path = 0;
1021     const char *rgb_path = "/etc/X11/rgb.txt";
1022
1023     while ((n = getopt(argc, argv, "p:r:x:")) != -1) {
1024         switch (n) {
1025         case 'p':
1026             palette_path = optarg;
1027             break;
1028         case 'r':
1029             rgb_path = optarg;
1030             break;
1031 #if USE_EXTENDED_COLORS
1032         case 'x':
1033             {
1034                 char *s = optarg;
1035                 while (*s) {
1036                     switch (*s++) {
1037                     case 'p':
1038                         use_extended_pairs = TRUE;
1039                         break;
1040                     case 'c':
1041                         use_extended_colors = TRUE;
1042                         break;
1043                     default:
1044                         usage();
1045                         break;
1046                     }
1047                 }
1048             }
1049             break;
1050 #endif
1051         default:
1052             usage();
1053             break;
1054         }
1055     }
1056
1057     if (optind < argc) {
1058         char **rgb_data = read_file(rgb_path);
1059
1060         if (rgb_data)
1061             rgb_table = parse_rgb(rgb_data);
1062
1063         if (isatty(fileno(stdout))) {
1064             in_curses = TRUE;
1065             initscr();
1066             cbreak();
1067             noecho();
1068             if (has_colors()) {
1069                 start_color();
1070                 init_palette(palette_path);
1071             }
1072             scrollok(stdscr, FALSE);
1073             endwin();
1074         }
1075         if (optind >= argc)
1076             giveup("expected at least one image filename");
1077
1078         for (n = optind; n < argc; ++n) {
1079             PICS_HEAD *pics;
1080             char **data = read_file(argv[n]);
1081
1082             if (data == 0) {
1083                 giveup("cannot read \"%s\"", argv[n]);
1084             }
1085             pics = read_picture(argv[n], data);
1086             if (in_curses) {
1087                 show_picture(pics);
1088             } else {
1089                 dump_picture(pics);
1090             }
1091             free_data(data);
1092             free_pics_head(pics);
1093         }
1094         free_data(rgb_data);
1095         free(rgb_table);
1096     } else {
1097         usage();
1098     }
1099     ExitProgram(EXIT_SUCCESS);
1100 }