URI: 
       tAdd new version based on xlib and imlib2 - croptool - Image cropping tool
  HTML git clone git://lumidify.org/croptool.git
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
   DIR commit c5c5ee818931792a57b3115d89790a4ace8a1d4a
   DIR parent b9f502b3551d3ae657214abb591e119b417db772
  HTML Author: lumidify <nobody@lumidify.org>
       Date:   Fri,  1 Jan 2021 22:18:46 +0100
       
       Add new version based on xlib and imlib2
       
       Diffstat:
         A .gitignore                          |       1 +
         A LICENSE                             |      15 +++++++++++++++
         M Makefile                            |      35 +++++++++++++++++++++++--------
         M README                              |      67 ++-----------------------------
         M TODO                                |       1 -
         A croptool.1                          |     106 ++++++++++++++++++++++++++++++
         M croptool.c                          |     890 ++++++++++++++++++++-----------
         C Makefile -> old/Makefile            |       0 
         A old/README                          |      70 +++++++++++++++++++++++++++++++
         C croptool.c -> old/croptool.c        |       0 
       
       10 files changed, 793 insertions(+), 392 deletions(-)
       ---
   DIR diff --git a/.gitignore b/.gitignore
       t@@ -0,0 +1 @@
       +croptool
   DIR diff --git a/LICENSE b/LICENSE
       t@@ -0,0 +1,15 @@
       +/*
       + * Copyright (c) 2021 lumidify <nobody[at]lumidify.org>
       + *
       + * Permission to use, copy, modify, and distribute this software for any
       + * purpose with or without fee is hereby granted, provided that the above
       + * copyright notice and this permission notice appear in all copies.
       + *
       + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
       + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
       + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
       + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
       + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
       + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
       + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
       + */
   DIR diff --git a/Makefile b/Makefile
       t@@ -1,19 +1,36 @@
       -CC = cc
       +.POSIX:
       +
       +NAME = croptool
       +VERSION = 1.2-dev
       +
        PREFIX = /usr/local
       +MANPREFIX = ${PREFIX}/man
       +
       +BIN = ${NAME}
       +SRC = ${BIN:=.c}
       +MAN1 = ${BIN:=.1}
       +
       +CFLAGS += `pkg-config --cflags x11` `imlib2-config --cflags`
       +LDFLAGS += `pkg-config --libs x11` `imlib2-config --libs` -lm
        
       -all: croptool
       +all: ${BIN}
        
       -croptool: croptool.c
       -        ${CC} -pedantic -Wno-deprecated-declarations -Wall -Werror croptool.c -o croptool -std=c99 `pkg-config --libs --cflags gtk+-2.0` -lm
       +${BIN}:
       +        ${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${SRC}
        
        install: all
       -        cp -f croptool ${PREFIX}/bin
       -        chmod 755 ${PREFIX}/bin/croptool
       +        mkdir -p "${DESTDIR}${PREFIX}/bin"
       +        cp -f ${BIN} "${DESTDIR}${PREFIX}/bin"
       +        chmod 755 ${PREFIX}/bin/${BIN}
       +        mkdir -p "${DESTDIR}${MANPREFIX}/man1"
       +        cp -f ${MAN1} "${DESTDIR}${MANPREFIX}/man1"
       +        chmod 644 "${DESTDIR}${MANPREFIX}/man1/${MAN1}"
        
        uninstall:
       -        rm -f ${PREFIX}/bin/croptool
       +        rm -f "${DESTDIR}${PREFIX}/bin/${BIN}
       +        rm -f "${DESTDIR}${MANPREFIX}/man1/${MAN1}"
        
        clean:
       -        rm -f croptool
       +        rm -f ${BIN}
        
       -.PHONY: clean install uninstall
       +.PHONY: all clean install uninstall
   DIR diff --git a/README b/README
       t@@ -1,66 +1,5 @@
       -Requirements: gtk2 (which requires cairo and the other crap anyways)
       +croptool - mass image cropping tool
        
       -This is a small image cropping tool. It was actually written to help
       -crop large amounts of pictures when digitizing books, but it can be
       -used for cropping single pictures as well. There are probably many
       -bugs still. Oh, and the code probably isn't that great.
       +REQUIREMENTS: xlib, imlib2
        
       -Note that the whole image is redrawn when changing the selection
       -because I'm too dumb to change that, so it may occasionally lag
       -a little bit. It barely lags on my nice laptop with a single-core
       -Intel Celeron 2.00 Ghz from 2008, though, so that shouldn't be a
       -huge problem.
       -
       -Just start it with "croptool <image files>" and a window will pop up.
       -Initially, no image is shown, so you first have to press enter or
       -right arrow to go to the first image. When an image is shown, you can
       -click on it to create a selection box. If you click near the edges or
       -corners of the box, you can change its size, and if you click anywhere
       -in the middle, you can move it. Clicking outside creates a new box.
       -I don't know if all of the collision logic is entirely correct, so
       -tell me if you notice any problems.
       -
       -Several keys are recognized:
       -* Enter and right arrow both go to the next image, but enter copies the
       -  selection box from the current image and uses it for the next picture,
       -  while right arrow just goes to the next image and only displays a
       -  selection box if it already had one. This is so that lots of pages
       -  of a digitized book can be cropped quickly since the selection box
       -  needs to be tweaked occasionally (my digitizing equipment, if it
       -  can be called that, isn't exactly very professional).
       -* Left arrow just goes to the previous picture.
       -* Delete removes the selection for the current image (this is then
       -  also not printed out at the end).
       -* Space bar resizes the image if the window was resized.
       -* Tab switches the color of the selection box between the two colors
       -  defined at the top of `croptool.c` (SELECTION_COLOR1, SELECTION_COLOR2).
       -
       -Note that resizing the window currently does not resize the images.
       -It will only take effect if you move to another image or press
       -space bar. A side effect of this is that the image usually is 
       -displayed at the wrong size when the window initially opens in a
       -tiling window manager because the window is first mapped at the
       -requested (500x500) size and then resized by the window manager.
       -Just press space bar if that happens (it hasn't bothered me too
       -much up til now, and I use dwm). There may be bugs lurking here
       -as well since the actual cropping box needs to be rescaled according
       -to how much the image was scaled for display.
       -
       -When the window is closed, the ImageMagick command (mogrify -crop...)
       -for cropping each of the pictures that had a selection box defined
       -is printed (including the image currently being edited). If the box
       -was completely outside of the image, nothing is printed. If only part
       -of it was outside of the image, it is adjusted so that only the part
       -inside the image is printed.
       -
       -Configuration:
       -
       -If you want to, you can edit a few things at the top of `bookcrop.c`.
       -COLLISION_PADDING is the number of pixels to check for collision if
       -an edge or corner is clicked.
       -SELECTION_COLOR1 and SELECTION_COLOR2 are the two colors for the
       -selection box that can be switched with tab.
       -If you want to change the command that is output, you can change
       -the function `print_cmd`. It just receives the filename, the coordinates
       -of the top left corner of the cropping box, and the width and height
       -of the box.
       +See croptool.1 for more information.
   DIR diff --git a/TODO b/TODO
       t@@ -1,2 +1 @@
       -Allow to change selection color from command line
        Proper path handling (allow paths including "'", etc.)
   DIR diff --git a/croptool.1 b/croptool.1
       t@@ -0,0 +1,106 @@
       +.Dd January 1, 2021
       +.Dt CROPTOOL 1
       +.Os
       +.Sh NAME
       +.Nm croptool
       +.Nd mass image cropping tool
       +.Sh SYNOPSIS
       +.Nm
       +.Op Ar -mr
       +.Op Ar -f format
       +.Op Ar -w width
       +.Op Ar -c padding
       +.Op Ar -p color
       +.Op Ar -s color
       +.Ar file ...
       +.Sh DESCRIPTION
       +.Nm
       +shows each of the given images and allows a cropping rectangle to be drawn.
       +On exit, the cropping command is printed out for each of the files. If a file
       +was skipped, nothing is printed out for it.
       +.Sh OPTIONS
       +.Bl -tag -width Ds
       +.It Fl m
       +Enable manual window redrawing (i.e. disable automatic redrawing) when the window
       +is resized. This may be useful on older machines that start accelerating global
       +warming when the image is redrawn constantly while resizing.
       +.It Fl r
       +Disable automatic redrawing while the cropping box is being dragged or resized,
       +for the same reason as
       +.Fl m .
       +.It Fl f Ar format
       +Set the format to be used when the cropping commands are output.
       +See OUTPUT FORMAT for details.
       +.It Fl w Ar width
       +Set the line width of the cropping rectangle. Default: 2.
       +.It Fl c Ar padding
       +Set the amount of padding used for collision with the mouse. This determines
       +how far away the mouse pointer has to be from an edge or corner of the
       +cropping rectangle to collide with it. Default: 10.
       +.It Fl p Ar color
       +Set the primary color for the cropping rectangle. Default: #000000.
       +.It Fl s Ar color
       +Set the secondary color for the cropping rectangle. Default: #FFFFFF.
       +.Sh OUTPUT FORMAT
       +The cropping commands for each image are output using the format given by
       +.Fl f
       +, or the default of 
       +.Ql mogrify -crop %wx%h+%l+%t '%f' .
       +The following substitutions are performed:
       +.Bl -tag -width Ds
       +.It %%
       +Print 
       +.Ql % .
       +.It %w
       +Print the width of the cropping rectangle.
       +.It %h
       +Print the height of the cropping rectangle.
       +.It %l
       +Print the location of the left side of the cropping rectangle.
       +.It %r
       +Print the location of the right side of the cropping rectangle.
       +.It %t
       +Print the location of the top side of the cropping rectangle.
       +.It %b
       +Print the location of the bottom side of the cropping rectangle.
       +.It %f
       +Print the filename of the image. Warning: This is printed out as is,
       +without any escaping. Yes, this should be fixed.
       +.El
       +.Pp
       +If an unknown substitution is encountered, a warning is printed to
       +stderr and the characters are printed out verbatim.
       +.Sh KEYBINDS
       +.Bl -tag -width Ds
       +.It ARROW LEFT
       +Go to the last image.
       +.It ARROW RIGHT
       +Go to the next image.
       +.It RETURN
       +Go to the next image, copying the current cropping rectangle.
       +.It TAB
       +Switch the color of the cropping rectangle between the primary and secondary colors.
       +.It DELETE
       +Delete the cropping rectangle of the current image.
       +.It SPACE
       +Redraw the window. This is useful when automatic resizing is disabled with
       +.Fl m .
       +.Sh MOUSE ACTIONS
       +.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
       +the corners, resize the rectangle regardless of dimension. When outside an
       +existing cropping rectangle, start a new cropping rectangle.
       +.Sh EXIT STATUS
       +.Ex -std
       +.Sh SEE ALSO
       +.Xr mogrify 1
       +.Sh BUGS
       +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.
       +.Sh AUTHORS
       +.An lumidify Aq Mt nobody@lumidify.org
   DIR diff --git a/croptool.c b/croptool.c
       t@@ -1,5 +1,5 @@
        /*
       - * Copyright (c) 2020 lumidify <nobody[at]lumidify.org>
       + * Copyright (c) 2021 lumidify <nobody[at]lumidify.org>
         *
         * Permission to use, copy, modify, and distribute this software for any
         * purpose with or without fee is hereby granted, provided that the above
       t@@ -14,28 +14,46 @@
         * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
         */
        
       +#include <math.h>
        #include <stdio.h>
       -#include <limits.h>
       +#include <errno.h>
       +#include <string.h>
        #include <stdlib.h>
       -#include <math.h>
       -#include <gtk/gtk.h>
       -#include <cairo/cairo.h>
       -#include <gdk/gdkkeysyms.h>
       +#include <stdlib.h>
       +#include <unistd.h>
       +#include <X11/Xlib.h>
       +#include <X11/Xutil.h>
       +#include <X11/keysym.h>
       +#include <X11/XF86keysym.h>
       +#include <X11/cursorfont.h>
       +#include <Imlib2.h>
        
        /* The number of pixels to check on each side when checking
         * if a corner or edge of the selection box was clicked 
         * (in order to change the size of the box) */
       -static const int COLLISION_PADDING = 10;
       +static int COLLISION_PADDING = 10;
        /* The color of the selection box */
       -static const char *SELECTION_COLOR1 = "#000";
       +static char *SELECTION_COLOR1 = "#000000";
        /* The second selection color - when tab is pressed */
       -static const char *SELECTION_COLOR2 = "#fff";
       -
       -/* Change this if you want a different output format. */
       -static void
       -print_cmd(const char *filename, int x, int y, int w, int h) {
       -        printf("mogrify -crop %dx%d+%d+%d '%s'\n", w, h, x, y, filename);
       -}
       +static char *SELECTION_COLOR2 = "#FFFFFF";
       +/* The width of the selection line */
       +static int LINE_WIDTH = 2;
       +/* When set to 1, the display is redrawn on window resize */
       +static short RESIZE_REDRAW = 1;
       +/* When set to 1, the selection is redrawn continually,
       +   not just when the mouse button is released */
       +static short SELECTION_REDRAW = 1;
       +/*
       +  The command printed for each image.
       +  %w: Width of cropped area.
       +  %h: Height of cropped area.
       +  %l: Left side of cropped area.
       +  %r: Right side of cropped area.
       +  %t: Top side of cropped area.
       +  %b: Bottom side of cropped area.
       +  %f: Filename of image.
       +*/
       +static char *CMD_FORMAT = "mogrify -crop %wx%h+%l+%t '%f'";
        
        struct Rect {
                int x0;
       t@@ -55,135 +73,397 @@ struct Selection {
                int orig_h;
                int scaled_w;
                int scaled_h;
       +        short valid;
        };
        
       -struct State {
       -        struct Selection **selections;
       +static struct {
       +        Display *dpy;
       +        GC gc;
       +        Window win;
       +        Visual *vis;
       +        Colormap cm;
       +        int screen;
       +        int depth;
       +
       +        struct Selection *selections;
                char **filenames;
                int cur_selection;
                int num_files;
                int window_w;
                int window_h;
       -        GdkPixbuf *cur_pixbuf;
                struct Point move_handle;
       -        gboolean moving;
       -        gboolean resizing;
       -        gboolean lock_x;
       -        gboolean lock_y;
       -        GdkColor col1;
       -        GdkColor col2;
       +        short moving;
       +        short resizing;
       +        short lock_x;
       +        short lock_y;
       +        XColor col1;
       +        XColor col2;
                int cur_col;
       -};
       +        Imlib_Image cur_image;
       +        Imlib_Updates updates;
       +} state;
       +
       +static struct {
       +        Cursor top;
       +        Cursor bottom;
       +        Cursor left;
       +        Cursor right;
       +        Cursor topleft;
       +        Cursor topright;
       +        Cursor bottomleft;
       +        Cursor bottomright;
       +        Cursor grab;
       +} cursors;
        
       -static void swap(int *a, int *b);
        static void sort_coordinates(int *x0, int *y0, int *x1, int *y1);
       +static void swap(int *a, int *b);
       +static void redraw();
       +static void print_cmd(const char *filename, int x, int y, int w, int h);
       +static void print_selection(struct Selection *sel, const char *filename);
        static int collide_point(int x, int y, int x_point, int y_point);
        static int collide_line(int x, int y, int x0, int y0, int x1, int y1);
        static int collide_rect(int x, int y, struct Rect rect);
       -static void redraw(GtkWidget *area, struct State *state);
       -static void destroy(GtkWidget *widget, gpointer data);
       -static gboolean draw_expose(GtkWidget *area, GdkEvent *event, gpointer data);
       -static gboolean button_press(GtkWidget *area, GdkEventButton *event, gpointer data);
       -static gboolean button_release(GtkWidget *area, GdkEventButton *event, gpointer data);
       -static gboolean drag_motion(GtkWidget *area, GdkEventMotion *event, gpointer data);
       -static gboolean key_press(GtkWidget *area, GdkEventKey *event, gpointer data);
       -static gboolean configure_event(GtkWidget *area, GdkEvent *event, gpointer data);
       -static void change_picture(GtkWidget *area, GdkPixbuf *new_pixbuf, int new_selection,
       -    int orig_w, int orig_h, struct State *state, gboolean copy_box);
       -static void next_picture(GtkWidget *area, struct State *state, gboolean copy_box);
       -static void last_picture(GtkWidget *area, struct State *state);
       -static GdkPixbuf *load_pixbuf(char *filename, int w, int h, int *actual_w, int *actual_h);
       -static void print_selection(struct Selection *sel, const char *filename);
       -static void clear_selection(GtkWidget *area, struct State *state);
       -static void resize_manual(GtkWidget *area, struct State *state);
       -static void switch_color(GtkWidget *area, struct State *state);
       -
       -int main(int argc, char *argv[]) {
       -        GtkWidget *window;
       -        gtk_init(&argc, &argv);
       +static void switch_color(void);
       +static void clear_selection(void);
       +static void last_picture(void);
       +static void next_picture(int copy_box);
       +static void change_picture(Imlib_Image new_image, int new_selection, int copy_box);
       +static void get_scaled_size(int orig_w, int orig_h, int *scaled_w, int *scaled_h);
       +static void set_selection(
       +    struct Selection *sel, int rect_x0, int rect_y0, int rect_x1,
       +    int rect_y1, int orig_w, int orig_h, int scaled_w, int scaled_h);
       +static void drag_motion(XEvent event);
       +static void resize_window(int w, int h);
       +static void button_release(XEvent event);
       +static void button_press(XEvent event);
       +static void key_press(XEvent event);
       +static void queue_update(int x, int y, int w, int h);
       +static int parse_small_positive_int(const char *str, int *value);
       +
       +int 
       +main(int argc, char **argv) {
       +        XEvent event;
       +        int running = 1;
       +        Atom wm_delete_msg;
       +        XSetWindowAttributes attrs;
       +        XGCValues gcv;
       +        char c;
       +
       +        while ((c = getopt(argc, argv, "f:w:c:mrp:s:")) != -1) {
       +                switch (c) {
       +                case 'f':
       +                        CMD_FORMAT = optarg;
       +                        break;
       +                case 'm':
       +                        RESIZE_REDRAW = 0;
       +                        break;
       +                case 'r':
       +                        SELECTION_REDRAW = 0;
       +                        break;
       +                case 'p':
       +                        SELECTION_COLOR1 = optarg;
       +                        break;
       +                case 's':
       +                        SELECTION_COLOR2 = optarg;
       +                        break;
       +                case 'c':
       +                        if (parse_small_positive_int(optarg, &COLLISION_PADDING)) {
       +                                fprintf(stderr, "Invalid collision padding.\n");
       +                                exit(1);
       +                        }
       +                        break;
       +                case 'w':
       +                        if (parse_small_positive_int(optarg, &LINE_WIDTH)) {
       +                                fprintf(stderr, "Invalid line width.\n");
       +                                exit(1);
       +                        }
       +                        break;
       +                default:
       +                        fprintf(stderr, "USAGE: croptool [-mr] [-f format] "
       +                            "[-w width] [-c padding] [-p color] [-s color] "
       +                            "[file ...]\n");
       +                        exit(1);
       +                        break;
       +                }
       +        }
        
       -        argc--;
       -        argv++;
       +        argc -= optind;
       +        argv += optind;
                if (argc < 1) {
                        fprintf(stderr, "No file given\n");
                        exit(1);
                }
        
       -        struct State *state = malloc(sizeof(struct State));
       -        state->cur_pixbuf = NULL;
       -        state->selections = malloc(argc * sizeof(struct Selection *));
       -        state->num_files = argc;
       -        state->filenames = argv;
       -        state->cur_selection = -1;
       -        state->moving = FALSE;
       -        state->resizing = FALSE;
       -        state->lock_x = FALSE;
       -        state->lock_y = FALSE;
       -        state->window_w = 0;
       -        state->window_h = 0;
       -        state->cur_col = 1;
       +        state.cur_image = NULL;
       +        state.selections = malloc(argc * sizeof(struct Selection));
       +        if (!state.selections) exit(1);
       +        state.num_files = argc;
       +        state.filenames = argv;
       +        state.cur_selection = -1;
       +        state.moving = 0;
       +        state.resizing = 0;
       +        state.lock_x = 0;
       +        state.lock_y = 0;
       +        state.window_w = 500;
       +        state.window_h = 500;
       +        state.cur_col = 1;
       +
                for (int i = 0; i < argc; i++) {
       -                state->selections[i] = NULL;
       +                state.selections[i].valid = 0;
                }
        
       -        window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
       -        gtk_window_set_title(GTK_WINDOW(window), "croptool");
       -        gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
       -        g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
       -
       -        GtkWidget *area = gtk_drawing_area_new();
       -        GTK_WIDGET_SET_FLAGS(area, GTK_CAN_FOCUS);
       -        gtk_widget_add_events(area,
       -            GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
       -            GDK_BUTTON_MOTION_MASK | GDK_KEY_PRESS_MASK |
       -            GDK_POINTER_MOTION_HINT_MASK | GDK_POINTER_MOTION_MASK);
       -        gtk_container_add(GTK_CONTAINER(window), area);
       -
       -        g_signal_connect(area, "expose-event", G_CALLBACK(draw_expose), state);
       -        g_signal_connect(area, "button-press-event", G_CALLBACK(button_press), state);
       -        g_signal_connect(area, "button-release-event", G_CALLBACK(button_release), state);
       -        g_signal_connect(area, "motion-notify-event", G_CALLBACK(drag_motion), state);
       -        g_signal_connect(window, "configure-event", G_CALLBACK(configure_event), state);
       -        g_signal_connect(window, "key-press-event", G_CALLBACK(key_press), state);
       -
       -        gtk_widget_show_all(window);
       -
       -        GdkColormap *cmap = gdk_drawable_get_colormap(area->window);
       -        gdk_colormap_alloc_color(cmap, &state->col1, FALSE, TRUE);
       -        gdk_color_parse(SELECTION_COLOR1, &state->col1);
       -        gdk_colormap_alloc_color(cmap, &state->col2, FALSE, TRUE);
       -        gdk_color_parse(SELECTION_COLOR2, &state->col2);
       -        g_object_unref(cmap);
       -
       -        gtk_main();
       +        state.dpy = XOpenDisplay(NULL);
       +        state.screen = DefaultScreen(state.dpy);
       +        state.vis = DefaultVisual(state.dpy, state.screen);
       +        state.depth = DefaultDepth(state.dpy, state.screen);
       +        state.cm = DefaultColormap(state.dpy, state.screen);
       +
       +        memset(&attrs, 0, sizeof(attrs));
       +        attrs.background_pixmap = None;
       +        attrs.colormap = state.cm;
       +        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);
       +
       +        memset(&gcv, 0, sizeof(gcv));
       +        gcv.line_width = LINE_WIDTH;
       +        state.gc = XCreateGC(state.dpy, state.win, GCLineWidth, &gcv);
       +
       +        if (!XParseColor(state.dpy, state.cm, SELECTION_COLOR1, &state.col1)) {
       +                fprintf(stderr, "Primary color invalid.\n");
       +                exit(1);
       +        }
       +        XAllocColor(state.dpy, state.cm, &state.col1);
       +        if (!XParseColor(state.dpy, state.cm, SELECTION_COLOR2, &state.col2)) {
       +                fprintf(stderr, "Secondary color invalid.\n");
       +                exit(1);
       +        }
       +        XAllocColor(state.dpy, state.cm, &state.col2);
       +
       +        XSelectInput(state.dpy, state.win, StructureNotifyMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | PointerMotionMask | ExposureMask);
       +        XMapWindow(state.dpy, state.win);
       +
       +        wm_delete_msg = XInternAtom(state.dpy, "WM_DELETE_WINDOW", False);
       +        XSetWMProtocols(state.dpy, state.win, &wm_delete_msg, 1);
       +
       +        cursors.top = XCreateFontCursor(state.dpy, XC_top_side);
       +        cursors.bottom = XCreateFontCursor(state.dpy, XC_bottom_side);
       +        cursors.left = XCreateFontCursor(state.dpy, XC_left_side);
       +        cursors.right = XCreateFontCursor(state.dpy, XC_right_side);
       +        cursors.topleft = XCreateFontCursor(state.dpy, XC_top_left_corner);
       +        cursors.topright = XCreateFontCursor(state.dpy, XC_top_right_corner);
       +        cursors.bottomleft = XCreateFontCursor(state.dpy, XC_bottom_left_corner);
       +        cursors.bottomright = XCreateFontCursor(state.dpy, XC_bottom_right_corner);
       +        cursors.grab = XCreateFontCursor(state.dpy, XC_fleur);
       +
       +        imlib_set_cache_size(2048 * 2048);
       +        imlib_set_color_usage(128);
       +        imlib_context_set_dither(1);
       +        imlib_context_set_display(state.dpy);
       +        imlib_context_set_visual(state.vis);
       +        imlib_context_set_colormap(state.cm);
       +        imlib_context_set_drawable(state.win);
       +        state.updates = imlib_updates_init();
       +
       +        next_picture(0);
       +        redraw();
       +
       +        while (running) {
       +                do {
       +                        XNextEvent(state.dpy, &event);
       +                        switch (event.type) {
       +                        case Expose:
       +                                queue_update(event.xexpose.x, event.xexpose.y,
       +                                    event.xexpose.width, event.xexpose.height);
       +                                break;
       +                        case ConfigureNotify:
       +                                if (RESIZE_REDRAW)
       +                                        resize_window(event.xconfigure.width, event.xconfigure.height);
       +                                break;
       +                        case ButtonPress:
       +                                if (event.xbutton.button == Button1)
       +                                        button_press(event);
       +                                break;
       +                        case ButtonRelease:
       +                                if (event.xbutton.button == Button1)
       +                                        button_release(event);
       +                                break;
       +                        case MotionNotify:
       +                                drag_motion(event);
       +                                break;
       +                        case KeyPress:
       +                                key_press(event);
       +                                break;
       +                        case ClientMessage:
       +                                if (event.xclient.data.l[0] == wm_delete_msg)
       +                                        running = 0;
       +                        default:
       +                                break;
       +                        }
       +                } while (XPending(state.dpy));
       +
       +                redraw();
       +        }
        
                for (int i = 0; i < argc; i++) {
       -                if (state->selections[i]) {
       -                        print_selection(state->selections[i], argv[i]);
       -                        free(state->selections[i]);
       +                if (state.selections[i].valid) {
       +                        print_selection(&state.selections[i], argv[i]);
                        }
                }
       -        if (state->cur_pixbuf)
       -                g_object_unref(G_OBJECT(state->cur_pixbuf));
       -        free(state->selections);
       -        free(state);
       +        if (state.cur_image) {
       +                imlib_context_set_image(state.cur_image);
       +                imlib_free_image();
       +        }
       +        free(state.selections);
       +        XDestroyWindow(state.dpy, state.win);
       +        XCloseDisplay(state.dpy);
        
                return 0;
        }
        
       +/* TODO: Allow printing filename without ending */
       +/* TODO: Escape filename properly */
       +static void
       +print_cmd(const char *filename, int x, int y, int w, int h) {
       +        short percent = 0;
       +        char *c;
       +        int length = 0;
       +        int start_index = 0;
       +        for (c = CMD_FORMAT; *c != '\0'; c++) {
       +                if (percent)
       +                        start_index++;
       +                if (*c == '%') {
       +                        if (length) {
       +                                printf("%.*s", length, CMD_FORMAT + start_index);
       +                                start_index += length;
       +                                length = 0;
       +                        }
       +                        if (percent)
       +                                printf("%%");
       +                        percent++;
       +                        percent %= 2;
       +                        start_index++;
       +                } else if (percent && *c == 'w') {
       +                        printf("%d", w);
       +                        percent = 0;
       +                } else if (percent && *c == 'h') {
       +                        printf("%d", h);
       +                        percent = 0;
       +                } else if (percent && *c == 'l') {
       +                        printf("%d", x);
       +                        percent = 0;
       +                } else if (percent && *c == 't') {
       +                        printf("%d", y);
       +                        percent = 0;
       +                } else if (percent && *c == 'r') {
       +                        printf("%d", x + w);
       +                        percent = 0;
       +                } else if (percent && *c == 'b') {
       +                        printf("%d", y + h);
       +                        percent = 0;
       +                } else if (percent && *c == 'f') {
       +                        printf("%s", filename);
       +                        percent = 0;
       +                } else if (percent) {
       +                        fprintf(stderr, "Warning: Unknown substitution '%c' in format string.\n", *c);
       +                        printf("%%%c", *c);
       +                        percent = 0;
       +                } else {
       +                        length++;
       +                }
       +        }
       +        if (length)
       +                printf("%.*s", length, CMD_FORMAT + start_index);
       +        printf("\n");
       +}
       +
       +/* Parses integer between 0 and 100 (non-inclusive).
       +   Returns 1 on error, 0 otherwise.
       +   The result is stored in *value. */
       +static int
       +parse_small_positive_int(const char *str, int *value) {
       +        char *end;
       +        long l = strtol(str, &end, 10);
       +        if (str == end || *end != '\0') {
       +                return 1; 
       +        } else if (l <= 0 || l >= 100 || ((l == LONG_MIN ||
       +            l == LONG_MAX) && errno == ERANGE)) {
       +                return 1;
       +        }
       +        *value = (int)l;
       +
       +        return 0;
       +}
       +
       +static void
       +queue_update(int x, int y, int w, int h) {
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
       +                return;
       +        struct Selection *sel = &state.selections[state.cur_selection];
       +        if (x > sel->scaled_w || y > sel->scaled_h)
       +                return;
       +        state.updates = imlib_update_append_rect(
       +            state.updates, x, y,
       +            w + x > sel->scaled_w ? sel->scaled_w - x : w,
       +            h + y > sel->scaled_h ? sel->scaled_h - y : h);
       +}
       +
       +static void
       +redraw(void) {
       +        Imlib_Image buffer;
       +        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;
       +        }
       +        struct Selection *sel = &state.selections[state.cur_selection];
       +        state.updates = imlib_updates_merge_for_rendering(state.updates, sel->scaled_w, sel->scaled_h);
       +        for (current_update = state.updates; current_update;
       +            current_update = imlib_updates_get_next(current_update)) {
       +                int up_x, up_y, up_w, up_h;
       +                imlib_updates_get_coordinates(current_update, &up_x, &up_y, &up_w, &up_h);
       +                buffer = imlib_create_image(up_w, up_h);
       +                imlib_context_set_blend(0);
       +                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,
       +                    -up_x, -up_y,
       +                    state.selections[state.cur_selection].scaled_w,
       +                    state.selections[state.cur_selection].scaled_h);
       +                imlib_render_image_on_drawable(up_x, up_y);
       +                imlib_free_image();
       +        }
       +        if (state.updates)
       +                imlib_updates_free(state.updates);
       +        state.updates = imlib_updates_init();
       +
       +        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);
       +
       +        XColor col = state.cur_col == 1 ? state.col1 : state.col2;
       +        XSetForeground(state.dpy, state.gc, col.pixel);
       +        struct Rect rect = sel->rect;
       +        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);
       +}
       +
        static void
        swap(int *a, int *b) {
       -        int tmp = *a;
       -        *a = *b;
       -        *b = tmp;
       +        int tmp = *a;
       +        *a = *b;
       +        *b = tmp;
        }
        
        static void
        sort_coordinates(int *x0, int *y0, int *x1, int *y1) {
       -        if (*x0 > *x1)
       -                swap(x0, x1);
       -        if(*y0 > *y1)
       -                swap(y0, y1);
       +        if (*x0 > *x1)
       +                swap(x0, x1);
       +        if(*y0 > *y1)
       +                swap(y0, y1);
        }
        
        static void
       t@@ -210,27 +490,6 @@ print_selection(struct Selection *sel, const char *filename) {
                print_cmd(filename, x0, y0, x1 - x0, y1 - y0);
        }
        
       -static GdkPixbuf *
       -load_pixbuf(char *filename, int w, int h, int *actual_w, int *actual_h) {
       -        (void)gdk_pixbuf_get_file_info(filename, actual_w, actual_h);
       -        /* *actual_w and *actual_h can be garbage if the file doesn't exist */
       -        w = w < *actual_w || *actual_w < 0 ? w : *actual_w;
       -        h = h < *actual_h || *actual_h < 0 ? h : *actual_h;
       -        GError *err = NULL;
       -        GdkPixbuf *pix = gdk_pixbuf_new_from_file_at_size(filename, w, h, &err);
       -        if (err) {
       -                fprintf(stderr, "%s\n", err->message);
       -                g_error_free(err);
       -                return NULL;
       -        }
       -        return pix;
       -}
       -
       -static void
       -destroy(GtkWidget *widget, gpointer data) {
       -        gtk_main_quit();
       -}
       -
        static int
        collide_point(int x, int y, int x_point, int y_point) {
                return (abs(x - x_point) <= COLLISION_PADDING) &&
       t@@ -258,14 +517,13 @@ collide_rect(int x, int y, struct Rect rect) {
                return (x0 <= x) && (x <= x1) && (y0 <= y) && (y <= y1);
        }
        
       -static gboolean
       -button_press(GtkWidget *area, GdkEventButton *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        if (state->cur_selection < 0 || !state->selections[state->cur_selection])
       -                return FALSE;
       -        struct Rect *rect = &state->selections[state->cur_selection]->rect;
       -        gint x = event->x;
       -        gint y = event->y;
       +static void
       +button_press(XEvent event) {
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
       +                return;
       +        struct Rect *rect = &state.selections[state.cur_selection].rect;
       +        int x = event.xbutton.x;
       +        int y = event.xbutton.y;
                int x0 = rect->x0, x1 = rect->x1;
                int y0 = rect->y0, y1 = rect->y1;
                if (collide_point(x, y, x0, y0)) {
       t@@ -285,153 +543,146 @@ button_press(GtkWidget *area, GdkEventButton *event, gpointer data) {
                        rect->x1 = x;
                        rect->y1 = y;
                } else if (collide_line(x, y, x0, y0, x1, y0)) {
       -                state->lock_y = TRUE;
       +                state.lock_y = 1;
                        swap(&rect->x0, &rect->x1);
                        rect->y0 = rect->y1;
                        rect->y1 = y;
                } else if (collide_line(x, y, x0, y0, x0, y1)) {
       -                state->lock_x = TRUE;
       +                state.lock_x = 1;
                        swap(&rect->y0, &rect->y1);
                        rect->x0 = rect->x1;
                        rect->x1 = x;
                } else if (collide_line(x, y, x1, y1, x0, y1)) {
       -                state->lock_y = TRUE;
       +                state.lock_y = 1;
                        rect->y1 = y;
                } else if (collide_line(x, y, x1, y1, x1, y0)) {
       -                state->lock_x = TRUE;
       +                state.lock_x = 1;
                        rect->x1 = x;
                } else if (collide_rect(x, y, *rect)) {
       -                state->moving = TRUE;
       -                state->move_handle.x = x;
       -                state->move_handle.y = y;
       +                state.moving = 1;
       +                state.move_handle.x = x;
       +                state.move_handle.y = y;
                } else {
                        rect->x0 = x;
                        rect->y0 = y;
                        rect->x1 = x;
                        rect->y1 = y;
                }
       -        state->resizing = TRUE;
       -        return FALSE;
       +        state.resizing = 1;
        }
        
       -static gboolean
       -button_release(GtkWidget *area, GdkEventButton *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        state->moving = FALSE;
       -        state->resizing = FALSE;
       -        state->lock_x = FALSE;
       -        state->lock_y = FALSE;
       -        return FALSE;
       +static void
       +button_release(XEvent event) {
       +        state.moving = 0;
       +        state.resizing = 0;
       +        state.lock_x = 0;
       +        state.lock_y = 0;
       +        if (!SELECTION_REDRAW)
       +                queue_update(0, 0, state.window_w, state.window_h);
        }
        
        static void
       -redraw(GtkWidget *area, struct State *state) {
       -        if (!state->cur_pixbuf)
       -                return;
       -        cairo_t *cr;
       -        cr = gdk_cairo_create(area->window);
       -
       -        gdk_cairo_set_source_pixbuf(cr, state->cur_pixbuf, 0, 0);
       -        cairo_paint(cr);
       -
       -        GdkColor col = state->cur_col == 1 ? state->col1 : state->col2;
       -        if (state->selections[state->cur_selection]) {
       -                struct Rect rect = state->selections[state->cur_selection]->rect;
       -                gdk_cairo_set_source_color(cr, &col);
       -                cairo_move_to(cr, rect.x0, rect.y0);
       -                cairo_line_to(cr, rect.x1, rect.y0);
       -                cairo_line_to(cr, rect.x1, rect.y1);
       -                cairo_line_to(cr, rect.x0, rect.y1);
       -                cairo_line_to(cr, rect.x0, rect.y0);
       -                cairo_stroke(cr);
       -        }
       +resize_window(int w, int h) {
       +        int actual_w, actual_h;
       +        struct Selection *sel;
       +        state.window_w = w;
       +        state.window_h = h;
        
       -        cairo_destroy(cr);
       -}
       -
       -static gboolean
       -configure_event(GtkWidget *area, GdkEvent *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        state->window_w = event->configure.width;
       -        state->window_h = event->configure.height;
       -        if (state->cur_selection == -1 && state->window_w > 0 && state->window_h > 0) {
       -                next_picture(area, state, FALSE);
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
       +                return;
       +        sel = &state.selections[state.cur_selection];
       +        get_scaled_size(sel->orig_w, sel->orig_h, &actual_w, &actual_h);
       +        if (actual_w != sel->scaled_w) {
       +                if (sel->rect.x0 != -200) {
       +                        /* If there is a selection, we need to convert it to the new scale.
       +                         * This only takes width into account because the aspect ratio
       +                         * should have been preserved anyways */
       +                        double scale = (double)actual_w / sel->scaled_w;
       +                        sel->rect.x0 = round(sel->rect.x0 * scale);
       +                        sel->rect.y0 = round(sel->rect.y0 * scale);
       +                        sel->rect.x1 = round(sel->rect.x1 * scale);
       +                        sel->rect.y1 = round(sel->rect.y1 * scale);
       +                }
       +                sel->scaled_w = actual_w;
       +                sel->scaled_h = actual_h;
       +                queue_update(0, 0, sel->scaled_w, sel->scaled_h);
                }
       -        return FALSE;
       -}
       -
       -static gboolean
       -draw_expose(GtkWidget *area, GdkEvent *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        if (state->cur_selection < 0)
       -                return FALSE;
       -        redraw(area, state);
       -        return FALSE;
        }
        
       -static gboolean
       -drag_motion(GtkWidget *area, GdkEventMotion *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        if (state->cur_selection < 0 || !state->selections[state->cur_selection])
       -                return FALSE;
       -        struct Rect *rect = &state->selections[state->cur_selection]->rect;
       -        gint x = event->x;
       -        gint y = event->y;
       -        if (state->moving == TRUE) {
       -                int x_delta = x - state->move_handle.x;
       -                int y_delta = y - state->move_handle.y;
       +static void
       +drag_motion(XEvent event) {
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
       +                return;
       +        struct Rect *rect = &state.selections[state.cur_selection].rect;
       +        int x = event.xbutton.x;
       +        int y = event.xbutton.y;
       +        int x0 = rect->x0, x1 = rect->x1;
       +        int y0 = rect->y0, y1 = rect->y1;
       +        sort_coordinates(&x0, &y0, &x1, &y1);
       +        if (state.moving) {
       +                int x_delta = x - state.move_handle.x;
       +                int y_delta = y - state.move_handle.y;
                        rect->x0 += x_delta;
                        rect->y0 += y_delta;
                        rect->x1 += x_delta;
                        rect->y1 += y_delta;
       -                state->move_handle.x = x;
       -                state->move_handle.y = y;
       -        } else if (state->resizing == TRUE) {
       -                if (state->lock_y != TRUE)
       +                state.move_handle.x = x;
       +                state.move_handle.y = y;
       +        } else if (state.resizing) {
       +                if (!state.lock_y)
                                rect->x1 = x;
       -                if (state->lock_x != TRUE)
       +                if (!state.lock_x)
                                rect->y1 = y;
                } else {
       -                int x0 = rect->x0, x1 = rect->x1;
       -                int y0 = rect->y0, y1 = rect->y1;
       -                sort_coordinates(&x0, &y0, &x1, &y1);
       -                GdkCursor *c = NULL;
       -                GdkCursor *old = gdk_window_get_cursor(area->window);
       -                if (old)
       -                        gdk_cursor_unref(old);
       +                Cursor c = None;
                        if (collide_point(x, y, x0, y0)) {
       -                        c = gdk_cursor_new(GDK_TOP_LEFT_CORNER);
       +                        c = cursors.topleft;
                        } else if (collide_point(x, y, x1, y0)) {
       -                        c = gdk_cursor_new(GDK_TOP_RIGHT_CORNER);
       +                        c = cursors.topright;
                        } else if (collide_point(x, y, x0, y1)) {
       -                        c = gdk_cursor_new(GDK_BOTTOM_LEFT_CORNER);
       +                        c = cursors.bottomleft;
                        } else if (collide_point(x, y, x1, y1)) {
       -                        c = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER);
       +                        c = cursors.bottomright;
                        } else if (collide_line(x, y, x0, y0, x1, y0)) {
       -                        c = gdk_cursor_new(GDK_TOP_SIDE);
       +                        c = cursors.top;
                        } else if (collide_line(x, y, x1, y1, x0, y1)) {
       -                        c = gdk_cursor_new(GDK_BOTTOM_SIDE);
       +                        c = cursors.bottom;
                        } else if (collide_line(x, y, x1, y1, x1, y0)) {
       -                        c = gdk_cursor_new(GDK_RIGHT_SIDE);
       +                        c = cursors.right;
                        } else if (collide_line(x, y, x0, y0, x0, y1)) {
       -                        c = gdk_cursor_new(GDK_LEFT_SIDE);
       +                        c = cursors.left;
                        } else if (collide_rect(x, y, *rect)) {
       -                        c = gdk_cursor_new(GDK_FLEUR);
       +                        c = cursors.grab;
                        }
       -                gdk_window_set_cursor(area->window, c);
       -                return FALSE;
       +                XDefineCursor(state.dpy, state.win, c);
       +                return;
                }
        
       -        gtk_widget_queue_draw(area);
       -        return FALSE;
       +        if (SELECTION_REDRAW) {
       +                queue_update(
       +                    x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
       +                    y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
       +                    x1 - x0 + LINE_WIDTH * 2, LINE_WIDTH * 2);
       +                queue_update(
       +                    x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
       +                    y1 - LINE_WIDTH > 0 ? y1 - LINE_WIDTH : 0,
       +                    x1 - x0 + LINE_WIDTH * 2, LINE_WIDTH * 2);
       +                queue_update(
       +                    x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
       +                    y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
       +                    LINE_WIDTH * 2, y1 - y0 + LINE_WIDTH * 2);
       +                queue_update(
       +                    x1 - LINE_WIDTH > 0 ? x1 - LINE_WIDTH : 0,
       +                    y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
       +                    LINE_WIDTH * 2, y1 - y0 + LINE_WIDTH * 2);
       +        }
        }
        
       -static struct Selection *
       -create_selection(
       -        int rect_x0, int rect_y0, int rect_x1, int rect_y1,
       -        int orig_w, int orig_h, int scaled_w, int scaled_h) {
       +static void
       +set_selection(
       +        struct Selection *sel, int rect_x0, int rect_y0, int rect_x1,
       +        int rect_y1, int orig_w, int orig_h, int scaled_w, int scaled_h) {
        
       -        struct Selection *sel = malloc(sizeof(struct Selection));
                sel->rect.x0 = rect_x0;
                sel->rect.y0 = rect_y0;
                sel->rect.x1 = rect_x1;
       t@@ -440,35 +691,50 @@ create_selection(
                sel->orig_h = orig_h;
                sel->scaled_w = scaled_w;
                sel->scaled_h = scaled_h;
       -        return sel;
        }
        
        static void
       -change_picture(
       -        GtkWidget *area, GdkPixbuf *new_pixbuf,
       -        int new_selection, int orig_w, int orig_h,
       -        struct State *state, gboolean copy_box) {
       -
       -        if (state->cur_pixbuf) {
       -                g_object_unref(G_OBJECT(state->cur_pixbuf));
       -                state->cur_pixbuf = NULL;
       +get_scaled_size(int orig_w, int orig_h, int *scaled_w, int *scaled_h) {
       +        double scale_w, scale_h;
       +        scale_w = (double)state.window_w / (double)orig_w;
       +        scale_h = (double)state.window_h / (double)orig_h;
       +        if (orig_w <= state.window_w && orig_h <= state.window_h) {
       +                *scaled_w = orig_w;
       +                *scaled_h = orig_h;
       +        } else if (scale_w * orig_h > state.window_h) {
       +                *scaled_w = (int)(scale_h * orig_w);
       +                *scaled_h = state.window_h;
       +        } else {
       +                *scaled_w = state.window_w;
       +                *scaled_h = (int)(scale_w * orig_h);
                }
       -        state->cur_pixbuf = new_pixbuf;
       -        int old_selection = state->cur_selection;
       -        state->cur_selection = new_selection;
       -
       -        struct Selection *sel = state->selections[state->cur_selection];
       -        int actual_w = gdk_pixbuf_get_width(state->cur_pixbuf);
       -        int actual_h = gdk_pixbuf_get_height(state->cur_pixbuf);
       -        if (copy_box == TRUE && old_selection >= 0 && old_selection < state->num_files) {
       -                struct Selection *old = state->selections[old_selection];
       -                if (sel)
       -                        free(sel);
       -                sel = create_selection(old->rect.x0, old->rect.y0, old->rect.x1, old->rect.y1,
       +}
       +
       +static void
       +change_picture(Imlib_Image new_image, int new_selection, int copy_box) {
       +        int orig_w, orig_h, actual_w, actual_h;
       +        XSetStandardProperties(state.dpy, state.win, state.filenames[new_selection], NULL, None, NULL, 0, NULL);
       +        if (state.cur_image) {
       +                imlib_context_set_image(state.cur_image);
       +                imlib_free_image();
       +        }
       +        state.cur_image = new_image;
       +        imlib_context_set_image(state.cur_image);
       +        int old_selection = state.cur_selection;
       +        state.cur_selection = new_selection;
       +
       +        orig_w = imlib_image_get_width();
       +        orig_h = imlib_image_get_height();
       +        get_scaled_size(orig_w, orig_h, &actual_w, &actual_h);
       +
       +        struct Selection *sel = &state.selections[state.cur_selection];
       +        if (copy_box && old_selection >= 0 && old_selection < state.num_files) {
       +                struct Selection *old = &state.selections[old_selection];
       +                set_selection(sel, old->rect.x0, old->rect.y0, old->rect.x1, old->rect.y1,
                                orig_w, orig_h, actual_w, actual_h);
       -        } else if (!sel) {
       +        } else if (!sel->valid) {
                        /* Just fill it with -200 so we can check later if it has been used yet */
       -                sel = create_selection(-200, -200, -200, -200, orig_w, orig_h, actual_w, actual_h);
       +                set_selection(sel, -200, -200, -200, -200, orig_w, orig_h, actual_w, actual_h);
                } else if (sel->rect.x0 != -200 && actual_w != sel->scaled_w) {
                        /* If there is a selection, we need to convert it to the new scale.
                         * This only takes width into account because the aspect ratio
       t@@ -481,101 +747,89 @@ change_picture(
                }
                sel->scaled_w = actual_w;
                sel->scaled_h = actual_h;
       -        state->selections[state->cur_selection] = sel;
       -        gtk_widget_queue_draw(area);
       +        sel->valid = 1;
       +        queue_update(0, 0, sel->scaled_w, sel->scaled_h);
        }
        
        static void
       -next_picture(GtkWidget *area, struct State *state, gboolean copy_box) {
       -        if (state->cur_selection + 1 >= state->num_files)
       +next_picture(int copy_box) {
       +        if (state.cur_selection + 1 >= state.num_files)
                        return;
       -        GdkPixbuf *tmp_pixbuf = NULL;
       -        int tmp_cur_selection = state->cur_selection;
       -        int orig_w, orig_h;
       +        Imlib_Image tmp_image = NULL;
       +        int tmp_cur_selection = state.cur_selection;
                /* loop until we find a loadable file */
       -        while (!tmp_pixbuf && tmp_cur_selection + 1 < state->num_files) {
       +        while (!tmp_image && tmp_cur_selection + 1 < state.num_files) {
                        tmp_cur_selection++;
       -                tmp_pixbuf = load_pixbuf(
       -                    state->filenames[tmp_cur_selection],
       -                    state->window_w, state->window_h, &orig_w, &orig_h);
       +                tmp_image = imlib_load_image(state.filenames[tmp_cur_selection]);
                }
       -        if (!tmp_pixbuf)
       +        if (!tmp_image)
                        return;
       -        change_picture(area, tmp_pixbuf, tmp_cur_selection, orig_w, orig_h, state, copy_box);
       +
       +        change_picture(tmp_image, tmp_cur_selection, copy_box);
        }
        
        static void
       -last_picture(GtkWidget *area, struct State *state) {
       -        if (state->cur_selection <= 0)
       +last_picture(void) {
       +        if (state.cur_selection <= 0)
                        return;
       -        GdkPixbuf *tmp_pixbuf = NULL;
       -        int tmp_cur_selection = state->cur_selection;
       -        int orig_w, orig_h;
       +        Imlib_Image tmp_image = NULL;
       +        int tmp_cur_selection = state.cur_selection;
                /* loop until we find a loadable file */
       -        while (!tmp_pixbuf && tmp_cur_selection > 0) {
       +        while (!tmp_image && tmp_cur_selection > 0) {
                        tmp_cur_selection--;
       -                tmp_pixbuf = load_pixbuf(
       -                    state->filenames[tmp_cur_selection],
       -                    state->window_w, state->window_h, &orig_w, &orig_h);
       +                tmp_image = imlib_load_image(state.filenames[tmp_cur_selection]);
                }
        
       -        if (!tmp_pixbuf)
       +        if (!tmp_image)
                        return;
       -        change_picture(area, tmp_pixbuf, tmp_cur_selection, orig_w, orig_h, state, FALSE);
       +
       +        change_picture(tmp_image, tmp_cur_selection, 0);
        }
        
        static void
       -clear_selection(GtkWidget *area, struct State *state) {
       -        if (state->cur_selection < 0 || !state->selections[state->cur_selection])
       +clear_selection(void) {
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
                        return;
       -        struct Selection *sel = state->selections[state->cur_selection];
       +        struct Selection *sel = &state.selections[state.cur_selection];
                sel->rect.x0 = sel->rect.x1 = sel->rect.y0 = sel->rect.y1 = -200;
       -        gtk_widget_queue_draw(area);
       +        queue_update(0, 0, sel->scaled_w, sel->scaled_h);
        }
        
        static void
       -resize_manual(GtkWidget *area, struct State *state) {
       -        if (state->cur_selection < 0 || !state->selections[state->cur_selection])
       +switch_color(void) {
       +        if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
                        return;
       -        int orig_w, orig_h;
       -        GdkPixbuf *tmp_pixbuf = load_pixbuf(
       -            state->filenames[state->cur_selection],
       -            state->window_w, state->window_h, &orig_w, &orig_h);
       -        if (!tmp_pixbuf)
       -                return;
       -        change_picture(area, tmp_pixbuf, state->cur_selection, orig_w, orig_h, state, FALSE);
       +        state.cur_col = state.cur_col == 1 ? 2 : 1;
       +        queue_update(0, 0, state.window_w, state.window_h);
        }
        
        static void
       -switch_color(GtkWidget *area, struct State *state) {
       -        if (state->cur_selection < 0 || !state->selections[state->cur_selection])
       -                return;
       -        state->cur_col = state->cur_col == 1 ? 2 : 1;
       -        gtk_widget_queue_draw(area);
       -}
       -
       -static gboolean
       -key_press(GtkWidget *area, GdkEventKey *event, gpointer data) {
       -        struct State *state = (struct State *)data;
       -        switch (event->keyval) {
       -        case GDK_KEY_Left:
       -                last_picture(area, state);
       +key_press(XEvent event) {
       +        XWindowAttributes attrs;
       +        char buf[64];
       +        KeySym sym;
       +        XLookupString(&event.xkey, buf, sizeof(buf), &sym, NULL);
       +        switch (sym) {
       +        case XK_Left:
       +                last_picture();
       +                break;
       +        case XK_Right:
       +                next_picture(0);
                        break;
       -        case GDK_KEY_Right:
       -                next_picture(area, state, FALSE);
       +        case XK_Return:
       +                next_picture(1);
                        break;
       -        case GDK_KEY_Return:
       -                next_picture(area, state, TRUE);
       +        case XK_Delete:
       +                clear_selection();
                        break;
       -        case GDK_KEY_Delete:
       -                clear_selection(area, state);
       +        case XK_Tab:
       +                switch_color();
                        break;
       -        case GDK_KEY_space:
       -                resize_manual(area, state);
       +        case XK_space:
       +                XGetWindowAttributes(state.dpy, state.win, &attrs);
       +                resize_window(attrs.width, attrs.height);
                        break;
       -        case GDK_KEY_Tab:
       -                switch_color(area, state);
       +        default:
                        break;
                }
       -        return FALSE;
        }
   DIR diff --git a/Makefile b/old/Makefile
   DIR diff --git a/old/README b/old/README
       t@@ -0,0 +1,70 @@
       +Note (2021-01-01): This is now the old gtk2 version. The new version
       +is based on xlib and imlib2, but this version should still keep
       +working. What follows is the original README.
       +
       +Requirements: gtk2 (which requires cairo and the other crap anyways)
       +
       +This is a small image cropping tool. It was actually written to help
       +crop large amounts of pictures when digitizing books, but it can be
       +used for cropping single pictures as well. There are probably many
       +bugs still. Oh, and the code probably isn't that great.
       +
       +Note that the whole image is redrawn when changing the selection
       +because I'm too dumb to change that, so it may occasionally lag
       +a little bit. It barely lags on my nice laptop with a single-core
       +Intel Celeron 2.00 Ghz from 2008, though, so that shouldn't be a
       +huge problem.
       +
       +Just start it with "croptool <image files>" and a window will pop up.
       +Initially, no image is shown, so you first have to press enter or
       +right arrow to go to the first image. When an image is shown, you can
       +click on it to create a selection box. If you click near the edges or
       +corners of the box, you can change its size, and if you click anywhere
       +in the middle, you can move it. Clicking outside creates a new box.
       +I don't know if all of the collision logic is entirely correct, so
       +tell me if you notice any problems.
       +
       +Several keys are recognized:
       +* Enter and right arrow both go to the next image, but enter copies the
       +  selection box from the current image and uses it for the next picture,
       +  while right arrow just goes to the next image and only displays a
       +  selection box if it already had one. This is so that lots of pages
       +  of a digitized book can be cropped quickly since the selection box
       +  needs to be tweaked occasionally (my digitizing equipment, if it
       +  can be called that, isn't exactly very professional).
       +* Left arrow just goes to the previous picture.
       +* Delete removes the selection for the current image (this is then
       +  also not printed out at the end).
       +* Space bar resizes the image if the window was resized.
       +* Tab switches the color of the selection box between the two colors
       +  defined at the top of `croptool.c` (SELECTION_COLOR1, SELECTION_COLOR2).
       +
       +Note that resizing the window currently does not resize the images.
       +It will only take effect if you move to another image or press
       +space bar. A side effect of this is that the image usually is 
       +displayed at the wrong size when the window initially opens in a
       +tiling window manager because the window is first mapped at the
       +requested (500x500) size and then resized by the window manager.
       +Just press space bar if that happens (it hasn't bothered me too
       +much up til now, and I use dwm). There may be bugs lurking here
       +as well since the actual cropping box needs to be rescaled according
       +to how much the image was scaled for display.
       +
       +When the window is closed, the ImageMagick command (mogrify -crop...)
       +for cropping each of the pictures that had a selection box defined
       +is printed (including the image currently being edited). If the box
       +was completely outside of the image, nothing is printed. If only part
       +of it was outside of the image, it is adjusted so that only the part
       +inside the image is printed.
       +
       +Configuration:
       +
       +If you want to, you can edit a few things at the top of `bookcrop.c`.
       +COLLISION_PADDING is the number of pixels to check for collision if
       +an edge or corner is clicked.
       +SELECTION_COLOR1 and SELECTION_COLOR2 are the two colors for the
       +selection box that can be switched with tab.
       +If you want to change the command that is output, you can change
       +the function `print_cmd`. It just receives the filename, the coordinates
       +of the top left corner of the cropping box, and the width and height
       +of the box.
   DIR diff --git a/croptool.c b/old/croptool.c