shithub: qoistream

ref: 45418db47966d92700a285df680b6edfd0d1a43b
dir: /qoirecv.c/

View raw version
#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;
}