377 lines
11 KiB
C
377 lines
11 KiB
C
#if 0
|
|
/*─────────────────────────────────────────────────────────────────╗
|
|
│ 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/ │
|
|
╚─────────────────────────────────────────────────────────────────*/
|
|
#endif
|
|
#include "dsp/core/core.h"
|
|
#include "dsp/core/twixt8.h"
|
|
#include "dsp/scale/scale.h"
|
|
#include "dsp/tty/itoa8.h"
|
|
#include "dsp/tty/quant.h"
|
|
#include "libc/alg/arraylist2.h"
|
|
#include "libc/calls/calls.h"
|
|
#include "libc/calls/ioctl.h"
|
|
#include "libc/calls/struct/stat.h"
|
|
#include "libc/calls/struct/winsize.h"
|
|
#include "libc/limits.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/stdio/stdio.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/sysv/consts/fileno.h"
|
|
#include "libc/sysv/consts/map.h"
|
|
#include "libc/sysv/consts/o.h"
|
|
#include "libc/sysv/consts/prot.h"
|
|
#include "libc/sysv/consts/termios.h"
|
|
#include "libc/x/x.h"
|
|
#include "third_party/stb/stb_image.h"
|
|
|
|
/**
|
|
* Hat tips go out to Hornet, CRTC, DESiRE, Hans Petter Jansson, Guihua
|
|
* Cui, Ming Ronnier Luoand anyone else Mister Rogers would describe as
|
|
* the The Helpers Of This World who've helped us learn more about the
|
|
* cool yet exceedingly difficult things possible to do with graphics.
|
|
*/
|
|
|
|
#define DIN99 1
|
|
#define GAMMA 4.1
|
|
#define GAMMA_A .05
|
|
#define GAMMA_B 50.
|
|
#define GAMMA_C .055
|
|
#define COLORSPACE_SRC kSrgbD65ToXyzD50
|
|
#define COLORSPACE_TTY kSrgbD65ToXyzD50
|
|
|
|
#define SQR(X) ((X) * (X))
|
|
#define DEG(X) ((X)*M_PI / 180)
|
|
|
|
const double kXyzKappa = 24389 / 27.;
|
|
const double kXyzEpsilon = 216 / 24389.;
|
|
|
|
const double kSrgbD65ToXyzD50[3][3] = {
|
|
{.4360747, .3850649, .1430804},
|
|
{.2225045, .7168786, .0606169},
|
|
{.0139322, .0971045, .7141733},
|
|
};
|
|
|
|
const double kCieRgbEToXyz[3][3] = {
|
|
{.4887180, .3106803, .2006017},
|
|
{.1762044, .8129847, .0108109},
|
|
{.0000000, .0102048, .9897952},
|
|
};
|
|
|
|
const unsigned char kShadePct[] = {0300, 0200, 0100};
|
|
const unsigned short kShadeRune[] = {u'▓', u'▒', u'░'};
|
|
|
|
const unsigned char kAnsiTango[16][3] = {
|
|
{46, 52, 54}, {204, 0, 0}, {78, 154, 6}, {196, 160, 0},
|
|
{52, 101, 164}, {117, 80, 123}, {6, 152, 154}, {211, 215, 207},
|
|
{85, 87, 83}, {239, 41, 41}, {138, 226, 52}, {252, 233, 79},
|
|
{114, 159, 207}, {173, 127, 168}, {52, 226, 226}, {238, 238, 236},
|
|
};
|
|
|
|
const unsigned char kCga[16][3] = {
|
|
{0, 0, 0}, {170, 0, 0}, {0, 170, 0}, {170, 85, 0},
|
|
{0, 0, 170}, {170, 0, 170}, {0, 170, 170}, {170, 170, 170},
|
|
{85, 85, 85}, {255, 85, 85}, {85, 255, 85}, {255, 255, 85},
|
|
{85, 85, 255}, {255, 85, 255}, {85, 255, 255}, {255, 255, 255},
|
|
};
|
|
|
|
struct Blends {
|
|
size_t i, n;
|
|
struct Blend {
|
|
unsigned char color[4];
|
|
unsigned char bg, fg;
|
|
unsigned short rune;
|
|
} * p;
|
|
} blends;
|
|
|
|
static double Gamma(double x) {
|
|
if (x < GAMMA_A) return x / GAMMA_B;
|
|
return pow(1 / (1 + GAMMA_C) * (x + GAMMA_C), GAMMA);
|
|
}
|
|
|
|
static void *LoadRgb(double rgb[3], const unsigned char pix[3]) {
|
|
rgb[0] = Gamma(1. / 255 * pix[0]);
|
|
rgb[1] = Gamma(1. / 255 * pix[1]);
|
|
rgb[2] = Gamma(1. / 255 * pix[2]);
|
|
return rgb;
|
|
}
|
|
|
|
static double Lab(double x) {
|
|
return x > kXyzEpsilon ? cbrtf(x) : (kXyzKappa * x + 16) / 116;
|
|
}
|
|
|
|
static void XyzToLab(double lab[3], const double d50xyz[3]) {
|
|
double Y, L, a, b;
|
|
Y = Lab(d50xyz[1]);
|
|
L = 116 * Y - 16;
|
|
a = 500 * (Lab(d50xyz[0]) - Y);
|
|
b = 200 * (Y - Lab(d50xyz[2]));
|
|
lab[0] = L, lab[1] = a, lab[2] = b;
|
|
}
|
|
|
|
static void XyzToDin99d(unsigned char din99d[3], const double d50xyz[3]) {
|
|
double e, f, G, C, h, xyz[3], lab[3];
|
|
memcpy(xyz, d50xyz, sizeof(xyz));
|
|
xyz[0] = 1.12f * xyz[0] - .12f * xyz[2];
|
|
XyzToLab(lab, xyz);
|
|
e = (+lab[1] * cosf(DEG(50)) + lab[2] * sinf(DEG(50)));
|
|
f = (-lab[1] * sinf(DEG(50)) + lab[2] + cosf(DEG(50))) * 1.14;
|
|
G = sqrtf(SQR(e) + SQR(f));
|
|
C = logf(1 + .06 * G) * 22.5f;
|
|
h = atan2f(f, e) + DEG(50);
|
|
h = fmodf(h, DEG(360));
|
|
if (h < 0) h += DEG(360);
|
|
din99d[0] = MIN(255, MAX(0, 325.22f * logf(1 + .0036 * lab[0]) * 2.5f));
|
|
din99d[1] = MIN(255, MAX(0, C * cos(h) * 2.5f + 128));
|
|
din99d[2] = MIN(255, MAX(0, C * sin(h) * 2.5f + 128));
|
|
}
|
|
|
|
static void AddColor(int bg, int fg, int rune, int r, int g, int b, int x) {
|
|
int i;
|
|
if (blends.i) {
|
|
for (i = blends.i; --i;) {
|
|
if (blends.p[i].color[0] == r && blends.p[i].color[1] == g &&
|
|
blends.p[i].color[2] == b) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
CHECK_NE(
|
|
-1,
|
|
APPEND(&blends.p, &blends.i, &blends.n,
|
|
(&(struct Blend){
|
|
.bg = bg, .fg = fg, .rune = rune, .color = {r, g, b, x}})));
|
|
}
|
|
|
|
static void AddBlend(int bgxterm, int fgxterm, int rune, int shade,
|
|
const unsigned char bgrgb[4],
|
|
const unsigned char fgrgb[4]) {
|
|
AddColor(bgxterm, fgxterm, rune, twixt8(bgrgb[0], fgrgb[0], shade),
|
|
twixt8(bgrgb[1], fgrgb[1], shade), twixt8(bgrgb[2], fgrgb[2], shade),
|
|
0);
|
|
}
|
|
|
|
static void MakeBlends(const unsigned char palette[16][3]) {
|
|
int r, i, j, k;
|
|
double rgb[3], xyz[3];
|
|
unsigned char tab[256][4];
|
|
unsigned char cube[6] = {0, 0137, 0207, 0257, 0327, 0377};
|
|
unsigned char seqs[2][2] = {{16, 255}};
|
|
for (i = 0; i < 16; ++i) {
|
|
tab[i][0] = palette[i][0];
|
|
tab[i][1] = palette[i][1];
|
|
tab[i][2] = palette[i][2];
|
|
tab[i][3] = i;
|
|
}
|
|
for (i = 16; i < 232; ++i) {
|
|
tab[i][0] = cube[((i - 020) / 044) % 6];
|
|
tab[i][1] = cube[((i - 020) / 6) % 6];
|
|
tab[i][2] = cube[(i - 020) % 6];
|
|
tab[i][3] = i;
|
|
}
|
|
for (i = 232; i < 256; ++i) {
|
|
tab[i][0] = 8 + (i - 232) * 10;
|
|
tab[i][1] = 8 + (i - 232) * 10;
|
|
tab[i][2] = 8 + (i - 232) * 10;
|
|
tab[i][3] = i;
|
|
}
|
|
#if DIN99
|
|
for (i = 0; i < 256; ++i) {
|
|
LoadRgb(rgb, tab[i]);
|
|
vmatmul3(xyz, rgb, COLORSPACE_TTY);
|
|
XyzToDin99d(tab[i], xyz);
|
|
}
|
|
#endif
|
|
for (r = 0; r < ARRAYLEN(seqs); ++r) {
|
|
for (i = seqs[r][0]; i <= seqs[r][1]; ++i) {
|
|
for (j = seqs[r][0]; j <= seqs[r][1]; ++j) {
|
|
if (i == j) {
|
|
AddColor(i, 0, 0, tab[i][0], tab[i][1], tab[i][2], 0);
|
|
} else {
|
|
for (k = 0; k < ARRAYLEN(kShadeRune); ++k) {
|
|
AddBlend(i, j, kShadeRune[k], kShadePct[k], tab[i], tab[j]);
|
|
AddBlend(j, i, kShadeRune[k], kShadePct[k], tab[j], tab[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static int PickColor(unsigned char color[3]) {
|
|
int i, best, least, dist;
|
|
best = -1;
|
|
least = INT_MAX;
|
|
for (i = 0; i < blends.i; ++i) {
|
|
dist = SQR(blends.p[i].color[0] - color[0]) +
|
|
SQR(blends.p[i].color[1] - color[1]) +
|
|
SQR(blends.p[i].color[2] - color[2]);
|
|
if (dist < least) {
|
|
least = dist;
|
|
best = i;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
static void SetBg(int x) {
|
|
if (0 <= x && x < 16) {
|
|
if (x < 8) {
|
|
x += 40;
|
|
} else {
|
|
x -= 8;
|
|
x *= 100;
|
|
}
|
|
printf("\e[%dm", x);
|
|
} else {
|
|
/* assert(false); */
|
|
printf("\e[48;5;%dm", x);
|
|
}
|
|
}
|
|
|
|
static void SetFg(int x) {
|
|
if (0 <= x && x < 16) {
|
|
if (x < 8) {
|
|
x += 30;
|
|
} else {
|
|
x -= 8;
|
|
x *= 90;
|
|
}
|
|
printf("\e[%dm", x);
|
|
} else {
|
|
/* assert(false); */
|
|
printf("\e[38;5;%dm", x);
|
|
}
|
|
}
|
|
|
|
static void PrintImage(long yn, long xn, unsigned char src[4][yn][xn]) {
|
|
int y, x, c;
|
|
double rgb[3], xyz[3];
|
|
unsigned char din99d[3];
|
|
for (y = 0; y < yn; ++y) {
|
|
printf("\n");
|
|
for (x = 0; x < xn; ++x) {
|
|
din99d[0] = src[0][y][x];
|
|
din99d[1] = src[1][y][x];
|
|
din99d[2] = src[2][y][x];
|
|
#if DIN99
|
|
LoadRgb(rgb, din99d);
|
|
vmatmul3(xyz, rgb, COLORSPACE_SRC);
|
|
XyzToDin99d(din99d, xyz);
|
|
#endif
|
|
c = PickColor(din99d);
|
|
if (blends.p[c].rune) {
|
|
SetBg(blends.p[c].bg);
|
|
SetFg(blends.p[c].fg);
|
|
printf("%hc", blends.p[c].rune);
|
|
} else {
|
|
SetBg(blends.p[c].bg);
|
|
printf(" ");
|
|
}
|
|
}
|
|
printf("\e[0m");
|
|
}
|
|
printf("\r");
|
|
}
|
|
|
|
static void *Deinterlace(long dcn, long dyn, long dxn,
|
|
unsigned char dst[dcn][dyn][dxn], long syw, long sxw,
|
|
long scw, const unsigned char src[syw][sxw][scw],
|
|
long syn, long sxn, long sy0, long sx0, long sc0) {
|
|
long y, x, c;
|
|
for (y = 0; y < dyn; ++y) {
|
|
for (x = 0; x < dxn; ++x) {
|
|
for (c = 0; c < dcn; ++c) {
|
|
dst[c][y][x] = src[sy0 + y][sx0 + x][sc0 + c];
|
|
}
|
|
}
|
|
}
|
|
return dst;
|
|
}
|
|
|
|
static void GetTermSize(int out_rows[1], int out_cols[1]) {
|
|
struct winsize ws;
|
|
ws.ws_row = 24;
|
|
ws.ws_col = 80;
|
|
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) == -1) {
|
|
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
|
|
}
|
|
out_rows[0] = ws.ws_row;
|
|
out_cols[0] = ws.ws_col;
|
|
}
|
|
|
|
static void ProcessImage(long syn, long sxn, unsigned char img[syn][sxn][4]) {
|
|
int dyn, dxn;
|
|
double ry, rx;
|
|
GetTermSize(&dyn, &dxn);
|
|
ry = syn, rx = sxn;
|
|
ry /= dyn, rx /= dxn;
|
|
PrintImage(
|
|
dyn, dxn,
|
|
EzGyarados(4, dyn, dxn, gc(xmemalign(32, dyn * dxn * 4)), 4, syn, sxn,
|
|
Deinterlace(4, syn, sxn, gc(xmemalign(32, syn * sxn * 4)), syn,
|
|
sxn, 4, img, syn, sxn, 0, 0, 0),
|
|
0, 4, dyn, dxn, syn, sxn, ry, rx, 0, 0));
|
|
}
|
|
|
|
static void WithImageFile(const char *path,
|
|
void fn(long yn, long xn,
|
|
unsigned char src[yn][xn][4])) {
|
|
struct stat st;
|
|
void *map, *data;
|
|
int fd, yn, xn, cn;
|
|
CHECK_NE(-1, (fd = open(path, O_RDONLY)), "%s", path);
|
|
CHECK_NE(-1, fstat(fd, &st));
|
|
CHECK_GT(st.st_size, 0);
|
|
CHECK_LE(st.st_size, INT_MAX);
|
|
CHECK_NE(MAP_FAILED,
|
|
(map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0)));
|
|
CHECK_NOTNULL(
|
|
(data = stbi_load_from_memory(map, st.st_size, &xn, &yn, NULL, 4)), "%s",
|
|
path);
|
|
CHECK_NE(-1, munmap(map, st.st_size));
|
|
CHECK_NE(-1, close(fd));
|
|
fn(yn, xn, data);
|
|
free(data);
|
|
}
|
|
|
|
static void PrintData(void) {
|
|
int i;
|
|
for (i = 0; i < blends.i; ++i) {
|
|
printf("%3d %3d %3d %3d %3d %d\n", blends.p[i].color[0],
|
|
blends.p[i].color[1], blends.p[i].color[2], blends.p[i].bg,
|
|
blends.p[i].fg, blends.p[i].rune);
|
|
}
|
|
}
|
|
|
|
void *OpenRgbMap(const char *path) {
|
|
int fd;
|
|
void *map;
|
|
size_t size;
|
|
size = 256 * 256 * 256 * 3;
|
|
CHECK_NE(-1, (fd = open(path, O_RDWR | O_CREAT | O_TRUNC, 0644)));
|
|
CHECK_NE(-1, ftruncate(fd, size));
|
|
CHECK_NE(MAP_FAILED,
|
|
(map = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)));
|
|
CHECK_NE(-1, close(fd));
|
|
return map;
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
int i;
|
|
MakeBlends(kCga);
|
|
for (i = 1; i < argc; ++i) {
|
|
WithImageFile(argv[i], ProcessImage);
|
|
}
|
|
return 0;
|
|
}
|