1 /*
2 icat -- Outputs an image on a 256-color or 24 bit color enabled terminal with UTF-8 locale
3 Andreas Textor <textor.andreas@googlemail.com>
4
5 Compile: gcc -Wall -pedantic -std=c99 -D_BSD_SOURCE -o icat icat.c -lImlib2
6
7 Copyright (c) 2012 Andreas Textor. All rights reserved.
8
9 Redistribution and use in source and binary forms, with or without
10 modification, are permitted provided that the following conditions are met:
11
12 1. Redistributions of source code must retain the above copyright notice, this
13 list of conditions and the following disclaimer.
14
15 2. Redistributions in binary form must reproduce the above copyright notice,
16 this list of conditions and the following disclaimer in the documentation
17 and/or other materials provided with the distribution.
18
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
20 ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21 WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
23 ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26 ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
31 #include <stdio.h>
32 #include <stdlib.h>
33 #include <stdint.h>
34 #include <stdbool.h>
35 #include <string.h>
36 #include <getopt.h>
37 #include <unistd.h>
38 #include <sys/ioctl.h>
39 #include <Imlib2.h>
40
41 #define VERSION "0.5"
42
43 enum {MODE_NOTHING, MODE_INDEXED, MODE_24_BIT, MODE_BOTH};
44
45 static uint32_t colors[] = {
46 // Colors 0 to 15: original ANSI colors
47 0x000000, 0xcd0000, 0x00cd00, 0xcdcd00, 0x0000ee, 0xcd00cd, 0x00cdcd, 0xe5e5e5,
48 0x7f7f7f, 0xff0000, 0x00ff00, 0xffff00, 0x5c5cff, 0xff00ff, 0x00ffff, 0xffffff,
49 // Color cube colors
50 0x000000, 0x00005f, 0x000087, 0x0000af, 0x0000d7, 0x0000ff, 0x005f00, 0x005f5f,
51 0x005f87, 0x005faf, 0x005fd7, 0x005fff, 0x008700, 0x00875f, 0x008787, 0x0087af,
52 0x0087d7, 0x0087ff, 0x00af00, 0x00af5f, 0x00af87, 0x00afaf, 0x00afd7, 0x00afff,
53 0x00d700, 0x00d75f, 0x00d787, 0x00d7af, 0x00d7d7, 0x00d7ff, 0x00ff00, 0x00ff5f,
54 0x00ff87, 0x00ffaf, 0x00ffd7, 0x00ffff, 0x5f0000, 0x5f005f, 0x5f0087, 0x5f00af,
55 0x5f00d7, 0x5f00ff, 0x5f5f00, 0x5f5f5f, 0x5f5f87, 0x5f5faf, 0x5f5fd7, 0x5f5fff,
56 0x5f8700, 0x5f875f, 0x5f8787, 0x5f87af, 0x5f87d7, 0x5f87ff, 0x5faf00, 0x5faf5f,
57 0x5faf87, 0x5fafaf, 0x5fafd7, 0x5fafff, 0x5fd700, 0x5fd75f, 0x5fd787, 0x5fd7af,
58 0x5fd7d7, 0x5fd7ff, 0x5fff00, 0x5fff5f, 0x5fff87, 0x5fffaf, 0x5fffd7, 0x5fffff,
59 0x870000, 0x87005f, 0x870087, 0x8700af, 0x8700d7, 0x8700ff, 0x875f00, 0x875f5f,
60 0x875f87, 0x875faf, 0x875fd7, 0x875fff, 0x878700, 0x87875f, 0x878787, 0x8787af,
61 0x8787d7, 0x8787ff, 0x87af00, 0x87af5f, 0x87af87, 0x87afaf, 0x87afd7, 0x87afff,
62 0x87d700, 0x87d75f, 0x87d787, 0x87d7af, 0x87d7d7, 0x87d7ff, 0x87ff00, 0x87ff5f,
63 0x87ff87, 0x87ffaf, 0x87ffd7, 0x87ffff, 0xaf0000, 0xaf005f, 0xaf0087, 0xaf00af,
64 0xaf00d7, 0xaf00ff, 0xaf5f00, 0xaf5f5f, 0xaf5f87, 0xaf5faf, 0xaf5fd7, 0xaf5fff,
65 0xaf8700, 0xaf875f, 0xaf8787, 0xaf87af, 0xaf87d7, 0xaf87ff, 0xafaf00, 0xafaf5f,
66 0xafaf87, 0xafafaf, 0xafafd7, 0xafafff, 0xafd700, 0xafd75f, 0xafd787, 0xafd7af,
67 0xafd7d7, 0xafd7ff, 0xafff00, 0xafff5f, 0xafff87, 0xafffaf, 0xafffd7, 0xafffff,
68 0xd70000, 0xd7005f, 0xd70087, 0xd700af, 0xd700d7, 0xd700ff, 0xd75f00, 0xd75f5f,
69 0xd75f87, 0xd75faf, 0xd75fd7, 0xd75fff, 0xd78700, 0xd7875f, 0xd78787, 0xd787af,
70 0xd787d7, 0xd787ff, 0xd7af00, 0xd7af5f, 0xd7af87, 0xd7afaf, 0xd7afd7, 0xd7afff,
71 0xd7d700, 0xd7d75f, 0xd7d787, 0xd7d7af, 0xd7d7d7, 0xd7d7ff, 0xd7ff00, 0xd7ff5f,
72 0xd7ff87, 0xd7ffaf, 0xd7ffd7, 0xd7ffff, 0xff0000, 0xff005f, 0xff0087, 0xff00af,
73 0xff00d7, 0xff00ff, 0xff5f00, 0xff5f5f, 0xff5f87, 0xff5faf, 0xff5fd7, 0xff5fff,
74 0xff8700, 0xff875f, 0xff8787, 0xff87af, 0xff87d7, 0xff87ff, 0xffaf00, 0xffaf5f,
75 0xffaf87, 0xffafaf, 0xffafd7, 0xffafff, 0xffd700, 0xffd75f, 0xffd787, 0xffd7af,
76 0xffd7d7, 0xffd7ff, 0xffff00, 0xffff5f, 0xffff87, 0xffffaf, 0xffffd7, 0xffffff,
77 // >= 233: Grey ramp
78 0x000000, 0x121212, 0x1c1c1c, 0x262626, 0x303030, 0x3a3a3a, 0x444444, 0x4e4e4e,
79 0x585858, 0x626262, 0x6c6c6c, 0x767676, 0x808080, 0x8a8a8a, 0x949494, 0x9e9e9e,
80 0xa8a8a8, 0xb2b2b2, 0xbcbcbc, 0xc6c6c6, 0xd0d0d0, 0xdadada,
81 };
82
83 // Find an xterm color value that matches an ARGB color
rgb2xterm(Imlib_Color * pixel)84 uint8_t rgb2xterm(Imlib_Color* pixel) {
85 uint8_t c, match = 0;
86 uint32_t r, g, b, d, distance;
87
88 distance = 1000000000;
89 for(c = 0; c <= 253; c++) {
90 r = ((0xff0000 & colors[c]) >> 16) - pixel->red;
91 g = ((0x00ff00 & colors[c]) >> 8) - pixel->green;
92 b = (0x0000ff & colors[c]) - pixel->blue;
93 d = r * r + g * g + b * b;
94 if (d < distance) {
95 distance = d;
96 match = c;
97 }
98 }
99
100 return match;
101 }
102
print_usage()103 void print_usage() {
104 printf("icat (" VERSION ") outputs an image on a 256-color or 24-bit color enabled terminal with UTF-8 locale.\n"
105 "Usage: icat [-h|--help] [-x value] [-y value] [-w|--width value] [-k|--keep]\n"
106 " [-m|--mode indexed|24bit|both] imagefile [imagefile...]\n"
107 " -h | --help -- Display this message\n"
108 " -x value -- Specify the column to print the image in (min. 1)\n"
109 " -y value -- Specify the row to print the image in (min. 1)\n"
110 " This is ignored when more than one image is printed.\n"
111 " -w | --width value\n"
112 " Instead of resizing the image to fit the terminal width,\n"
113 " use the provided value as the desired width.\n"
114 " -k | --keep -- Keep image size, i.e. do not automatically resize image to fit\n"
115 " the terminal width.\n"
116 " -m | --mode indexed|24bit|both\n"
117 " Use indexed (256-color, the default), 24-bit color, or both.\n"
118 " imagefile -- The image to print. If the file name is \"-\", the file is\n"
119 " read from stdin.\n"
120 "Big images are automatically resized to your terminal width, unless with the -k option.\n"
121 "You can achieve the same effect with convert (from the ImageMagick package):\n"
122 " convert -resize $((COLUMNS - 2))x image.png - | icat -\n"
123 "Or read images from another source:\n"
124 " curl -sL http://example.com/image.png | convert -resize $((COLUMNS - 2))x - - | icat -\n");
125 }
126
127 // Find out and return the number of columns in the terminal
terminal_width()128 int terminal_width() {
129 int cols = 80;
130 #ifdef TIOCGSIZE
131 struct ttysize ts;
132 ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
133 cols = ts.ts_cols;
134 #elif defined(TIOCGWINSZ)
135 struct winsize ts;
136 ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
137 cols = ts.ws_col;
138 #endif /* TIOCGSIZE */
139 return cols;
140 }
141
142 // Resize image, but only if the user explicitly provided the image width,
143 // or the image wouldn't fit in the terminal.
resize_image_if_necessary(int * width,int * height,const int user_width)144 void resize_image_if_necessary(int *width, int *height, const int user_width) {
145 int resized_width;
146
147 if (!user_width) {
148 int cols = terminal_width();
149 if(cols > *width) return;
150
151 resized_width = cols;
152 } else {
153 resized_width = user_width;
154 }
155
156 float ratio = ((float)resized_width) / *width;
157 int resized_height = *height * ratio;
158
159 Imlib_Image resized_image = imlib_create_cropped_scaled_image(0, 0,
160 *width, *height, resized_width, resized_height);
161 imlib_free_image_and_decache();
162 imlib_context_set_image(resized_image);
163
164 *width = resized_width;
165 *height = resized_height;
166 }
167
168 // Prints two pixels inside one character, p1 below p2.
169 // Characters in terminal fonts are usually twice as high
170 // as they are wide.
print_pixels(Imlib_Color * p1,Imlib_Color * p2,int mode)171 void print_pixels(Imlib_Color* p1, Imlib_Color* p2, int mode) {
172 // Newer xterms should support 24-bit color with the ESC[38;2;<r>;<g>;<b>m sequence.
173 // For backward compatibility, we insert the old ESC[38;5;<color_index>m before it.
174 static char* upper = "▀";
175 static char* lower = "▄";
176 if (p1->alpha == 0 && p2->alpha == 0) {
177 // Both pixels are transparent
178 printf("\x1b[0m ");
179 } else if (p1->alpha == 0 && p2->alpha != 0) {
180 // Only lower pixel is transparent
181 fputs("\x1b[0m", stdout);
182 if(mode & MODE_INDEXED) {
183 uint8_t col2 = rgb2xterm(p2);
184 printf("\x1b[38;5;%dm", col2);
185 }
186 if(mode & MODE_24_BIT) {
187 printf("\x1b[38;2;%d;%d;%dm", p2->red, p2->green, p2->blue);
188 }
189 fputs(upper, stdout);
190 } else if (p1->alpha != 0 && p2->alpha == 0) {
191 // Only upper pixel is transparent
192 fputs("\x1b[0m", stdout);
193 if(mode & MODE_INDEXED) {
194 uint8_t col1 = rgb2xterm(p1);
195 printf("\x1b[38;5;%dm", col1);
196 }
197 if(mode & MODE_24_BIT) {
198 printf("\x1b[38;2;%d;%d;%dm", p1->red, p1->green, p1->blue);
199 }
200 fputs(lower, stdout);
201 } else {
202 // Both pixels are opaque
203 if(mode & MODE_INDEXED) {
204 uint8_t col1 = rgb2xterm(p1);
205 uint8_t col2 = rgb2xterm(p2);
206 printf("\x1b[38;5;%dm\x1b[48;5;%dm", col1, col2);
207 }
208 if(mode & MODE_24_BIT) {
209 printf("\x1b[38;2;%d;%d;%dm\x1b[48;2;%d;%d;%dm", p1->red, p1->green, p1->blue, p2->red, p2->green, p2->blue);
210 }
211 fputs(lower, stdout);
212 }
213 }
214
main(int argc,char * argv[])215 int main(int argc, char* argv[]) {
216 char *filename;
217 static int display_help = 0;
218 Imlib_Image image = NULL;
219 unsigned int x = 0;
220 unsigned int y = 0;
221 unsigned int user_w = 0;
222 int c;
223 bool keep_size = false;
224 int mode = MODE_INDEXED;
225
226 for(;;) {
227 static struct option long_options[] = {
228 {"help", no_argument, &display_help, 1},
229 {"x", required_argument, 0, 'x'},
230 {"y", required_argument, 0, 'y'},
231 {"width",required_argument, 0, 'w'},
232 {"keep", no_argument, 0, 'k'},
233 {"mode", required_argument, 0, 'm'},
234 {0, 0, 0, 0}
235 };
236
237 c = getopt_long(argc, argv, "hx:y:w:km:", long_options, NULL);
238
239 if (c == -1)
240 break;
241
242 switch (c) {
243 case 'x':
244 x = atoi(optarg);
245 if (x < 0) {
246 x = 0;
247 }
248 break;
249
250 case 'y':
251 y = atoi(optarg);
252 if (y < 0) {
253 y = 0;
254 }
255 break;
256
257 case 'w':
258 user_w = atoi(optarg);
259 if(user_w < 1) {
260 printf("Image width must be larger than 0\n");
261 exit(1);
262 }
263 break;
264
265 case 'h':
266 display_help = 1;
267 break;
268
269 case 'k':
270 keep_size = true;
271 break;
272
273 case 'm':
274 if(strcmp(optarg, "indexed") == 0) {
275 mode = MODE_INDEXED;
276 } else if(strcmp(optarg, "24bit") == 0) {
277 mode = MODE_24_BIT;
278 } else if(strcmp(optarg, "both") == 0) {
279 mode = MODE_BOTH;
280 } else {
281 printf("Mode must be one of 'indexed', '24bit', or 'both'.\n");
282 exit(1);
283 }
284 break;
285
286 case '?':
287 break;
288
289 default:
290 display_help = 1;
291 break;
292 }
293 }
294
295 if (display_help == 1 || optind == argc) {
296 print_usage();
297 exit(0);
298 }
299
300 // Ignore y value when more than one picture is printed
301 if (argc - optind > 1) {
302 y = 0;
303 }
304
305 for (int i = optind; i < argc; i++) {
306 // Read from stdin and write temp file. Although a temp file
307 // is ugly, imlib can not seek in a pipe and therefore not
308 // read an image from it.
309 if (strcmp(argv[i], "-") == 0) {
310 int tempfile;
311 char tempfile_name[] = "/tmp/icatXXXXXX";
312 if ((tempfile = mkstemp(tempfile_name)) < 0) {
313 perror("mkstemp");
314 exit(EXIT_FAILURE);
315 }
316 filename = tempfile_name;
317 char buf;
318 while (read(0, &buf, 1) > 0) {
319 write(tempfile, &buf, 1);
320 }
321 } else {
322 filename = argv[i];
323 }
324
325 // Load image
326 image = imlib_load_image_immediately(filename);
327 if (!image) {
328 fprintf(stderr, "Could not load image: %s\n", filename);
329 exit(EXIT_FAILURE);
330 }
331
332 imlib_context_set_image(image);
333 int width = imlib_image_get_width();
334 int height = imlib_image_get_height();
335
336 // Unless told to retain image size, check if resize if needed
337 if (!keep_size) {
338 resize_image_if_necessary(&width, &height, user_w);
339 }
340
341 // If an y-value is given, position the cursor in that line (and
342 // given or default column)
343 if (y > 0) {
344 printf("\x1b[%d;%dH", y, x);
345 }
346
347 Imlib_Color pixel1;
348 Imlib_Color pixel2;
349 for (int h = 0; h < height; h += 2) {
350 // If an x-offset is given, position the cursor in that column
351 if (x > 0) {
352 printf("\x1b[%dG", x);
353 }
354
355 // Draw a horizontal line. Each console line consists of two
356 // pixel lines in order to keep the pixels square (most console
357 // fonts have two times the height for the width of each character)
358 for (int w = 0; w < width; w++) {
359 imlib_image_query_pixel(w, h + 1, &pixel1);
360 imlib_image_query_pixel(w, h, &pixel2);
361 print_pixels(&pixel1, &pixel2, mode);
362 }
363 printf("\x1b[0m\n");
364 }
365
366 imlib_free_image_and_decache();
367 }
368 return 0;
369 }
370