printimage: keep aspect ratio when scaling to fit (#479)

When `printimage` is invoked without `-w` or `-h`, we now preserve the
aspect ratio of each input image when scaling it to fit in the window.
A new flag `-i` ignores the aspect ratio, recovering the old behavior
when neither `-w` nor `-h` is passed. When `-i` is passed alongside
exactly one of `-w` or `-h`, the other dimension is just taken from the
window size, ignoring aspect ratio.

We also unconditionally print a newline between images to prevent them
from overlapping.

wchargin-branch: printimage-fit
wchargin-source: 4c2cfcffe9ce1a3b30c0ff051e3c6a2c166ae1c7
This commit is contained in:
William Chargin 2022-07-11 15:58:21 -07:00 committed by GitHub
parent 9308463b0f
commit 0272f638a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -52,15 +52,17 @@ static struct Flags {
bool dither; bool dither;
bool ruler; bool ruler;
bool magikarp; bool magikarp;
bool trailingnewline;
long half; long half;
bool full; bool full;
bool ignoreaspect;
long width; long width;
long height; long height;
enum TtyBlocksSelection blocks; enum TtyBlocksSelection blocks;
enum TtyQuantizationAlgorithm quant; enum TtyQuantizationAlgorithm quant;
} g_flags; } g_flags;
struct winsize g_winsize;
static wontreturn void PrintUsage(int rc, FILE *f) { static wontreturn void PrintUsage(int rc, FILE *f) {
fprintf(f, "Usage: %s%s", program_invocation_name, "\ fprintf(f, "Usage: %s%s", program_invocation_name, "\
[FLAGS] [PATH]\n\ [FLAGS] [PATH]\n\
@ -70,12 +72,13 @@ FLAGS\n\
-o PATH output path\n\ -o PATH output path\n\
-w INT manual width\n\ -w INT manual width\n\
-h INT manual height\n\ -h INT manual height\n\
-f display full size\n\
-i ignore aspect ratio\n\
-4 unicode blocks\n\ -4 unicode blocks\n\
-a ansi color mode\n\ -a ansi color mode\n\
-t true color mode\n\ -t true color mode\n\
-2 use half blocks\n\ -2 use half blocks\n\
-3 ibm cp437 blocks\n\ -3 ibm cp437 blocks\n\
-f display full size\n\
-s unsharp sharpening\n\ -s unsharp sharpening\n\
-x xterm256 color mode\n\ -x xterm256 color mode\n\
-m use magikarp scaling\n\ -m use magikarp scaling\n\
@ -104,14 +107,13 @@ static int ParseNumberOption(const char *arg) {
static void GetOpts(int *argc, char *argv[]) { static void GetOpts(int *argc, char *argv[]) {
int opt; int opt;
struct winsize ws;
g_flags.quant = kTtyQuantTrue; g_flags.quant = kTtyQuantTrue;
g_flags.blocks = IsWindows() ? kTtyBlocksCp437 : kTtyBlocksUnicode; g_flags.blocks = IsWindows() ? kTtyBlocksCp437 : kTtyBlocksUnicode;
if (*argc == 2 && if (*argc == 2 &&
(strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0)) { (strcmp(argv[1], "--help") == 0 || strcmp(argv[1], "-help") == 0)) {
PrintUsage(EXIT_SUCCESS, stdout); PrintUsage(EXIT_SUCCESS, stdout);
} }
while ((opt = getopt(*argc, argv, "?vpmfrtxads234o:w:h:")) != -1) { while ((opt = getopt(*argc, argv, "?vpmfirtxads234o:w:h:")) != -1) {
switch (opt) { switch (opt) {
case 'o': case 'o':
g_flags.out = optarg; g_flags.out = optarg;
@ -123,16 +125,17 @@ static void GetOpts(int *argc, char *argv[]) {
g_flags.unsharp = true; g_flags.unsharp = true;
break; break;
case 'w': case 'w':
g_flags.trailingnewline = true;
g_flags.width = ParseNumberOption(optarg); g_flags.width = ParseNumberOption(optarg);
break; break;
case 'h': case 'h':
g_flags.trailingnewline = true;
g_flags.height = ParseNumberOption(optarg); g_flags.height = ParseNumberOption(optarg);
break; break;
case 'f': case 'f':
g_flags.full = true; g_flags.full = true;
break; break;
case 'i':
g_flags.ignoreaspect = true;
break;
case '2': case '2':
g_flags.half = true; g_flags.half = true;
break; break;
@ -169,14 +172,11 @@ static void GetOpts(int *argc, char *argv[]) {
PrintUsage(EX_USAGE, stderr); PrintUsage(EX_USAGE, stderr);
} }
} }
if (!g_flags.full && (!g_flags.width && !g_flags.height)) { g_winsize.ws_col = 80;
ws.ws_col = 80; g_winsize.ws_row = 24;
ws.ws_row = 24; if (!g_flags.full && (!g_flags.width || !g_flags.height)) {
if (ioctl(STDIN_FILENO, TIOCGWINSZ, &ws) != -1 || ioctl(STDIN_FILENO, TIOCGWINSZ, &g_winsize) != -1 ||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) != -1) { ioctl(STDOUT_FILENO, TIOCGWINSZ, &g_winsize);
g_flags.width = ws.ws_col * (1 + !g_flags.half);
g_flags.height = ws.ws_row * 2;
}
} }
ttyquantsetup(g_flags.quant, kTtyQuantRgb, g_flags.blocks); ttyquantsetup(g_flags.quant, kTtyQuantRgb, g_flags.blocks);
} }
@ -335,9 +335,7 @@ static void PrintImageSerious(long yn, long xn, unsigned char RGB[3][yn][xn],
} }
} }
p = ttyraster(vt, (void *)TTY, tyn, txn, bg, fg); p = ttyraster(vt, (void *)TTY, tyn, txn, bg, fg);
*p++ = '\r'; p = stpcpy(p, "\e[0m\r\n");
if (g_flags.trailingnewline) *p++ = '\n';
p = stpcpy(p, "\e[0m");
ttywrite(STDOUT_FILENO, vt, p - vt); ttywrite(STDOUT_FILENO, vt, p - vt);
} }
@ -363,7 +361,7 @@ void WithImageFile(const char *path,
void fn(long yn, long xn, unsigned char RGB[3][yn][xn])) { void fn(long yn, long xn, unsigned char RGB[3][yn][xn])) {
struct stat st; struct stat st;
void *map, *data, *data2; void *map, *data, *data2;
int fd, yn, xn, cn, dyn, dxn, syn, sxn; int fd, yn, xn, cn, dyn, dxn, syn, sxn, wyn, wxn;
CHECK_NE(-1, (fd = open(path, O_RDONLY)), "%s", path); CHECK_NE(-1, (fd = open(path, O_RDONLY)), "%s", path);
CHECK_NE(-1, fstat(fd, &st)); CHECK_NE(-1, fstat(fd, &st));
CHECK_GT(st.st_size, 0); CHECK_GT(st.st_size, 0);
@ -385,16 +383,28 @@ void WithImageFile(const char *path,
data, 0, yn, 0, xn); data, 0, yn, 0, xn);
cn = 3; cn = 3;
} }
if (g_flags.height || g_flags.width) { if (!g_flags.full) {
syn = yn; syn = yn;
sxn = xn; sxn = xn;
dyn = g_flags.height; dyn = g_flags.height;
dxn = g_flags.width; dxn = g_flags.width;
if (dyn && !dxn) { wyn = g_winsize.ws_row * 2;
dxn = dyn * xn * (1 + !g_flags.half) / yn; wxn = g_winsize.ws_col;
if (g_flags.ignoreaspect) {
if (!dyn) dyn = wyn;
if (!dxn) dxn = wxn * (1 + !g_flags.half);
} }
if (dxn && !dyn) { if (!dyn && !dxn) {
dyn = dxn * yn / (xn * (1 + !g_flags.half)); if (sxn * wyn > syn * wxn) {
dxn = wxn * (1 + !g_flags.half);
} else {
dyn = wyn;
}
}
if (dyn && !dxn) {
dxn = dyn * sxn * (1 + !g_flags.half) / syn;
} else if (dxn && !dyn) {
dyn = dxn * syn / (sxn * (1 + !g_flags.half));
} }
if (g_flags.magikarp) { if (g_flags.magikarp) {
while (HALF(syn) > dyn || HALF(sxn) > dxn) { while (HALF(syn) > dyn || HALF(sxn) > dxn) {