• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
logmsg(const char * fmt,...)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
logmsg2(const char * fmt,...)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
close_log(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
cleanup(int code)182 cleanup(int code)
183 {
184     pause_curses();
185     close_log();
186     ExitProgram(code);
187     /* NOTREACHED */
188 }
189 
190 static void
failed(const char * msg)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
warning(const char * fmt,...)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
free_data(char ** data)220 free_data(char **data)
221 {
222     if (data != 0) {
223 	free(data[0]);
224 	free(data);
225     }
226 }
227 
228 static PICS_HEAD *
free_pics_head(PICS_HEAD * pics)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
begin_c_values(int size)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
compare_c_values(const void * p,const void * q)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
check_c_values(int ln)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
gather_c_values(int fg)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
finish_c_values(PICS_HEAD * head)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
dispose_c_values(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
is_file(const char * filename,struct stat * sb)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 **
read_file(const char * filename)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
usage(int ok)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
giveup(const char * fmt,...)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 **
read_palette(const char * filename)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
init_palette(const char * palette_file)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
map_color(int value)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
bytes_of(int value)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 *
skip_s(char * s)761 skip_s(char *s)
762 {
763     while (isspace(UChar(*s)))
764 	s++;
765     return s;
766 }
767 
768 static const char *
skip_cs(const char * s)769 skip_cs(const char *s)
770 {
771     while (isspace(UChar(*s)))
772 	s++;
773     return s;
774 }
775 
776 static char *
skip_word(char * s)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
match_c(const char * source,const char * pattern,...)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
match_colors(const char * source,int cpp,char * arg1,char * arg2,char * arg3)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 *
parse_rgb(char ** data)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
CaselessCmp(const char * a,const char * b)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 *
lookup_rgb(const char * 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 *
parse_xbm(char ** data)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 *
parse_xpm(char ** data)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 *
parse_img(const char * filename)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 *
read_picture(const char * filename,char ** data)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
dump_picture(PICS_HEAD * pics)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
init_display(const char * palette_path,int opt_d)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
show_picture(PICS_HEAD * pics)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
compare_fg_counts(const void * a,const void * b)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
report_colors(PICS_HEAD * pics)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* */
VERSION_COMMON()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 }
1836