]> ncurses.scripts.mit.edu Git - ncurses.git/blob - test/picsmap.c
ncurses 6.0 - patch 20170812
[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.90 2017/08/12 17:21:48 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 improve use of rgb-names using tsearch.
37  * TODO when not using tsearch, dispense with intermediate index
38  * TODO count/report uses of each color, giving percentiles.
39  *
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 <stdarg.h>
54 #include <sys/types.h>
55 #include <sys/stat.h>
56
57 #if HAVE_TSEARCH
58 #include <search.h>
59 #endif
60
61 #undef CUR                      /* use only the curses interface */
62
63 #define  L_BLOCK '['
64 #define  R_BLOCK ']'
65
66 #define  L_CURLY '{'
67 #define  R_CURLY '}'
68
69 #define okCOLOR(n)      ((n) >= 0 && (n) < COLORS)
70 #define okRGB(n)        ((n) >= 0 && (n) <= 1000)
71 #define Scaled256(n)    (NCURSES_COLOR_T) (int)(((n) * 1000.0) / 256)
72 #define ScaledColor(n)  (NCURSES_COLOR_T) (int)(((n) * 1000.0) / scale)
73
74 #define RGB_PATH "/etc/X11/rgb.txt"
75
76 typedef int NUM_COLOR;
77
78 typedef struct {
79     char ch;                    /* nominal character to display */
80     NUM_COLOR fg;               /* foreground color */
81 } PICS_CELL;
82
83 typedef struct {
84     NUM_COLOR fgcol;
85     unsigned short which;
86     unsigned short count;
87 } FG_NODE;
88
89 typedef struct {
90     char *name;
91     short high;
92     short wide;
93     int colors;
94     FG_NODE **fgcol;
95     PICS_CELL *cells;
96 } PICS_HEAD;
97
98 typedef struct {
99     const char *name;
100     int value;
101 } RGB_NAME;
102
103 typedef struct {
104     short red;
105     short green;
106     short blue;
107 } RGB_DATA;
108
109 typedef struct {
110     size_t file;
111     size_t name;
112     size_t list;
113     size_t data;
114     size_t head;
115     size_t pair;
116     size_t cell;
117 } HOW_MUCH;
118
119 #define stop_curses() if (in_curses) endwin()
120
121 #define debugmsg if (debugging) logmsg
122 #define debugmsg2 if (debugging) logmsg2
123
124 static void cleanup(int) GCC_NORETURN;
125 static void giveup(const char *fmt,...) GCC_PRINTFLIKE(1, 2);
126 static void logmsg(const char *fmt,...) GCC_PRINTFLIKE(1, 2);
127 static void logmsg2(const char *fmt,...) GCC_PRINTFLIKE(1, 2);
128 static void warning(const char *fmt,...) GCC_PRINTFLIKE(1, 2);
129
130 static FILE *logfp = 0;
131 static bool in_curses = FALSE;
132 static bool debugging = FALSE;
133 static bool quiet = FALSE;
134 static int slow_time = -1;
135 static RGB_NAME *rgb_table;
136 static RGB_DATA *all_colors;
137 static HOW_MUCH how_much;
138
139 static int reading_last;
140 static int reading_size;
141 static FG_NODE **reading_ncols;
142
143 #if HAVE_TSEARCH
144 static void *reading_ntree;
145 #endif
146
147 #if HAVE_ALLOC_PAIR && HAVE_INIT_EXTENDED_COLOR
148 #define USE_EXTENDED_COLORS 1
149 static bool use_extended_pairs = FALSE;
150 static bool use_extended_colors = FALSE;
151 #else
152 #define USE_EXTENDED_COLORS 0
153 #endif
154
155 static void
156 logmsg(const char *fmt,...)
157 {
158     if (logfp != 0) {
159         va_list ap;
160         va_start(ap, fmt);
161         vfprintf(logfp, fmt, ap);
162         va_end(ap);
163         fputc('\n', logfp);
164         fflush(logfp);
165     }
166 }
167
168 static void
169 logmsg2(const char *fmt,...)
170 {
171     if (logfp != 0) {
172         va_list ap;
173         va_start(ap, fmt);
174         vfprintf(logfp, fmt, ap);
175         va_end(ap);
176         fflush(logfp);
177     }
178 }
179
180 static void
181 close_log(void)
182 {
183     if (logfp != 0) {
184         logmsg("Allocations:");
185         logmsg("%8ld file", (long) how_much.file);
186         logmsg("%8ld name", (long) how_much.name);
187         logmsg("%8ld list", (long) how_much.list);
188         logmsg("%8ld data", (long) how_much.data);
189         logmsg("%8ld head", (long) how_much.head);
190         logmsg("%8ld pair", (long) how_much.pair);
191         logmsg("%8ld cell", (long) how_much.cell);
192         logmsg("%8ld window", LINES * COLS * (long) sizeof(NCURSES_CH_T));
193         fclose(logfp);
194         logfp = 0;
195     }
196 }
197
198 static void
199 cleanup(int code)
200 {
201     stop_curses();
202     close_log();
203     ExitProgram(code);
204     /* NOTREACHED */
205 }
206
207 static void
208 failed(const char *msg)
209 {
210     int save = errno;
211     perror(msg);
212     logmsg("failed with %s", strerror(save));
213     cleanup(EXIT_FAILURE);
214 }
215
216 static void
217 warning(const char *fmt,...)
218 {
219     if (logfp != 0) {
220         va_list ap;
221         va_start(ap, fmt);
222         vfprintf(logfp, fmt, ap);
223         va_end(ap);
224         fputc('\n', logfp);
225         fflush(logfp);
226     } else {
227         va_list ap;
228         va_start(ap, fmt);
229         vfprintf(stderr, fmt, ap);
230         va_end(ap);
231         fputc('\n', stderr);
232         cleanup(EXIT_FAILURE);
233     }
234 }
235
236 static void
237 free_data(char **data)
238 {
239     if (data != 0) {
240         free(data[0]);
241         free(data);
242     }
243 }
244
245 static PICS_HEAD *
246 free_pics_head(PICS_HEAD * pics)
247 {
248     if (pics != 0) {
249 #if !(HAVE_TSEARCH && HAVE_TDESTROY)
250         int n;
251         if (pics->fgcol != 0) {
252             for (n = 0; n < pics->colors; ++n) {
253                 free(pics->fgcol[n]);
254             }
255         }
256 #endif
257         free(pics->fgcol);
258         free(pics->cells);
259         free(pics->name);
260         free(pics);
261         pics = 0;
262     }
263     return pics;
264 }
265
266 static void
267 begin_c_values(int size)
268 {
269     reading_last = 0;
270     reading_size = size;
271     reading_ncols = typeCalloc(FG_NODE *, size + 1);
272     how_much.pair += (sizeof(FG_NODE *) * (size_t) size);
273 }
274
275 #if HAVE_TSEARCH
276 static int
277 compare_c_values(const void *p, const void *q)
278 {
279     const FG_NODE *a = (const FG_NODE *) p;
280     const FG_NODE *b = (const FG_NODE *) q;
281     return (a->fgcol - b->fgcol);
282 }
283
284 #ifdef DEBUG_TSEARCH
285 static void
286 check_c_values(int ln)
287 {
288     static int oops = 5;
289     FG_NODE **ft;
290     int n;
291     if (oops-- <= 0)
292         return;
293     for (n = 0; n < reading_last; ++n) {
294         if (reading_ncols[n] == 0)
295             continue;
296         ft = tfind(reading_ncols[n], &reading_ntree, compare_c_values);
297         if (ft != 0 && *ft != 0) {
298             if ((*ft)->fgcol != reading_ncols[n]->fgcol) {
299                 logmsg("@%d, %d:%d (%d) %p %p fgcol %06X %06X", ln, n,
300                        reading_last - 1,
301                        reading_size,
302                        (*ft), reading_ncols[n],
303                        reading_ncols[n]->fgcol,
304                        (*ft)->fgcol);
305             }
306             if ((*ft)->which != reading_ncols[n]->which) {
307                 logmsg("@%d, %d:%d (%d) %p %p which %d %d", ln, n,
308                        reading_last - 1,
309                        reading_size,
310                        (*ft), reading_ncols[n],
311                        reading_ncols[n]->which,
312                        (*ft)->which);
313             }
314         } else {
315             logmsg("@%d, %d:%d (%d) %p %p null %06X %d", ln, n,
316                    reading_last - 1,
317                    reading_size,
318                    ft, reading_ncols[n],
319                    reading_ncols[n]->fgcol,
320                    reading_ncols[n]->which);
321         }
322     }
323 }
324 #else
325 #define check_c_values(n)       /* nothing */
326 #endif
327 #endif
328
329 #undef MAX
330 #define MAX(a,b) ((a)>(b)?(a):(b))
331
332 static int
333 gather_c_values(int fg)
334 {
335     int found = -1;
336 #if HAVE_TSEARCH
337     FG_NODE **ft;
338     FG_NODE *find = typeMalloc(FG_NODE, 1);
339
340     how_much.pair += sizeof(FG_NODE);
341     find->fgcol = fg;
342     find->which = 0;
343
344     check_c_values(__LINE__);
345     if ((ft = tfind(find, &reading_ntree, compare_c_values)) != 0) {
346         found = (*ft)->which;
347     } else {
348         if (reading_last + 1 >= reading_size) {
349             int more = ((MAX(reading_last, reading_size) + 2) * 3) / 2;
350             FG_NODE **p = typeRealloc(FG_NODE *, more, reading_ncols);
351             if (p == 0)
352                 goto done;
353
354             /* FIXME - this won't reallocate pointers for tsearch */
355             how_much.pair -= (sizeof(FG_NODE *) * (size_t) reading_size);
356             how_much.pair += (sizeof(FG_NODE *) * (size_t) more);
357             reading_size = more;
358             reading_ncols = p;
359             memset(reading_ncols + reading_last, 0,
360                    sizeof(FG_NODE *) * (size_t) (more - reading_last));
361             check_c_values(__LINE__);
362         }
363         reading_ncols[reading_last] = find;
364         find->which = (unsigned short) reading_last++;
365         if ((ft = tsearch(find, &reading_ntree, compare_c_values)) != 0) {
366             found = find->which;
367             check_c_values(__LINE__);
368         }
369     }
370 #else
371     int n;
372
373     for (n = 0; n < reading_last; ++n) {
374         if (reading_ncols[n]->fgcol == fg) {
375             found = n;
376             break;
377         }
378     }
379     if (found < 0) {
380         FG_NODE *node = typeMalloc(FG_NODE, 1);
381         how_much.pair += sizeof(FG_NODE);
382         if (reading_last + 2 >= reading_size) {
383             int more = ((reading_last + 2) * 3) / 2;
384             FG_NODE **p = typeRealloc(FG_NODE *, more, reading_ncols);
385             if (p == 0)
386                 goto done;
387
388             how_much.pair -= (sizeof(FG_NODE *) * reading_size);
389             how_much.pair += (sizeof(FG_NODE *) * more);
390             reading_size = more;
391             reading_ncols = p;
392             memset(reading_ncols + reading_last, 0,
393                    sizeof(FG_NODE) * (more - reading_last));
394         }
395         node->fgcol = fg;
396         node->which = reading_last;
397         reading_ncols[reading_last] = node;
398         found = reading_last++;
399     }
400 #endif
401   done:
402     return found;
403 }
404
405 static void
406 finish_c_values(PICS_HEAD * head)
407 {
408     head->colors = reading_last;
409     head->fgcol = reading_ncols;
410
411     reading_last = 0;
412     reading_size = 0;
413     reading_ncols = 0;
414 }
415
416 static void
417 dispose_c_values(void)
418 {
419     int n;
420 #if HAVE_TSEARCH
421     if (reading_ntree != 0) {
422 #if HAVE_TDESTROY
423         tdestroy(reading_ntree, free);
424 #else
425         for (n = 0; n < reading_last; ++n) {
426             tdelete(reading_ncols[n], &reading_ntree, compare_c_values);
427         }
428 #endif
429         reading_ntree = 0;
430     }
431 #endif
432     if (reading_ncols != 0) {
433         for (n = 0; n < reading_last; ++n) {
434             free(reading_ncols[n]);
435         }
436         free(reading_ncols);
437         reading_ncols = 0;
438     }
439     reading_last = 0;
440     reading_size = 0;
441 }
442
443 static int
444 is_file(const char *filename, struct stat *sb)
445 {
446     int result = 0;
447     if (stat(filename, sb) == 0
448         && (sb->st_mode & S_IFMT) == S_IFREG
449         && sb->st_size != 0) {
450         result = 1;
451     }
452     debugmsg("is_file(%s) %d", filename, result);
453     return result;
454 }
455
456 /*
457  * Simplify reading xbm/xpm files by first making an array of lines.  Blank
458  * lines are filtered out.
459  */
460 static char **
461 read_file(const char *filename)
462 {
463     char **result = 0;
464     struct stat sb;
465
466     if (!quiet) {
467         stop_curses();
468         printf("** %s\n", filename);
469     }
470
471     if (is_file(filename, &sb)) {
472         size_t size = (size_t) sb.st_size;
473         char *blob = typeMalloc(char, size + 1);
474         bool had_line = TRUE;
475         bool binary = FALSE;
476         unsigned j;
477         unsigned k = 0;
478
479         result = typeCalloc(char *, size + 1);
480         how_much.file += ((size + 1) * 2);
481
482         if (blob != 0 && result != 0) {
483             FILE *fp = fopen(filename, "r");
484             if (fp != 0) {
485                 logmsg("opened %s", filename);
486                 if (fread(blob, sizeof(char), size, fp) == size) {
487                     for (j = 0; (size_t) j < size; ++j) {
488                         if (blob[j] == '\0' ||
489                             (UChar(blob[j]) < 32 &&
490                              !isspace(blob[j])) ||
491                             (UChar(blob[j]) >= 128 && UChar(blob[j]) < 160))
492                             binary = TRUE;
493                         if (blob[j] == '\n') {
494                             blob[j] = '\0';
495                             if (k && !binary) {
496                                 debugmsg2("[%5d] %s\n", k, result[k - 1]);
497                             }
498                             had_line = TRUE;
499                         } else if (had_line) {
500                             had_line = FALSE;
501                             result[k++] = blob + j;
502                         }
503                     }
504                     result[k] = 0;
505                     if (k && !binary) {
506                         debugmsg2("[%5d] %s\n", k, result[k - 1]);
507                     }
508                 }
509                 fclose(fp);
510             } else {
511                 logmsg("cannot open %s", filename);
512             }
513         }
514         if (k == 0) {
515             debugmsg("...file is empty");
516             free(blob);
517             free(result);
518             result = 0;
519         } else if (binary) {
520             debugmsg("...file is non-text");
521         }
522     }
523     return result;
524 }
525
526 static void
527 usage(void)
528 {
529     static const char *msg[] =
530     {
531         "Usage: picsmap [options] [imagefile [...]]"
532         ,"Read/display one or more xbm/xpm files (possibly use \"convert\")"
533         ,""
534         ,"Options:"
535         ,"  -d           add debugging information to logfile"
536         ,"  -l logfile   write informational messages to logfile"
537         ,"  -p palette   color-palette file (default \"$TERM.dat\")"
538         ,"  -q           less verbose"
539         ,"  -r rgb-path  xpm uses X rgb color-names (default \"" RGB_PATH "\")"
540         ,"  -s SECS      pause for SECS seconds after display vs getch"
541 #if USE_EXTENDED_COLORS
542         ,"  -x [pc]      use extension (p=extended-pairs, c=extended-colors)"
543         ,"               Either/both extension may be given"
544 #endif
545     };
546     size_t n;
547
548     stop_curses();
549
550     fflush(stdout);
551     for (n = 0; n < SIZEOF(msg); n++)
552         fprintf(stderr, "%s\n", msg[n]);
553     cleanup(EXIT_FAILURE);
554 }
555
556 static void
557 giveup(const char *fmt,...)
558 {
559     va_list ap;
560
561     stop_curses();
562     fflush(stdout);
563
564     va_start(ap, fmt);
565     vfprintf(stderr, fmt, ap);
566     fputc('\n', stderr);
567     va_end(ap);
568
569     if (logfp) {
570         va_start(ap, fmt);
571         vfprintf(logfp, fmt, ap);
572         fputc('\n', logfp);
573         va_end(ap);
574         fflush(logfp);
575     }
576
577     usage();
578 }
579
580 /*
581  * Palette files are named for $TERM values.  However, there are fewer palette
582  * files than $TERM's.  Although there are known problems (some cannot even get
583  * black and white correct), for the purpose of comparison, pretending that
584  * those map into "xterm" is useful.
585  */
586 static char **
587 read_palette(const char *filename)
588 {
589     static const char *data_dir = DATA_DIR;
590     char **result = 0;
591     char *full_name = malloc(strlen(data_dir) + 20 + strlen(filename));
592     char *s;
593     struct stat sb;
594
595     if (full_name != 0) {
596         int tries;
597         for (tries = 0; tries < 8; ++tries) {
598
599             *(s = full_name) = '\0';
600             if (tries & 1) {
601                 if (strchr(filename, '/') == 0) {
602                     sprintf(full_name, "%s/", data_dir);
603                 } else {
604                     continue;
605                 }
606             }
607             s += strlen(s);
608
609             strcpy(s, filename);
610             if (tries & 4) {
611                 char *t = s;
612                 int num;
613                 char chr;
614                 int found = 0;
615                 while (*t != '\0') {
616                     if (*t == '-') {
617                         if (sscanf(t, "-%d%c", &num, &chr) == 2 &&
618                             chr == 'c' &&
619                             !strncmp(strchr(t, chr), "color", 5)) {
620                             found = 1;
621                         }
622                         break;
623                     }
624                     ++t;
625                 }
626                 if (found && (t != s)
627                     && strncmp(s, "xterm", (size_t) (t - s))) {
628                     sprintf(s, "xterm%s", filename + (t - s));
629                 } else {
630                     continue;
631                 }
632             }
633             s += strlen(s);
634
635             if (tries & 2) {
636                 int len = (int) strlen(filename);
637                 if (len <= 4 || strcmp(filename + len - 4, ".dat")) {
638                     strcpy(s, ".dat");
639                 } else {
640                     continue;
641                 }
642             }
643             if (is_file(full_name, &sb))
644                 goto ok;
645         }
646         goto failed;
647       ok:
648         result = read_file(full_name);
649       failed:
650         free(full_name);
651     }
652     return result;
653 }
654
655 static void
656 init_palette(const char *palette_file)
657 {
658     if (palette_file != 0) {
659         char **data = read_palette(palette_file);
660         int cp;
661
662         all_colors = typeMalloc(RGB_DATA, (unsigned) COLORS);
663         how_much.data += (sizeof(RGB_DATA) * (unsigned) COLORS);
664
665         for (cp = 0; cp < COLORS; ++cp) {
666             color_content((short) cp,
667                           &all_colors[cp].red,
668                           &all_colors[cp].green,
669                           &all_colors[cp].blue);
670         }
671         if (data != 0) {
672             int n;
673             int red, green, blue;
674             int scale = 1000;
675             int c;
676             for (n = 0; data[n] != 0; ++n) {
677                 if (sscanf(data[n], "scale:%d", &c) == 1) {
678                     scale = c;
679                 } else if (sscanf(data[n], "%d:%d %d %d",
680                                   &c,
681                                   &red,
682                                   &green,
683                                   &blue) == 4
684                            && okCOLOR(c)
685                            && okRGB(red)
686                            && okRGB(green)
687                            && okRGB(blue)) {
688                     /* *INDENT-EQLS* */
689                     all_colors[c].red   = ScaledColor(red);
690                     all_colors[c].green = ScaledColor(green);
691                     all_colors[c].blue  = ScaledColor(blue);
692                 }
693             }
694         }
695         free_data(data);
696         /* *INDENT-EQLS* */
697     } else if (COLORS > 1) {
698         int power2 = 1;
699         int shift = 0;
700
701         while (power2 < COLORS) {
702             ++shift;
703             power2 <<= 1;
704         }
705
706         if ((power2 != COLORS) || ((shift % 3) != 0)) {
707             if (all_colors == 0) {
708                 init_palette(getenv("TERM"));
709             }
710             if (all_colors == 0) {
711                 giveup("With %d colors, you need a palette-file", COLORS);
712             }
713         }
714     }
715 }
716
717 /*
718  * Map the 24-bit RGB value to a color index if using a palette, otherwise to a
719  * direct color value.
720  */
721 static int
722 map_color(int value)
723 {
724     int result = value;
725
726     if (result < 0) {
727         result = -1;
728     } else {
729         /* *INDENT-EQLS* */
730         int red   = (value & 0xff0000) >> 16;
731         int green = (value & 0x00ff00) >> 8;
732         int blue  = (value & 0x0000ff) >> 0;
733
734         if (all_colors != 0) {
735 #define Diff2(n,m) ((m) - all_colors[n].m) * ((m) - all_colors[n].m)
736 #define Diff2S(n) Diff2(n,red) + Diff2(n,green) + Diff2(n,blue)
737             int d2 = Diff2S(0);
738             int n;
739
740             /* *INDENT-EQLS* */
741             red   = Scaled256(red);
742             green = Scaled256(green);
743             blue  = Scaled256(blue);
744
745             for (result = 0, n = 1; n < COLORS; ++n) {
746                 int d = Diff2(n, red) + Diff2(n, green) + Diff2(n, blue);
747                 if (d < d2) {
748                     d2 = d;
749                     result = n;
750                 }
751             }
752         } else {                /* direct color */
753             int power2 = 1;
754             int shifts = 8;
755
756             while (power2 < COLORS) {
757                 power2 <<= 3;
758                 shifts--;
759             }
760
761             if (shifts > 0) {
762                 /* TODO: round up */
763                 red >>= shifts;
764                 green >>= shifts;
765                 blue >>= shifts;
766                 result = ((red << (2 * (8 - shifts)))
767                           + (green << (8 - shifts))
768                           + blue);
769             }
770         }
771     }
772     return result;
773 }
774
775 static int
776 bytes_of(int value)
777 {
778     if (value & 7) {
779         value |= 7;
780         value++;
781     }
782     return value;
783 }
784
785 static int match_c(const char *, const char *,...) GCC_SCANFLIKE(2,3);
786
787 static char *
788 skip_s(char *s)
789 {
790     while (isspace(UChar(*s)))
791         s++;
792     return s;
793 }
794
795 static const char *
796 skip_cs(const char *s)
797 {
798     while (isspace(UChar(*s)))
799         s++;
800     return s;
801 }
802
803 static char *
804 skip_word(char *s)
805 {
806     s = skip_s(s);
807     while (isgraph(UChar(*s)))
808         s++;
809     return s;
810 }
811
812 static int
813 match_c(const char *source, const char *pattern,...)
814 {
815     int limit = (int) strlen(source);
816     const char *last_s = source + limit;
817     va_list ap;
818     int ch;
819     int *ip;
820     char *cp;
821     long lv;
822
823     va_start(ap, pattern);
824
825     limit = -1;
826     while (*pattern != '\0') {
827         ch = UChar(*pattern++);
828         /* blank in the pattern matches zero-or-more blanks in source */
829         if (isspace(ch)) {
830             source = skip_cs(source);
831             continue;
832         }
833         /* %c, %d, %s are like sscanf except for special treatment of blanks */
834         if (ch == '%' && *pattern != '\0' && strchr("cdnsx", *pattern)) {
835             bool found = FALSE;
836             ch = *pattern++;
837             switch (ch) {
838             case 'c':
839                 cp = va_arg(ap, char *);
840                 do {
841                     *cp++ = *source++;
842                 } while (--limit > 0);
843                 break;
844             case 'd':
845             case 'x':
846                 limit = -1;
847                 ip = va_arg(ap, int *);
848                 lv = strtol(source, &cp, ch == 'd' ? 10 : 16);
849                 if (cp != 0 && cp != source) {
850                     *ip = (int) lv;
851                     source = cp;
852                 } else {
853                     goto finish;
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         char *t;
903         const char *s = skip_cs(source);
904         size_t have = strlen(source);
905
906         if (*s++ == '"' && have > ((size_t) cpp + 2)) {
907             memcpy(arg1, s, (size_t) cpp);
908             s += cpp;
909             while (*s++ == '\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 static RGB_NAME *
979 lookup_rgb(const char *name)
980 {
981     RGB_NAME *result = 0;
982     if (rgb_table != 0) {
983         int n;
984         for (n = 0; rgb_table[n].name != 0; ++n) {
985             if (!strcasecmp(name, rgb_table[n].name)) {
986                 result = &rgb_table[n];
987                 break;
988             }
989         }
990     }
991     return result;
992 }
993
994 static PICS_HEAD *
995 parse_xbm(char **data)
996 {
997     int n;
998     int state = 0;
999     char buf[BUFSIZ];
1000     int num;
1001     char ch;
1002     char *s;
1003     char *t;
1004     PICS_HEAD *result;
1005     size_t which = 0;
1006     size_t cells = 0;
1007
1008     debugmsg("called parse_xbm");
1009
1010     result = typeCalloc(PICS_HEAD, 1);
1011     how_much.head += sizeof(PICS_HEAD);
1012
1013     for (n = 0; data[n] != 0; ++n) {
1014         if (strlen(s = data[n]) >= sizeof(buf) - 1)
1015             continue;
1016         switch (state) {
1017         case 0:
1018         case 1:
1019         case 2:
1020             if (sscanf(s, "#define %s %d%c", buf, &num, &ch) >= 2) {
1021                 if ((t = strstr(buf, "_width")) != 0) {
1022                     state |= 1;
1023                     result->wide = (short) bytes_of(num);
1024                 } else if ((t = strstr(buf, "_height")) != 0) {
1025                     state |= 2;
1026                     result->high = (short) num;
1027                 }
1028                 *t = '\0';
1029                 if (result->name) {
1030                     if (strcmp(result->name, buf)) {
1031                         goto finish;
1032                     }
1033                 } else {
1034                     result->name = strdup(buf);
1035                 }
1036             }
1037             break;
1038         case 3:
1039             if (sscanf(s, "static char %[^_ ]_bits[]%c", buf, &ch) >= 1) {
1040                 if (strcmp(result->name, buf)) {
1041                     goto finish;
1042                 }
1043                 state = 4;
1044                 cells = (size_t) (result->wide * result->high);
1045
1046                 result->cells = typeCalloc(PICS_CELL, cells);
1047                 how_much.cell += (sizeof(PICS_CELL) * cells);
1048
1049                 if ((s = strchr(s, L_CURLY)) == 0)
1050                     break;
1051                 ++s;
1052             } else {
1053                 break;
1054             }
1055         case 4:
1056             while (*s != '\0') {
1057                 while (isspace(UChar(*s))) {
1058                     ++s;
1059                 }
1060                 if (isdigit(UChar(*s))) {
1061                     long value = strtol(s, &t, 0);
1062                     int b;
1063                     if (t != s || value > 255 || value < 0) {
1064                         s = t;
1065                     } else {
1066                         state = -1;
1067                         goto finish;
1068                     }
1069                     /* TODO: which order? */
1070                     for (b = 0; b < 8; ++b) {
1071                         if (((1L << b) & value) != 0) {
1072                             result->cells[which].ch = '*';
1073                             result->cells[which].fg = 1;
1074                         } else {
1075                             result->cells[which].ch = ' ';
1076                             result->cells[which].fg = 0;
1077                         }
1078                         if (++which > cells) {
1079                             state = -1;
1080                             goto finish;
1081                         }
1082                     }
1083                 }
1084                 if (*s == R_CURLY) {
1085                     state = 5;
1086                     goto finish;
1087                 } else if (*s == ',') {
1088                     ++s;
1089                 }
1090             }
1091             break;
1092         default:
1093             break;
1094         }
1095     }
1096   finish:
1097     if (state < 4) {
1098         debugmsg("...state was only %d", state);
1099         if (result) {
1100             result = free_pics_head(result);
1101         }
1102     } else {
1103         begin_c_values(2);
1104         gather_c_values(0);
1105         gather_c_values(0xffffff);
1106         finish_c_values(result);
1107     }
1108     return result;
1109 }
1110
1111 static PICS_HEAD *
1112 parse_xpm(char **data)
1113 {
1114     int state = 0;
1115     PICS_HEAD *result;
1116     RGB_NAME *by_name;
1117     int n;
1118     int cells = 0;
1119     int cpp = 1;                /* chars per pixel */
1120     int num[6];
1121     int found;
1122     int which = 0;
1123     int num_colors = 0;
1124     char ch;
1125     const char *cs;
1126     char *s;
1127     char buf[BUFSIZ];
1128     char arg1[BUFSIZ];
1129     char arg2[BUFSIZ];
1130     char arg3[BUFSIZ];
1131     char **list = 0;
1132
1133     debugmsg("called parse_xpm");
1134
1135     result = typeCalloc(PICS_HEAD, 1);
1136     how_much.head += sizeof(PICS_HEAD);
1137
1138     for (n = 0; data[n] != 0; ++n) {
1139         if (strlen(s = data[n]) >= sizeof(buf) - 1)
1140             continue;
1141         switch (state) {
1142         case 0:
1143             if (match_c(s, " /* XPM */ ")) {
1144                 state = 1;
1145             }
1146             break;
1147         case 1:
1148             if (match_c(s, " static char * %s [] = %c ", arg1, &ch) &&
1149                 ch == L_CURLY) {
1150                 result->name = strdup(arg1);
1151                 state = 2;
1152             }
1153             break;
1154         case 2:
1155             if (match_c(s, " \" %d %d %d %d \" , ",
1156                         num + 0, num + 1, num + 2, num + 3) ||
1157                 match_c(s, " \" %d %d %d %d %d %d \" , ",
1158                         num + 0, num + 1, num + 2, num + 3, num + 4, num + 5)) {
1159                 result->wide = (short) num[0];
1160                 result->high = (short) num[1];
1161                 result->colors = num[2];
1162
1163                 begin_c_values(num[2]);
1164
1165                 cells = (result->wide * result->high);
1166
1167                 result->cells = typeCalloc(PICS_CELL, cells);
1168                 how_much.cell += sizeof(PICS_CELL) * (size_t) cells;
1169
1170                 list = typeCalloc(char *, result->colors);
1171                 how_much.list += sizeof(char *) * (size_t) result->colors;
1172
1173                 cpp = num[3];
1174                 state = 3;
1175             }
1176             break;
1177         case 3:
1178             if (!match_colors(s, cpp, arg1, arg2, arg3)) {
1179                 break;
1180             }
1181             num_colors++;
1182             list[reading_last] = strdup(arg1);
1183             if ((by_name = lookup_rgb(arg3)) != 0) {
1184                 found = gather_c_values(by_name->value);
1185             } else if (*arg3 == '#') {
1186                 char *rgb = arg3 + 1;
1187                 unsigned long value = strtoul(rgb, &s, 16);
1188                 switch ((int) strlen(rgb)) {
1189                 case 6:
1190                     break;
1191                 case 12:
1192                     value = (((value >> 24) & 0xff0000L)
1193                              | ((value >> 16) & 0xff00L)
1194                              | ((value >> 8) & 0xffL));
1195                     break;
1196                 default:
1197                     warning("unexpected rgb value %s", rgb);
1198                     break;
1199                 }
1200                 found = gather_c_values((int) value);
1201             } else {
1202                 found = gather_c_values(0);     /* actually an error */
1203             }
1204             debugmsg("  [%d:%d] %06X", num_colors, result->colors,
1205                      reading_ncols[(found >= 0) ? found : 0]->fgcol);
1206             if (num_colors >= result->colors) {
1207                 finish_c_values(result);
1208                 state = 4;
1209             }
1210             break;
1211         case 4:
1212             if (*(cs = skip_cs(s)) == '"') {
1213                 ++cs;
1214                 while (*cs != '\0' && *cs != '"') {
1215                     int c;
1216
1217                     /* FIXME - factor out */
1218                     for (c = 0; c < result->colors; ++c) {
1219                         if (!strncmp(cs, list[c], (size_t) cpp)) {
1220                             result->cells[which].ch = list[c][0];
1221                             result->cells[which].fg = c;
1222                             break;
1223                         }
1224                     }
1225
1226                     if (result->cells[which].ch == 0) {
1227                         result->cells[which].ch = '?';
1228                         result->cells[which].fg = 0;
1229                     }
1230
1231                     if (++which >= cells) {
1232                         state = 5;
1233                         break;
1234                     }
1235                     for (c = cpp; c > 0; --c, ++cs) ;
1236                 }
1237             }
1238             break;
1239         }
1240     }
1241
1242     if (result && list) {
1243         for (n = 0; n < result->colors; ++n)
1244             free(list[n]);
1245         free(list);
1246     }
1247
1248     if (state < 5) {
1249         debugmsg("...state was only %d", state);
1250         result = free_pics_head(result);
1251     }
1252
1253     if (result) {
1254         debugmsg("...allocated %d colors", result->colors);
1255     }
1256
1257     return result;
1258 }
1259
1260 /*
1261  * The obscurely-named "convert" is provided by ImageMagick
1262  */
1263 static PICS_HEAD *
1264 parse_img(const char *filename)
1265 {
1266     char *cmd = malloc(strlen(filename) + 256);
1267     FILE *pp;
1268     char buffer[BUFSIZ];
1269     char dummy[BUFSIZ];
1270     bool okay = TRUE;
1271     PICS_HEAD *result;
1272     int pic_x = 0;
1273     int pic_y = 0;
1274     int width = in_curses ? COLS : 80;
1275
1276     sprintf(cmd, "identify \"%s\"", filename);
1277     if (quiet)
1278         strcat(cmd, " 2>/dev/null");
1279
1280     logmsg("...opening pipe to %s", cmd);
1281
1282     result = typeCalloc(PICS_HEAD, 1);
1283     how_much.head += sizeof(PICS_HEAD);
1284
1285     if ((pp = popen(cmd, "r")) != 0) {
1286         if (fgets(buffer, sizeof(buffer), pp) != 0) {
1287             size_t n = strlen(filename);
1288             debugmsg2("...read %s", buffer);
1289             if (strlen(buffer) > n &&
1290                 !strncmp(buffer, filename, n) &&
1291                 isspace(UChar(buffer[n])) &&
1292                 sscanf(skip_word(buffer + n), " %dx%d ", &pic_x, &pic_y) == 2) {
1293                 /* distort image to make it show normally on terminal */
1294                 pic_x = (166 * pic_x) / 100;
1295             } else {
1296                 pic_x = pic_y = 0;
1297             }
1298         }
1299         pclose(pp);
1300     }
1301     if (pic_x <= 0 || pic_y <= 0)
1302         goto finish;
1303
1304     sprintf(cmd, "convert " "-resize %dx%d\\! " "-thumbnail %dx \"%s\" "
1305             "-define txt:compliance=SVG txt:-",
1306             pic_x, pic_y, width, filename);
1307     if (quiet)
1308         strcat(cmd, " 2>/dev/null");
1309
1310     logmsg("...opening pipe to %s", cmd);
1311     if ((pp = popen(cmd, "r")) != 0) {
1312         int count = 0;
1313         int col = 0;
1314         int row = 0;
1315         int len = 0;
1316         while (fgets(buffer, sizeof(buffer), pp) != 0) {
1317             debugmsg2("[%5d] %s", count + 1, buffer);
1318             if (strlen(buffer) > 160) {         /* 80 columns would be enough */
1319                 okay = FALSE;
1320                 break;
1321             }
1322             if (count++ == 0) {
1323                 if (match_c(buffer,
1324                             "# ImageMagick pixel enumeration: %d,%d,%d,%s ",
1325                             &col, &row, &len, dummy)) {
1326                     result->name = strdup(filename);
1327                     result->wide = (short) col;
1328                     result->high = (short) row;
1329
1330                     begin_c_values(256);
1331
1332                     result->cells = typeCalloc(PICS_CELL, (size_t) (col * row));
1333                     how_much.cell += (sizeof(PICS_CELL) * (size_t) (col * row));
1334                 } else {
1335                     okay = FALSE;
1336                     break;
1337                 }
1338             } else {
1339                 /* subsequent lines begin "col,row: (r,g,b,a) #RGB" */
1340                 int r, g, b, nocolor;
1341                 unsigned check;
1342                 int which, c;
1343                 char *t;
1344                 char *s = t = strchr(buffer, '#');
1345                 if (s != 0) {
1346                     /* after the "#RGB", there are differences - just ignore */
1347                     while (*s != '\0' && !isspace(UChar(*s)))
1348                         ++s;
1349                     *++s = '\0';
1350                 }
1351                 if (match_c(buffer,
1352                             "%d,%d: (%d,%d,%d,%d) #%x ",
1353                             &col, &row,
1354                             &r, &g, &b, &nocolor,
1355                             &check)) {
1356                     if ((s - t) > 8)    /* 6 hex digits vs 8 */
1357                         check /= 256;
1358                     if (r > 255 ||
1359                         g > 255 ||
1360                         b > 255 ||
1361                         check != (unsigned) ((r << 16) | (g << 8) | b)) {
1362                         okay = FALSE;
1363                         break;
1364                     }
1365                     c = gather_c_values((int) check);
1366                     which = col + (row * result->wide);
1367                     result->cells[which].ch = ((in_curses ||
1368                                                 check == 0xffffff)
1369                                                ? ' '
1370                                                : '#');
1371                     result->cells[which].fg = ((c >= 0 && c < reading_last)
1372                                                ? c
1373                                                : -1);
1374                 } else {
1375                     okay = FALSE;
1376                     break;
1377                 }
1378             }
1379         }
1380         finish_c_values(result);
1381         pclose(pp);
1382         if (okay) {
1383             /* FIXME - is this trimming needed? */
1384             for (len = result->colors; len > 3; len--) {
1385                 if (result->fgcol[len - 1] == 0) {
1386                     result->colors = len - 1;
1387                 } else {
1388                     break;
1389                 }
1390             }
1391         }
1392     }
1393   finish:
1394     free(cmd);
1395
1396     if (!okay) {
1397         result = free_pics_head(result);
1398     }
1399
1400     return result;
1401 }
1402
1403 static PICS_HEAD *
1404 read_picture(const char *filename, char **data)
1405 {
1406     PICS_HEAD *pics;
1407     if ((pics = parse_xbm(data)) == 0) {
1408         dispose_c_values();
1409         if ((pics = parse_xpm(data)) == 0) {
1410             dispose_c_values();
1411             if ((pics = parse_img(filename)) == 0) {
1412                 dispose_c_values();
1413                 free_data(data);
1414                 warning("unexpected file-format for \"%s\"", filename);
1415             } else if (pics->high == 0 || pics->wide == 0) {
1416                 dispose_c_values();
1417                 free_data(data);
1418                 pics = free_pics_head(pics);
1419                 warning("no picture found in \"%s\"", filename);
1420             }
1421         }
1422     }
1423     return pics;
1424 }
1425
1426 #define fg_color(pics,n) (pics->fgcol[n]->fgcol)
1427
1428 static void
1429 dump_picture(PICS_HEAD * pics)
1430 {
1431     int y, x;
1432
1433     printf("Name %s\n", pics->name);
1434     printf("Size %dx%d\n", pics->high, pics->wide);
1435     printf("Color\n");
1436     for (y = 0; y < pics->colors; ++y) {
1437         if (fg_color(pics, y) < 0) {
1438             printf(" %3d: %d\n", y, fg_color(pics, y));
1439         } else {
1440             printf(" %3d: #%06x\n", y, fg_color(pics, y));
1441         }
1442     }
1443     for (y = 0; y < pics->high; ++y) {
1444         for (x = 0; x < pics->wide; ++x) {
1445             putchar(pics->cells[y * pics->wide + x].ch);
1446         }
1447         putchar('\n');
1448     }
1449 }
1450
1451 static void
1452 show_picture(PICS_HEAD * pics)
1453 {
1454     int y, x;
1455     int n;
1456     int my_pair, my_color;
1457
1458     debugmsg("called show_picture");
1459 #if USE_EXTENDED_COLORS
1460     reset_color_pairs();
1461 #else
1462     wclear(curscr);
1463     clear();
1464 #endif
1465     if (has_colors()) {
1466         logmsg("...using %d colors", pics->colors);
1467         for (n = 0; n < pics->colors; ++n) {
1468             my_pair = (n + 1);
1469             my_color = map_color(fg_color(pics, n));
1470 #if USE_EXTENDED_COLORS
1471             if (use_extended_pairs) {
1472                 init_extended_pair(my_pair, my_color, my_color);
1473             } else
1474 #endif
1475             {
1476                 my_pair &= 0x7fff;
1477                 my_color &= 0x7fff;
1478                 init_pair((short) my_pair, (short) my_color, (short) my_color);
1479             }
1480         }
1481         attrset(COLOR_PAIR(1));
1482         erase();
1483     }
1484     for (y = 0; y < pics->high; ++y) {
1485         if (y >= LINES)
1486             break;
1487         move(y, 0);
1488         for (x = 0; x < pics->wide; ++x) {
1489             if (x >= COLS)
1490                 break;
1491             n = (y * pics->wide + x);
1492             my_pair = pics->cells[n].fg + 1;
1493 #if USE_EXTENDED_COLORS
1494             if (use_extended_pairs) {
1495                 cchar_t temp;
1496                 wchar_t wch[2];
1497                 wch[0] = (wchar_t) pics->cells[n].ch;
1498                 wch[1] = 0;
1499                 setcchar(&temp, wch, A_NORMAL, (short) my_pair, &my_pair);
1500                 add_wch(&temp);
1501             } else
1502 #endif
1503             {
1504                 attrset(COLOR_PAIR(my_pair));
1505                 addch((chtype) pics->cells[n].ch);
1506             }
1507         }
1508     }
1509     if (slow_time >= 0) {
1510         refresh();
1511         if (slow_time > 0) {
1512 #ifdef NCURSES_VERSION
1513             napms(1000 * slow_time);
1514 #else
1515             sleep((unsigned) slow_time);
1516 #endif
1517         }
1518     } else {
1519         mvgetch(0, 0);
1520     }
1521     if (!quiet)
1522         endwin();
1523 }
1524
1525 int
1526 main(int argc, char *argv[])
1527 {
1528     int n;
1529     const char *palette_path = 0;
1530     const char *rgb_path = "/etc/X11/rgb.txt";
1531
1532     while ((n = getopt(argc, argv, "dl:p:qr:s:x:")) != -1) {
1533         switch (n) {
1534         case 'd':
1535             debugging = TRUE;
1536             break;
1537         case 'l':
1538             if ((logfp = fopen(optarg, "a")) == 0)
1539                 failed(optarg);
1540             break;
1541         case 'p':
1542             palette_path = optarg;
1543             break;
1544         case 'q':
1545             quiet = TRUE;
1546             break;
1547         case 'r':
1548             rgb_path = optarg;
1549             break;
1550         case 's':
1551             slow_time = atoi(optarg);
1552             break;
1553 #if USE_EXTENDED_COLORS
1554         case 'x':
1555             {
1556                 char *s = optarg;
1557                 while (*s) {
1558                     switch (*s++) {
1559                     case 'p':
1560                         use_extended_pairs = TRUE;
1561                         break;
1562                     case 'c':
1563                         use_extended_colors = TRUE;
1564                         break;
1565                     default:
1566                         usage();
1567                         break;
1568                     }
1569                 }
1570             }
1571             break;
1572 #endif
1573         default:
1574             usage();
1575             break;
1576         }
1577     }
1578
1579     if (optind < argc) {
1580         char **rgb_data = read_file(rgb_path);
1581
1582         if (rgb_data)
1583             rgb_table = parse_rgb(rgb_data);
1584
1585         if (isatty(fileno(stdout))) {
1586             in_curses = TRUE;
1587             initscr();
1588             cbreak();
1589             noecho();
1590             curs_set(0);
1591             if (has_colors()) {
1592                 start_color();
1593                 init_palette(palette_path);
1594             }
1595             scrollok(stdscr, FALSE);
1596             endwin();
1597         }
1598         if (optind >= argc)
1599             giveup("expected at least one image filename");
1600
1601         for (n = optind; n < argc; ++n) {
1602             PICS_HEAD *pics;
1603             char **data = read_file(argv[n]);
1604
1605             if (data == 0) {
1606                 warning("cannot read \"%s\"", argv[n]);
1607                 continue;
1608             }
1609             if ((pics = read_picture(argv[n], data)) != 0) {
1610                 if (in_curses) {
1611                     show_picture(pics);
1612                 } else {
1613                     dump_picture(pics);
1614                 }
1615                 dispose_c_values();
1616                 free_data(data);
1617                 free_pics_head(pics);
1618             }
1619         }
1620         free_data(rgb_data);
1621         free(rgb_table);
1622         free(all_colors);
1623     } else {
1624         usage();
1625     }
1626
1627     cleanup(EXIT_SUCCESS);
1628 }