commit 5b4a35ead5c0c6eae4773cc6eaf5b757e4e4b265
parent 501a86cb94f006a55ec476f1361de5cc5a4fd66c
Author: Markus Teich <markus.teich@stusta.mhn.de>
Date:   Sun, 29 Jun 2014 23:43:01 +0200
add png support
Diffstat:
5 files changed, 256 insertions(+), 21 deletions(-)
diff --git a/LICENSE b/LICENSE
@@ -1,6 +1,8 @@
 The MIT License (MIT)
 
-Copyright (c) 2014 markus.teich@stusta.mhn.de
+Copyright (c) 2014 Markus Teich
+
+png handling stuff adapted from meh by John Hawthorn
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff --git a/config.mk b/config.mk
@@ -12,7 +12,7 @@ X11LIB = /usr/X11R6/lib
 
 # includes and libs
 INCS = -I. -I/usr/include -I${X11INC}
-LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11
+LIBS = -L/usr/lib -lc -lm -L${X11LIB} -lX11 -lpng
 
 # flags
 CPPFLAGS = -DVERSION=\"${VERSION}\" -D_BSD_SOURCE -D_XOPEN_SOURCE=600
diff --git a/example b/example
@@ -1,9 +1,11 @@
 sent
 why?
+@nyan.png
 easy to use
-few dependencies (X11)
+depends on Xlib, libpng
 no bloat
 how?
 sent FILENAME
 one slide per line
+ @FILE.png
 thanks / questions?
diff --git a/nyan.png b/nyan.png
Binary files differ.
diff --git a/sent.c b/sent.c
@@ -1,8 +1,10 @@
 /* See LICENSE for licence details. */
 #include <errno.h>
 #include <math.h>
+#include <png.h>
 #include <stdarg.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <stdlib.h>
 #include <string.h>
 #include <X11/keysym.h>
@@ -19,8 +21,27 @@ char *argv0;
 #define LEN(a)     (sizeof(a) / sizeof(a)[0])
 #define LIMIT(x, a, b)    (x) = (x) < (a) ? (a) : (x) > (b) ? (b) : (x)
 
+typedef enum {
+	NONE = 0,
+	LOADED = 1,
+	SCALED = 2,
+	DRAWN = 4
+} imgstate;
+
+struct image {
+	unsigned char *buf;
+	unsigned int bufwidth, bufheight;
+	imgstate state;
+	XImage *ximg;
+	FILE *f;
+	png_structp png_ptr;
+	png_infop info_ptr;
+	int numpasses;
+};
+
 typedef struct {
-	char* text;
+	char *text;
+	struct image *img;
 } Slide;
 
 /* Purely graphic info */
@@ -32,6 +53,7 @@ typedef struct {
 	XSetWindowAttributes attrs;
 	int scr;
 	int w, h;
+	int uw, uh; /* usable dimensions for drawing text and images */
 } XWindow;
 
 /* Drawing Context linked list*/
@@ -60,12 +82,11 @@ typedef struct {
 	const Arg arg;
 } Shortcut;
 
-/* function definitions used in config.h */
-static void advance(const Arg *);
-static void quit(const Arg *);
-
-/* config.h for applying patches and the configuration. */
-#include "config.h"
+static struct image *pngopen(char *filename);
+static int pngread(struct image *img);
+static int pngprepare(struct image *img);
+static void pngscale(struct image *img);
+static void pngdraw(struct image *img);
 
 static Bool xfontisscalable(char *name);
 static XFontStruct *xloadqueryscalablefont(char *name, int size);
@@ -75,6 +96,7 @@ static void eprintf(const char *, ...);
 static void load(FILE *fp);
 static void advance(const Arg *arg);
 static void quit(const Arg *arg);
+static void resize(int width, int height);
 static void run();
 static void usage();
 static void xdraw();
@@ -86,7 +108,10 @@ static void bpress(XEvent *);
 static void cmessage(XEvent *);
 static void expose(XEvent *);
 static void kpress(XEvent *);
-static void resize(XEvent *);
+static void configure(XEvent *);
+
+/* config.h for applying patches and the configuration. */
+#include "config.h"
 
 /* Globals */
 static Slide *slides = NULL;
@@ -100,11 +125,193 @@ static char *opt_font = NULL;
 static void (*handler[LASTEvent])(XEvent *) = {
 	[ButtonPress] = bpress,
 	[ClientMessage] = cmessage,
-	[ConfigureNotify] = resize,
+	[ConfigureNotify] = configure,
 	[Expose] = expose,
 	[KeyPress] = kpress,
 };
 
