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
(HTM) 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);
+ }
+ }
+ }
+}