commit c5c5ee818931792a57b3115d89790a4ace8a1d4a
parent b9f502b3551d3ae657214abb591e119b417db772
Author: lumidify <nobody@lumidify.org>
Date: Fri, 1 Jan 2021 22:18:46 +0100
Add new version based on xlib and imlib2
Diffstat:
10 files changed, 793 insertions(+), 392 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -0,0 +1 @@
+croptool
diff --git a/LICENSE b/LICENSE
@@ -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.
+ */
diff --git a/Makefile b/Makefile
@@ -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
diff --git a/README b/README
@@ -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.
diff --git a/TODO b/TODO
@@ -1,2 +1 @@
-Allow to change selection color from command line
Proper path handling (allow paths including "'", etc.)
diff --git a/croptool.1 b/croptool.1
@@ -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
diff --git a/croptool.c b/croptool.c
@@ -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
@@ -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;
@@ -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
@@ -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) &&
@@ -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)) {
@@ -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;
@@ -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
@@ -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;
}
diff --git a/Makefile b/old/Makefile
diff --git a/old/README b/old/README
@@ -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.
diff --git a/croptool.c b/old/croptool.c