+struct image *pngopen(char *filename)
+{
+	FILE *f;
+	unsigned char buf[8];
+	struct image *img;
+
+	if (!(f = fopen(filename, "rb"))) {
+		eprintf("could not open file %s:", filename);
+		return NULL;
+	}
+
+	if (fread(buf, 1, 8, f) != 8 || png_sig_cmp(buf, 1, 8))
+		return NULL;
+
+	img = malloc(sizeof(struct image));
+	if (!(img->png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL,
+					NULL, NULL))) {
+		free(img);
+		return NULL;
+	}
+	if (!(img->info_ptr = png_create_info_struct(img->png_ptr))) {
+		png_destroy_read_struct(&img->png_ptr, NULL, NULL);
+		free(img);
+		return NULL;
+	}
+	if (setjmp(png_jmpbuf(img->png_ptr))) {
+		png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
+		free(img);
+		return NULL;
+	}
+
+	img->f = f;
+	rewind(f);
+	png_init_io(img->png_ptr, f);
+	png_read_info(img->png_ptr, img->info_ptr);
+	img->bufwidth = png_get_image_width(img->png_ptr, img->info_ptr);
+	img->bufheight = png_get_image_height(img->png_ptr, img->info_ptr);
+
+	return img;
+}
+
+int pngread(struct image *img)
+{
+	unsigned int y;
+	png_bytepp row_pointers;
+
+	if (!img)
+		return 0;
+
+	if (img->state & LOADED)
+		return 2;
+
+	if (img->buf)
+		free(img->buf);
+	if (!(img->buf = malloc(3 * img->bufwidth * img->bufheight)))
+		return 0;
+
+	if (setjmp(png_jmpbuf(img->png_ptr))) {
+		png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
+		return 0;
+	}
+
+	{
+		int color_type = png_get_color_type(img->png_ptr, img->info_ptr);
+		int bit_depth = png_get_bit_depth(img->png_ptr, img->info_ptr);
+		if (color_type == PNG_COLOR_TYPE_PALETTE)
+			png_set_expand(img->png_ptr);
+		if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
+			png_set_expand(img->png_ptr);
+		if (png_get_valid(img->png_ptr, img->info_ptr, PNG_INFO_tRNS))
+			png_set_expand(img->png_ptr);
+		if (bit_depth == 16)
+			png_set_strip_16(img->png_ptr);
+		if (color_type == PNG_COLOR_TYPE_GRAY
+				|| color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
+			png_set_gray_to_rgb(img->png_ptr);
+
+		png_color_16 my_background = {.red = 0xff, .green = 0xff, .blue = 0xff};
+		png_color_16p image_background;
+
+		if (png_get_bKGD(img->png_ptr, img->info_ptr, &image_background))
+			png_set_background(img->png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0);
+		else
+			png_set_background(img->png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 2, 1.0);
+
+		if (png_get_interlace_type(img->png_ptr, img->info_ptr) == PNG_INTERLACE_ADAM7)
+			img->numpasses = png_set_interlace_handling(img->png_ptr);
+		else
+			img->numpasses = 1;
+		png_read_update_info(img->png_ptr, img->info_ptr);
+	}
+
+	row_pointers = (png_bytepp)malloc(img->bufheight * sizeof(png_bytep));
+	for (y = 0; y < img->bufheight; y++)
+		row_pointers[y] = img->buf + y * img->bufwidth * 3;
+
+	png_read_image(img->png_ptr, row_pointers);
+	free(row_pointers);
+
+	png_destroy_read_struct(&img->png_ptr, &img->info_ptr, NULL);
+	fclose(img->f);
+	img->state |= LOADED;
+
+	return 1;
+}
+
+int pngprepare(struct image *img)
+{
+	int depth = DefaultDepth(xw.dpy, xw.scr);
+	int width = xw.uw;
+	int height = xw.uh;
+
+	if (xw.uw * img->bufheight > xw.uh * img->bufwidth)
+		width = img->bufwidth * xw.uh / img->bufheight;
+	else
+		height = img->bufheight * xw.uw / img->bufwidth;
+
+	if (depth < 24) {
+		eprintf("display depths <24 not supported.");
+		return 0;
+	}
+
+	if (!(img->ximg = XCreateImage(xw.dpy, CopyFromParent, depth, ZPixmap, 0,
+				NULL, width, height, 32, 0))) {
+		eprintf("could not create XImage");
+		return 0;
+	}
+
+	if (!(img->ximg->data = malloc(img->ximg->bytes_per_line * height))) {
+		eprintf("could not alloc data section for XImage");
+		XDestroyImage(img->ximg);
+		img->ximg = NULL;
+		return 0;
+	}
+
+	if (!XInitImage(img->ximg)) {
+		eprintf("could not init XImage");
+		free(img->ximg->data);
+		XDestroyImage(img->ximg);
+		img->ximg = NULL;
+		return 0;
+	}
+
+	pngscale(img);
+	img->state |= SCALED;
+	return 1;
+}
+
+void pngscale(struct image *img)
+{
+	unsigned int x, y;
+	unsigned int width = img->ximg->width;
+	unsigned int height = img->ximg->height;
+	char* __restrict__ newBuf = img->ximg->data;
+	unsigned char * __restrict__ ibuf;
+	unsigned int jdy = img->ximg->bytes_per_line / 4 - width;
+	unsigned int dx = (img->bufwidth << 10) / width;
+
+	for (y = 0; y < height; y++) {
+		unsigned int bufx = img->bufwidth / width;
+		ibuf = &img->buf[y * img->bufheight / height * img->bufwidth * 3];
+
+		for (x = 0; x < width; x++) {
+			*newBuf++ = (ibuf[(bufx >> 10)*3+2]);
+			*newBuf++ = (ibuf[(bufx >> 10)*3+1]);
+			*newBuf++ = (ibuf[(bufx >> 10)*3+0]);
+			newBuf++;
+			bufx += dx;
+		}
+		newBuf += jdy;
+	}
+}
+
+void pngdraw(struct image *img)
+{
+	int xoffset = (xw.w - img->ximg->width) / 2;
+	int yoffset = (xw.h - img->ximg->height) / 2;
+	XPutImage(xw.dpy, xw.win, dc.gc, img->ximg, 0, 0,
+			xoffset, yoffset, img->ximg->width, img->ximg->height);
+	XFlush(xw.dpy);
+	img->state |= DRAWN;
+}
 
 Bool xfontisscalable(char *name)
 {
@@ -177,8 +384,7 @@ struct DC *getfontsize(char *str, size_t len, int *width, int *height)
 
 	do {
 		XTextExtents(cur->font, str, len, &unused, &unused, &unused, &info);
-		if (info.width > usablewidth * xw.w
-				|| info.ascent + info.descent > usableheight * xw.h)
+		if (info.width > xw.uw || info.ascent + info.descent > xw.uh)
 			break;
 		pre = cur;
 	} while ((cur = cur->next));
@@ -245,6 +451,8 @@ void load(FILE *fp)
 			*p = '\0';
 		if (!(slides[i].text = strdup(buf)))
 			eprintf("cannot strdup %u bytes:", strlen(buf)+1);
+		if (slides[i].text[0] == '@')
+			slides[i].img = pngopen(slides[i].text + 1);
 	}
 	if (slides)
 		slides[i].text = NULL;
@@ -256,8 +464,14 @@ void advance(const Arg *arg)
 	int new_idx = idx + arg->i;
 	LIMIT(new_idx, 0, slidecount-1);
 	if (new_idx != idx) {
+		if (slides[idx].img)
+			slides[idx].img->state &= ~(DRAWN | SCALED);
 		idx = new_idx;
 		xdraw();
+		if (slidecount > idx + 1 && slides[idx + 1].img && !pngread(slides[idx + 1].img))
+			eprintf("could not read image %s", slides[idx + 1].text + 1);
+		if (0 < idx && slides[idx - 1].img && !pngread(slides[idx - 1].img))
+			eprintf("could not read image %s", slides[idx - 1].text + 1);
 	}
 }
 
