Skip to content

Instantly share code, notes, and snippets.

@jart
Last active December 20, 2023 11:13
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jart/7428b2b955dfd6eff7b6d31e00414508 to your computer and use it in GitHub Desktop.
Save jart/7428b2b955dfd6eff7b6d31e00414508 to your computer and use it in GitHub Desktop.
/*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;
}
@pepa65
Copy link

pepa65 commented Oct 31, 2023

How can the aspect ratio be preserved (while still fitting in the 'box' of the terminal?
Either by the width not going full so the height fits, or going full width while the top part of the image has scrolled off the terminal window..?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment