ref: 55134acef2cf72c82556521348d2917939faee1b
parent: f4a3042aada0c8012de84907baae3ab193032a43
author: Sigrid Solveig Haflínudóttir <[email protected]>
date: Wed Jul 19 19:42:36 EDT 2023
qoisend
--- /dev/null
+++ b/Makefile
@@ -1,0 +1,41 @@
+TARG=qoirecv
+DESTDIR?=
+PREFIX?=/usr/local
+BIN=${DESTDIR}${PREFIX}/bin
+MAN=${DESTDIR}${PREFIX}/share/man/man1
+SDL2_CFLAGS=$$(pkg-config --cflags sdl2)
+SDL2_LDFLAGS=$$(pkg-config --libs sdl2)
+CFLAGS?=-O2 -pipe -g -Wall
+CFLAGS+=${SDL2_CFLAGS}
+LDFLAGS?=
+LDFLAGS+=${SDL2_LDFLAGS}
+
+OBJS=\
+ parg.o\
+ qoirecv.o\
+
+.PHONY: all default install uninstall clean
+
+all: default
+
+default: ${TARG}
+
+install: ${TARG} ${TARG}.1
+ install -d ${BIN}
+ install -m 755 ${TARG} ${BIN}
+ install -d ${MAN}
+ install -m 644 ${TARG}.1 ${MAN}
+
+uninstall:
+ rm -f ${BIN}/${TARG}
+ rm -f ${MAN}/${TARG}.1
+
+${TARG}: ${OBJS}
+ ${CC} -o $@ ${OBJS} ${LDFLAGS}
+
+.SUFFIXES: .c .o
+.c.o:
+ ${CC} -o $@ -c $< ${CFLAGS}
+
+clean:
+ rm -f ${TARG} ${OBJS}
--- a/README.md
+++ b/README.md
@@ -6,4 +6,10 @@
like a cheap screen sharing. Each frame is lossless but smaller than
raw images (~620Kb vs ~8Mb for full hd, for example).
-Work in progress.
+`qoisend` is used on 9front side, `qoirecv` on Unix side. Default port is 12345.
+
+ # linux
+ $ qoirecv
+
+ # 9front
+ % video/qoisend /dev/screen | aux/trampoline tcp!unixhost!12345
--- a/main.c
+++ /dev/null
@@ -1,278 +1,0 @@
-#include <u.h>
-#include <libc.h>
-#include <thread.h>
-#include <tos.h>
-
-enum {
- Opind = 0x00,
- Opdif = 0x40,
- Oplum = 0x80,
- Oprun = 0xc0,
- Oprgb = 0xfe,
-};
-
-#define Nsec 1000000000ULL
-
-typedef struct Img Img;
-typedef union Pix Pix;
-
-struct Img {
- int w;
- int h;
- u8int bgrx[];
-};
-
-union Pix {
- struct {
- u8int r, g, b, a;
- };
- u32int v;
-};
-
-static Channel *frame, *done;
-int mainstacksize = 8192;
-
-static uvlong
-nanosec(void)
-{
- static uvlong fasthz, xstart;
- uvlong x, div;
-
- if(fasthz == ~0ULL)
- return nsec() - xstart;
-
- if(fasthz == 0){
- if(_tos->cyclefreq){
- cycles(&xstart);
- fasthz = _tos->cyclefreq;
- } else {
- xstart = nsec();
- fasthz = ~0ULL;
- fprint(2, "cyclefreq not available, falling back to nsec()\n");
- fprint(2, "you might want to disable aux/timesync\n");
- return 0;
- }
- }
- cycles(&x);
- x -= xstart;
-
- /* this is ugly */
- for(div = Nsec; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);
-
- return x / (fasthz / div);
-}
-
-static void
-nsleep(uvlong ns)
-{
- uvlong start, end;
-
- start = nanosec();
- end = start + ns;
- ns = start;
- do{
- if(end - ns > 85*Nsec)
- sleep(80);
- else if (end - ns > 25*Nsec)
- sleep(20);
- else if (end - ns > 1*Nsec)
- sleep(1);
- else
- break;
- ns = nanosec();
- }while(ns < end);
-}
-
-static Img *
-imgread(int f, int w, int h)
-{
- int r, n, e;
- Img *i;
-
- e = w*h*4;
- i = malloc(sizeof(*i) + e);
- i->w = w;
- i->h = h;
- for(n = 0; n < e; n += r){
- if((r = pread(f, i->bgrx+n, e-n, n+5*12)) <= 0){
- free(i);
- return nil;
- }
- }
-
- return i;
-}
-
-static void
-encthread(void *)
-{
- Pix p[64], pix, prevpix;
- int i, j, run, indpos;
- Img *img, *prev;
- u8int *b, *x;
-
- threadsetname("qoi/encthread");
- memset(p, 0, sizeof(p));
- memset(&prevpix, 0, sizeof(0));
- prevpix.a = 255;
- prev = nil;
- b = nil;
- for(;;){
- if(recv(frame, &img) < 0)
- break;
- if(prev != nil && memcmp(img->bgrx, prev->bgrx, img->w*img->h*4) == 0){
- free(img);
- continue;
- }else if(prev == nil){
- b = malloc(14 + img->w*img->h*4); /* a lot of extra for the worst case */
- b[0] = 'q';
- b[1] = 'o';
- b[2] = 'i';
- b[3] = 'f';
- b[4] = img->w>>24;
- b[5] = img->w>>16;
- b[6] = img->w>>8;
- b[7] = img->w;
- b[8] = img->h>>24;
- b[9] = img->h>>16;
- b[10] = img->h>>8;
- b[11] = img->h;
- b[12] = 3;
- b[13] = 0;
- }
-
- x = img->bgrx;
- run = 0;
- j = 14;
- for(i = 0; i < img->w*img->h; i++){
- pix.b = *x++;
- pix.g = *x++;
- pix.r = *x++;
- x++;
-
- if(pix.v == prevpix.v){
- run++;
- if(run == 62 || i+1 == img->w*img->h){
- b[j++] = Oprun | (run-1);
- run = 0;
- }
- }else{
- if(run > 0){
- b[j++] = Oprun | (run-1);
- run = 0;
- }
- indpos = (pix.r*3 + pix.g*5 + pix.b*7 + 255*11) % 64;
- if(pix.v == p[indpos].v){
- b[j++] = Opind | indpos;
- }else{
- signed char Δr, Δg, Δb, Δgr, Δgb;
- p[indpos].v = pix.v;
- Δr = pix.r - prevpix.r;
- Δg = pix.g - prevpix.g;
- Δb = pix.b - prevpix.b;
- Δgr = Δr - Δg;
- Δgb = Δb - Δg;
- if(Δr > -3 && Δr < 2 && Δg > -3 && Δg < 2 && Δb > -3 && Δb < 2){
- b[j++] = Opdif | (Δr + 2)<<4 | (Δg + 2)<<2 | (Δb + 2);
- }else if(Δgr > -9 && Δgr < 8 && Δg > -33 && Δg < 32 && Δgb > -9 && Δgb < 8){
- b[j++] = Oplum | (Δg + 32);
- b[j++] = (Δgr + 8)<<4 | (Δgb + 8);
- }else{
- b[j++] = Oprgb;
- b[j++] = pix.r;
- b[j++] = pix.g;
- b[j++] = pix.b;
- }
- }
- }
- prevpix.v = pix.v;
- }
- /* padding */
- for(i = 0; i < 7; i++)
- b[j++] = 0;
- b[j++] = 1;
-
- if(write(1, b, j) != j)
- break;
-
- free(prev);
- prev = img;
- }
- sendul(done, 0);
-
- threadexits(nil);
-}
-
-static void
-usage(void)
-{
- fprint(2, "usage: %s [-f FPS] FILE\n", argv0);
- threadexitsall("usage");
-}
-
-void
-threadmain(int argc, char **argv)
-{
- uvlong fstart, fend, f₀, nframes;
- int fps, in, w, h, one, debug;
- char tmp[61], *f[5];
- Img *img;
-
- one = 0;
- debug = 0;
- fps = 30;
- ARGBEGIN{
- case '1':
- one++;
- break;
- case 'd':
- debug++;
- break;
- case 'f':
- fps = atoi(EARGF(usage()));
- break;
- default:
- usage();
- }ARGEND
-
- if(argc != 1)
- usage();
- if((in = open(*argv, OREAD)) < 0)
- sysfatal("input: %r");
-
- tmp[60] = 0;
- if(readn(in, tmp, 60) != 60 || tokenize(tmp, f, 5) != 5)
- sysfatal("invalid image");
- if(strcmp(f[0]+1, "8r8g8b8") != 0)
- sysfatal("only [ax]8r8g8b8 is supported");
- w = atoi(f[3]) - atoi(f[1]);
- h = atoi(f[4]) - atoi(f[2]);
- frame = chancreate(sizeof(void*), 1);
- done = chancreate(sizeof(ulong), 1);
- proccreate(encthread, nil, mainstacksize);
-
- f₀ = nanosec();
- for(nframes = 0;; nframes++){
- fstart = nanosec();
- if((img = imgread(in, w, h)) == nil)
- break;
- if(sendp(frame, img) != 1)
- break;
- if(one){
- chanclose(frame);
- recvul(done);
- }
- fend = nanosec();
-
- if(debug && nframes > 0 && (nframes % fps) == 0){
- fprint(2, "avg fps: %llud\n", nframes/((fend - f₀)/Nsec));
- f₀ = nanosec();
- nframes = -1;
- }
-
- if(Nsec/fps > (fend - fstart))
- nsleep(Nsec/fps - (fend - fstart));
- }
-
- threadexitsall(nil);
-}
--- a/mkfile
+++ b/mkfile
@@ -1,12 +1,12 @@
</$objtype/mkfile
BIN=/$objtype/bin/video
-TARG=qoi
+TARG=qoisend
HFILES=\
OFILES=\
- main.$O\
+ qoisend.$O\
default:V: all
--- /dev/null
+++ b/parg.c
@@ -1,0 +1,354 @@
+/*
+ * parg - parse argv
+ *
+ * Written in 2015-2016 by Joergen Ibsen
+ *
+ * To the extent possible under law, the author(s) have dedicated all
+ * copyright and related and neighboring rights to this software to the
+ * public domain worldwide. This software is distributed without any
+ * warranty. <http://creativecommons.org/publicdomain/zero/1.0/>
+ */
+
+#include "parg.h"
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+/*
+ * Check if state is at end of argv.
+ */
+static int
+is_argv_end(const struct parg_state *ps, int argc, char *const argv[])
+{
+ return ps->optind >= argc || argv[ps->optind] == NULL;
+}
+
+/*
+ * Match nextchar against optstring.
+ */
+static int
+match_short(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring)
+{
+ const char *p = strchr(optstring, *ps->nextchar);
+
+ if (p == NULL) {
+ ps->optopt = *ps->nextchar++;
+ return '?';
+ }
+
+ /* If no option argument, return option */
+ if (p[1] != ':') {
+ return *ps->nextchar++;
+ }
+
+ /* If more characters, return as option argument */
+ if (ps->nextchar[1] != '\0') {
+ ps->optarg = &ps->nextchar[1];
+ ps->nextchar = NULL;
+ return *p;
+ }
+
+ /* If option argument is optional, return option */
+ if (p[2] == ':') {
+ return *ps->nextchar++;
+ }
+
+ /* Option argument required, so return next argv element */
+ if (is_argv_end(ps, argc, argv)) {
+ ps->optopt = *ps->nextchar++;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+
+ ps->optarg = argv[ps->optind++];
+ ps->nextchar = NULL;
+ return *p;
+}
+
+/*
+ * Match string at nextchar against longopts.
+ */
+static int
+match_long(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring,
+ const struct parg_option *longopts, int *longindex)
+{
+ size_t len;
+ int num_match = 0;
+ int match = -1;
+ int i;
+
+ len = strcspn(ps->nextchar, "=");
+
+ for (i = 0; longopts[i].name; ++i) {
+ if (strncmp(ps->nextchar, longopts[i].name, len) == 0) {
+ match = i;
+ num_match++;
+ /* Take if exact match */
+ if (longopts[i].name[len] == '\0') {
+ num_match = 1;
+ break;
+ }
+ }
+ }
+
+ /* Return '?' on no or ambiguous match */
+ if (num_match != 1) {
+ ps->optopt = 0;
+ ps->nextchar = NULL;
+ return '?';
+ }
+
+ assert(match != -1);
+
+ if (longindex) {
+ *longindex = match;
+ }
+
+ if (ps->nextchar[len] == '=') {
+ /* Option argument present, check if extraneous */
+ if (longopts[match].has_arg == PARG_NOARG) {
+ ps->optopt = longopts[match].flag ? 0 : longopts[match].val;
+ ps->nextchar = NULL;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+ else {
+ ps->optarg = &ps->nextchar[len + 1];
+ }
+ }
+ else if (longopts[match].has_arg == PARG_REQARG) {
+ /* Option argument required, so return next argv element */
+ if (is_argv_end(ps, argc, argv)) {
+ ps->optopt = longopts[match].flag ? 0 : longopts[match].val;
+ ps->nextchar = NULL;
+ return optstring[0] == ':' ? ':' : '?';
+ }
+
+ ps->optarg = argv[ps->optind++];
+ }
+
+ ps->nextchar = NULL;
+
+ if (longopts[match].flag != NULL) {
+ *longopts[match].flag = longopts[match].val;
+ return 0;
+ }
+
+ return longopts[match].val;
+}
+
+void
+parg_init(struct parg_state *ps)
+{
+ ps->optarg = NULL;
+ ps->optind = 1;
+ ps->optopt = '?';
+ ps->nextchar = NULL;
+}
+
+int
+parg_getopt(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring)
+{
+ return parg_getopt_long(ps, argc, argv, optstring, NULL, NULL);
+}
+
+int
+parg_getopt_long(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring,
+ const struct parg_option *longopts, int *longindex)
+{
+ assert(ps != NULL);
+ assert(argv != NULL);
+ assert(optstring != NULL);
+
+ ps->optarg = NULL;
+
+ if (argc < 2) {
+ return -1;
+ }
+
+ /* Advance to next element if needed */
+ if (ps->nextchar == NULL || *ps->nextchar == '\0') {
+ if (is_argv_end(ps, argc, argv)) {
+ return -1;
+ }
+
+ ps->nextchar = argv[ps->optind++];
+
+ /* Check for nonoption element (including '-') */
+ if (ps->nextchar[0] != '-' || ps->nextchar[1] == '\0') {
+ ps->optarg = ps->nextchar;
+ ps->nextchar = NULL;
+ return 1;
+ }
+
+ /* Check for '--' */
+ if (ps->nextchar[1] == '-') {
+ if (ps->nextchar[2] == '\0') {
+ ps->nextchar = NULL;
+ return -1;
+ }
+
+ if (longopts != NULL) {
+ ps->nextchar += 2;
+
+ return match_long(ps, argc, argv, optstring,
+ longopts, longindex);
+ }
+ }
+
+ ps->nextchar++;
+ }
+
+ /* Match nextchar */
+ return match_short(ps, argc, argv, optstring);
+}
+
+/*
+ * Reverse elements of `v` from `i` to `j`.
+ */
+static void
+reverse(char *v[], int i, int j)
+{
+ while (j - i > 1) {
+ char *tmp = v[i];
+ v[i] = v[j - 1];
+ v[j - 1] = tmp;
+ ++i;
+ --j;
+ }
+}
+
+/*
+ * Reorder elements of `argv` with no special cases.
+ *
+ * This function assumes there is no `--` element, and the last element
+ * is not an option missing a required argument.
+ *
+ * The algorithm is described here:
+ * http://hardtoc.com/2016/11/07/reordering-arguments.html
+ */
+static int
+parg_reorder_simple(int argc, char *argv[],
+ const char *optstring,
+ const struct parg_option *longopts)
+{
+ struct parg_state ps;
+ int change;
+ int l = 0;
+ int m = 0;
+ int r = 0;
+
+ if (argc < 2) {
+ return argc;
+ }
+
+ do {
+ int nextind;
+ int c;
+
+ parg_init(&ps);
+
+ nextind = ps.optind;
+
+ /* Parse until end of argument */
+ do {
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+ } while (ps.nextchar != NULL && *ps.nextchar != '\0');
+
+ change = 0;
+
+ do {
+ /* Find next non-option */
+ for (l = nextind; c != 1 && c != -1;) {
+ l = ps.optind;
+
+ do {
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+ } while (ps.nextchar != NULL && *ps.nextchar != '\0');
+ }
+
+ /* Find next option */
+ for (m = l; c == 1;) {
+ m = ps.optind;
+
+ do {
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+ } while (ps.nextchar != NULL && *ps.nextchar != '\0');
+ }
+
+ /* Find next non-option */
+ for (r = m; c != 1 && c != -1;) {
+ r = ps.optind;
+
+ do {
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+ } while (ps.nextchar != NULL && *ps.nextchar != '\0');
+ }
+
+ /* Find next option */
+ for (nextind = r; c == 1;) {
+ nextind = ps.optind;
+
+ do {
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+ } while (ps.nextchar != NULL && *ps.nextchar != '\0');
+ }
+
+ if (m < r) {
+ change = 1;
+ reverse(argv, l, m);
+ reverse(argv, m, r);
+ reverse(argv, l, r);
+ }
+ } while (c != -1);
+ } while (change != 0);
+
+ return l + (r - m);
+}
+
+int
+parg_reorder(int argc, char *argv[],
+ const char *optstring,
+ const struct parg_option *longopts)
+{
+ struct parg_state ps;
+ int lastind;
+ int optend;
+ int c;
+
+ assert(argv != NULL);
+ assert(optstring != NULL);
+
+ if (argc < 2) {
+ return argc;
+ }
+
+ parg_init(&ps);
+
+ /* Find end of normal arguments */
+ do {
+ lastind = ps.optind;
+
+ c = parg_getopt_long(&ps, argc, argv, optstring, longopts, NULL);
+
+ /* Check for trailing option with error */
+ if ((c == '?' || c == ':') && is_argv_end(&ps, argc, argv)) {
+ lastind = ps.optind - 1;
+ break;
+ }
+ } while (c != -1);
+
+ optend = parg_reorder_simple(lastind, argv, optstring, longopts);
+
+ /* Rotate `--` or trailing option with error into position */
+ if (lastind < argc) {
+ reverse(argv, optend, lastind);
+ reverse(argv, optend, lastind + 1);
+ ++optend;
+ }
+
+ return optend;
+}
--- /dev/null
+++ b/parg.h
@@ -1,0 +1,192 @@
+/*
+ * parg - parse argv
+ *
+ * Written in 2015-2016 by Joergen Ibsen
+ *
+ * To the extent possible under law, the author(s) have dedicated all
+ * copyright and related and neighboring rights to this software to the
+ * public domain worldwide. This software is distributed without any
+ * warranty. <http://creativecommons.org/publicdomain/zero/1.0/>
+ */
+
+#ifndef PARG_H_INCLUDED
+#define PARG_H_INCLUDED
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define PARG_VER_MAJOR 1 /**< Major version number */
+#define PARG_VER_MINOR 0 /**< Minor version number */
+#define PARG_VER_PATCH 2 /**< Patch version number */
+#define PARG_VER_STRING "1.0.2" /**< Version number as a string */
+
+/**
+ * Structure containing state between calls to parser.
+ *
+ * @see parg_init
+ */
+struct parg_state {
+ const char *optarg; /**< Pointer to option argument, if any */
+ int optind; /**< Next index in argv to process */
+ int optopt; /**< Option value resulting in error, if any */
+ const char *nextchar; /**< Next character to process */
+};
+
+/**
+ * Structure for supplying long options to `parg_getopt_long()`.
+ *
+ * @see parg_getopt_long
+ */
+struct parg_option {
+ const char *name; /**< Name of option */
+ int has_arg; /**< Option argument status */
+ int *flag; /**< Pointer to flag variable */
+ int val; /**< Value of option */
+};
+
+/**
+ * Values for `has_arg` flag in `parg_option`.
+ *
+ * @see parg_option
+ */
+typedef enum {
+ PARG_NOARG, /**< No argument */
+ PARG_REQARG, /**< Required argument */
+ PARG_OPTARG /**< Optional argument */
+} parg_arg_num;
+
+/**
+ * Initialize `ps`.
+ *
+ * Must be called before using state with a parser.
+ *
+ * @see parg_state
+ *
+ * @param ps pointer to state
+ */
+void
+parg_init(struct parg_state *ps);
+
+/**
+ * Parse next short option in `argv`.
+ *
+ * Elements in `argv` that contain short options start with a single dash
+ * followed by one or more option characters, and optionally an option
+ * argument for the last option character. Examples are '`-d`', '`-ofile`',
+ * and '`-dofile`'.
+ *
+ * Consecutive calls to this function match the command-line arguments in
+ * `argv` against the short option characters in `optstring`.
+ *
+ * If an option character in `optstring` is followed by a colon, '`:`', the
+ * option requires an argument. If it is followed by two colons, the option
+ * may take an optional argument.
+ *
+ * If a match is found, `optarg` points to the option argument, if any, and
+ * the value of the option character is returned.
+ *
+ * If a match is found, but is missing a required option argument, `optopt`
+ * is set to the option character. If the first character in `optstring` is
+ * '`:`', then '`:`' is returned, otherwise '`?`' is returned.
+ *
+ * If no option character in `optstring` matches a short option, `optopt`
+ * is set to the option character, and '`?`' is returned.
+ *
+ * If an element of argv does not contain options (a nonoption element),
+ * `optarg` points to the element, and `1` is returned.
+ *
+ * An element consisting of a single dash, '`-`', is returned as a nonoption.
+ *
+ * Parsing stops and `-1` is returned, when the end of `argv` is reached, or
+ * if an element contains '`--`'.
+ *
+ * Works similarly to `getopt`, if `optstring` were prefixed by '`-`'.
+ *
+ * @param ps pointer to state
+ * @param argc number of elements in `argv`
+ * @param argv array of pointers to command-line arguments
+ * @param optstring string containing option characters
+ * @return option value on match, `1` on nonoption element, `-1` on end of
+ * arguments, '`?`' on unmatched option, '`?`' or '`:`' on option argument
+ * error
+ */
+int
+parg_getopt(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring);
+
+/**
+ * Parse next long or short option in `argv`.
+ *
+ * Elements in `argv` that contain a long option start with two dashes
+ * followed by a string, and optionally an equal sign and an option argument.
+ * Examples are '`--help`' and '`--size=5`'.
+ *
+ * If no exact match is found, an unambiguous prefix of a long option will
+ * match. For example, if '`foo`' and '`foobar`' are valid long options, then
+ * '`--fo`' is ambiguous and will not match, '`--foo`' matches exactly, and
+ * '`--foob`' is an unambiguous prefix and will match.
+ *
+ * If a long option match is found, and `flag` is `NULL`, `val` is returned.
+ *
+ * If a long option match is found, and `flag` is not `NULL`, `val` is stored
+ * in the variable `flag` points to, and `0` is returned.
+ *
+ * If a long option match is found, but is missing a required option argument,
+ * or has an option argument even though it takes none, `optopt` is set to
+ * `val` if `flag` is `NULL`, and `0` otherwise. If the first character in
+ * `optstring` is '`:`', then '`:`' is returned, otherwise '`?`' is returned.
+ *
+ * If `longindex` is not `NULL`, the index of the entry in `longopts` that
+ * matched is stored there.
+ *
+ * If no long option in `longopts` matches a long option, '`?`' is returned.
+ *
+ * Handling of nonoptions and short options is like `parg_getopt()`.
+ *
+ * If no short options are required, an empty string, `""`, should be passed
+ * as `optstring`.
+ *
+ * Works similarly to `getopt_long`, if `optstring` were prefixed by '`-`'.
+ *
+ * @see parg_getopt
+ *
+ * @param ps pointer to state
+ * @param argc number of elements in `argv`
+ * @param argv array of pointers to command-line arguments
+ * @param optstring string containing option characters
+ * @param longopts array of `parg_option` structures
+ * @param longindex pointer to variable to store index of matching option in
+ * @return option value on match, `0` for flag option, `1` on nonoption
+ * element, `-1` on end of arguments, '`?`' on unmatched or ambiguous option,
+ * '`?`' or '`:`' on option argument error
+ */
+int
+parg_getopt_long(struct parg_state *ps, int argc, char *const argv[],
+ const char *optstring,
+ const struct parg_option *longopts, int *longindex);
+
+/**
+ * Reorder elements of `argv` so options appear first.
+ *
+ * If there are no long options, `longopts` may be `NULL`.
+ *
+ * The return value can be used as `argc` parameter for `parg_getopt()` and
+ * `parg_getopt_long()`.
+ *
+ * @param argc number of elements in `argv`
+ * @param argv array of pointers to command-line arguments
+ * @param optstring string containing option characters
+ * @param longopts array of `parg_option` structures
+ * @return index of first nonoption in `argv` on success, `-1` on error
+ */
+int
+parg_reorder(int argc, char *argv[],
+ const char *optstring,
+ const struct parg_option *longopts);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* PARG_H_INCLUDED */
--- /dev/null
+++ b/qoirecv.c
@@ -1,0 +1,235 @@
+#define _DEFAULT_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <arpa/inet.h>
+#include <SDL.h>
+#include "parg.h"
+
+enum {
+ Opind = 0x00,
+ Opdif = 0x40,
+ Oplum = 0x80,
+ Oprun = 0xc0,
+ Oprgb = 0xfe,
+};
+
+typedef struct Pix Pix;
+
+struct Pix {
+ uint8_t r, g, b, a;
+};
+
+int
+main(int argc, char **argv)
+{
+ int port, c, ls, s, n, w, h, m, ow, oh;
+ uint8_t hdr[14], t[8], *o, *b;
+ Pix p[64], prevpix, pix;
+ struct sockaddr_in addr;
+ struct parg_state ps;
+ SDL_Renderer *rend;
+ SDL_Texture *tex;
+ SDL_Window *win;
+ socklen_t alen;
+ SDL_Event e;
+ FILE *f;
+
+ parg_init(&ps);
+ port = 12345;
+ while((c = parg_getopt(&ps, argc, argv, "hp:")) >= 0){
+ switch(c){
+ case 'p':
+ port = atoi(ps.optarg);
+ break;
+ default:
+ case 'h':
+ fprintf(stderr, "usage: qoirecv [-p PORT]\n");
+ return 0;
+ break;
+ case '?':
+ fprintf(stderr, "unknown option -%c\n", ps.optopt);
+ return 1;
+ break;
+ }
+ }
+
+ if((ls = socket(AF_INET, SOCK_STREAM, 0)) < 0){
+ perror("socket");
+ return 1;
+ }
+ setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
+ setsockopt(ls, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
+ memset(&addr, 0, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_addr.s_addr = INADDR_ANY;
+ addr.sin_port = htons(port);
+ if(bind(ls, (struct sockaddr*)&addr, sizeof(addr)) < 0){
+ perror("bind");
+ return 1;
+ }
+ if(listen(ls, 0) < 0){
+ perror("listen");
+ return 1;
+ }
+ if(SDL_Init(SDL_INIT_VIDEO) < 0){
+ fprintf(stderr, "SDL_Init: %s\n", SDL_GetError());
+ return 1;
+ }
+ win = SDL_CreateWindow("qoirecv", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 256, 256, SDL_WINDOW_HIDDEN | SDL_WINDOW_ALLOW_HIGHDPI);
+ if(win == NULL){
+ fprintf(stderr, "SDL_CreateWindow: %s\n", SDL_GetError());
+ return 1;
+ }
+ if((rend = SDL_CreateRenderer(win, -1, 0)) == NULL){
+ fprintf(stderr, "SDL_CreateRenderer: %s\n", SDL_GetError());
+ return 1;
+ }
+ SDL_SetRenderDrawColor(rend, 0, 0, 0, 255);
+
+ tex = NULL;
+ b = NULL;
+ ow = oh = 0;
+ for(;;){
+ alen = sizeof(addr);
+ if((s = accept(ls, (struct sockaddr*)&addr, &alen)) < 0){
+ perror("accept");
+ return 1;
+ }
+ if((f = fdopen(s, "rb")) == NULL){
+ perror("fdopen");
+ goto err;
+ }
+ for(;;){
+ while(SDL_PollEvent(&e) > 0){
+ if(e.type == SDL_QUIT){
+ close(s);
+ close(ls);
+ SDL_Quit();
+ return 0;
+ }
+ }
+ if(fread(hdr, 1, sizeof(hdr), f) != sizeof(hdr)){
+ perror("header");
+ goto err;
+ }
+ if(memcmp(hdr, "qoif", 4) != 0 || hdr[12] != 3 || hdr[13] != 0){
+ fprintf(stderr, "invalid image format (magic=%c%c%c%c channels=%d colorspace=%d)\n",
+ hdr[0], hdr[1], hdr[2], hdr[3], hdr[12], hdr[13]
+ );
+ goto err;
+ }
+ w = hdr[4]<<24 | hdr[5]<<16 | hdr[6]<<8 | hdr[7];
+ h = hdr[8]<<24 | hdr[9]<<16 | hdr[10]<<8 | hdr[11];
+ if(w < 1 || h < 1 || w > 16384 || h > 16384){
+ fprintf(stderr, "sketchy image dimensions (%dx%d)\n", w, h);
+ goto err;
+ }
+ if(tex == NULL || ow != w || oh != h){
+ if((o = realloc(b, w*h*3)) == NULL){
+ perror("memory");
+ goto err;
+ }
+ b = o;
+ if(tex != NULL)
+ SDL_DestroyTexture(tex);
+ tex = SDL_CreateTexture(rend, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STATIC, w, h);
+ if(tex == NULL){
+ fprintf(stderr, "SDL_CreateTexture: %s\n", SDL_GetError());
+ goto err;
+ }
+ SDL_SetTextureBlendMode(tex, SDL_BLENDMODE_NONE);
+ SDL_RenderSetLogicalSize(rend, w, h);
+ SDL_RenderClear(rend);
+ SDL_RenderPresent(rend);
+ SDL_SetWindowSize(win, w, h);
+ SDL_SetWindowPosition(win, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
+ SDL_ShowWindow(win);
+ ow = w;
+ oh = h;
+ }
+
+ memset(p, 0, sizeof(p));
+ prevpix.r = 0;
+ prevpix.g = 0;
+ prevpix.b = 0;
+ prevpix.a = 255;
+ pix.a = 255;
+ o = b;
+ for(m = w*h; m > 0;){
+ if((c = fgetc(f)) == EOF){
+ fprintf(stderr, "unexpected EOF\n");
+ goto err;
+ }
+ if(c == Oprgb){
+ if(fread(t, 1, 3, f) != 3){
+ perror("Oprgb");
+ goto err;
+ }
+ pix.r = t[0];
+ pix.g = t[1];
+ pix.b = t[2];
+ }else if((c & 0xc0) == Oplum){
+ if((t[0] = fgetc(f)) == EOF){
+ perror("Oplum");
+ goto err;
+ }
+ pix.g = -32 + (c & 0x3f);
+ pix.r = pix.g - 8 + (t[0] >> 4) + prevpix.r;
+ pix.b = pix.g - 8 + (t[0] & 0x0f) + prevpix.b;
+ pix.g += prevpix.g;
+ }else if((c & 0xc0) == Opdif){
+ pix.b = prevpix.b - 2 + ((c>>0) & 3);
+ pix.g = prevpix.g - 2 + ((c>>2) & 3);
+ pix.r = prevpix.r - 2 + ((c>>4) & 3);
+ }else if((c & 0xc0) == Opind){
+ pix = p[c];
+ }else if(c == 0xff){ /* rgba */
+ fprintf(stderr, "unexpected Oprgba\n");
+ goto err;
+ }else{ /* run */
+ if((n = (c&0x3f)+1) > m){
+ fprintf(stderr, "run out of bounds: %d > %d", n, m);
+ goto err;
+ }
+ do{
+ *o++ = pix.r;
+ *o++ = pix.g;
+ *o++ = pix.b;
+ m--;
+ }while(--n > 0);
+ continue;
+ }
+ prevpix = pix;
+ p[(pix.r*3 + pix.g*5 + pix.b*7 + 255*11) & 63] = pix;
+ *o++ = pix.r;
+ *o++ = pix.g;
+ *o++ = pix.b;
+ m--;
+ }
+ if(fread(t, 1, 8, f) != 8){
+ perror("padding");
+ goto err;
+ }
+ if(memcmp(t, "\0\0\0\0\0\0\0\1", 7) != 0){
+ fprintf(stderr, "invalid padding: %02x%02x%02x%02x%02x%02x%02x%02x\n",
+ t[0], t[1], t[2], t[3], t[4], t[5], t[6], t[7]
+ );
+ goto err;
+ }
+ if(SDL_UpdateTexture(tex, NULL, b, w*3) != 0){
+ fprintf(stderr, "SDL_UpdateTexture: %s\n", SDL_GetError());
+ goto err;
+ }
+ SDL_RenderCopy(rend, tex, NULL, NULL);
+ SDL_RenderPresent(rend);
+ continue;
+err:
+ close(s);
+ break;
+ }
+ }
+
+ return 0;
+}
--- /dev/null
+++ b/qoisend.c
@@ -1,0 +1,282 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <tos.h>
+
+enum {
+ Opind = 0x00,
+ Opdif = 0x40,
+ Oplum = 0x80,
+ Oprun = 0xc0,
+ Oprgb = 0xfe,
+};
+
+#define Nsec 1000000000ULL
+
+typedef struct Img Img;
+typedef union Pix Pix;
+
+struct Img {
+ int w;
+ int h;
+ u8int bgrx[];
+};
+
+union Pix {
+ struct {
+ u8int r, g, b, a;
+ };
+ u32int v;
+};
+
+static Channel *frame, *done;
+int mainstacksize = 8192;
+
+static uvlong
+nanosec(void)
+{
+ static uvlong fasthz, xstart;
+ uvlong x, div;
+
+ if(fasthz == ~0ULL)
+ return nsec() - xstart;
+
+ if(fasthz == 0){
+ if(_tos->cyclefreq){
+ cycles(&xstart);
+ fasthz = _tos->cyclefreq;
+ } else {
+ xstart = nsec();
+ fasthz = ~0ULL;
+ fprint(2, "cyclefreq not available, falling back to nsec()\n");
+ fprint(2, "you might want to disable aux/timesync\n");
+ return 0;
+ }
+ }
+ cycles(&x);
+ x -= xstart;
+
+ /* this is ugly */
+ for(div = Nsec; x < 0x1999999999999999ULL && div > 1 ; div /= 10ULL, x *= 10ULL);
+
+ return x / (fasthz / div);
+}
+
+static void
+nsleep(uvlong ns)
+{
+ uvlong start, end;
+
+ start = nanosec();
+ end = start + ns;
+ ns = start;
+ do{
+ if(end - ns > 85*Nsec)
+ sleep(80);
+ else if (end - ns > 25*Nsec)
+ sleep(20);
+ else if (end - ns > 1*Nsec)
+ sleep(1);
+ else
+ break;
+ ns = nanosec();
+ }while(ns < end);
+}
+
+static Img *
+imgread(int in)
+{
+ char tmp[61], *f[5];
+ int r, n, e, w, h;
+ Img *i;
+
+ tmp[60] = 0;
+ if(pread(in, tmp, 60, 0) != 60 || tokenize(tmp, f, 5) != 5)
+ sysfatal("invalid image");
+ if(strcmp(f[0]+1, "8r8g8b8") != 0)
+ sysfatal("only [ax]8r8g8b8 is supported");
+ w = atoi(f[3]) - atoi(f[1]);
+ h = atoi(f[4]) - atoi(f[2]);
+
+ e = w*h*4;
+ i = malloc(sizeof(*i) + e);
+ i->w = w;
+ i->h = h;
+ for(n = 0; n < e; n += r){
+ if((r = pread(in, i->bgrx+n, e-n, n+5*12)) <= 0){
+ free(i);
+ return nil;
+ }
+ }
+
+ return i;
+}
+
+static void
+encthread(void *)
+{
+ int i, j, bsz, run, indpos;
+ Pix p[64], pix, prevpix;
+ Img *img, *prev;
+ u8int *b, *x;
+
+ threadsetname("qoi/encthread");
+ prev = nil;
+ b = nil;
+ bsz = 0;
+ for(;;){
+ if(recv(frame, &img) < 0)
+ break;
+ if(prev != nil && memcmp(img->bgrx, prev->bgrx, img->w*img->h*4) == 0){
+ free(img);
+ continue;
+ }else if(bsz < (i = 14+img->w*img->h*4)){
+ b = realloc(b, bsz = i); /* a lot of extra for the worst case */
+ if(b == nil)
+ sysfatal("memory");
+ b[0] = 'q';
+ b[1] = 'o';
+ b[2] = 'i';
+ b[3] = 'f';
+ b[12] = 3;
+ b[13] = 0;
+ }
+ b[4] = img->w>>24;
+ b[5] = img->w>>16;
+ b[6] = img->w>>8;
+ b[7] = img->w;
+ b[8] = img->h>>24;
+ b[9] = img->h>>16;
+ b[10] = img->h>>8;
+ b[11] = img->h;
+ memset(p, 0, sizeof(p));
+ memset(&prevpix, 0, sizeof(0));
+ prevpix.a = 255;
+
+ x = img->bgrx;
+ run = 0;
+ j = 14;
+ for(i = 0; i < img->w*img->h; i++){
+ pix.b = *x++;
+ pix.g = *x++;
+ pix.r = *x++;
+ x++;
+
+ if(pix.v == prevpix.v){
+ run++;
+ if(run == 62 || i+1 == img->w*img->h){
+ b[j++] = Oprun | (run-1);
+ run = 0;
+ }
+ }else{
+ if(run > 0){
+ b[j++] = Oprun | (run-1);
+ run = 0;
+ }
+ indpos = (pix.r*3 + pix.g*5 + pix.b*7 + 255*11) % 64;
+ if(pix.v == p[indpos].v){
+ b[j++] = Opind | indpos;
+ }else{
+ signed char Δr, Δg, Δb, Δgr, Δgb;
+ p[indpos].v = pix.v;
+ Δr = pix.r - prevpix.r;
+ Δg = pix.g - prevpix.g;
+ Δb = pix.b - prevpix.b;
+ Δgr = Δr - Δg;
+ Δgb = Δb - Δg;
+ if(Δr > -3 && Δr < 2 && Δg > -3 && Δg < 2 && Δb > -3 && Δb < 2){
+ b[j++] = Opdif | (Δr + 2)<<4 | (Δg + 2)<<2 | (Δb + 2);
+ }else if(Δgr > -9 && Δgr < 8 && Δg > -33 && Δg < 32 && Δgb > -9 && Δgb < 8){
+ b[j++] = Oplum | (Δg + 32);
+ b[j++] = (Δgr + 8)<<4 | (Δgb + 8);
+ }else{
+ b[j++] = Oprgb;
+ b[j++] = pix.r;
+ b[j++] = pix.g;
+ b[j++] = pix.b;
+ }
+ }
+ }
+ prevpix.v = pix.v;
+ }
+ /* padding */
+ for(i = 0; i < 7; i++)
+ b[j++] = 0;
+ b[j++] = 1;
+
+ if(write(1, b, j) != j)
+ break;
+
+ free(prev);
+ prev = img;
+ }
+ sendul(done, 0);
+
+ threadexits(nil);
+}
+
+static void
+usage(void)
+{
+ fprint(2, "usage: %s [-f FPS] FILE\n", argv0);
+ threadexitsall("usage");
+}
+
+void
+threadmain(int argc, char **argv)
+{
+ uvlong fstart, fend, f₀, nframes;
+ int fps, in, one, debug;
+ Img *img;
+
+ one = 0;
+ debug = 0;
+ fps = 60;
+ ARGBEGIN{
+ case '1':
+ one++;
+ break;
+ case 'd':
+ debug++;
+ break;
+ case 'f':
+ fps = atoi(EARGF(usage()));
+ break;
+ default:
+ usage();
+ }ARGEND
+
+ if(argc != 1)
+ usage();
+ if((in = open(*argv, OREAD)) < 0)
+ sysfatal("input: %r");
+
+ frame = chancreate(sizeof(void*), 1);
+ done = chancreate(sizeof(ulong), 1);
+ proccreate(encthread, nil, mainstacksize);
+
+ f₀ = nanosec();
+ for(nframes = 0;; nframes++){
+ fstart = nanosec();
+ if((img = imgread(in)) == nil)
+ continue;
+ if(sendp(frame, img) != 1)
+ break;
+ if(one){
+ chanclose(frame);
+ recvul(done);
+ }
+ fend = nanosec();
+
+ if(debug && nframes > 0 && (nframes % fps) == 0){
+ fprint(2, "avg fps: %llud\n", nframes/((fend - f₀)/Nsec));
+ f₀ = nanosec();
+ nframes = -1;
+ }
+
+ if(Nsec/fps > (fend - fstart))
+ nsleep(Nsec/fps - (fend - fstart));
+ }
+
+ threadexitsall(nil);
+}