]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/picsmap.c
ncurses 6.4 - patch 20240420
[ncurses.git] / test / picsmap.c
1 /****************************************************************************
2  * Copyright 2018-2022,2023 Thomas E. Dickey                                *
3  * Copyright 2017,2018 Free Software Foundation, Inc.                       *
4  *                                                                          *
5  * Permission is hereby granted, free of charge, to any person obtaining a  *
6  * copy of this software and associated documentation files (the            *
7  * "Software"), to deal in the Software without restriction, including      *
8  * without limitation the rights to use, copy, modify, merge, publish,      *
9  * distribute, distribute with modifications, sublicense, and/or sell       *
10  * copies of the Software, and to permit persons to whom the Software is    *
11  * furnished to do so, subject to the following conditions:                 *
12  *                                                                          *
13  * The above copyright notice and this permission notice shall be included  *
14  * in all copies or substantial portions of the Software.                   *
15  *                                                                          *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS  *
17  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF               *
18  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.   *
19  * IN NO EVENT SHALL THE ABOVE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,   *
20  * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR    *
21  * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR    *
22  * THE USE OR OTHER DEALINGS IN THE SOFTWARE.                               *
23  *                                                                          *
24  * Except as contained in this notice, the name(s) of the above copyright   *
25  * holders shall not be used in advertising or otherwise to promote the     *
26  * sale, use or other dealings in this Software without prior written       *
27  * authorization.                                                           *
28  ****************************************************************************/
29 /*
30  * $Id: picsmap.c,v 1.149 2023/04/23 23:20:37 tom Exp $
31  *
32  * Author: Thomas E. Dickey
33  *
34  * A little more interesting than "dots", read a simple image into memory and
35  * measure the time taken to paint it normally vs randomly.
36  *
37  * TODO improve use of rgb-names using tsearch.
38  *
39  * TODO add option to dump picture in non-optimized mode, e.g., like tput.
40  * TODO write cells/second to stderr (or log)
41  * TODO write picture left-to-right/top-to-bottom
42  * TODO write picture randomly
43  * TODO add one-shot option vs repeat-count before exiting
44  * TODO add option "-xc" for init_color vs init_extended_color
45  * TODO add option "-xa" for init_pair vs alloc_pair
46  * TODO use pad to allow pictures larger than screen
47  * TODO add option to just use convert (which can scale) vs builtin xbm/xpm.
48  * TODO add scr_dump and scr_restore calls
49  * TODO add option for assume_default_colors
50  */
51 #include <test.priv.h>
52
53 #include <sys/types.h>
54 #include <sys/stat.h>
55
56 #if HAVE_TSEARCH
57 #include <search.h>
58 #endif
59
60 #undef CUR                      /* use only the curses interface */
61
62 #define  L_BLOCK '['
63 #define  R_BLOCK ']'
64
65 #define  L_CURLY '{'
66 #define  R_CURLY '}'
67
68 #define MaxSCALE        1000    /* input curses ranges 0..1000 */
69 #define MaxRGB          255     /* output color ranges 0..255 */
70 #define okCOLOR(n)      ((n) >= 0 && (n) < COLORS)
71 #define okSCALE(n)      ((n) >= 0 && (n) <= MaxSCALE)
72 #define Scaled256(n)    (NCURSES_COLOR_T) (int)(((double)(n) * MaxSCALE) / 255)
73 #define ScaledColor(n)  (NCURSES_COLOR_T) (int)(((double)(n) * MaxSCALE) / scale)
74
75 #ifndef RGB_PATH
76 #define RGB_PATH "/etc/X11/rgb.txt"
77 #endif
78
79 #include <picsmap.h>
80
81 typedef struct {
82     size_t file;
83     size_t name;
84     size_t list;
85     size_t data;
86     size_t head;
87     size_t pair;
88     size_t cell;
89 } HOW_MUCH;
90
91 #undef MAX
92 #define MAX(a,b) ((a)>(b)?(a):(b))
93
94 /*
95  * tfind will return null on failure, so we map subscripts starting at one.
96  */
97 #define P2I(n) (((int)(my_intptr_t)(n)) - 1)
98 #define I2P(n) (void *)(my_intptr_t)((n) + 1)
99
100 #define pause_curses() if (in_curses) stop_curses()
101
102 #define debugmsg if (debugging) logmsg
103 #define debugmsg2 if (debugging) logmsg2
104
105 static GCC_NORETURN void cleanup(int);
106 static void giveup(const char *fmt, ...) GCC_PRINTFLIKE(1, 2);
107 static void logmsg(const char *fmt, ...) GCC_PRINTFLIKE(1, 2);
108 static void logmsg2(const char *fmt, ...) GCC_PRINTFLIKE(1, 2);
109 static void warning(const char *fmt, ...) GCC_PRINTFLIKE(1, 2);
110 static int gather_c_values(int);
111
112 static FILE *logfp = 0;
113 static double aspect_ratio = 0.6;
114 static bool in_curses = FALSE;
115 static bool debugging = FALSE;
116 static bool quiet = FALSE;
117 static int slow_time = -1;
118 static RGB_NAME *rgb_table;
119 static RGB_DATA *all_colors;
120 static HOW_MUCH how_much;
121
122 static int reading_last;
123 static int reading_size;
124 static FG_NODE *reading_ncols;
125
126 #if HAVE_TSEARCH
127 static void *reading_ntree;
128 #endif
129
130 #if HAVE_ALLOC_PAIR && USE_EXTENDED_COLOR
131 #define USE_EXTENDED_COLORS 1
132 static bool use_extended_pairs = FALSE;
133 static bool use_extended_colors = FALSE;
134 #else
135 #define USE_EXTENDED_COLORS 0
136 #endif
137
138 static void
139 logmsg(const char *fmt, ...)
140 {
141     if (logfp != 0) {
142         va_list ap;
143         va_start(ap, fmt);
144         vfprintf(logfp, fmt, ap);
145         va_end(ap);
146         fputc('\n', logfp);
147         fflush(logfp);
148     }
149 }
150
151 static void
152 logmsg2(const char *fmt, ...)
153 {
154     if (logfp != 0) {
155         va_list ap;
156         va_start(ap, fmt);
157         vfprintf(logfp, fmt, ap);
158         va_end(ap);
159         fflush(logfp);
160     }
161 }
162
163 static void
164 close_log(void)
165 {
166     if (logfp != 0) {
167         logmsg("Allocations:");
168         logmsg("%8ld file", (long) how_much.file);
169         logmsg("%8ld name", (long) how_much.name);
170         logmsg("%8ld list", (long) how_much.list);
171         logmsg("%8ld data", (long) how_much.data);
172         logmsg("%8ld head", (long) how_much.head);
173         logmsg("%8ld pair", (long) how_much.pair);
174         logmsg("%8ld cell", (long) how_much.cell);
175         logmsg("%8ld window", LINES * COLS * (long) sizeof(NCURSES_CH_T));
176         fclose(logfp);
177         logfp = 0;
178     }
179 }
180
181 static void
182 cleanup(int code)
183 {
184     pause_curses();
185     close_log();
186     ExitProgram(code);
187     /* NOTREACHED */
188 }
189
190 static void
191 failed(const char *msg)
192 {
193     int save = errno;
194     perror(msg);
195     logmsg("failed with %s", strerror(save));
196     cleanup(EXIT_FAILURE);
197 }
198
199 static void
200 warning(const char *fmt, ...)
201 {
202     if (logfp != 0) {
203         va_list ap;
204         va_start(ap, fmt);
205         vfprintf(logfp, fmt, ap);
206         va_end(ap);
207         fputc('\n', logfp);
208         fflush(logfp);
209     } else {
210         va_list ap;
211         va_start(ap, fmt);
212         vfprintf(stderr, fmt, ap);
213         va_end(ap);
214         fputc('\n', stderr);
215         cleanup(EXIT_FAILURE);
216     }
217 }
218
219 static void
220 free_data(char **data)
221 {
222     if (data != 0) {
223         free(data[0]);
224         free(data);
225     }
226 }
227
228 static PICS_HEAD *
229 free_pics_head(PICS_HEAD * pics)
230 {
231     if (pics != 0) {
232         free(pics->fgcol);
233         free(pics->cells);
234         free(pics->name);
235         free(pics);
236         pics = 0;
237     }
238     return pics;
239 }
240
241 static void
242 begin_c_values(int size)
243 {
244     reading_last = 0;
245     reading_size = size;
246     reading_ncols = typeCalloc(FG_NODE, size + 1);
247     how_much.pair += (sizeof(FG_NODE) * (size_t) size);
248     /* black is always the first slot, to work around P2I/I2P logic */
249     gather_c_values(0);
250 }
251
252 #if HAVE_TSEARCH
253 static int
254 compare_c_values(const void *p, const void *q)
255 {
256     const int a = P2I(p);
257     const int b = P2I(q);
258     return (reading_ncols[a].fgcol - reading_ncols[b].fgcol);
259 }
260
261 #ifdef DEBUG_TSEARCH
262 static void
263 check_c_values(int ln)
264 {
265     static int oops = 5;
266     FG_NODE **ft;
267     int n;
268     for (n = 0; n < reading_last; ++n) {
269         ft = tfind(I2P(n), &reading_ntree, compare_c_values);
270         if (ft != 0) {
271             int q = P2I(*ft);
272             if (reading_ncols[q].fgcol != reading_ncols[n].fgcol) {
273                 logmsg("@%d, %d:%d (%d) %d %d fgcol %06X %06X", ln, n,
274                        reading_last - 1,
275                        reading_size,
276                        q, n,
277                        reading_ncols[n].fgcol,
278                        reading_ncols[q].fgcol);
279             }
280         } else {
281             logmsg("@%d, %d:%d (%d) ? %d null %06X", ln, n,
282                    reading_last - 1,
283                    reading_size,
284                    n,
285                    reading_ncols[n].fgcol);
286             if (oops-- <= 0)
287                 return;
288         }
289     }
290 }
291 #else
292 #define check_c_values(n)       /* nothing */
293 #endif
294 #endif
295
296 static int
297 gather_c_values(int fg)
298 {
299     int found = -1;
300 #if HAVE_TSEARCH
301     FG_NODE **ft;
302     int next = reading_last;
303
304     reading_ncols[next].fgcol = fg;
305     reading_ncols[next].count = 0;
306
307     check_c_values(__LINE__);
308     if ((ft = tfind(I2P(next), &reading_ntree, compare_c_values)) != 0) {
309         found = P2I(*ft);
310     } else {
311         if (reading_last + 2 >= reading_size) {
312             int more = ((MAX(reading_last, reading_size) + 2) * 3) / 2;
313             int last = reading_last + 1;
314             FG_NODE *p = typeRealloc(FG_NODE, more, reading_ncols);
315             if (p == 0)
316                 goto done;
317
318             reading_size = more;
319             reading_ncols = p;
320             memset(reading_ncols + last, 0,
321                    sizeof(FG_NODE) * (size_t) (more - last));
322             check_c_values(__LINE__);
323         }
324         ++reading_last;
325         how_much.pair += sizeof(FG_NODE);
326         if ((ft = tsearch(I2P(next), &reading_ntree, compare_c_values)) != 0) {
327             found = P2I(*ft);
328             if (found != next)
329                 logmsg("OOPS expected slot %d, got %d", next, found);
330             debugmsg("allocated color #%d as #%06X", next, fg);
331             check_c_values(__LINE__);
332         }
333     }
334 #else
335     int n;
336
337     for (n = 0; n < reading_last; ++n) {
338         if (reading_ncols[n].fgcol == fg) {
339             found = n;
340             break;
341         }
342     }
343     if (found < 0) {
344         if (reading_last + 2 >= reading_size) {
345             int more = ((reading_last + 2) * 3) / 2;
346             FG_NODE *p = typeRealloc(FG_NODE, more, reading_ncols);
347             if (p == 0)
348                 goto done;
349
350             how_much.pair -= (sizeof(FG_NODE) * (size_t) reading_size);
351             how_much.pair += (sizeof(FG_NODE) * (size_t) more);
352             reading_size = more;
353             reading_ncols = p;
354             memset(reading_ncols + reading_last, 0,
355                    sizeof(FG_NODE) * (size_t) (more - reading_last));
356         }
357         reading_ncols[reading_last].fgcol = fg;
358         found = reading_last++;
359     }
360 #endif
361   done:
362     return found;
363 }
364
365 static void
366 finish_c_values(PICS_HEAD * head)
367 {
368     head->colors = reading_last;
369     head->fgcol = reading_ncols;
370
371     reading_last = 0;
372     reading_size = 0;
373     reading_ncols = 0;
374 }
375
376 static void
377 dispose_c_values(void)
378 {
379 #if HAVE_TSEARCH
380     if (reading_ntree != 0) {
381         int n;
382         for (n = 0; n < reading_last; ++n) {
383             tdelete(I2P(n), &reading_ntree, compare_c_values);
384         }
385         reading_ntree = 0;
386     }
387 #endif
388     if (reading_ncols != 0) {
389         free(reading_ncols);
390         reading_ncols = 0;
391     }
392     reading_last = 0;
393     reading_size = 0;
394 }
395
396 static int
397 is_file(const char *filename, struct stat *sb)
398 {
399     int result = 0;
400     if (stat(filename, sb) == 0
401         && (sb->st_mode & S_IFMT) == S_IFREG
402         && sb->st_size != 0) {
403         result = 1;
404     }
405     debugmsg("is_file(%s) %d", filename, result);
406     return result;
407 }
408
409 /*
410  * Simplify reading xbm/xpm files by first making an array of lines.  Blank
411  * lines are filtered out.
412  */
413 static char **
414 read_file(const char *filename)
415 {
416     char **result = 0;
417     struct stat sb;
418
419     if (!quiet) {
420         pause_curses();
421         printf("** %s\n", filename);
422     }
423
424     if (is_file(filename, &sb)) {
425         size_t size = (size_t) sb.st_size;
426         char *blob = typeCalloc(char, size + 1);
427         bool binary = FALSE;
428         unsigned k = 0;
429
430         result = typeCalloc(char *, size + 1);
431         how_much.file += ((size + 1) * 2);
432
433         if (blob != 0 && result != 0) {
434             FILE *fp = fopen(filename, "r");
435             if (fp != 0) {
436                 logmsg("opened %s", filename);
437
438                 if (fread(blob, sizeof(char), size, fp) == size) {
439                     bool had_line = TRUE;
440                     unsigned j;
441
442                     for (j = 0; (size_t) j < size; ++j) {
443                         if (blob[j] == '\0' ||
444                             (UChar(blob[j]) < 32 &&
445                              !isspace(UChar(blob[j]))) ||
446                             (UChar(blob[j]) >= 128 && UChar(blob[j]) < 160)) {
447                             binary = TRUE;
448                         }
449                         if (blob[j] == '\n') {
450                             blob[j] = '\0';
451                             if (k && !binary) {
452                                 debugmsg2("[%5d] %s\n", k, result[k - 1]);
453                             }
454                             had_line = TRUE;
455                         } else if (had_line) {
456                             had_line = FALSE;
457                             result[k++] = blob + j;
458                         }
459                     }
460                     result[k] = 0;
461                     if (k && !binary) {
462                         debugmsg2("[%5d] %s\n", k, result[k - 1]);
463                     }
464                 }
465                 fclose(fp);
466             } else {
467                 logmsg("cannot open %s", filename);
468             }
469         }
470         if (k == 0) {
471             debugmsg("...file is empty");
472             free(blob);
473             free(result);
474             result = 0;
475         } else if (binary) {
476             debugmsg("...file is non-text");
477         }
478     }
479     return result;
480 }
481
482 static void
483 usage(int ok)
484 {
485     static const char *msg[] =
486     {
487         "Usage: picsmap [options] [imagefile [...]]"
488         ,"Read/display one or more xbm/xpm files (possibly use \"convert\")"
489         ,""
490         ,USAGE_COMMON
491         ,"Options:"
492         ," -a ratio aspect-ratio correction for ImageMagick"
493 #if HAVE_USE_DEFAULT_COLORS
494         ," -d       invoke use_default_colors"
495 #endif
496         ," -L       add debugging information to logfile"
497         ," -l FILE  write informational messages to FILE"
498         ," -p FILE  color-palette file (default \"$TERM.dat\")"
499         ," -q       less verbose"
500         ," -r FILE  xpm uses X rgb color-names in FILE (default \"" RGB_PATH "\")"
501         ," -s SECS  pause for SECS seconds after display vs getch"
502 #if USE_EXTENDED_COLORS
503         ," -x [pc]  use extension (p=extended-pairs, c=extended-colors)"
504         ,"          Either/both extension may be given"
505 #endif
506     };
507     size_t n;
508
509     pause_curses();
510
511     fflush(stdout);
512     for (n = 0; n < SIZEOF(msg); n++)
513         fprintf(stderr, "%s\n", msg[n]);
514     cleanup(ok ? EXIT_SUCCESS : EXIT_FAILURE);
515 }
516
517 static void
518 giveup(const char *fmt, ...)
519 {
520     va_list ap;
521
522     pause_curses();
523     fflush(stdout);
524
525     va_start(ap, fmt);
526     vfprintf(stderr, fmt, ap);
527     fputc('\n', stderr);
528     va_end(ap);
529
530     if (logfp) {
531         va_start(ap, fmt);
532         vfprintf(logfp, fmt, ap);
533         fputc('\n', logfp);
534         va_end(ap);
535         fflush(logfp);
536     }
537
538     usage(FALSE);
539 }
540
541 /*
542  * Palette files are named for $TERM values.  However, there are fewer palette
543  * files than $TERM's.  Although there are known problems (some cannot even get
544  * black and white correct), for the purpose of comparison, pretending that
545  * those map into "xterm" is useful.
546  */
547 static char **
548 read_palette(const char *filename)
549 {
550     static const char *data_dir = DATA_DIR;
551     char **result = 0;
552     size_t last = strlen(filename);
553     size_t need = (strlen(data_dir) + 20 + last);
554     char *full_name = malloc(need);
555     char *s;
556     struct stat sb;
557
558     if (full_name != 0) {
559         int tries;
560         for (tries = 0; tries < 8; ++tries) {
561
562             *(s = full_name) = '\0';
563             if (tries & 1) {
564                 if (strchr(filename, '/') == 0) {
565                     _nc_SPRINTF(full_name, _nc_SLIMIT(need) "%s/", data_dir);
566                 } else {
567                     continue;
568                 }
569             }
570             s += strlen(s);
571             if (((size_t) (s - full_name) + last + 1) >= need)
572                 continue;
573
574             _nc_STRCAT(full_name, filename, need);
575             if (tries & 4) {
576                 char *t = s;
577                 char *tc;
578                 int num;
579                 char chr;
580                 int found = 0;
581                 while (*t != '\0') {
582                     if (*t == '-') {
583                         if (sscanf(t, "-%d%c", &num, &chr) == 2 &&
584                             chr == 'c' &&
585                             (tc = strchr(t, chr)) != 0 &&
586                             !(strncmp) (tc, "color", 5)) {
587                             found = 1;
588                         }
589                         break;
590                     }
591                     ++t;
592                 }
593                 if (found && (t != s)
594                     && (strncmp) (s, "xterm", (size_t) (t - s))) {
595                     _nc_SPRINTF(s, _nc_SLIMIT(need - (size_t) (s - full_name))
596                                 "xterm%s", filename + (t - s));
597                 } else {
598                     continue;
599                 }
600             }
601
602             if (tries & 2) {
603                 int len = (int) strlen(filename);
604                 if (len <= 4 || strcmp(filename + len - 4, ".dat")) {
605                     _nc_STRCAT(full_name, ".dat", need);
606                 } else {
607                     continue;
608                 }
609             }
610             if (is_file(full_name, &sb))
611                 goto ok;
612         }
613         goto failed;
614       ok:
615         result = read_file(full_name);
616       failed:
617         free(full_name);
618     }
619     return result;
620 }
621
622 static void
623 init_palette(const char *palette_file)
624 {
625     if (palette_file != 0) {
626         char **data = read_palette(palette_file);
627
628         all_colors = typeMalloc(RGB_DATA, (unsigned) COLORS);
629         how_much.data += (sizeof(RGB_DATA) * (unsigned) COLORS);
630
631 #if HAVE_COLOR_CONTENT
632         {
633             int cp;
634             for (cp = 0; cp < COLORS; ++cp) {
635                 color_content((short) cp,
636                               &all_colors[cp].red,
637                               &all_colors[cp].green,
638                               &all_colors[cp].blue);
639             }
640         }
641 #else
642         memset(all_colors, 0, sizeof(RGB_DATA) * (size_t) COLORS);
643 #endif
644         if (data != 0) {
645             int n;
646             int red, green, blue;
647             int scale = MaxSCALE;
648             int c;
649             for (n = 0; data[n] != 0; ++n) {
650                 if (sscanf(data[n], "scale:%d", &c) == 1) {
651                     scale = c;
652                 } else if (sscanf(data[n], "%d:%d %d %d",
653                                   &c,
654                                   &red,
655                                   &green,
656                                   &blue) == 4
657                            && okCOLOR(c)
658                            && okSCALE(red)
659                            && okSCALE(green)
660                            && okSCALE(blue)) {
661                     /* *INDENT-EQLS* */
662                     all_colors[c].red   = ScaledColor(red);
663                     all_colors[c].green = ScaledColor(green);
664                     all_colors[c].blue  = ScaledColor(blue);
665                 }
666             }
667         }
668         free_data(data);
669         /* *INDENT-EQLS* */
670     } else if (COLORS > 1) {
671         int power2 = 1;
672         int shift = 0;
673
674         while (power2 < COLORS) {
675             ++shift;
676             power2 <<= 1;
677         }
678
679         if ((power2 != COLORS) || ((shift % 3) != 0)) {
680             if (all_colors == 0) {
681                 init_palette(getenv("TERM"));
682                 if (all_colors == 0) {
683                     giveup("With %d colors, you need a palette-file", COLORS);
684                 }
685             }
686         }
687     }
688 }
689
690 /*
691  * Map the 24-bit RGB value to a color index if using a palette, otherwise to a
692  * direct color value.
693  */
694 static int
695 map_color(int value)
696 {
697     int result = value;
698
699     if (result < 0) {
700         result = -1;
701     } else {
702         /* *INDENT-EQLS* */
703         int red   = (value & 0xff0000) >> 16;
704         int green = (value & 0x00ff00) >> 8;
705         int blue  = (value & 0x0000ff) >> 0;
706
707         if (all_colors != 0) {
708 #define Diff2(n,m) ((m) - all_colors[n].m) * ((m) - all_colors[n].m)
709 #define Diff2S(n) Diff2(n,red) + Diff2(n,green) + Diff2(n,blue)
710             int d2 = Diff2S(0);
711             int n;
712
713             /* *INDENT-EQLS* */
714             red   = Scaled256(red);
715             green = Scaled256(green);
716             blue  = Scaled256(blue);
717
718             for (result = 0, n = 1; n < COLORS; ++n) {
719                 int d = Diff2(n, red) + Diff2(n, green) + Diff2(n, blue);
720                 if (d < d2) {
721                     d2 = d;
722                     result = n;
723                 }
724             }
725         } else {                /* direct color */
726             int power2 = 1;
727             int shifts = 8;
728
729             while (power2 < COLORS) {
730                 power2 <<= 3;
731                 shifts--;
732             }
733
734             if (shifts > 0) {
735                 /* TODO: round up */
736                 red >>= shifts;
737                 green >>= shifts;
738                 blue >>= shifts;
739                 result = ((red << (2 * (8 - shifts)))
740                           + (green << (8 - shifts))
741                           + blue);
742             }
743         }
744     }
745     return result;
746 }
747
748 static int
749 bytes_of(int value)
750 {
751     if (value & 7) {
752         value |= 7;
753         value++;
754     }
755     return value;
756 }
757
758 static int match_c(const char *, const char *, ...) GCC_SCANFLIKE(2,3);
759
760 static char *
761 skip_s(char *s)
762 {
763     while (isspace(UChar(*s)))
764         s++;
765     return s;
766 }
767
768 static const char *
769 skip_cs(const char *s)
770 {
771     while (isspace(UChar(*s)))
772         s++;
773     return s;
774 }
775
776 static char *
777 skip_word(char *s)
778 {
779     s = skip_s(s);
780     while (isgraph(UChar(*s)))
781         s++;
782     return s;
783 }
784
785 static int
786 match_c(const char *source, const char *pattern, ...)
787 {
788     int limit = (int) strlen(source);
789     const char *last_s = source + limit;
790     va_list ap;
791     int ch;
792     int *ip;
793     char *cp;
794     float *fp;
795     long lv;
796
797     va_start(ap, pattern);
798
799     limit = -1;
800     while (*pattern != '\0') {
801         ch = UChar(*pattern++);
802         /* blank in the pattern matches zero-or-more blanks in source */
803         if (isspace(ch)) {
804             source = skip_cs(source);
805             continue;
806         }
807         /* %c, %d, %s are like sscanf except for special treatment of blanks */
808         if (ch == '%' && *pattern != '\0' && strchr("%cdnfsx", *pattern)) {
809             bool found = FALSE;
810             ch = *pattern++;
811             switch (ch) {
812             case '%':
813                 source++;
814                 break;
815             case 'c':
816                 cp = va_arg(ap, char *);
817                 do {
818                     *cp++ = *source++;
819                 } while (--limit > 0);
820                 break;
821             case 'd':
822             case 'x':
823                 limit = -1;
824                 ip = va_arg(ap, int *);
825                 lv = strtol(source, &cp, ch == 'd' ? 10 : 16);
826                 if (cp != 0 && cp != source) {
827                     *ip = (int) lv;
828                     source = cp;
829                 } else {
830                     goto finish;
831                 }
832                 break;
833             case 'f':
834                 /* floating point for pixels... */
835                 fp = va_arg(ap, float *);
836                 lv = strtol(source, &cp, 10);
837                 if (cp == 0 || cp == source)
838                     goto finish;
839                 *fp = (float) lv;
840                 source = cp;
841                 if (*source == '.') {
842                     lv = strtol(++source, &cp, 10);
843                     if (cp == 0 || cp == source)
844                         goto finish;
845                     {
846                         float scale = 1.0f;
847                         int digits = (int) (cp - source);
848                         while (digits-- > 0) {
849                             scale *= 10.0f;
850                         }
851                         *fp += (float) lv / scale;
852                     }
853                     source = cp;
854                 }
855                 break;
856             case 'n':
857                 /* not really sscanf... */
858                 limit = *va_arg(ap, int *);
859                 break;
860             case 's':
861                 limit = -1;
862                 cp = va_arg(ap, char *);
863                 while (*source != '\0') {
864                     ch = UChar(*source);
865                     if (isspace(ch)) {
866                         break;
867                     } else if (found && (ch == *skip_cs(pattern))) {
868                         break;
869                     } else {
870                         *cp++ = *source++;
871                         found = TRUE;
872                     }
873                 }
874                 *cp = '\0';
875                 break;
876             }
877             continue;
878         }
879         /* other characters are matched literally */
880         if (*source++ != ch) {
881             break;
882         }
883     }
884   finish:
885
886     va_end(ap);
887     if (source > last_s)
888         source = last_s;
889     return (*source || *pattern) ? 0 : 1;
890 }
891
892 static int
893 match_colors(const char *source, int cpp, char *arg1, char *arg2, char *arg3)
894 {
895     int result = 0;
896
897     /* most files use a quasi-fixed format */
898     if (match_c(source, " \"%n%c %s %s \" , ", &cpp, arg1, arg2, arg3)) {
899         arg1[cpp] = '\0';
900         result = 1;
901     } else {
902         const char *s = skip_cs(source);
903         size_t have = strlen(source);
904
905         if (*s++ == '"' && have > ((size_t) cpp + 2)) {
906             memcpy(arg1, s, (size_t) cpp);
907             s += cpp;
908             while (*s++ == '\t') {
909                 char *t;
910                 for (t = arg2; (*s != '\0') && strchr("\t\"", *s) == 0;) {
911                     if (*s == ' ') {
912                         s = skip_cs(s);
913                         break;
914                     }
915                     *t++ = *s++;
916                     *t = '\0';
917                 }
918                 for (t = arg3; (*s != '\0') && strchr("\t\"", *s) == 0;) {
919                     *t++ = *s++;
920                     *t = '\0';
921                 }
922                 if (!strcmp(arg2, "c")) {
923                     result = 1;
924                     break;
925                 }
926             }
927         }
928     }
929     return result;
930 }
931
932 static RGB_NAME *
933 parse_rgb(char **data)
934 {
935     char buf[BUFSIZ];
936     int n;
937     unsigned long r, g, b;
938     char *s, *t;
939     size_t item = 0;
940     size_t need;
941     RGB_NAME *result = 0;
942
943     for (need = 0; data[need] != 0; ++need) ;
944
945     result = typeCalloc(RGB_NAME, need + 2);
946     how_much.name += (sizeof(RGB_NAME) * (need + 2));
947
948     for (n = 0; data[n] != 0; ++n) {
949         if (strlen(t = data[n]) >= sizeof(buf) - 1)
950             continue;
951         if (*(s = skip_s(t)) == '!')
952             continue;
953
954         r = strtoul(s, &t, 10);
955         s = skip_s(t);
956         g = strtoul(s, &t, 10);
957         s = skip_s(t);
958         b = strtoul(s, &t, 10);
959         s = skip_s(t);
960
961         result[item].name = s;
962         t = s + strlen(s);
963         while (t-- != s && isspace(UChar(*t))) {
964             *t = '\0';
965         }
966         result[item].value = (int) ((r & 0xff) << 16 |
967                                     (g & 0xff) << 8 |
968                                     (b & 0xff));
969         ++item;
970     }
971
972     result[item].name = "none";
973     result[item].value = -1;
974
975     return result;
976 }
977
978 #define LOWERCASE(c) ((isalpha(UChar(c)) && isupper(UChar(c))) ? tolower(UChar(c)) : (c))
979
980 static int
981 CaselessCmp(const char *a, const char *b)
982 {                               /* strcasecmp isn't portable */
983     while (*a && *b) {
984         int cmp = LOWERCASE(*a) - LOWERCASE(*b);
985         if (cmp != 0)
986             break;
987         a++, b++;
988     }
989     return LOWERCASE(*a) - LOWERCASE(*b);
990 }
991
992 static RGB_NAME *
993 lookup_rgb(const char *name)
994 {
995     RGB_NAME *result = 0;
996     if (rgb_table != 0) {
997         int n;
998         for (n = 0; rgb_table[n].name != 0; ++n) {
999             if (!CaselessCmp(name, rgb_table[n].name)) {
1000                 result = &rgb_table[n];
1001                 break;
1002             }
1003         }
1004     }
1005     return result;
1006 }
1007
1008 static PICS_HEAD *
1009 parse_xbm(char **data)
1010 {
1011     int n;
1012     int state = 0;
1013     char buf[2048];
1014     int num;
1015     char ch;
1016     char *s;
1017     char *t;
1018     PICS_HEAD *result;
1019     size_t which = 0;
1020     size_t cells = 0;
1021
1022     debugmsg("called parse_xbm");
1023
1024     result = typeCalloc(PICS_HEAD, 1);
1025     how_much.head += sizeof(PICS_HEAD);
1026
1027     begin_c_values(2);
1028     gather_c_values(0);
1029     gather_c_values(0xffffff);
1030
1031     for (n = 0; data[n] != 0; ++n) {
1032         if (strlen(s = data[n]) >= sizeof(buf) - 1)
1033             continue;
1034         switch (state) {
1035         case 0:
1036         case 1:
1037         case 2:
1038             if (sscanf(s, "#define %1024s %d%c", buf, &num, &ch) >= 2) {
1039                 if ((t = strstr(buf, "_width")) != 0) {
1040                     state |= 1;
1041                     result->wide = (short) bytes_of(num);
1042                 } else if ((t = strstr(buf, "_height")) != 0) {
1043                     state |= 2;
1044                     result->high = (short) num;
1045                 } else {
1046                     break;
1047                 }
1048                 *t = '\0';
1049                 if (result->name) {
1050                     if (strcmp(result->name, buf)) {
1051                         goto finish;
1052                     }
1053                 } else {
1054                     result->name = strdup(buf);
1055                 }
1056             }
1057             break;
1058         case 3:
1059             if (sscanf(s, "static char %1024[^_ ]_bits[]%c", buf, &ch) >= 1) {
1060                 if (strcmp(result->name, buf)) {
1061                     goto finish;
1062                 }
1063                 state = 4;
1064                 cells = (size_t) (result->wide * result->high);
1065
1066                 result->cells = typeCalloc(PICS_CELL, cells);
1067                 how_much.cell += (sizeof(PICS_CELL) * cells);
1068
1069                 if ((s = strchr(s, L_CURLY)) == 0)
1070                     break;
1071                 ++s;
1072             } else {
1073                 break;
1074             }
1075         case 4:
1076             while (*s != '\0') {
1077                 while (isspace(UChar(*s))) {
1078                     ++s;
1079                 }
1080                 if (isdigit(UChar(*s))) {
1081                     long value = strtol(s, &t, 0);
1082                     int b;
1083                     if (t != s || value > MaxRGB || value < 0) {
1084                         s = t;
1085                     } else {
1086                         state = -1;
1087                         goto finish;
1088                     }
1089                     for (b = 0; b < 8; ++b) {
1090                         if (((1L << b) & value) != 0) {
1091                             result->cells[which].ch = '*';
1092                             result->cells[which].fg = 1;
1093                             reading_ncols[1].count++;
1094                         } else {
1095                             result->cells[which].ch = ' ';
1096                             result->cells[which].fg = 0;
1097                             reading_ncols[0].count++;
1098                         }
1099                         if (++which > cells) {
1100                             state = -1;
1101                             goto finish;
1102                         }
1103                     }
1104                 }
1105                 if (*s == R_CURLY) {
1106                     state = 5;
1107                     goto finish;
1108                 } else if (*s == ',') {
1109                     ++s;
1110                 }
1111             }
1112             break;
1113         default:
1114             break;
1115         }
1116     }
1117   finish:
1118     if (state < 4) {
1119         debugmsg("...state was only %d", state);
1120         if (result) {
1121             result = free_pics_head(result);
1122         }
1123     } else {
1124         finish_c_values(result);
1125     }
1126     return result;
1127 }
1128
1129 static PICS_HEAD *
1130 parse_xpm(char **data)
1131 {
1132     int state = 0;
1133     PICS_HEAD *result;
1134     RGB_NAME *by_name;
1135     int n;
1136     int cells = 0;
1137     int cpp = 1;                /* chars per pixel */
1138     int num[6];
1139     int found;
1140     int which = 0;
1141     int num_colors = 0;
1142     char ch;
1143     const char *cs;
1144     char *s;
1145     char buf[BUFSIZ];
1146     char arg1[BUFSIZ];
1147     char arg2[BUFSIZ];
1148     char arg3[BUFSIZ];
1149     char **list = 0;
1150
1151     debugmsg("called parse_xpm");
1152
1153     result = typeCalloc(PICS_HEAD, 1);
1154     how_much.head += sizeof(PICS_HEAD);
1155
1156     for (n = 0; data[n] != 0; ++n) {
1157         if (strlen(s = data[n]) >= sizeof(buf) - 1)
1158             continue;
1159         switch (state) {
1160         case 0:
1161             if (match_c(s, " /* XPM */ ")) {
1162                 state = 1;
1163             }
1164             break;
1165         case 1:
1166             if (match_c(s, " static char * %s [] = %c ", arg1, &ch) &&
1167                 ch == L_CURLY) {
1168                 result->name = strdup(arg1);
1169                 state = 2;
1170             }
1171             break;
1172         case 2:
1173             if (match_c(s, " \" %d %d %d %d \" , ",
1174                         num + 0, num + 1, num + 2, num + 3) ||
1175                 match_c(s, " \" %d %d %d %d %d %d \" , ",
1176                         num + 0, num + 1, num + 2, num + 3, num + 4, num + 5)) {
1177                 result->wide = (short) num[0];
1178                 result->high = (short) num[1];
1179                 result->colors = num[2];
1180
1181                 begin_c_values(num[2]);
1182
1183                 cells = (result->wide * result->high);
1184
1185                 result->cells = typeCalloc(PICS_CELL, cells);
1186                 how_much.cell += sizeof(PICS_CELL) * (size_t) cells;
1187
1188                 list = typeCalloc(char *, result->colors + 1);
1189                 how_much.list += sizeof(char *) * (size_t) (result->colors + 1);
1190
1191                 cpp = num[3];
1192                 state = 3;
1193             }
1194             break;
1195         case 3:
1196             if (!match_colors(s, cpp, arg1, arg2, arg3)) {
1197                 break;
1198             }
1199             num_colors++;
1200             free(list[reading_last]);
1201             list[reading_last] = strdup(arg1);
1202             if ((by_name = lookup_rgb(arg3)) != 0) {
1203                 found = gather_c_values(by_name->value);
1204             } else if (*arg3 == '#') {
1205                 char *rgb = arg3 + 1;
1206                 unsigned long value = strtoul(rgb, &s, 16);
1207                 switch ((int) strlen(rgb)) {
1208                 case 6:
1209                     break;
1210                 case 12:
1211                     value = (((value >> 24) & 0xff0000L)
1212                              | ((value >> 16) & 0xff00L)
1213                              | ((value >> 8) & 0xffL));
1214                     break;
1215                 default:
1216                     warning("unexpected rgb value %s", rgb);
1217                     break;
1218                 }
1219                 found = gather_c_values((int) value);
1220             } else {
1221                 found = gather_c_values(0);     /* actually an error */
1222             }
1223             debugmsg("  [%d:%d] %06X", num_colors, result->colors,
1224                      reading_ncols[(found >= 0) ? found : 0].fgcol);
1225             if (num_colors >= result->colors) {
1226                 finish_c_values(result);
1227                 state = 4;
1228                 if (list[0] == 0)
1229                     list[0] = strdup("\033");
1230             }
1231             break;
1232         case 4:
1233             if (*(cs = skip_cs(s)) == '"') {
1234                 ++cs;
1235                 while (*cs != '\0' && *cs != '"') {
1236                     int c;
1237
1238                     /* FIXME - factor out */
1239                     for (c = 0; c < result->colors; ++c) {
1240                         if (list[c] == 0) {
1241                             /* should not happen... */
1242                             continue;
1243                         }
1244                         if (!(strncmp) (cs, list[c], (size_t) cpp)) {
1245                             result->cells[which].ch = list[c][0];
1246                             result->cells[which].fg = c;
1247                             result->fgcol[c].count++;
1248                             break;
1249                         }
1250                     }
1251
1252                     if (result->cells[which].ch == 0) {
1253                         result->cells[which].ch = '?';
1254                         result->cells[which].fg = 0;
1255                     }
1256
1257                     if (++which >= cells) {
1258                         state = 5;
1259                         break;
1260                     }
1261                     for (c = cpp; c > 0; --c, ++cs) {
1262                         if (*cs == '\0')
1263                             break;
1264                     }
1265                 }
1266             }
1267             break;
1268         }
1269     }
1270
1271     if (result && list) {
1272         for (n = 0; n < result->colors; ++n)
1273             free(list[n]);
1274         free(list);
1275     }
1276
1277     if (state < 5) {
1278         debugmsg("...state was only %d", state);
1279         result = free_pics_head(result);
1280     }
1281
1282     if (result) {
1283         debugmsg("...allocated %d colors", result->colors);
1284     }
1285
1286     return result;
1287 }
1288
1289 /*
1290  * The obscurely-named "convert" is provided by ImageMagick
1291  */
1292 static PICS_HEAD *
1293 parse_img(const char *filename)
1294 {
1295     size_t need = strlen(filename) + 256;
1296     char *cmd = malloc(need);
1297     FILE *pp;
1298     char buffer[BUFSIZ];
1299     char dummy[BUFSIZ];
1300     bool okay = TRUE;
1301     PICS_HEAD *result;
1302     int pic_x = 0;
1303     int pic_y = 0;
1304     int width = in_curses ? COLS : 80;
1305
1306     _nc_SPRINTF(cmd, _nc_SLIMIT(need) "identify \"%s\"", filename);
1307     if (quiet)
1308         _nc_STRCAT(cmd, " 2>/dev/null", need);
1309
1310     logmsg("...opening pipe to %s", cmd);
1311
1312     result = typeCalloc(PICS_HEAD, 1);
1313     how_much.head += sizeof(PICS_HEAD);
1314
1315     if ((pp = popen(cmd, "r")) != 0) {
1316         if (fgets(buffer, sizeof(buffer), pp) != 0) {
1317             size_t n = strlen(filename);
1318             debugmsg2("...read %s", buffer);
1319             if (strlen(buffer) > n &&
1320                 !(strncmp) (buffer, filename, n) &&
1321                 isspace(UChar(buffer[n])) &&
1322                 sscanf(skip_word(buffer + n), " %dx%d ", &pic_x, &pic_y) == 2) {
1323                 /* distort image to make it show normally on terminal */
1324                 pic_x = (int) ((double) pic_x / aspect_ratio);
1325             } else {
1326                 pic_x = pic_y = 0;
1327             }
1328         }
1329         pclose(pp);
1330     }
1331     if (pic_x <= 0 || pic_y <= 0)
1332         goto finish;
1333
1334     _nc_SPRINTF(cmd, _nc_SLIMIT(need)
1335                 "convert " "-resize %dx%d\\! " "-thumbnail %dx \"%s\" "
1336                 "-define txt:compliance=SVG txt:-",
1337                 pic_x, pic_y, width, filename);
1338     if (quiet)
1339         _nc_STRCAT(cmd, " 2>/dev/null", need);
1340
1341     logmsg("...opening pipe to %s", cmd);
1342     if ((pp = popen(cmd, "r")) != 0) {
1343         int count = 0;
1344         int col = 0;
1345         int row = 0;
1346         int len = 0;
1347         while (fgets(buffer, sizeof(buffer), pp) != 0) {
1348             debugmsg2("[%5d] %s", count + 1, buffer);
1349             if (strlen(buffer) > 160) {         /* 80 columns would be enough */
1350                 okay = FALSE;
1351                 break;
1352             }
1353             if (count++ == 0) {
1354                 if (match_c(buffer,
1355                             "# ImageMagick pixel enumeration: %d,%d,%d,%s ",
1356                             &col, &row, &len, dummy)) {
1357                     result->name = strdup(filename);
1358                     result->wide = (short) col;
1359                     result->high = (short) row;
1360
1361                     begin_c_values(256);
1362
1363                     result->cells = typeCalloc(PICS_CELL, (size_t) (col * row));
1364                     how_much.cell += (sizeof(PICS_CELL) * (size_t) (col * row));
1365                 } else {
1366                     okay = FALSE;
1367                     break;
1368                 }
1369             } else {
1370                 /*
1371                  * subsequent lines begin "col,row: (r,g,b,a) #RGB".
1372                  * Those r/g/b could be integers (0..255) or float-percentages.
1373                  */
1374                 int r, g, b, nocolor;
1375                 float rf, gf, bf;
1376                 unsigned check;
1377                 char *t;
1378                 char *s = t = strchr(buffer, '#');
1379                 bool matched = FALSE;
1380
1381                 if (s != 0) {
1382                     /* after the "#RGB", there are differences - just ignore */
1383                     while (*s != '\0' && !isspace(UChar(*s)))
1384                         ++s;
1385                     *++s = '\0';
1386                 }
1387
1388                 if (match_c(buffer,
1389                             "%d,%d: (%d,%d,%d,%d) #%x ",
1390                             &col, &row,
1391                             &r, &g, &b, &nocolor,
1392                             &check)) {
1393                     matched = TRUE;
1394                 } else if (match_c(buffer,
1395                                    "%d,%d: (%f%%,%f%%,%f%%,%d) #%x ",
1396                                    &col, &row,
1397                                    &rf, &gf, &bf, &nocolor,
1398                                    &check) ||
1399                            match_c(buffer,
1400                                    "%d,%d: (%f%%,%f%%,%f%%) #%x ",
1401                                    &col, &row,
1402                                    &rf, &gf, &bf,
1403                                    &check)) {
1404                     matched = TRUE;
1405
1406 #define fp_fix(n) (int) (MaxRGB * (((n) > 100.0 ? 100.0 : (n)) / 100.0))
1407
1408                     r = fp_fix(rf);
1409                     g = fp_fix(gf);
1410                     b = fp_fix(bf);
1411                 }
1412                 if ((s - t) > 8)        /* 6 hex digits vs 8 */
1413                     check /= 256;
1414                 if (matched) {
1415                     int which, c;
1416                     int want_r = (check >> 16) & 0xff;
1417                     int want_g = (check >> 8) & 0xff;
1418                     int want_b = (check >> 0) & 0xff;
1419
1420 #define fp_err(tst,ref) ((tst > MaxRGB) || ((tst - ref)*(tst - ref)) > 4)
1421
1422                     if (fp_err(r, want_r) ||
1423                         fp_err(g, want_g) ||
1424                         fp_err(b, want_b)) {
1425                         okay = FALSE;
1426                         break;
1427                     }
1428                     c = gather_c_values((int) check);
1429                     which = col + (row * result->wide);
1430                     result->cells[which].ch = ((in_curses ||
1431                                                 check == 0xffffff)
1432                                                ? ' '
1433                                                : '#');
1434                     if (c >= 0 && c < reading_last) {
1435                         result->cells[which].fg = c;
1436                         reading_ncols[c].count++;
1437                     } else {
1438                         result->cells[which].fg = -1;
1439                     }
1440                 } else {
1441                     okay = FALSE;
1442                     break;
1443                 }
1444             }
1445         }
1446         finish_c_values(result);
1447         pclose(pp);
1448         if (okay) {
1449             /* FIXME - is this trimming needed? */
1450             for (len = result->colors; len > 3; len--) {
1451                 if (result->fgcol[len - 1].fgcol == 0) {
1452                     result->colors = len - 1;
1453                 } else {
1454                     break;
1455                 }
1456             }
1457         }
1458     }
1459   finish:
1460     free(cmd);
1461
1462     if (!okay) {
1463         result = free_pics_head(result);
1464     }
1465
1466     return result;
1467 }
1468
1469 static PICS_HEAD *
1470 read_picture(const char *filename, char **data)
1471 {
1472     PICS_HEAD *pics;
1473     if ((pics = parse_xbm(data)) == 0) {
1474         dispose_c_values();
1475         if ((pics = parse_xpm(data)) == 0) {
1476             dispose_c_values();
1477             if ((pics = parse_img(filename)) == 0) {
1478                 dispose_c_values();
1479                 free_data(data);
1480                 warning("unexpected file-format for \"%s\"", filename);
1481             } else if (pics->high == 0 || pics->wide == 0) {
1482                 dispose_c_values();
1483                 free_data(data);
1484                 pics = free_pics_head(pics);
1485                 warning("no picture found in \"%s\"", filename);
1486             }
1487         }
1488     }
1489     return pics;
1490 }
1491
1492 #define fg_color(pics,n) (pics->fgcol[n].fgcol)
1493
1494 static void
1495 dump_picture(PICS_HEAD * pics)
1496 {
1497     int y, x;
1498
1499     printf("Name %s\n", pics->name);
1500     printf("Size %dx%d\n", pics->high, pics->wide);
1501     printf("Color\n");
1502     for (y = 0; y < pics->colors; ++y) {
1503         if (fg_color(pics, y) < 0) {
1504             printf(" %3d: %d\n", y, fg_color(pics, y));
1505         } else {
1506             printf(" %3d: #%06x\n", y, fg_color(pics, y));
1507         }
1508     }
1509     for (y = 0; y < pics->high; ++y) {
1510         for (x = 0; x < pics->wide; ++x) {
1511             putchar(pics->cells[y * pics->wide + x].ch);
1512         }
1513         putchar('\n');
1514     }
1515 }
1516
1517 #ifndef USE_DISPLAY_DRIVER
1518 static void
1519 init_display(const char *palette_path, int opt_d)
1520 {
1521     (void) opt_d;
1522     if (isatty(fileno(stdout))) {
1523         in_curses = TRUE;
1524         setlocale(LC_ALL, "");
1525         initscr();
1526         cbreak();
1527         noecho();
1528         curs_set(0);
1529         if (has_colors()) {
1530             start_color();
1531 #if HAVE_USE_DEFAULT_COLORS
1532             if (opt_d)
1533                 use_default_colors();
1534 #endif
1535             init_palette(palette_path);
1536         }
1537         scrollok(stdscr, FALSE);
1538         stop_curses();
1539     }
1540 }
1541
1542 static void
1543 show_picture(PICS_HEAD * pics)
1544 {
1545     int y, x;
1546     int n;
1547
1548     debugmsg("called show_picture");
1549     logmsg("...using %dx%d screen", LINES, COLS);
1550 #if HAVE_RESET_COLOR_PAIRS
1551     reset_color_pairs();
1552 #elif HAVE_CURSCR
1553     wclear(curscr);
1554     clear();
1555 #endif
1556     if (has_colors()) {
1557         logmsg("...using %d colors", pics->colors);
1558         for (n = 0; n < pics->colors; ++n) {
1559             int my_pair = (n + 1);
1560             int my_color = map_color(fg_color(pics, n));
1561 #if USE_EXTENDED_COLORS
1562             if (use_extended_pairs) {
1563                 init_extended_pair(my_pair, my_color, my_color);
1564             } else
1565 #endif
1566             {
1567                 my_pair &= 0x7fff;
1568                 my_color &= 0x7fff;
1569                 init_pair((short) my_pair, (short) my_color, (short) my_color);
1570             }
1571         }
1572         attrset(COLOR_PAIR(1));
1573         erase();
1574     }
1575     for (y = 0; y < pics->high; ++y) {
1576         if (y >= LINES)
1577             break;
1578         move(y, 0);
1579
1580         for (x = 0; x < pics->wide; ++x) {
1581             int my_pair;
1582
1583             if (x >= COLS)
1584                 break;
1585             n = (y * pics->wide + x);
1586             my_pair = pics->cells[n].fg + 1;
1587 #if USE_EXTENDED_COLORS
1588             if (use_extended_pairs) {
1589                 cchar_t temp;
1590                 wchar_t wch[2];
1591                 wch[0] = (wchar_t) pics->cells[n].ch;
1592                 wch[1] = 0;
1593                 setcchar(&temp, wch, A_NORMAL, (short) my_pair, &my_pair);
1594                 add_wch(&temp);
1595             } else
1596 #endif
1597             {
1598                 attrset(COLOR_PAIR(my_pair));
1599                 addch((chtype) pics->cells[n].ch);
1600             }
1601         }
1602     }
1603     if (slow_time >= 0) {
1604         refresh();
1605         if (slow_time > 0) {
1606 #ifdef NCURSES_VERSION
1607             napms(1000 * slow_time);
1608 #else
1609             sleep((unsigned) slow_time);
1610 #endif
1611         }
1612     } else {
1613         wmove(stdscr, 0, 0);
1614         getch();
1615     }
1616     if (!quiet)
1617         endwin();
1618 }
1619 #endif
1620
1621 static int
1622 compare_fg_counts(const void *a, const void *b)
1623 {
1624     const FG_NODE *p = (const FG_NODE *) a;
1625     const FG_NODE *q = (const FG_NODE *) b;
1626     return (q->count - p->count);
1627 }
1628
1629 static void
1630 report_colors(PICS_HEAD * pics)
1631 {
1632     int accum;
1633     double level;
1634     int j;
1635     int shift;
1636     int total;
1637     char buffer[256];
1638
1639     if (logfp == 0)
1640         return;
1641
1642     qsort(pics->fgcol, (size_t) pics->colors, sizeof(FG_NODE), compare_fg_counts);
1643     /*
1644      * For debugging, show a (short) list of the colors used.
1645      */
1646     if (debugging && (pics->colors < 1000)) {
1647         int digits = 0;
1648         int high;
1649         int wide = 4;
1650         for (j = pics->colors; j != 0; j /= 10) {
1651             ++digits;
1652             if (j < 10)
1653                 ++digits;
1654         }
1655         if (digits > 8)
1656             digits = 8;
1657         logmsg("These colors were used:");
1658         high = (pics->colors + wide - 1) / wide;
1659         for (j = 0; j < high && j < pics->colors; ++j) {
1660             int k;
1661             char *s = buffer;
1662             *s = '\0';
1663             for (k = 0; k < wide; ++k) {
1664                 int n = j + (k * high);
1665                 size_t want = (sizeof(buffer) - (size_t) (s - buffer));
1666                 if (want < 100 || want >= sizeof(buffer))
1667                     break;
1668                 if (n >= pics->colors)
1669                     break;
1670                 if (k) {
1671                     *s++ = ' ';
1672                     if (digits < 8) {
1673                         _nc_SPRINTF(s, _nc_SLIMIT(want) "%*s", 8 - digits,
1674                                     " ");
1675                         s += strlen(s);
1676                     }
1677                 }
1678                 if (pics->fgcol[n].fgcol >= 0) {
1679                     _nc_SPRINTF(s, _nc_SLIMIT(want) "%3d #%06X %*d", n,
1680                                 pics->fgcol[n].fgcol,
1681                                 digits, pics->fgcol[n].count);
1682                 } else {
1683                     _nc_SPRINTF(s, _nc_SLIMIT(want) "%3d (empty) %*d", n,
1684                                 digits, pics->fgcol[n].count);
1685                 }
1686                 s += strlen(s);
1687                 if ((s - buffer) > 100)
1688                     break;
1689             }
1690             logmsg("%s", buffer);
1691         }
1692     }
1693
1694     /*
1695      * Given the list of colors sorted by the number of times they are used,
1696      * log a short report showing the number of colors for 90%, 99%, 99.9%,
1697      * etc.
1698      */
1699     logmsg("Number of colors versus number of cells");
1700     total = pics->high * pics->wide;
1701     accum = 0;
1702     level = 0.1;
1703     shift = 1;
1704     for (j = 0; j < pics->colors; ++j) {
1705         accum += pics->fgcol[j].count;
1706         if (accum >= (total * (1.0 - level))) {
1707             int after = (shift > 2) ? shift - 2 : 0;
1708             logmsg("%8d colors (%.1f%%) in %d cells (%.*f%%)",
1709                    j + 1,
1710                    (100.0 * (j + 1)) / pics->colors,
1711                    accum,
1712                    after, (100.0 * accum) / total);
1713             if (accum >= total)
1714                 break;
1715             level /= 10.0;
1716             shift++;
1717         }
1718     }
1719 }
1720 /* *INDENT-OFF* */
1721 VERSION_COMMON()
1722 /* *INDENT-ON* */
1723
1724 int
1725 main(int argc, char *argv[])
1726 {
1727     int ch;
1728     int opt_d = FALSE;
1729     char ignore_ch;
1730     const char *palette_path = 0;
1731     const char *rgb_path = RGB_PATH;
1732
1733     while ((ch = getopt(argc, argv, OPTS_COMMON "a:dLl:p:qr:s:x:")) != -1) {
1734         switch (ch) {
1735         case 'a':
1736             if (sscanf(optarg, "%lf%c", &aspect_ratio, &ignore_ch) != 1
1737                 || aspect_ratio < 0.1
1738                 || aspect_ratio > 10.) {
1739                 fprintf(stderr, "Expected a number in [0.1 to 10.]: %s\n", optarg);
1740                 usage(FALSE);
1741             }
1742             break;
1743 #if HAVE_USE_DEFAULT_COLORS
1744         case 'd':
1745             opt_d = TRUE;
1746             break;
1747 #endif
1748         case 'L':
1749             debugging = TRUE;
1750             break;
1751         case 'l':
1752             if ((logfp = fopen(optarg, "a")) == 0)
1753                 failed(optarg);
1754             break;
1755         case 'p':
1756             palette_path = optarg;
1757             break;
1758         case 'q':
1759             quiet = TRUE;
1760             break;
1761         case 'r':
1762             rgb_path = optarg;
1763             break;
1764         case 's':
1765             slow_time = atoi(optarg);
1766             break;
1767 #if USE_EXTENDED_COLORS
1768         case 'x':
1769             {
1770                 char *s = optarg;
1771                 while (*s) {
1772                     switch (*s++) {
1773                     case 'p':
1774                         use_extended_pairs = TRUE;
1775                         break;
1776                     case 'c':
1777                         use_extended_colors = TRUE;
1778                         break;
1779                     default:
1780                         usage(FALSE);
1781                         break;
1782                     }
1783                 }
1784             }
1785             break;
1786 #endif
1787         case OPTS_VERSION:
1788             show_version(argv);
1789             ExitProgram(EXIT_SUCCESS);
1790         default:
1791             usage(ch == OPTS_USAGE);
1792             /* NOTREACHED */
1793         }
1794     }
1795
1796     if (optind < argc) {
1797         char **rgb_data = read_file(rgb_path);
1798         int n;
1799
1800         if (rgb_data)
1801             rgb_table = parse_rgb(rgb_data);
1802
1803         init_display(palette_path, opt_d);
1804         if (optind >= argc)
1805             giveup("expected at least one image filename");
1806
1807         for (n = optind; n < argc; ++n) {
1808             PICS_HEAD *pics;
1809             char **data = read_file(argv[n]);
1810
1811             if (data == 0) {
1812                 warning("cannot read \"%s\"", argv[n]);
1813                 continue;
1814             }
1815             if ((pics = read_picture(argv[n], data)) != 0) {
1816                 if (in_curses) {
1817                     show_picture(pics);
1818                 } else {
1819                     dump_picture(pics);
1820                 }
1821                 report_colors(pics);
1822                 dispose_c_values();
1823                 free_data(data);
1824                 free_pics_head(pics);
1825             }
1826         }
1827         free_data(rgb_data);
1828         free(rgb_table);
1829         free(all_colors);
1830     } else {
1831         usage(FALSE);
1832     }
1833
1834     cleanup(EXIT_SUCCESS);
1835 }