ncurses 6.0 - patch 20170520
[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.17 2017/05/21 00:22:22 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 handle hex color-codes for xpm other than 3-bytes
37  * TODO read rgb.txt to handle xpm color names other than "None"
38  * TODO read "convert" via pipe (from ImageMagick)
39  * TODO write cells/second to stderr (or log)
40  * TODO write picture left-to-right/top-to-bottom
41  * TODO write picture randomly
42  * TODO add one-shot option vs repeat-count before exiting
43  * TODO add option for assumed palette of terminal
44  * TODO add option for init_color
45  * TODO use pad to allow pictures larger than screen
46  */
47 #include <test.priv.h>
48
49 #include <sys/types.h>
50 #include <sys/stat.h>
51
52 #define  L_CURL '{'
53 #define  R_CURL '}'
54
55 typedef struct {
56     int ch;                     /* nominal character to display */
57     int fg;                     /* foreground color */
58 } PICS_CELL;
59
60 typedef struct {
61     int fg;
62     int bg;
63 } PICS_PAIR;
64
65 typedef struct {
66     char *name;
67     int high;
68     int wide;
69     int colors;
70     PICS_PAIR *pairs;
71     PICS_CELL *cells;
72 } PICS_HEAD;
73
74 static bool in_curses = FALSE;
75
76 static void
77 free_data(char **data)
78 {
79     free(data[0]);
80     free(data);
81 }
82
83 static void
84 free_pics_head(PICS_HEAD * pics)
85 {
86     free(pics->pairs);
87     free(pics->cells);
88     free(pics);
89 }
90
91 /*
92  * Simplify reading xbm/xpm files by first making an array of lines.  Blank
93  * lines are filtered out.
94  */
95 static char **
96 read_file(const char *filename)
97 {
98     char **result = 0;
99     struct stat sb;
100
101     printf("** %s\n", filename);
102     if (stat(filename, &sb) == 0
103         && (sb.st_mode & S_IFMT) == S_IFREG
104         && sb.st_size != 0) {
105         size_t size = (size_t) sb.st_size;
106         char *blob = typeMalloc(char, size + 1);
107         bool had_line = TRUE;
108         unsigned j;
109         unsigned k = 0;
110
111         result = typeCalloc(char *, size + 1);
112         if (blob != 0 && result != 0) {
113             FILE *fp = fopen(filename, "r");
114             if (fp != 0) {
115                 if (fread(blob, sizeof(char), size, fp) == size) {
116                     for (j = 0; (size_t) j < size; ++j) {
117                         if (blob[j] == '\n') {
118                             blob[j] = '\0';
119                             had_line = TRUE;
120                         } else if (had_line) {
121                             had_line = FALSE;
122                             result[k++] = blob + j;
123                         }
124                     }
125                     result[k] = 0;
126                 }
127                 fclose(fp);
128             }
129         }
130         if (k == 0) {
131             free(blob);
132             free(result);
133             result = 0;
134         }
135     }
136     return result;
137 }
138
139 static void
140 usage(void)
141 {
142     static const char *msg[] =
143     {
144         "Usage: picsmap [xbm-file [...]]"
145     };
146     size_t n;
147
148     fflush(stdout);
149     for (n = 0; n < SIZEOF(msg); n++)
150         fprintf(stderr, "%s\n", msg[n]);
151     ExitProgram(EXIT_FAILURE);
152 }
153
154 static void
155 giveup(const char *fmt,...)
156 {
157     va_list ap;
158     if (in_curses)
159         endwin();
160     fflush(stdout);
161     va_start(ap, fmt);
162     vfprintf(stderr, fmt, ap);
163     fputc('\n', stderr);
164     va_end(ap);
165     usage();
166 }
167
168 static int
169 map_color(int value)
170 {
171     int r = (value & 0xff0000) >> 16;
172     int g = (value & 0x00ff00) >> 8;
173     int b = (value & 0x0000ff) >> 0;
174     /* TODO simple mapping into COLOR_BLACK .. COLOR_WHITE */
175     int result = ((r >= 128) << 2) + ((g >= 128) << 1) + (b >= 128);
176     return result;
177 }
178
179 static int
180 bytes_of(int value)
181 {
182     if (value & 7) {
183         value |= 7;
184         value++;
185     }
186     return value;
187 }
188
189 static int match_c(const char *, const char *,...) GCC_SCANFLIKE(2,3);
190
191 static const char *
192 skip_s(const char *s)
193 {
194     while (isspace(UChar(*s)))
195         s++;
196     return s;
197 }
198
199 static int
200 match_c(const char *source, const char *pattern,...)
201 {
202     const char *last_s = source + strlen(source);
203     va_list ap;
204     int ch;
205     int *ip;
206     char *cp;
207     long lv;
208
209     va_start(ap, pattern);
210
211     while (*pattern != '\0') {
212         ch = UChar(*pattern++);
213         /* blank in the pattern matches zero-or-more blanks in source */
214         if (isspace(ch)) {
215             source = skip_s(source);
216             continue;
217         }
218         /* %c, %d, %s are like sscanf except for special treatment of blanks */
219         if (ch == '%' && *pattern != '\0' && strchr("cds", *pattern)) {
220             bool found = FALSE;
221             ch = *pattern++;
222             switch (ch) {
223             case 'c':
224                 cp = va_arg(ap, char *);
225                 *cp = *source++;
226                 break;
227             case 'd':
228                 ip = va_arg(ap, int *);
229                 lv = strtol(source, &cp, 0);
230                 if (cp != 0 && cp != source) {
231                     *ip = (int) lv;
232                     source = cp;
233                 } else {
234                     goto finish;
235                 }
236                 break;
237             case 's':
238                 cp = va_arg(ap, char *);
239                 while (*source != '\0') {
240                     ch = UChar(*source);
241                     if (isspace(ch)) {
242                         break;
243                     } else if (found && (ch == *skip_s(pattern))) {
244                         break;
245                     } else {
246                         *cp++ = *source++;
247                         found = TRUE;
248                     }
249                 }
250                 *cp = '\0';
251                 break;
252             }
253             continue;
254         }
255         /* other characters are matched literally */
256         if (*source++ != ch) {
257             break;
258         }
259     }
260   finish:
261
262     va_end(ap);
263     if (source > last_s)
264         source = last_s;
265     return (*source || *pattern) ? 0 : 1;
266 }
267
268 static PICS_HEAD *
269 parse_xbm(char **data)
270 {
271     int n;
272     int state = 0;
273     char buf[BUFSIZ];
274     int num;
275     char ch;
276     char *s;
277     char *t;
278     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
279     size_t which = 0;
280     size_t cells = 0;
281
282     for (n = 0; data[n] != 0; ++n) {
283         if (strlen(s = data[n]) >= sizeof(buf) - 1)
284             continue;
285         switch (state) {
286         case 0:
287         case 1:
288         case 2:
289             if (sscanf(s, "#define %s %d%c", buf, &num, &ch) >= 2) {
290                 if ((t = strstr(buf, "_width")) != 0) {
291                     state |= 1;
292                     result->wide = bytes_of(num);
293                 } else if ((t = strstr(buf, "_height")) != 0) {
294                     state |= 2;
295                     result->high = num;
296                 }
297                 *t = '\0';
298                 if (result->name) {
299                     if (strcmp(result->name, buf)) {
300                         goto finish;
301                     }
302                 } else {
303                     result->name = strdup(buf);
304                 }
305             }
306             break;
307         case 3:
308             if (sscanf(s, "static char %[^_ ]_bits[]%c", buf, &ch) >= 1) {
309                 if (strcmp(result->name, buf)) {
310                     goto finish;
311                 }
312                 state = 4;
313                 cells = (size_t) (result->wide * result->high);
314                 result->cells = typeCalloc(PICS_CELL, cells);
315                 if ((s = strchr(s, L_CURL)) == 0)
316                     break;
317                 ++s;
318             } else {
319                 break;
320             }
321         case 4:
322             while (*s != '\0') {
323                 while (isspace(UChar(*s))) {
324                     ++s;
325                 }
326                 if (isdigit(UChar(*s))) {
327                     long value = strtol(s, &t, 0);
328                     int b;
329                     if (t != s || value > 255 || value < 0) {
330                         s = t;
331                     } else {
332                         state = -1;
333                         goto finish;
334                     }
335                     /* TODO: which order? */
336                     for (b = 0; b < 8; ++b) {
337                         if (((1L << b) & value) != 0) {
338                             result->cells[which].ch = '*';
339                             result->cells[which].fg = 1;
340                         } else {
341                             result->cells[which].ch = ' ';
342                             result->cells[which].fg = 0;
343                         }
344                         if (++which > cells) {
345                             state = -1;
346                             goto finish;
347                         }
348                     }
349                 }
350                 if (*s == R_CURL) {
351                     state = 5;
352                     goto finish;
353                 } else if (*s == ',') {
354                     ++s;
355                 }
356             }
357             break;
358         default:
359             break;
360         }
361     }
362   finish:
363     if (state < 4) {
364         if (result) {
365             free(result->pairs);
366             free(result->cells);
367             free(result);
368             result = 0;
369         }
370     } else {
371         result->colors = 2;
372         result->pairs = typeCalloc(PICS_PAIR, 2);
373         result->pairs[1].fg = 0xffffff;
374     }
375     return result;
376 }
377
378 static PICS_HEAD *
379 parse_xpm(char **data)
380 {
381     int state = 0;
382     PICS_HEAD *result = typeCalloc(PICS_HEAD, 1);
383     int n;
384     int cells = 0;
385     int color = 0;
386     int cpp = 1;                /* chars per pixel */
387     int num[6];
388     int which = 0;
389     char ch;
390     const char *cs;
391     char *s;
392     char buf[BUFSIZ];
393     char arg1[BUFSIZ];
394     char arg2[BUFSIZ];
395     char arg3[BUFSIZ];
396     char **list = 0;
397
398     for (n = 0; data[n] != 0; ++n) {
399         if (strlen(s = data[n]) >= sizeof(buf) - 1)
400             continue;
401         switch (state) {
402         case 0:
403             if (match_c(s, " /* XPM */ ")) {
404                 state = 1;
405             }
406             break;
407         case 1:
408             if (match_c(s, " static char * %s [] = %c ", arg1, &ch) &&
409                 ch == L_CURL) {
410                 result->name = strdup(arg1);
411                 state = 2;
412             }
413             break;
414         case 2:
415             if (match_c(s, " \" %d %d %d %d \" , ",
416                         num + 0, num + 1, num + 2, num + 3) ||
417                 match_c(s, " \" %d %d %d %d %d %d \" , ",
418                         num + 0, num + 1, num + 2, num + 3, num + 4, num + 5)) {
419                 result->wide = num[0];
420                 result->high = num[1];
421                 result->colors = num[2];
422                 result->pairs = typeCalloc(PICS_PAIR, result->colors);
423                 cells = (size_t) (result->wide * result->high);
424                 result->cells = typeCalloc(PICS_CELL, cells);
425                 list = typeCalloc(char *, result->colors);
426                 cpp = num[3];
427                 state = 3;
428             }
429             break;
430         case 3:
431             if (match_c(s, " \" %s %s %s \" , ", arg1, arg2, arg3)) {
432                 ;
433             } else if (match_c(s, " \" %s %s \" , ", arg2, arg3)) {
434                 strcpy(arg1, " ");
435             } else {
436                 break;
437             }
438             while ((int) strlen(arg1) < cpp)
439                 strcat(arg1, " ");
440             list[color] = strdup(arg1);
441             if (!strcmp(arg3, "None")) {
442                 result->pairs[color].fg = -1;
443             } else if (*arg3 == '#') {
444                 unsigned long value = strtoul(arg3 + 1, &s, 16);
445                 result->pairs[color].fg = (int) value;
446             } else {
447                 result->pairs[color].fg = 0;    /* actually an error */
448             }
449             if (++color >= result->colors)
450                 state = 4;
451             break;
452         case 4:
453             if (*(cs = skip_s(s)) == '"') {
454                 ++cs;
455                 while (*cs != '\0' && *cs != '"') {
456                     int c;
457
458                     for (c = 0; c < result->colors; ++c) {
459                         if (!strncmp(cs, list[c], cpp)) {
460                             result->cells[which].ch = list[c][0];
461                             result->cells[which].fg = c;
462                             break;
463                         }
464                     }
465
466                     if (result->cells[which].ch == 0) {
467                         result->cells[which].ch = '?';
468                         result->cells[which].fg = 0;
469                     }
470
471                     if (++which >= cells) {
472                         state = 5;
473                         break;
474                     }
475                     for (c = cpp; c > 0; --c, ++cs) ;
476                 }
477             }
478             break;
479         }
480     }
481
482     if (result && list) {
483         for (n = 0; n < result->colors; ++n)
484             free(list[n]);
485         free(list);
486     }
487
488     if (state < 5) {
489         free_pics_head(result);
490         result = 0;
491     }
492
493     return result;
494 }
495
496 static PICS_HEAD *
497 read_picture(const char *filename, char **data)
498 {
499     PICS_HEAD *pics;
500     if ((pics = parse_xbm(data)) == 0) {
501         if ((pics = parse_xpm(data)) == 0) {
502             free_data(data);
503             giveup("unexpected file-format for \"%s\"", filename);
504         }
505     }
506     return pics;
507 }
508
509 static void
510 dump_picture(PICS_HEAD * pics)
511 {
512     int y, x;
513
514     printf("Name %s\n", pics->name);
515     printf("Size %dx%d\n", pics->high, pics->wide);
516     printf("Color\n");
517     for (y = 0; y < pics->colors; ++y) {
518         if (pics->pairs[y].fg < 0) {
519             printf(" %3d: %d\n", y, pics->pairs[y].fg);
520         } else {
521             printf(" %3d: #%06x\n", y, pics->pairs[y].fg);
522         }
523     }
524     for (y = 0; y < pics->high; ++y) {
525         for (x = 0; x < pics->wide; ++x) {
526             putchar(pics->cells[y * pics->wide + x].ch);
527         }
528         putchar('\n');
529     }
530 }
531
532 static void
533 show_picture(PICS_HEAD * pics)
534 {
535     int y, x;
536     int n;
537
538     if (!in_curses) {
539         in_curses = TRUE;
540         initscr();
541         cbreak();
542         noecho();
543         if (has_colors())
544             start_color();
545     }
546     scrollok(stdscr, FALSE);
547     if (has_colors()) {
548         for (n = 0; n < pics->colors; ++n) {
549             init_pair((short) (n + 1),
550                       (short) map_color(pics->pairs[n].fg),
551                       COLOR_BLACK);
552         }
553         attrset(COLOR_PAIR(1));
554         erase();
555     }
556     for (y = 0; y < pics->high; ++y) {
557         if (y >= LINES)
558             break;
559         move(y, 0);
560         for (x = 0; x < pics->wide; ++x) {
561             if (x >= COLS)
562                 break;
563             n = (y * pics->wide + x);
564             attrset(COLOR_PAIR(pics->cells[n].fg + 1));
565             addch((chtype) pics->cells[n].ch);
566         }
567     }
568     mvgetch(0, 0);
569     endwin();
570 }
571
572 int
573 main(int argc, char *argv[])
574 {
575     int n;
576
577     if (argc > 1) {
578         for (n = 1; n < argc; ++n) {
579             char **data = read_file(argv[n]);
580             PICS_HEAD *pics;
581             if (data == 0) {
582                 giveup("cannot read \"%s\"", argv[n]);
583             }
584             if ((pics = read_picture(argv[n], data)) == 0) {
585                 free_data(data);
586                 giveup("unexpected file-format for \"%s\"", argv[n]);
587             }
588             if (isatty(fileno(stdout))) {
589                 show_picture(pics);
590             } else {
591                 dump_picture(pics);
592             }
593             free_data(data);
594             free_pics_head(pics);
595         }
596     } else {
597         usage();
598     }
599     ExitProgram(EXIT_SUCCESS);
600 }