@@ -266,6 +480,14 @@ void quit(const Arg *arg)
 	running = 0;
 }
 
+void resize(int width, int height)
+{
+	xw.w = width;
+	xw.h = height;
+	xw.uw = usablewidth * width;
+	xw.uh = usableheight * height;
+}
+
 void run()
 {
 	XEvent ev;
@@ -274,8 +496,7 @@ void run()
 	while (1) {
 		XNextEvent(xw.dpy, &ev);
 		if (ev.type == ConfigureNotify) {
-			xw.w = ev.xconfigure.width;
-			xw.h = ev.xconfigure.height;
+			resize(ev.xconfigure.width, ev.xconfigure.height);
 		} else if (ev.type == MapNotify) {
 			break;
 		}
@@ -300,10 +521,19 @@ void xdraw()
 	int height;
 	int width;
 	struct DC *dc = getfontsize(slides[idx].text, line_len, &width, &height);
+	struct image *im = slides[idx].img;
 
 	XClearWindow(xw.dpy, xw.win);
-	XDrawString(xw.dpy, xw.win, dc->gc, (xw.w - width)/2, (xw.h + height)/2,
-			slides[idx].text, line_len);
+
+	if (!im)
+		XDrawString(xw.dpy, xw.win, dc->gc, (xw.w - width)/2, (xw.h + height)/2,
+				slides[idx].text, line_len);
+	else if (!(im->state & LOADED) && !pngread(im))
+		eprintf("could not read image %s", slides[idx].text + 1);
+	else if (!(im->state & SCALED) && !pngprepare(im))
+		eprintf("could not prepare image %s for drawing", slides[idx].text + 1);
+	else if (!(im->state & DRAWN))
+		pngdraw(im);
 }
 
 void xhints()
@@ -427,10 +657,11 @@ void kpress(XEvent *e)
 			shortcuts[i].func(&(shortcuts[i].arg));
 }
 
-void resize(XEvent *e)
+void configure(XEvent *e)
 {
-	xw.w = e->xconfigure.width;
-	xw.h = e->xconfigure.height;
+	resize(e->xconfigure.width, e->xconfigure.height);
+	if (slides[idx].img)
+		slides[idx].img->state &= ~(DRAWN | SCALED);
 	xdraw();
 }