cosmopolitan/tool/build/img2code.c

268 lines
8.3 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/scale/scale.h"
#include "libc/calls/calls.h"
#include "libc/calls/struct/stat.h"
#include "libc/conv/conv.h"
#include "libc/fmt/bing.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/runtime.h"
#include "libc/stdio/stdio.h"
#include "libc/str/str.h"
#include "libc/sysv/consts/ex.h"
#include "libc/sysv/consts/exit.h"
#include "libc/sysv/consts/map.h"
#include "libc/sysv/consts/o.h"
#include "libc/sysv/consts/prot.h"
#include "libc/testlib/testlib.h"
#include "third_party/dtoa/dtoa.h"
#include "third_party/getopt/getopt.h"
#include "third_party/stb/stb_image.h"
#include "third_party/xed/x86.h"
#define USAGE \
" [FLAGS] [PATH]\n\
\n\
Example:\n\
\n\
img2code -cw79 -p2 foo.png\n\
\n\
Flags:\n\
-c compress range\n\
-o PATH out\n\
-r FLEX ratio\n\
-w FLEX new width\n\
-h FLEX new height\n\
-p FLEX pixel y ratio\n\
-? shows this information\n\
\n"
static struct Flags {
const char *o;
double r, p;
int w, h;
bool c;
} flags_ = {
.o = "-",
.p = 1,
};
static noreturn void PrintUsage(int rc, FILE *f) {
fprintf(f, "Usage: %s%s", program_invocation_name, USAGE);
exit(rc);
}
static void GetOpts(int *argc, char *argv[]) {
int opt;
if (*argc == 2 &&
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0)) {
PrintUsage(EXIT_SUCCESS, stdout);
}
while ((opt = getopt(*argc, argv, "?co:r:w:h:p:")) != -1) {
switch (opt) {
case 'c':
flags_.c = true;
break;
case 'o':
flags_.o = optarg;
break;
case 'r':
flags_.r = strtod(optarg, NULL);
break;
case 'p':
flags_.p = strtod(optarg, NULL);
break;
case 'w':
flags_.w = strtol(optarg, NULL, 0);
break;
case 'h':
flags_.h = strtol(optarg, NULL, 0);
break;
case '?':
PrintUsage(EXIT_SUCCESS, stdout);
default:
PrintUsage(EX_USAGE, stderr);
}
}
if (optind == *argc) {
argv[(*argc)++] = "-";
}
}
static void GetOutputGeometry(long syn, long sxn, long *out_dyn, long *out_dxn,
double *out_ry, double *out_rx) {
double ry, rx;
long dyn, dxn;
if (!flags_.h && !flags_.w) {
if (flags_.r) {
ry = flags_.r * flags_.p;
rx = flags_.r;
} else {
ry = flags_.p;
rx = 1;
}
dyn = round(syn / ry);
dxn = round(sxn / rx);
} else if (flags_.w && !flags_.h) {
if (flags_.r) {
rx = 1. * sxn / flags_.w;
ry = flags_.r * flags_.p;
dxn = flags_.w;
dyn = round(syn / ry);
} else {
rx = 1. * sxn / flags_.w;
ry = flags_.p * rx;
dxn = flags_.w;
dyn = round(syn / ry);
}
} else if (flags_.h && !flags_.w) {
if (flags_.r) {
rx = flags_.r;
ry = flags_.p * syn / flags_.h;
dxn = flags_.w;
dyn = round(syn / ry);
} else {
ry = flags_.p * syn / flags_.h;
rx = ry;
dyn = flags_.h;
dxn = round(syn / rx);
}
} else {
ry = flags_.p;
rx = 1;
dyn = round(flags_.h / ry);
dxn = flags_.w;
}
*out_dyn = dyn;
*out_dxn = dxn;
*out_ry = ry;
*out_rx = rx;
}
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 CompressRange(long cn, long yn, long xn,
unsigned char img[cn][yn][xn]) {
static const char R[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
double f;
long c, y, x, L, H;
L = R[0], H = R[strlen(R) - 1];
for (c = 0; c < cn; ++c) {
for (y = 0; y < yn; ++y) {
for (x = 0; x < xn; ++x) {
f = img[c][y][x];
f /= 255;
f *= H - L;
f += L;
img[c][y][x] = MIN(H, MAX(L, lround(f)));
}
}
}
}
static void PrintCode(FILE *f, long cn, long yn, long xn,
unsigned char img[cn][yn][xn]) {
long c, y, x;
if (flags_.c) CompressRange(cn, yn, xn, img);
for (c = 0; c < cn; ++c) {
fputc('\n', f);
for (y = 0; y < yn; ++y) {
fputc('\n', f);
for (x = 0; x < xn; ++x) {
fputwc(bing(img[c][y][x], 0), f);
}
}
}
fputc('\n', f);
}
static void ProcessImage(const char *path, long scn, long syn, long sxn,
unsigned char img[syn][sxn][scn], FILE *f) {
double ry, rx;
long dyn, dxn, cn;
GetOutputGeometry(syn, sxn, &dyn, &dxn, &ry, &rx);
if (dyn == syn && dxn == sxn) {
/* TODO(jart): Why doesn't Gyarados no-op? */
PrintCode(f, scn, dyn, dxn,
Deinterlace(scn, syn, sxn, gc(memalign(32, scn * syn * sxn)), syn,
sxn, scn, img, syn, sxn, 0, 0, 0));
} else {
PrintCode(
f, scn, dyn, dxn,
EzGyarados(scn, dyn, dxn, gc(memalign(32, scn * dyn * dxn)), scn, syn,
sxn,
Deinterlace(scn, syn, sxn, gc(memalign(32, scn * syn * sxn)),
syn, sxn, scn, img, syn, sxn, 0, 0, 0),
0, scn, dyn, dxn, syn, sxn, ry, rx, 0, 0));
}
}
static void WithImageFile(const char *path, FILE *f,
void fn(const char *path, long cn, long yn, long xn,
unsigned char src[yn][xn][cn], FILE *f)) {
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, &cn, 0)), "%s",
path);
CHECK_NE(-1, munmap(map, st.st_size));
CHECK_NE(-1, close(fd));
fn(path, cn, yn, xn, data, f);
free(data);
}
int main(int argc, char *argv[]) {
int i;
FILE *f;
showcrashreports();
GetOpts(&argc, argv);
stbi_set_unpremultiply_on_load(true);
CHECK_NOTNULL((f = fopen(flags_.o, "w")));
for (i = optind; i < argc; ++i) {
WithImageFile(argv[i], f, ProcessImage);
}
return fclose(f);
}