]> git.draconx.ca Git - rrace.git/blob - t/ewmhicon.c
Add a funky dithered 16-colour mode to ewmhicon.
[rrace.git] / t / ewmhicon.c
1 /*
2  * Test app for _NET_WM_ICON formatting.
3  * Copyright © 2022 Nick Bowler
4  *
5  * Use a fake colour scheme to generate an icon of the chosen size (16x16,
6  * 24x24, 32x32 or 48x48) and display the pixels as characters.
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
20  */
21
22 #include <config.h>
23 #include <stdio.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <limits.h>
27 #include <errno.h>
28 #include <assert.h>
29 #include <getopt.h>
30
31 #include "ewmhicon.h"
32 #include "icon.h"
33 #include "colour.h"
34 #include "help.h"
35
36 enum {
37         SOPT_MAX = UCHAR_MAX,
38         LOPT_W32VGA,
39         LOPT_MAX
40 };
41
42 static const char *progname = "ewmhicon";
43 static const char sopts[] = "c:VH";
44 static const struct option lopts[] = {
45         { "colourseq", 1, NULL, 'c' },
46         { "w32vga",    0, NULL, LOPT_W32VGA },
47         { "version",   0, NULL, 'V' },
48         { "help",      0, NULL, 'H' },
49         { 0 }
50 };
51
52 #define S8TO16(x) ((x) * 0xfffful / 0xff)
53
54 #define RGB8(r, g, b) { \
55         0xff000000 | (r << 16) | (g << 8) | b, \
56         S8TO16(r), S8TO16(g), S8TO16(b) }
57
58 #define COLOUR_SYS_XCOLOR(r, g, b) \
59         RGB8(0x ## r ## ul, 0x ## g ## ul, 0x ## b ## ul)
60
61 #undef COLOUR_SYSTEM
62 #define COLOUR_SYSTEM XCOLOR
63
64 #define COLOURTAB_(n) { n ## _PRIMARY, n ## _DARK, n ## _LIGHT }
65 #define COLOURTAB(n) COLOURTAB_(COLOUR ## n)
66
67 static const XColor colours[7][3] = {
68         {
69                 { 0xffff0000, 0xffff },
70                 { 0xff00ff00, 0, 0xffff },
71                 { 0xff0000ff, 0, 0, 0xffff }
72         },
73         COLOURTAB(0), COLOURTAB(1), COLOURTAB(2),
74         COLOURTAB(3), COLOURTAB(4), COLOURTAB(5)
75 };
76
77 static void print_usage(FILE *f)
78 {
79         fprintf(f, "Usage: %s size\n", progname);
80         if (f != stdout)
81                 fprintf(f, "Try %s --help for more information.\n", progname);
82 }
83
84 static void print_help(void)
85 {
86         const struct option *opt;
87
88         print_usage(stdout);
89         putchar('\n');
90         puts("Options:");
91         for (opt = lopts; opt->name; opt++) {
92                 if (help_print_optstring(opt, "ARG", 20))
93                         putchar('\n');
94         }
95         putchar('\n');
96
97         printf("Report bugs to <%s>.\n", PACKAGE_BUGREPORT);
98 }
99
100 static void print_version(void)
101 {
102         printf("ewmhicon (%s) %s\n", PACKAGE, PACKAGE_VERSION);
103         printf("Copyright (C) 2022 Nick Bowler\n");
104         puts("License GPLv3+: GNU GPL version 3 or any later version");
105         puts("This is free software: you are free to change and redistribute it.");
106         puts("There is NO WARRANTY, to the extent permitted by law.");
107 }
108
109
110 #define COLOUR_SYS_HEX(r, g, b) 0x ## r ## g ## b ## ul
111
112 #undef COLOUR_SYSTEM
113 #define COLOUR_SYSTEM HEX
114
115 /*
116  * Returns true iff this pixel is an "edge" -- that is, either there
117  * is no pixel to the right or below or those pixels are a different
118  * colour.
119  */
120 static int is_edge(unsigned x, unsigned y, unsigned w, unsigned h,
121                    unsigned long *p, unsigned long colour)
122 {
123         return y+1 >= h || x+1 >= w
124             || (p[1] & 0xffffff) != colour
125             || (p[w] & 0xffffff) != colour;
126 }
127
128 static void print_xpm_w32vga(unsigned long size, unsigned long *icon_buf)
129 {
130         unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
131         unsigned x, y, i, j, n;
132
133         const char colourchars[16] = "#rgybmc-+RGYBMC ";
134         unsigned long palette[16] = {
135                 0x000000, 0x800000, 0x008000, 0x808000,
136                 0x000080, 0x800080, 0x008080, 0xc0c0c0,
137                 0x808080, 0xff0000, 0x00ff00, 0xffff00,
138                 0x0000ff, 0xff00ff, 0x00ffff, 0xffffff
139         };
140
141         unsigned long prev_colour = 0;
142         unsigned used_colours, v;
143
144         size = (unsigned long) w * h;
145         for (i = 0; i < size; i++) {
146                 unsigned long *p = &icon_buf[i];
147                 char *c;
148
149                 x = i%w, y=i/w;
150
151                 switch (*p & 0xffffff) {
152                 /* red */
153                 case COLOUR0_PRIMARY:
154                         *p = y&1 ? ( x&1 ? 'r' : 'm' ) : ( x&1 ? 'y' : 'r' );
155                         break;
156                 case COLOUR0_LIGHT:
157                         *p = (x^y)&1 ? '+' : 'r';
158                         break;
159                 case COLOUR0_DARK:
160                         *p = 'r';
161                         break;
162                 /* orange */
163                 case COLOUR1_PRIMARY:
164                         *p = y&1 ? 'y' : x&1 ? '+' : 'R';
165                         break;
166                 case COLOUR1_LIGHT:
167                         *p = (x^y)&1 ? '+' : 'y';
168                         break;
169                 case COLOUR1_DARK:
170                         *p = (x^y)&1 ? 'r' : 'y';
171                         break;
172                 /* yellow */
173                 case COLOUR2_PRIMARY:
174                         *p = (x^y)&1 ? '+' : 'Y';
175                         break;
176                 case COLOUR2_LIGHT:
177                         *p = (x^y)&1 ? '-' : 'Y';
178                         break;
179                 case COLOUR2_DARK:
180                         *p = (x^y)&1 ? '+' : 'Y';
181                         if (is_edge(x, y, w, h, p, COLOUR2_DARK))
182                                 *p = (x^y)&1 ? '+' : 'y';
183                         break;
184                 /* green */
185                 case COLOUR3_PRIMARY:
186                         *p = y&1 ? 'g' : x&1 ? '+' : 'g';
187                         break;
188                 case COLOUR3_LIGHT:
189                         *p = (x^y)&1 ? '+' : 'g';
190                         break;
191                 case COLOUR3_DARK:
192                         *p = 'g';
193                         if (is_edge(x, y, w, h, p, COLOUR3_DARK))
194                                 *p = (x^y)&1 ? '#' : 'g';
195                         break;
196                 /* blue */
197                 case COLOUR4_PRIMARY:
198                         *p = (x^y)&1 ? 'b' : 'c';
199                         break;
200                 case COLOUR4_LIGHT:
201                         *p = 'c';
202                         if (is_edge(x, y, w, h, p, COLOUR4_LIGHT))
203                                 *p = (x^y)&1 ? 'b' : 'c';
204                         break;
205                 case COLOUR4_DARK:
206                         *p = 'b';
207                         if (is_edge(x, y, w, h, p, COLOUR4_DARK))
208                                 *p = (x^y)&1 ? '#' : 'b';
209                         break;
210                 /* white */
211                 case COLOUR5_PRIMARY:
212                         *p = (x^y)&1 ? '-' : ' ';
213                         break;
214                 case COLOUR5_LIGHT:
215                         *p = ' ';
216                         break;
217                 case COLOUR5_DARK:
218                         *p = (x^y)&1 ? '+' : ' ';
219                         break;
220                 /* dummy */
221                 case 0xff0000: *p = 'R'; break;
222                 case 0x00ff00: *p = 'G'; break;
223                 case 0x0000ff: *p = 'B'; break;
224                 }
225
226                 c = strchr(colourchars, *p);
227                 assert(c && c - colourchars < 16);
228                 used_colours |= 1u << (c-colourchars);
229         }
230
231         for (n = 0, v = used_colours; v; n++)
232                 v &= v-1;
233
234         puts("/* XPM */");
235         puts("static char *icon[] = {");
236         printf("\"%u %u %u 1\",\n", w, h, n);
237         for (i = 0; i < sizeof colourchars; i++) {
238                 if (!(used_colours & (1u << i)))
239                         continue;
240
241                 printf("\"%c c #%.6lx\",\n", colourchars[i], palette[i]);
242         }
243
244         for (y = 0; y < h; y++) {
245                 putchar('"');
246                 for (x = 0; x < w; x++) {
247                         putchar(icon_buf[y*h+x]);
248                 }
249                 printf("\"%.*s\n", y+1<h, ",");
250         }
251
252         puts("};");
253 }
254
255 static void
256 print_xpm(const char *colourseq, unsigned long size, unsigned long *icon_buf)
257 {
258         const char colourchars[21] = ".%+"",Rr""-Oo""'Yy"":Gg"";Bb"" Ww";
259         unsigned w = size & 0xffff, h = (size >> 16) & 0xffff;
260         unsigned x, y, i, j, n;
261
262         n = 7;
263         for (i = 0; i < 7; i++)
264                 n -= !strchr(colourseq, '0'+i);
265
266         puts("/* XPM */");
267         puts("static char *icon[] = {");
268         printf("\"%u %u %u 1\",\n", w, h, 3*n);
269         for (i = 0; i < 7; i++) {
270                 if (!strchr(colourseq, '0'+i))
271                         continue;
272
273                 for (j = 0; j < 3; j++) {
274                         printf("\"%c c #%.6lx\",\n", colourchars[3*i+j],
275                                        colours[i][j].pixel & 0xffffff);
276                 }
277         }
278
279         for (y = 0; y < h; y++) {
280                 putchar('"');
281                 for (x = 0; x < w; x++) {
282                         unsigned long val = icon_buf[y*h+x];
283                         int c = '#';
284
285                         for (i = 0; i < sizeof colourchars; i++) {
286                                 if (colours[i/3][i%3].pixel == val) {
287                                         c = colourchars[i];
288                                         break;
289                                 }
290                         }
291
292                         putchar(c);
293                 }
294                 printf("\"%.*s\n", y+1<h, ",");
295         }
296         puts("};");
297 }
298
299 static int decode_colourseq(char *out, const char *arg)
300 {
301         int i;
302
303         for (i = 0; i < 9; i++) {
304                 switch (arg[i]) {
305                 case 0: return 0;
306                 case 'R': case 'r': out[i] = '1'; break;
307                 case 'O': case 'o': out[i] = '2'; break;
308                 case 'Y': case 'y': out[i] = '3'; break;
309                 case 'G': case 'g': out[i] = '4'; break;
310                 case 'B': case 'b': out[i] = '5'; break;
311                 case 'W': case 'w': out[i] = '6'; break;
312                 case '1': case '2': case '3': case '4': case '5': case '6':
313                         case '0': out[i] = arg[i]; break;
314                 default: goto err;
315                 }
316         }
317
318         if (arg[i]) {
319 err:
320                 fprintf(stderr, "%s: error: invalid colour sequence '%s'\n",
321                                 progname, arg);
322                 return -1;
323         }
324
325         return 0;
326 }
327
328 /* Convert the user sequence into a list of colour index specifiers */
329 static unsigned long *expand_seq(unsigned long *out, const char *seq)
330 {
331         int i;
332
333         for (i = 0; i < 9; i++) {
334                 out[i] = 0x30303 * (seq[i]-'0') + 0x20100;
335         }
336
337         return out;
338 }
339
340 static unsigned long decode_size(char *size_spec)
341 {
342         unsigned long w, h;
343         char c = 0;
344         size_t n;
345
346         n = strspn(size_spec, "0123456789");
347         switch (size_spec[n]) {
348         default:
349                 goto err_invalid;
350         case 0: case 'x': case 'X':
351                 c = size_spec[n];
352                 size_spec[n] = 0;
353         }
354
355         w = strtoul(size_spec, NULL, 10);
356         size_spec[n] = c;
357         if (w > 0xffff)
358                 goto err_range;
359
360         if (!c) {
361                 h = w;
362         } else {
363                 n = strspn(size_spec += n+1, "0123456789");
364                 if (size_spec[n])
365                         goto err_invalid;
366
367                 h = strtoul(size_spec, NULL, 10);
368                 if (h > 0xffff)
369                         goto err_range;
370         }
371
372         return (h << 16) | w;
373 err_range:
374         fprintf(stderr, "%s: %s: %s\n", progname, size_spec, strerror(ERANGE));
375         return 0;
376 err_invalid:
377         fprintf(stderr, "%s: %s: %s\n", progname, size_spec,
378                                         "invalid size specification");
379         return 0;
380 }
381
382 static unsigned long *find_icon(unsigned long size, unsigned long *ewmhicon)
383 {
384         unsigned long w = size & 0xffff, h = (size >> 16) & 0xffff;
385         unsigned long i;
386
387         if (!size)
388                 return NULL;
389
390         for (i = 0; i < EWMH_ICON_NELEM-2;) {
391                 unsigned long icon_w = ewmhicon[i];
392                 unsigned long icon_h = ewmhicon[i+1];
393
394                 if (w == icon_w && h == icon_h) {
395                         i += 2;
396                         break;
397                 }
398
399                 assert(icon_w < ULONG_MAX / icon_h);
400                 assert(i < ULONG_MAX - icon_w*icon_h);
401                 i += 2 + icon_w*icon_h;
402         }
403
404         if (i < EWMH_ICON_NELEM)
405                 return &ewmhicon[i];
406
407         fprintf(stderr, "%s: error: no %lux%lu icon found\n", progname, w, h);
408         return NULL;
409 }
410
411 int main(int argc, char **argv)
412 {
413         unsigned long *ewmhicon, *icon, size, buf[9];
414         char colourseq[10] = "000000000";
415         int w32vga = 0;
416         int opt;
417
418         if (argc > 0)
419                 progname = argv[0];
420
421         while ((opt = getopt_long(argc, argv, sopts, lopts, 0)) != -1) {
422                 switch (opt) {
423                 case 'c':
424                         if (decode_colourseq(colourseq, optarg) != 0)
425                                 return EXIT_FAILURE;
426                         break;
427                 case LOPT_W32VGA:
428                         w32vga = 1;
429                         break;
430                 case 'H':
431                         print_help();
432                         return EXIT_SUCCESS;
433                 case 'V':
434                         print_version();
435                         return EXIT_SUCCESS;
436                 default:
437                         print_usage(stderr);
438                         return EXIT_FAILURE;
439                 }
440         }
441
442         if (argc != optind+1) {
443                 printf("%s: error: size not specified\n", progname);
444                 print_usage(stderr);
445                 return EXIT_FAILURE;
446         }
447
448         size = decode_size(argv[optind]);
449         if (!size)
450                 return EXIT_FAILURE;
451
452         ewmhicon = ewmh_icon_generate(expand_seq(buf, colourseq), colours[0]);
453         if (!ewmhicon) {
454                 fprintf(stderr, "%s: failed to allocate memory\n", progname);
455                 return EXIT_FAILURE;
456         }
457
458         icon = find_icon(size, ewmhicon);
459         if (!icon)
460                 return EXIT_FAILURE;
461
462         if (w32vga) {
463                 print_xpm_w32vga(size, icon);
464         } else {
465                 print_xpm(colourseq, size, icon);
466         }
467
468         free(ewmhicon);
469         return 0;
470 }