diff options
author | sternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org> | 2020-12-14 19:01:53 +0100 |
---|---|---|
committer | sternenseemann <0rpkxez4ksa01gb3typccl0i@systemli.org> | 2020-12-14 19:13:21 +0100 |
commit | 7f995c6bc6ee9c9f44a7b6c512551da09ba0d728 (patch) | |
tree | 3c5a2cd63179e5fb1e920647e459074c1ae2010c | |
parent | 3dbf58d50dd977ab165c365fc6a932f6d36ea159 (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-- | Makefile | 2 | ||||
-rw-r--r-- | bitmap.c | 37 | ||||
-rw-r--r-- | bs-renderflipdot.c | 216 | ||||
-rw-r--r-- | include/buchstabensuppe/bitmap.h | 21 |
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 * @{ |