commit b4e5a51cc9a504d4386fe372378826d561babad4
parent 59805483b26574b37bb1784eaeb323dd71489f4b
Author: lumidify <nobody@lumidify.org>
Date:   Fri,  5 Mar 2021 23:02:35 +0100
Add double buffering
Diffstat:
4 files changed, 99 insertions(+), 19 deletions(-)
diff --git a/Makefile b/Makefile
@@ -13,6 +13,15 @@ MAN1 = ${BIN:=.1}
 CFLAGS += -D_POSIX_C_SOURCE=200809L `pkg-config --cflags x11` `imlib2-config --cflags`
 LDFLAGS += `pkg-config --libs x11` `imlib2-config --libs` -lm
 
+# Configuration options:
+
+# comment to disable double buffering
+CFLAGS += `pkg-config --cflags xext`
+LDFLAGS += `pkg-config --libs xext`
+
+# uncomment to disable double buffering
+#CFLAGS += -DNODB
+
 all: ${BIN}
 
 .c:
diff --git a/README b/README
@@ -1,8 +1,11 @@
 croptool - mass image cropping tool
 
 REQUIREMENTS: xlib, imlib2
+OPTIONAL: xext (for double-buffering extension)
 
 croptool is a simple tool to select cropping rectangles
 on images and print out a command to crop each image.
 
-See croptool.1 for more information.
+See Makefile for compile-time options.
+
+See croptool.1 for usage information.
diff --git a/croptool.1 b/croptool.1
@@ -1,4 +1,4 @@
-.Dd January 10, 2021
+.Dd March 5, 2021
 .Dt CROPTOOL 1
 .Os
 .Sh NAME
@@ -95,7 +95,7 @@ Redraw the window. This is useful when automatic redrawing is disabled with
 .Bl -tag -width Ds
 .It LEFT-CLICK
 When inside an existing cropping rectangle, drag it around. When on one of the
-edges, resize the rectangle, locking it to the that dimension. When on one of
+edges, resize the rectangle, locking it to that dimension. When on one of
 the corners, resize the rectangle regardless of dimension. When outside an
 existing cropping rectangle, start a new cropping rectangle.
 .El
@@ -110,9 +110,6 @@ existing cropping rectangle, start a new cropping rectangle.
 The filenames are printed out without any escaping, so filenames with quotes
 may cause issues depending on the output format.
 .Pp
-Nothing in particular has been done to prevent screen flicker, so there is
-flickering when resizing the window or cropping rectangle.
-.Pp
 Transparent portions of images should probably be shown differently, but I'm
 too lazy to fix that and don't really care at the moment.
 .Pp
diff --git a/croptool.c b/croptool.c
@@ -26,6 +26,9 @@
 #include <X11/keysym.h>
 #include <X11/XF86keysym.h>
 #include <X11/cursorfont.h>
+#ifndef NODB
+#include <X11/extensions/Xdbe.h>
+#endif
 #include <Imlib2.h>
 
 /* The number of pixels to check on each side when checking
@@ -84,6 +87,11 @@ static struct {
 	GC gc;
 	Window win;
 	Visual *vis;
+	Drawable drawable;
+	#ifndef NODB
+	XdbeBackBuffer back_buf;
+	int db_enabled;
+	#endif
 	Colormap cm;
 	int screen;
 	int depth;
@@ -284,12 +292,63 @@ setup(int argc, char *argv[]) {
 	state.depth = DefaultDepth(state.dpy, state.screen);
 	state.cm = DefaultColormap(state.dpy, state.screen);
 
+	#ifndef NODB
+	state.db_enabled = 0;
+	/* based on http://wili.cc/blog/xdbe.html */
+	int major, minor;
+	if (XdbeQueryExtension(state.dpy, &major, &minor)) {
+		int num_screens = 1;
+		Drawable screens[] = { DefaultRootWindow(state.dpy) };
+		XdbeScreenVisualInfo *info = XdbeGetVisualInfo(state.dpy, screens, &num_screens);
+		if (!info || num_screens < 1 || info->count < 1) {
+			fprintf(stderr, "Warning: No visuals support Xdbe, "
+					"double buffering disabled.\n");
+		} else {
+			XVisualInfo xvisinfo_templ;
+			xvisinfo_templ.visualid = info->visinfo[0].visual;
+			xvisinfo_templ.screen = 0;
+			xvisinfo_templ.depth = info->visinfo[0].depth;
+			int matches;
+			XVisualInfo *xvisinfo_match = XGetVisualInfo(
+			    state.dpy,
+			    VisualIDMask | VisualScreenMask | VisualDepthMask,
+			    &xvisinfo_templ, &matches
+			);
+			if (!xvisinfo_match || matches < 1) {
+				fprintf(stderr, "Warning: Couldn't match a Visual "
+						"with double buffering, double "
+						"buffering disabled.\n");
+			} else {
+				state.vis = xvisinfo_match->visual;
+				state.depth = xvisinfo_match->depth;
+				state.db_enabled = 1;
+			}
+			XFree(xvisinfo_match);
+		}
+		XdbeFreeVisualInfo(info);
+	} else {
+		fprintf(stderr, "Warning: No Xdbe support, double buffering disabled.\n");
+	}
+	#endif
+
 	memset(&attrs, 0, sizeof(attrs));
