ncurses 6.0 - patch 20170715
[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.52 2017/07/15 22:25:06 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\" "
818             "-define txt:compliance=SVG txt:-",
819             pic_x, pic_y, width, filename);
820
821     if ((pp = popen(cmd, "r")) != 0) {
822         int count = 0;
823         int col = 0;
824         int row = 0;
825         int len = 0;
826         while (fgets(buffer, sizeof(buffer), pp) != 0) {
827             if (strlen(buffer) > 160) {         /* 80 columns would be enough */
828                 failed = TRUE;
829                 break;
830             }
831             if (count++ == 0) {
832                 if (match_c(buffer,
833                             "# ImageMagick pixel enumeration: %d,%d,%d,srgba ",
834                             &col, &row, &len)) {
835                     result->name = strdup(filename);
836                     result->wide = col;
837                     result->high = row;
838                     result->colors = 256;
839                     result->pairs = typeCalloc(PICS_PAIR, result->colors);
840                     result->cells = typeCalloc(PICS_CELL, (size_t) (col * row));
841                 } else {
842                     failed = TRUE;
843                     break;
844                 }
845             } else {
846                 /* subsequent lines begin "col,row: (r,g,b,a) #RGB" */
847                 int r, g, b, nocolor;
848                 unsigned check;
849                 int which, c;
850                 char *t;
851                 char *s = t = strchr(buffer, '#');
852                 if (s != 0) {
853                     /* after the "#RGB", there are differences - just ignore */
854                     while (*s != '\0' && !isspace(UChar(*s)))
855                         ++s;
856                     *++s = '\0';
857                 }
858                 if (match_c(buffer,
859                             "%d,%d: (%d,%d,%d,%d) #%x ",
860                             &col, &row,
861                             &r, &g, &b, &nocolor,
862                             &check)) {
863                     if ((s - t) > 8)    /* 6 hex digits vs 8 */
864                         check /= 256;
865                     if (r > 255 ||
866                         g > 255 ||
867                         b > 255 ||
868                         check != (unsigned) ((r << 16) | (g << 8) | b)) {
869                         failed = TRUE;
870                         break;
871                     }
872                     for (c = 0; c < result->colors; ++c) {
873                         if (result->pairs[c].fg == (int) check) {
874                             break;
875                         } else if (result->pairs[c].fg == 0) {
876                             result->pairs[c].fg = (int) check;
877                             break;
878                         }
879                     }
880                     if (c >= result->colors) {
881                         int more = (result->colors * 3) / 2;
882                         PICS_PAIR *p = typeRealloc(PICS_PAIR, more, result->pairs);
883                         if (p != 0) {
884                             result->colors = more;
885                             result->pairs = p;
886                             result->pairs[c].fg = (int) check;
887                             result->pairs[c].bg = 0;
888                             while (++c < more) {
889                                 result->pairs[c].fg = 0;
890                                 result->pairs[c].bg = 0;
891                             }
892                         }
893                     }
894                     which = col + (row * result->wide);
895                     result->cells[which].ch = ((in_curses ||
896                                                 check == 0xffffff)
897                                                ? ' '
898                                                : '#');
899                     result->cells[which].fg = (c < result->colors) ? c : -1;
900                 } else {
901                     failed = TRUE;
902                     break;
903                 }
904             }
905         }
906         pclose(pp);
907         if (!failed) {
908             for (len = result->colors; len > 3; len--) {
909                 if (result->pairs[len - 1].fg == 0) {
910                     result->colors = len - 1;
911                 } else {
912                     break;
913                 }
914             }
915         }
916     }
917   finish:
918     free(cmd);
919
920     if (failed) {
921         free_pics_head(result);
922         result = 0;
923     }
924
925     return result;
926 }
927
928 static PICS_HEAD *
929 read_picture(const char *filename, char **data)
930 {
931     PICS_HEAD *pics;
932     if ((pics = parse_xbm(data)) == 0) {
933         if ((pics = parse_xpm(data)) == 0) {
934             if ((pics = parse_img(filename)) == 0) {
935                 free_data(data);
936                 giveup("unexpected file-format for \"%s\"", filename);
937             }
938         }
939     }
940     return pics;
941 }
942
943 static void
944 dump_picture(PICS_HEAD * pics)
945 {
946     int y, x;
947
948     printf("Name %s\n", pics->name);
949     printf("Size %dx%d\n", pics->high, pics->wide);
950     printf("Color\n");
951     for (y = 0; y < pics->colors; ++y) {
952         if (pics->pairs[y].fg < 0) {
953             printf(" %3d: %d\n", y, pics->pairs[y].fg);
954         } else {
955             printf(" %3d: #%06x\n", y, pics->pairs[y].fg);
956         }
957     }
958     for (y = 0; y < pics->high; ++y) {
959         for (x = 0; x < pics->wide; ++x) {
960             putchar(pics->cells[y * pics->wide + x].ch);
961         }
962         putchar('\n');
963     }
964 }
965
966 static void
967 show_picture(PICS_HEAD * pics)
968 {
969     int y, x;
970     int n;
971     int my_pair, my_color;
972
973     if (has_colors()) {
974         for (n = 0; n < pics->colors; ++n) {
975             my_pair = (n + 1);
976             my_color = map_color(pics->pairs[n].fg);
977 #if USE_EXTENDED_COLORS
978             if (use_extended_pairs) {
979                 init_extended_pair(my_pair, my_color, my_color);
980             } else
981 #endif
982             {
983                 my_pair &= 0x7fff;
984                 my_color &= 0x7fff;
985                 init_pair((short) my_pair, (short) my_color, (short) my_color);
986             }
987         }
988         attrset(COLOR_PAIR(1));
989         erase();
990     }
991     for (y = 0; y < pics->high; ++y) {
992         if (y >= LINES)
993             break;
994         move(y, 0);
995         for (x = 0; x < pics->wide; ++x) {
996             if (x >= COLS)
997                 break;
998             n = (y * pics->wide + x);
999             my_pair = pics->cells[n].fg + 1;
1000 #if USE_EXTENDED_COLORS
1001             if (use_extended_pairs) {
1002                 cchar_t temp;
1003                 wchar_t wch[2];
1004                 wch[0] = (wchar_t) pics->cells[n].ch;
1005                 wch[1] = 0;
1006                 setcchar(&temp, wch, A_NORMAL, (short) my_pair, &my_pair);
1007                 add_wch(&temp);
1008             } else
1009 #endif
1010             {
1011                 attrset(COLOR_PAIR(my_pair));
1012                 addch((chtype) pics->cells[n].ch);
1013             }
1014         }
1015     }
1016     mvgetch(0, 0);
1017     endwin();
1018 }
1019
1020 int
1021 main(int argc, char *argv[])
1022 {
1023     int n;
1024     const char *palette_path = 0;
1025     const char *rgb_path = "/etc/X11/rgb.txt";
1026
1027     while ((n = getopt(argc, argv, "p:r:x:")) != -1) {
1028         switch (n) {
1029         case 'p':
1030             palette_path = optarg;
1031             break;
1032         case 'r':
1033             rgb_path = optarg;
1034             break;
1035 #if USE_EXTENDED_COLORS
1036         case 'x':
1037             {
1038                 char *s = optarg;
1039                 while (*s) {
1040                     switch (*s++) {
1041                     case 'p':
1042                         use_extended_pairs = TRUE;
1043                         break;
1044                     case 'c':
1045                         use_extended_colors = TRUE;
1046                         break;
1047                     default:
1048                         usage();
1049                         break;
1050                     }
1051                 }
1052             }
1053             break;
1054 #endif
1055         default:
1056             usage();
1057             break;
1058         }
1059     }
1060
1061     if (optind < argc) {
1062         char **rgb_data = read_file(rgb_path);
1063
1064         if (rgb_data)
1065             rgb_table = parse_rgb(rgb_data);
1066
1067         if (isatty(fileno(stdout))) {
1068             in_curses = TRUE;
1069             initscr();
1070             cbreak();
1071             noecho();
1072             if (has_colors()) {
1073                 start_color();
1074                 init_palette(palette_path);
1075             }
1076             scrollok(stdscr, FALSE);
1077             endwin();
1078         }
1079         if (optind >= argc)
1080             giveup("expected at least one image filename");
1081
1082         for (n = optind; n < argc; ++n) {
1083             PICS_HEAD *pics;
1084             char **data = read_file(argv[n]);
1085
1086             if (data == 0) {
1087                 giveup("cannot read \"%s\"", argv[n]);
1088             }
1089             pics = read_picture(argv[n], data);
1090             if (in_curses) {
1091                 show_picture(pics);
1092             } else {
1093                 dump_picture(pics);
1094             }
1095             free_data(data);
1096             free_pics_head(pics);
1097         }
1098         free_data(rgb_data);
1099         free(rgb_table);
1100     } else {
1101         usage();
1102     }
1103     ExitProgram(EXIT_SUCCESS);
1104 }