266 lines
8.1 KiB
C
266 lines
8.1 KiB
C
/*-*- 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.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];
|
|
const char *convert;
|
|
if (isempty((convert = getenv("CONVERT"))) &&
|
|
!(IsWindows() && access((convert = "\\msys64\\mingw64\\bin\\convert.exe"),
|
|
X_OK) != -1) &&
|
|
!(convert = commandv("convert"))) {
|
|
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;
|
|
cancolor();
|
|
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;
|
|
}
|