Add vtv-from-ff. - vtv-tools - virtual terminal video tools DIR Log DIR Files DIR Refs DIR Tags DIR README DIR LICENSE --- DIR commit 98dc53ecd26b97ddee7326e7161cabdc2ebdaacf DIR parent a5e70d7b2d33c9277a613fc9e082d324371d0cfe HTML Author: Troels Henriksen <athas@sigkill.dk> Date: Mon, 14 Aug 2023 13:27:36 +0200 Add vtv-from-ff. Diffstat: A .gitignore | 1 + M Makefile | 9 +++++++-- A src/vtv-from-ff.c | 214 +++++++++++++++++++++++++++++++ 3 files changed, 222 insertions(+), 2 deletions(-) --- DIR diff --git a/.gitignore b/.gitignore @@ -0,0 +1 @@ +bin/vtv-from-ff DIR diff --git a/Makefile b/Makefile @@ -1,7 +1,12 @@ -# paths PREFIX ?= /usr/local -all: +CFLAGS?=-O -Wall -Wextra -pedantic +CC?=cc + +all: bin/vtv-from-ff + +bin/%: src/%.c + $(CC) -o $@ $< install: all @echo \# Installing executable files to ${PREFIX}/bin DIR diff --git a/src/vtv-from-ff.c b/src/vtv-from-ff.c @@ -0,0 +1,214 @@ +// Convert farbfeld image to vtv file. +// +// Can be run either in pipe mode or with file arguments. A file +// 'img.ff' is turned into a file 'img.vtv'. +// +// If you want to produce a vtv file that can be shown with +// vtv-player, the image should be 25 lines by 73 columns. + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> + +void def(FILE *f) { + fprintf(f, "\033[0m"); +} + +void fg_rgb(FILE *f, uint8_t r, uint8_t g, uint8_t b) { + fprintf(f, "\033[38;2;%d;%d;%dm", r, g, b); +} + +void bg_rgb(FILE *f, uint8_t r, uint8_t g, uint8_t b) { + fprintf(f, "\033[48;2;%d;%d;%dm", r, g, b); +} + +int read_be_uint16(FILE *f, uint16_t *x) { + uint8_t b; + + *x = 0; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x == (uint32_t)b << 8; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x += b; + + return 0; +} + +int read_be_uint32(FILE *f, uint32_t *x) { + uint8_t b; + + *x = 0; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x += (uint32_t)b << 24; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x += (uint32_t)b << 16; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x == (uint32_t)b << 8; + if (fread(&b, 1, 1, f) != 1) { return 1; } + *x += b; + + return 0; +} + +int load_ff(FILE *f, + uint16_t* *argbs_out, + uint32_t *width_out, + uint32_t *height_out) { + char magic[8]; + int ret = 1; + uint16_t* argbs = NULL; + uint32_t width = 0, height = 0; + + if (fread(magic, 1, 8, f) != 8) { + goto bad; + } + + if (memcmp(magic, "farbfeld", 8) != 0) { + goto bad; + } + + if (read_be_uint32(f, &width) != 0) { + goto bad; + } + + if (read_be_uint32(f, &height) != 0) { + goto bad; + } + + argbs = calloc(width*height*4, sizeof(uint16_t)); + + for (int i = 0; i < width; i++) { + for (int j = 0; j < height; j++) { + for (int l = 0; l < 4; l++) { + if (read_be_uint16(f, &argbs[i*(height*4)+(j*4)+l]) != 0) { + goto bad; + } + } + } + } + + *argbs_out = argbs; + *width_out = width; + *height_out = height; + return 0; + + bad: + free(argbs); + return 1; +} + +void render(int nrows, int ncols, const uint16_t *argbs, + uint32_t *fgs, uint32_t *bgs, char *chars) { + for (int i = 0; i < nrows; i++) { + for (int j = 0; j < ncols; j++) { + uint32_t r0 = argbs[(i*2)*(ncols*4)+j*4+0]; + uint32_t g0 = argbs[(i*2)*(ncols*4)+j*4+1]; + uint32_t b0 = argbs[(i*2)*(ncols*4)+j*4+2]; + uint32_t r1 = argbs[(i*2+1)*(ncols*4)+j*4+0]; + uint32_t g1 = argbs[(i*2+1)*(ncols*4)+j*4+1]; + uint32_t b1 = argbs[(i*2+1)*(ncols*4)+j*4+2]; + + uint32_t w0 = r0 << 16 | g0 << 8 | b0; + uint32_t w1 = r1 << 16 | g1 << 8 | b1; + fgs[i*ncols+j] = w0; + bgs[i*ncols+j] = w1; + chars[i*ncols+j] = 127; // Sentinel. + } + } +} + +void display(FILE *f, int nrows, int ncols, + const uint32_t *fgs, const uint32_t *bgs, const char *chars) { + for (int i = 0; i < nrows; i++) { + uint32_t prev_w0 = 0xdeadbeef; + uint32_t prev_w1 = 0xdeadbeef; + for (int j = 0; j < ncols; j++) { + double r0 = 0, g0 = 0, b0 = 0; + double r1 = 0, g1 = 0, b1 = 0; + uint32_t w0 = fgs[i*ncols+j]; + uint32_t w1 = bgs[i*ncols+j]; + if (w0 != prev_w0 || w1 != prev_w1) { + r0 = (w0>>16)&0xFF; + g0 = (w0>>8)&0xFF; + b0 = (w0>>0)&0xFF; + r1 = (w1>>16)&0xFF; + g1 = (w1>>8)&0xFF; + b1 = (w1>>0)&0xFF; + fg_rgb(f, r0, g0, b0); + bg_rgb(f, r1, g1, b1); + prev_w0 = w0; + prev_w1 = w1; + } + char c = chars[i*ncols+j]; + if (c == 127) { + fputs("▀", f); + } else { + fputc(c, f); + } + } + def(f); + fputc('\n', f); + } +} + +int convert(FILE *ff, FILE *vtv) { + uint32_t width, height; + uint16_t *argbs; + if (load_ff(ff, &argbs, &width, &height) != 0) { + return 1; + } + uint32_t *fgs = calloc(width*height, sizeof(uint32_t)); + uint32_t *bgs = calloc(width*height, sizeof(uint32_t)); + char *chars = calloc(width*height, sizeof(char)); + render(height, width, argbs, fgs, bgs, chars); + display(vtv, height/2, width, fgs, bgs, chars); + free(argbs); + free(fgs); + free(bgs); + free(chars); +} + +int main (int argc, char** argv) { + if (argc == 1) { + if (convert(stdin, stdout) != 0) { + fprintf(stderr, "%s: invalid farbfeld image on stdin.\n", argv[0]); + exit(1); + } + } else { + for (int i = 1; i < argc; i++) { + const char *ff_fname = argv[i]; + size_t len = strlen(ff_fname); + if (len >= 3 && strcmp(&ff_fname[len-3], ".ff") == 0) { + char *vtv_fname = malloc(len+2); + strncpy(vtv_fname, ff_fname, len-3); + strcpy(vtv_fname+len-3, ".vtv"); + printf("%s\n%s\n", ff_fname, vtv_fname); + FILE *ff = fopen(ff_fname, "r"); + if (ff == NULL) { + fprintf(stderr, "%s: could not open %s: %s\n", + argv[0], ff_fname, strerror(errno)); + } + FILE *vtv = fopen(vtv_fname, "w+"); + if (vtv == NULL) { + fprintf(stderr, "%s: could not open %s: %s\n", + argv[0], vtv_fname, strerror(errno)); + } + if (convert(ff, vtv) != 0) { + fprintf(stderr, "%s: invalid farbfeld image in %s.\n", + argv[0], ff_fname); + exit(1); + } + fclose(ff); + fclose(vtv); + free(vtv_fname); + } else { + fprintf(stderr, + "%s: argument %s does not have .ff extension.\n", + argv[0], ff_fname); + exit(1); + } + } + } +}