Skip to content

Instantly share code, notes, and snippets.

@jart

jart/printimage.c

Last active Mar 8, 2021
Embed
What would you like to do?
/*bin/echo ' -*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;coding:utf-8 -*-┤
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
╞══════════════════════════════════════════════════════════════════════════════╡
│ To the extent possible under law, Justine Tunney has waived │
│ all copyright and related or neighboring rights to this file, │
│ as it is written in the following disclaimers: │
│ • http://unlicense.org/ │
│ • http://creativecommons.org/publicdomain/zero/1.0/ │
╚────────────────────────────────────────────────────────────────────'>/dev/null
if ! [ "${0%.*}.exe" -nt "$0" ]; then
cc -O -o "${0%.*}.exe" "$0" || exit
fi
exec "${0%.*}.exe" "$@"
exit
OVERVIEW
Simple Terminal Image Printer
AUTHOR
Justine Tunney <jtunney@gmail.com>
DESCRIPTION
This program demonstrates a straightforward technique for displaying
PNG/GIF/JPG/etc. files from the command line. This program supports
xterm256 and 24-bit color w/ UNICODE half blocks. For example:
apt install build-essential imagemagick
./printimage.c lemur.png
NOTES
More advanced techniques exist for (1) gaining finer detail, as well
as (2) reducing the number of bytes emitted; this program is here to
get you started. */
#include <locale.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <termios.h>
#include <unistd.h>
#define SQR(X) ((X) * (X))
#define UNCUBE(x) x < 48 ? 0 : x < 115 ? 1 : (x - 35) / 40
#define ORDIE(X) \
do { \
if (!(X)) perror(#X), exit(1); \
} while (0)
static int want24bit_;
/**
* Quantizes 24-bit RGB to xterm256 code range [16,256).
*/
int rgb2xterm256(int r, int g, int b) {
unsigned char cube[] = {0, 0137, 0207, 0257, 0327, 0377};
int av, ir, ig, ib, il, qr, qg, qb, ql;
av = r * .299 + g * .587 + b * .114 + .5;
ql = (il = av > 238 ? 23 : (av - 3) / 10) * 10 + 8;
qr = cube[(ir = UNCUBE(r))];
qg = cube[(ig = UNCUBE(g))];
qb = cube[(ib = UNCUBE(b))];
if (SQR(qr - r) + SQR(qg - g) + SQR(qb - b) <=
SQR(ql - r) + SQR(ql - g) + SQR(ql - b)) {
return ir * 36 + ig * 6 + ib + 020;
} else {
return il + 0350;
}
}
/**
* Prints raw packed 8-bit RGB data from memory.
*/
void PrintImage(int yn, int xn, unsigned char rgb[yn][xn][3]) {
unsigned y, x;
for (y = 0; y < yn; y += 2) {
if (y) printf("\r\n");
for (x = 0; x < xn; ++x) {
if (want24bit_) {
printf("\033[48;2;%hhu;%hhu;%hhu;38;2;%hhu;%hhu;%hhum▄",
rgb[y + 0][x][0], rgb[y + 0][x][1], rgb[y + 0][x][2],
rgb[y + 1][x][0], rgb[y + 1][x][1], rgb[y + 1][x][2]);
} else {
printf("\033[48;5;%hhu;38;5;%hhum▄",
rgb2xterm256(rgb[y + 0][x][0],
rgb[y + 0][x][1],
rgb[y + 0][x][2]),
rgb2xterm256(rgb[y + 1][x][0],
rgb[y + 1][x][1],
rgb[y + 1][x][2]));
}
}
}
printf("\033[0m\r");
}
/**
* Determines dimensions of teletypewriter.
*/
void GetTermSize(int *out_rows, int *out_cols) {
struct winsize ws;
ws.ws_row = 20;
ws.ws_col = 80;
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
ioctl(STDIN_FILENO, TIOCGWINSZ, &ws);
*out_rows = ws.ws_row;
*out_cols = ws.ws_col;
}
void ReadAll(int fd, char *p, size_t n) {
ssize_t rc;
size_t got;
do {
ORDIE((rc = read(fd, p, n)) != -1);
got = rc;
if (!got && n) {
fprintf(stderr, "error: expected eof\n");
exit(EXIT_FAILURE);
}
p += got;
n -= got;
} while (n);
}
unsigned char *LoadImageOrDie(char *path, int yn, int xn) {
size_t size;
void *rgb;
char dim[10 + 1 + 10 + 1 + 1];
int pid, wstatus, readwrite[2];
sprintf(dim, "%ux%u" /* jfc */ "!", xn, yn);
pipe(readwrite);
if (!(pid = fork())) {
close(readwrite[0]);
dup2(readwrite[1], STDOUT_FILENO);
execvp("convert", /* apt install imagemagick */
(char *const[]){"convert", path, "-resize", dim, "-colorspace",
"RGB", "-depth", "8", "rgb:-", NULL});
_Exit(EXIT_FAILURE);
}
close(readwrite[1]);
ORDIE((rgb = malloc((size = yn * xn * 3))));
ReadAll(readwrite[0], rgb, size);
ORDIE(close(readwrite[0]) != -1);
ORDIE(waitpid(pid, &wstatus, 0) != -1);
ORDIE(WEXITSTATUS(wstatus) == 0);
return rgb;
}
int main(int argc, char *argv[]) {
void *rgb;
int i, yn, xn;
setlocale(LC_ALL, "C.UTF-8");
GetTermSize(&yn, &xn);
yn *= 2;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-t") == 0) {
want24bit_ = 1;
} else {
rgb = LoadImageOrDie(argv[i], yn, xn);
PrintImage(yn, xn, rgb);
free(rgb);
}
}
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment