about summary refs log tree commit diff
diff options
context:
space:
mode:
authorsternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-12-14 19:01:53 +0100
committersternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org>2020-12-14 19:13:21 +0100
commit7f995c6bc6ee9c9f44a7b6c512551da09ba0d728 (patch)
tree3c5a2cd63179e5fb1e920647e459074c1ae2010c
parent3dbf58d50dd977ab165c365fc6a932f6d36ea159 (diff)
feat(bitmap): add bs_view_bitarray producing flipdot binary format
Also adds bs-renderflipdot.exe a demo binary which is able to render
text on a flipdot display.
-rw-r--r--Makefile2
-rw-r--r--bitmap.c37
-rw-r--r--bs-renderflipdot.c216
-rw-r--r--include/buchstabensuppe/bitmap.h21
4 files changed, 275 insertions, 1 deletions
diff --git a/Makefile b/Makefile
index 7aefdc9..e4df115 100644
--- a/Makefile
+++ b/Makefile
@@ -5,7 +5,7 @@ INCDIR ?= $(PREFIX)/include
 
 .PHONY: all clean install check
 all:
-	redo demo.exe libbuchstabensuppe.a
+	redo bs-renderflipdot.exe demo.exe libbuchstabensuppe.a
 
 clean:
 	rm -f *.o *.a *.exe third_party/stb_truetype.o
diff --git a/bitmap.c b/bitmap.c
index d22fc10..5920d65 100644
--- a/bitmap.c
+++ b/bitmap.c
@@ -153,6 +153,43 @@ void bs_bitmap_print(bs_bitmap_t bitmap, bool binary) {
   }
 }
 
+uint8_t *bs_view_bitarray(bs_view_t view, size_t *size) {
+  int view_max_y = view.bs_view_offset_y + view.bs_view_height;
+  int view_max_x = view.bs_view_offset_x + view.bs_view_width;
+
+  *size = ceil((view.bs_view_height * view.bs_view_width) / 8);
+  uint8_t *array = malloc(sizeof(uint8_t) * (*size));
+  size_t array_pos = 0;
+
+  if(array == NULL) {
+    *size = 0;
+    return NULL;
+  }
+
+  for(int y = view.bs_view_offset_y; y < view_max_y; y++) {
+    for(int x = view.bs_view_offset_x; x < view_max_x; x = x + 8) {
+      uint8_t byte = 0;
+      int max_i = MIN(8, view_max_x - x);
+
+      for(int i = 0; i < max_i; i++) {
+        // reduce pixel to a single bit works regardless of monospace and
+        // grayscale bitmaps -- however grayscale bitmaps are not converted
+        // on the fly TODO
+        uint8_t pixel_val = bs_bitmap_get(view.bs_view_bitmap, x + i, y) > 0;
+        byte |= pixel_val << (7 - i);
+      }
+
+      array[array_pos++] = byte;
+
+      if(array_pos >= *size) {
+        return array;
+      }
+    }
+  }
+
+  return array;
+}
+
 unsigned char bs_pixel_invert_binary(unsigned char p) {
   return !p;
 }
diff --git a/bs-renderflipdot.c b/bs-renderflipdot.c
new file mode 100644
index 0000000..77e1e17
--- /dev/null
+++ b/bs-renderflipdot.c
@@ -0,0 +1,216 @@
+#define _POSIX_C_SOURCE 200112L /* getopt, getaddrinfo, ... */
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <buchstabensuppe.h>
+
+#include "util.h"
+
+#define DEFAULT_FONT_SIZE 16
+
+#define FLIPDOT_WIDTH 80
+#define FLIPDOT_HEIGHT 16
+
+void print_error(const char *name, const char *err) {
+  fputs(name, stderr);
+  fputs(": ", stderr);
+  fputs(err, stderr);
+  fputc('\n', stderr);
+}
+
+void print_usage(const char *name) {
+  print_error(name, "[-h HOST] [-p PORT] [-s FONTSIZE] [-f FONTPATH [-f FONTPATH ...]] [-i] [-n] TEXT");
+  print_error(name, "-?");
+}
+
+bool send_bitarray(const char *host, const char *port, uint8_t *bits, ssize_t bits_size, const char *progname) {
+  struct addrinfo *addrs;
+  struct addrinfo hints;
+
+  memset(&hints, 0, sizeof(hints));
+
+  // TODO: seems like flipdot impls are v4 only
+  hints.ai_family = AF_INET;
+  hints.ai_socktype = SOCK_DGRAM;
+  hints.ai_flags = AI_NUMERICSERV;
+
+  if(getaddrinfo(host, port, &hints, &addrs) != 0) {
+    print_error(progname, "could not look up target host");
+    return false;
+  }
+
+  if(addrs == NULL) {
+    return false;
+  }
+
+  int sockfd = socket(addrs->ai_family, SOCK_DGRAM, IPPROTO_UDP);
+
+  if(sockfd < 0) {
+    print_error(progname, "could not create socket");
+    freeaddrinfo(addrs);
+    return false;
+  }
+
+  bool result = sendto(sockfd, bits, bits_size, 0,
+    addrs->ai_addr, addrs->ai_addrlen) == bits_size;
+
+  close(sockfd);
+
+  freeaddrinfo(addrs);
+
+  return result;
+}
+
+int main(int argc, char **argv) {
+  const char *port = "2323";
+  const char *host = "localhost";
+  const char *text;
+  int font_size = DEFAULT_FONT_SIZE;
+
+  int opt;
+  int fontcount = 0;
+
+  bool dry_run = false;
+  bool invert = false;
+
+  bs_context_t ctx;
+  bs_context_init(&ctx);
+
+  ctx.bs_rendering_flags = BS_RENDER_BINARY;
+
+  bool opterr = false;
+
+  while(!opterr && (opt = getopt(argc, argv, "inh:p:s:f:?")) != -1) {
+    switch(opt) {
+      case 'i':
+        invert = true;
+        break;
+      case 'n':
+        dry_run = true;
+        break;
+      case 'h':
+        host = optarg;
+        break;
+      case 'p':
+        errno = 0;
+        port = optarg;
+        break;
+      case 's':
+        errno = 0;
+        font_size = atoi(optarg);
+        if(errno != 0 || font_size <= 0) {
+          print_error(argv[0], "font size passed is not an integer");
+          opterr = true;
+        }
+        break;
+      case 'f':
+        if(font_size == DEFAULT_FONT_SIZE) {
+          print_error(argv[0], "warning: no font size specified, using default");
+        }
+
+        if(bs_add_font(&ctx, optarg, 0, font_size)) {
+          fontcount++;
+        } else {
+          print_error(argv[0], "warning: could not add font");
+        }
+
+        break;
+      case '?':
+        print_usage(argv[0]);
+        fputc('\n', stderr);
+        fputs("  -h    hostname of the flipdots to use (default: localhost)\n", stderr);
+        fputs("  -p    port of the flipdots to use (default: 2323)\n", stderr);
+        fputs("  -f    font to use, can be specified multiple times, fallback in given order\n", stderr);
+        fputs("  -s    font size to use, must be specified before font(s) (default: 16)\n", stderr);
+        fputs("  -i    invert the bitmap (so text is black on white)\n", stderr);
+        fputs("  -n    dry run: only print the picture, don't send it\n", stderr);
+        fputs("  -?    display this help screen\n", stderr);
+        bs_context_free(&ctx);
+        return 0;
+        break;
+      default:
+        opterr = true;
+        break;
+    }
+  }
+
+  if(optind >= argc) {
+    opterr = true;
+    print_error(argv[0], "missing TEXT argument");
+  }
+
+  if(opterr) {
+    bs_context_free(&ctx);
+    print_usage(argv[0]);
+    return 1;
+  }
+
+  if(fontcount <= 0) {
+    bs_context_free(&ctx);
+    print_error(argv[0], "no valid fonts specified");
+    return 1;
+  }
+
+  int status = 0;
+
+  text = argv[optind];
+  size_t text_len = strlen(text);
+
+  bs_bitmap_t bitmap = bs_render_utf8_string(&ctx, text, text_len);
+
+  if(!dry_run) {
+    puts("Rendered image:");
+  }
+
+  if(invert) {
+    bs_bitmap_map(bitmap, bs_pixel_invert_binary);
+  }
+
+  if(!dry_run && (bitmap.bs_bitmap_width < FLIPDOT_WIDTH ||
+      bitmap.bs_bitmap_height < FLIPDOT_HEIGHT)) {
+    bs_bitmap_extend(&bitmap,
+      MAX(bitmap.bs_bitmap_width, FLIPDOT_WIDTH),
+      MAX(bitmap.bs_bitmap_height, FLIPDOT_HEIGHT),
+      invert);
+  }
+
+  bs_bitmap_print(bitmap, true);
+
+  if(!dry_run) {
+    printf("Sending image to %s:%s\n", host, port);
+
+    bs_view_t view;
+    view.bs_view_bitmap = bitmap;
+    view.bs_view_offset_x = 0;
+    view.bs_view_offset_y = 0;
+    view.bs_view_width = FLIPDOT_WIDTH;
+    view.bs_view_height = FLIPDOT_HEIGHT;
+
+    size_t size;
+    uint8_t *bits = bs_view_bitarray(view, &size);
+
+    if(size > 0) {
+      if(!send_bitarray(host, port, bits, (ssize_t) size, argv[0])) {
+        status = 1;
+      }
+    } else {
+      print_error(argv[0], "bs_view_bitarray failed");
+      status = 1;
+    }
+
+    if(bits != NULL) {
+      free(bits);
+    }
+  }
+
+  bs_bitmap_free(&bitmap);
+  bs_context_free(&ctx);
+
+  return status;
+}
diff --git a/include/buchstabensuppe/bitmap.h b/include/buchstabensuppe/bitmap.h
index 2ed9ba2..d17b82b 100644
--- a/include/buchstabensuppe/bitmap.h
+++ b/include/buchstabensuppe/bitmap.h
@@ -6,6 +6,7 @@
 #define BUCHSTABENSUPPE_BITMAP_H
 
 #include <stdbool.h>
+#include <stdint.h>
 
 /*!
  * @brief 8-bit bitmap
@@ -108,6 +109,26 @@ void bs_bitmap_copy(bs_bitmap_t destination, int offset_x,
  */
 void bs_bitmap_print(bs_bitmap_t bitmap, bool binary_image);
 
+/*
+ * @brief Compact a binary bitmap
+ *
+ * This converts a binary bitmap into a more compact
+ * representation which every byte stores 8 pixels
+ * instead of just 1. The highest bit represents the
+ * pixel that comes first in the bitmap.
+ *
+ * The resulting format is precisely what the
+ * [Flidpot UDP protocol](https://wiki.openlab-augsburg.de/Flipdots#per-udp-schnittstelle)
+ * requires.
+ *
+ * `view` describes the bitmap to be used and the area
+ * of it. `size` will hold the length of the returned array.
+ *
+ * On error `NULL` is returned and `size` is 0. The allocated
+ * memory must be freed by the caller.
+ */
+uint8_t *bs_view_bitarray(bs_view_t view, size_t *size);
+
 /*!
  * @name Bitmap Processing
  * @{