You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
264 lines
8.2 KiB
264 lines
8.2 KiB
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ |
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ |
|
╞══════════════════════════════════════════════════════════════════════════════╡ |
|
│ Copyright 2020 Justine Alexandra Roberts Tunney │ |
|
│ │ |
|
│ This program is free software; you can redistribute it and/or modify │ |
|
│ it under the terms of the GNU General Public License as published by │ |
|
│ the Free Software Foundation; version 2 of the License. │ |
|
│ │ |
|
│ This program is distributed in the hope that it will be useful, but │ |
|
│ WITHOUT ANY WARRANTY; without even the implied warranty of │ |
|
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ |
|
│ General Public License for more details. │ |
|
│ │ |
|
│ You should have received a copy of the GNU General Public License │ |
|
│ along with this program; if not, write to the Free Software │ |
|
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ |
|
│ 02110-1301 USA │ |
|
╚─────────────────────────────────────────────────────────────────────────────*/ |
|
#include "dsp/core/core.h" |
|
#include "libc/bits/safemacros.internal.h" |
|
#include "libc/calls/calls.h" |
|
#include "libc/calls/hefty/spawn.h" |
|
#include "libc/calls/ioctl.h" |
|
#include "libc/calls/struct/winsize.h" |
|
#include "libc/dce.h" |
|
#include "libc/fmt/fmt.h" |
|
#include "libc/log/check.h" |
|
#include "libc/log/log.h" |
|
#include "libc/macros.h" |
|
#include "libc/math.h" |
|
#include "libc/mem/mem.h" |
|
#include "libc/runtime/gc.h" |
|
#include "libc/runtime/runtime.h" |
|
#include "libc/stdio/stdio.h" |
|
#include "libc/str/str.h" |
|
#include "libc/sysv/consts/exit.h" |
|
#include "libc/sysv/consts/fileno.h" |
|
#include "libc/sysv/consts/ok.h" |
|
#include "libc/sysv/consts/termios.h" |
|
#include "libc/x/x.h" |
|
|
|
#define SQR(X) ((X) * (X)) |
|
#define DIST(X, Y) ((X) - (Y)) |
|
|
|
static int want24bit_; |
|
|
|
const int kXtermCube[] = {0, 0137, 0207, 0257, 0327, 0377}; |
|
|
|
static int rgbdist(int a, int b, int c, int x, int y, int z) { |
|
return SQR(DIST(a, x)) + SQR(DIST(b, y)) + SQR(DIST(c, z)); |
|
} |
|
|
|
static int uncube(int x) { |
|
return x < 48 ? 0 : x < 115 ? 1 : (x - 35) / 40; |
|
} |
|
|
|
static int DivideIntRound(int x, int y) { |
|
return (x + y / 2) / y; |
|
} |
|
|
|
static int XtermQuantizeLuma(int Y) { |
|
return DivideIntRound(Y - 8, 10); |
|
} |
|
|
|
static int XtermDequantizeLuma(int qY) { |
|
if (0 < qY && qY < 24) { |
|
return (qY * 10) + 8; |
|
} else if (qY > 0) { |
|
return 255; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static int XtermEncodeLuma(int qY) { |
|
if (0 < qY && qY < 24) { |
|
return qY + 232; |
|
} else if (qY > 0) { |
|
return 231; |
|
} else { |
|
return 16; |
|
} |
|
} |
|
|
|
static int XtermQuantizeChroma(int c) { |
|
return DivideIntRound(c - 55, 40); |
|
} |
|
|
|
static int XtermDequantizeChroma(int qc) { |
|
if (0 < qc && qc < 6) { |
|
return (qc * 40) + 55; |
|
} else if (qc > 0) { |
|
return 255; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static int XtermEncodeChromaComponent(int qC) { |
|
if (0 < qC && qC < 6) { |
|
return qC; |
|
} else if (qC > 0) { |
|
return 5; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
static int XtermEncodeChroma(int qR, int qG, int qB) { |
|
int xt; |
|
xt = 16; |
|
xt += XtermEncodeChromaComponent(qR) * 6 * 6; |
|
xt += XtermEncodeChromaComponent(qG) * 6; |
|
xt += XtermEncodeChromaComponent(qB) * 1; |
|
return xt; |
|
} |
|
|
|
/** |
|
* Quantizes 24-bit sRGB to xterm256 code range [16,256). |
|
*/ |
|
static int rgb2xterm256(unsigned char R, unsigned char G, unsigned char B) { |
|
double y, r, g, b, yr, yg, yb, ry, gy, by, gamma; |
|
int Y, qY, cY, qRY, qGY, qBY, qR, qG, qB, cR, cG, cB, xt; |
|
gamma = 2.4; |
|
yr = 871024 / 4096299.; |
|
yg = 8788810 / 12288897.; |
|
yb = 887015 / 12288897.; |
|
r = rgb2linpc(R / 255., gamma); |
|
g = rgb2linpc(G / 255., gamma); |
|
b = rgb2linpc(B / 255., gamma); |
|
y = yr * r + yg * g + yb * b; |
|
ry = (r - y) / (1 - yr + yg + yb); |
|
gy = (g - y) / (1 - yg + yr + yb); |
|
by = (b - y) / (1 - yb + yg + yr); |
|
Y = round(rgb2stdpc(y, gamma) * 255); |
|
qRY = round(rgb2stdpc(ry, gamma) * 6 + 3); |
|
qGY = round(rgb2stdpc(gy, gamma) * 6 + 3); |
|
qBY = round(rgb2stdpc(by, gamma) * 6 + 3); |
|
qY = XtermQuantizeLuma(Y); |
|
qR = XtermQuantizeChroma(qRY); |
|
qG = XtermQuantizeChroma(qGY); |
|
qB = XtermQuantizeChroma(qBY); |
|
cY = XtermDequantizeLuma(qY); |
|
cR = XtermDequantizeChroma(qRY); |
|
cG = XtermDequantizeChroma(qGY); |
|
cB = XtermDequantizeChroma(qBY); |
|
#if 0 |
|
LOGF("RGB(%3d,%3d,%3d) rgb(%f,%f,%f) y=%f", R, G, B, r, g, b, y); |
|
LOGF("RGB(%3d,%3d,%3d) yΔrgb(%f,%f,%f) XCUBE(%d,%d,%d)", R, G, B, ry, gy, by, |
|
qRY, qGY, qBY); |
|
LOGF("RGB(%3d,%3d,%3d) cRGB(%d,%d,%d) cY=%d qY=%d Y=%d", R, G, B, cR, cG, cB, |
|
cY, qY, Y); |
|
#endif |
|
if (rgbdist(cR, cG, cB, R, G, B) <= rgbdist(cY, cY, cY, R, G, B)) { |
|
xt = XtermEncodeChroma(qR, qG, qB); |
|
} else { |
|
xt = XtermEncodeLuma(qY); |
|
} |
|
/* LOGF("xt=%d", xt); */ |
|
return xt; |
|
} |
|
|
|
/** |
|
* Prints raw packed 8-bit RGB data from memory. |
|
*/ |
|
static void PrintImage(long yn, long xn, unsigned char RGB[yn][xn][4]) { |
|
long 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])); |
|
} |
|
} |
|
} |
|
if (IsWindows()) { |
|
printf("\033[0m\r\n"); |
|
} else { |
|
printf("\033[0m\r"); |
|
} |
|
} |
|
|
|
/** |
|
* Determines dimensions of teletypewriter. |
|
*/ |
|
static void GetTermSize(unsigned *out_rows, unsigned *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; |
|
} |
|
|
|
static void ReadAll(int fd, void *buf, size_t n) { |
|
char *p; |
|
ssize_t rc; |
|
size_t got; |
|
p = buf; |
|
do { |
|
CHECK_NE(-1, (rc = read(fd, p, n))); |
|
got = rc; |
|
CHECK(!(!got && n)); |
|
p += got; |
|
n -= got; |
|
} while (n); |
|
} |
|
|
|
static void LoadImageOrDie(const char *path, size_t size, long yn, long xn, |
|
unsigned char RGB[yn][xn][4]) { |
|
int pid, ws, fds[3]; |
|
char *convert, pathbuf[PATH_MAX]; |
|
if (isempty((convert = getenv("CONVERT"))) && |
|
!(IsWindows() && access((convert = "\\msys64\\mingw64\\bin\\convert.exe"), |
|
X_OK) != -1) && |
|
!(convert = commandv("convert", pathbuf))) { |
|
fputs("'convert' command not found\r\n" |
|
"please install imagemagick\r\n", |
|
stderr); |
|
exit(1); |
|
} |
|
fds[0] = STDIN_FILENO; |
|
fds[1] = -1; |
|
fds[2] = STDERR_FILENO; |
|
pid = spawnve(0, fds, convert, |
|
(char *const[]){"convert", path, "-resize", |
|
gc(xasprintf("%ux%u!", xn, yn)), "-colorspace", |
|
"RGB", "-depth", "8", "rgba:-", NULL}, |
|
environ); |
|
CHECK_NE(-1, pid); |
|
ReadAll(fds[1], RGB, size); |
|
CHECK_NE(-1, close(fds[1])); |
|
CHECK_NE(-1, waitpid(pid, &ws, 0)); |
|
CHECK_EQ(0, WEXITSTATUS(ws)); |
|
} |
|
|
|
int main(int argc, char *argv[]) { |
|
int i; |
|
void *rgb; |
|
size_t size; |
|
unsigned yn, xn; |
|
GetTermSize(&yn, &xn); |
|
yn *= 2; |
|
size = yn * xn * 4; |
|
CHECK_NOTNULL((rgb = valloc(size))); |
|
for (i = 1; i < argc; ++i) { |
|
if (strcmp(argv[i], "-t") == 0) { |
|
want24bit_ = 1; |
|
} else { |
|
LoadImageOrDie(argv[i], size, yn, xn, rgb); |
|
PrintImage(yn, xn, rgb); |
|
} |
|
} |
|
return 0; |
|
}
|
|
|