-	attrs.background_pixmap = None;
+	attrs.background_pixel = BlackPixel(state.dpy, state.screen);
 	attrs.colormap = state.cm;
+	attrs.bit_gravity = NorthWestGravity;
 	state.win = XCreateWindow(state.dpy, DefaultRootWindow(state.dpy), 0, 0,
 	    state.window_w, state.window_h, 0, state.depth,
-	    InputOutput, state.vis, CWBackPixmap | CWColormap, &attrs);
+	    InputOutput, state.vis, CWBackPixel | CWColormap | CWBitGravity, &attrs);
+
+	#ifndef NODB
+	if (state.db_enabled) {
+		state.back_buf = XdbeAllocateBackBufferName(state.dpy, state.win, XdbeCopied);
+		state.drawable = state.back_buf;
+	} else {
+		state.drawable = state.win;
+	}
+	#else
+	state.drawable = state.win;
+	#endif
 
 	memset(&gcv, 0, sizeof(gcv));
 	gcv.line_width = LINE_WIDTH;
@@ -327,7 +386,7 @@ setup(int argc, char *argv[]) {
 	imlib_context_set_display(state.dpy);
 	imlib_context_set_visual(state.vis);
 	imlib_context_set_colormap(state.cm);
-	imlib_context_set_drawable(state.win);
+	imlib_context_set_drawable(state.drawable);
 	state.updates = imlib_updates_init();
 
 	next_picture(0);
@@ -442,8 +501,8 @@ redraw(void) {
 	Imlib_Updates current_update;
 	if (!state.cur_image || state.cur_selection < 0) {
 		XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
-		XFillRectangle(state.dpy, state.win, state.gc, 0, 0, state.window_w, state.window_h);
-		return;
+		XFillRectangle(state.dpy, state.drawable, state.gc, 0, 0, state.window_w, state.window_h);
+		goto swap_buffers;
 	}
 	struct Selection *sel = &state.selections[state.cur_selection];
 	state.updates = imlib_updates_merge_for_rendering(state.updates, sel->scaled_w, sel->scaled_h);
@@ -456,11 +515,9 @@ redraw(void) {
 		imlib_context_set_image(buffer);
 		imlib_blend_image_onto_image(
 		    state.cur_image, 0, 0, 0,
-		    state.selections[state.cur_selection].orig_w,
-		    state.selections[state.cur_selection].orig_h,
+		    sel->orig_w, sel->orig_h,
 		    -up_x, -up_y,
-		    state.selections[state.cur_selection].scaled_w,
-		    state.selections[state.cur_selection].scaled_h);
+		    sel->scaled_w, sel->scaled_h);
 		imlib_render_image_on_drawable(up_x, up_y);
 		imlib_free_image();
 	}
@@ -470,8 +527,8 @@ redraw(void) {
 
 	/* wipe the black area around the image */
 	XSetForeground(state.dpy, state.gc, BlackPixel(state.dpy, state.screen));
-	XFillRectangle(state.dpy, state.win, state.gc, 0, sel->scaled_h, sel->scaled_w, state.window_h - sel->scaled_h);
-	XFillRectangle(state.dpy, state.win, state.gc, sel->scaled_w, 0, state.window_w - sel->scaled_w, state.window_h);
+	XFillRectangle(state.dpy, state.drawable, state.gc, 0, sel->scaled_h, sel->scaled_w, state.window_h - sel->scaled_h);
+	XFillRectangle(state.dpy, state.drawable, state.gc, sel->scaled_w, 0, state.window_w - sel->scaled_w, state.window_h);
 
 	/* draw the rectangle */
 	struct Rect rect = sel->rect;
@@ -479,8 +536,22 @@ redraw(void) {
 		XColor col = state.cur_col == 1 ? state.col1 : state.col2;
 		XSetForeground(state.dpy, state.gc, col.pixel);
 		sort_coordinates(&rect.x0, &rect.y0, &rect.x1, &rect.y1);
-		XDrawRectangle(state.dpy, state.win, state.gc, rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0);
+		XDrawRectangle(state.dpy, state.drawable, state.gc, rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0);
+	}
+
+swap_buffers:
+	#ifndef NODB
+	if (state.db_enabled) {
+		XdbeSwapInfo swap_info;
+		swap_info.swap_window = state.win;
+		swap_info.swap_action = XdbeCopied;
+
+		if (!XdbeSwapBuffers(state.dpy, &swap_info, 1))
+			fprintf(stderr, "Warning: Unable to swap buffers.\n");
 	}
+	#else
+	;
+	#endif
 }
 
 static void
@@ -864,7 +935,7 @@ switch_color(void) {
 static void
 key_press(XEvent event) {
 	XWindowAttributes attrs;
-	char buf[64];
+	char buf[32];
 	KeySym sym;
 	XLookupString(&event.xkey, buf, sizeof(buf), &sym, NULL);
 	switch (sym) {