croptool

Image cropping tool
git clone git://lumidify.org/croptool.git (fast, but not encrypted)
git clone https://lumidify.org/croptool.git (encrypted, but very slow)
git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/croptool.git (over tor)
Log | Files | Refs | README | LICENSE

croptool.c (17849B)


      1 /*
      2  * Copyright (c) 2020 lumidify <nobody[at]lumidify.org>
      3  *
      4  * Permission to use, copy, modify, and distribute this software for any
      5  * purpose with or without fee is hereby granted, provided that the above
      6  * copyright notice and this permission notice appear in all copies.
      7  *
      8  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
      9  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     10  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     11  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     12  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     13  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     14  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     15  */
     16 
     17 #include <stdio.h>
     18 #include <limits.h>
     19 #include <stdlib.h>
     20 #include <math.h>
     21 #include <gtk/gtk.h>
     22 #include <cairo/cairo.h>
     23 #include <gdk/gdkkeysyms.h>
     24 
     25 /* The number of pixels to check on each side when checking
     26  * if a corner or edge of the selection box was clicked 
     27  * (in order to change the size of the box) */
     28 static const int COLLISION_PADDING = 10;
     29 /* The color of the selection box */
     30 static const char *SELECTION_COLOR1 = "#000";
     31 /* The second selection color - when tab is pressed */
     32 static const char *SELECTION_COLOR2 = "#fff";
     33 
     34 /* Change this if you want a different output format. */
     35 static void
     36 print_cmd(const char *filename, int x, int y, int w, int h) {
     37 	printf("mogrify -crop %dx%d+%d+%d '%s'\n", w, h, x, y, filename);
     38 }
     39 
     40 struct Rect {
     41 	int x0;
     42 	int y0;
     43 	int x1;
     44 	int y1;
     45 };
     46 
     47 struct Point {
     48 	int x;
     49 	int y;
     50 };
     51 
     52 struct Selection {
     53 	struct Rect rect;
     54 	int orig_w;
     55 	int orig_h;
     56 	int scaled_w;
     57 	int scaled_h;
     58 };
     59 
     60 struct State {
     61 	struct Selection **selections;
     62 	char **filenames;
     63 	int cur_selection;
     64 	int num_files;
     65 	int window_w;
     66 	int window_h;
     67 	GdkPixbuf *cur_pixbuf;
     68 	struct Point move_handle;
     69 	gboolean moving;
     70 	gboolean resizing;
     71 	gboolean lock_x;
     72 	gboolean lock_y;
     73 	GdkColor col1;
     74 	GdkColor col2;
     75 	int cur_col;
     76 };
     77 
     78 static void swap(int *a, int *b);
     79 static void sort_coordinates(int *x0, int *y0, int *x1, int *y1);
     80 static int collide_point(int x, int y, int x_point, int y_point);
     81 static int collide_line(int x, int y, int x0, int y0, int x1, int y1);
     82 static int collide_rect(int x, int y, struct Rect rect);
     83 static void redraw(GtkWidget *area, struct State *state);
     84 static void destroy(GtkWidget *widget, gpointer data);
     85 static gboolean draw_expose(GtkWidget *area, GdkEvent *event, gpointer data);
     86 static gboolean button_press(GtkWidget *area, GdkEventButton *event, gpointer data);
     87 static gboolean button_release(GtkWidget *area, GdkEventButton *event, gpointer data);
     88 static gboolean drag_motion(GtkWidget *area, GdkEventMotion *event, gpointer data);
     89 static gboolean key_press(GtkWidget *area, GdkEventKey *event, gpointer data);
     90 static gboolean configure_event(GtkWidget *area, GdkEvent *event, gpointer data);
     91 static void change_picture(GtkWidget *area, GdkPixbuf *new_pixbuf, int new_selection,
     92     int orig_w, int orig_h, struct State *state, gboolean copy_box);
     93 static void next_picture(GtkWidget *area, struct State *state, gboolean copy_box);
     94 static void last_picture(GtkWidget *area, struct State *state);
     95 static GdkPixbuf *load_pixbuf(char *filename, int w, int h, int *actual_w, int *actual_h);
     96 static void print_selection(struct Selection *sel, const char *filename);
     97 static void clear_selection(GtkWidget *area, struct State *state);
     98 static void resize_manual(GtkWidget *area, struct State *state);
     99 static void switch_color(GtkWidget *area, struct State *state);
    100 
    101 int main(int argc, char *argv[]) {
    102 	GtkWidget *window;
    103 	gtk_init(&argc, &argv);
    104 
    105 	argc--;
    106 	argv++;
    107 	if (argc < 1) {
    108 		fprintf(stderr, "No file given\n");
    109 		exit(1);
    110 	}
    111 
    112 	struct State *state = malloc(sizeof(struct State));
    113 	state->cur_pixbuf = NULL;
    114 	state->selections = malloc(argc * sizeof(struct Selection *));
    115 	state->num_files = argc;
    116 	state->filenames = argv;
    117 	state->cur_selection = -1;
    118 	state->moving = FALSE;
    119 	state->resizing = FALSE;
    120 	state->lock_x = FALSE;
    121 	state->lock_y = FALSE;
    122 	state->window_w = 0;
    123 	state->window_h = 0;
    124 	state->cur_col = 1;
    125 	for (int i = 0; i < argc; i++) {
    126 		state->selections[i] = NULL;
    127 	}
    128 
    129 	window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    130 	gtk_window_set_title(GTK_WINDOW(window), "croptool");
    131 	gtk_window_set_default_size(GTK_WINDOW(window), 500, 500);
    132 	g_signal_connect(G_OBJECT(window), "destroy", G_CALLBACK(destroy), NULL);
    133 
    134 	GtkWidget *area = gtk_drawing_area_new();
    135 	GTK_WIDGET_SET_FLAGS(area, GTK_CAN_FOCUS);
    136 	gtk_widget_add_events(area,
    137 	    GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
    138 	    GDK_BUTTON_MOTION_MASK | GDK_KEY_PRESS_MASK |
    139 	    GDK_POINTER_MOTION_HINT_MASK | GDK_POINTER_MOTION_MASK);
    140 	gtk_container_add(GTK_CONTAINER(window), area);
    141 
    142 	g_signal_connect(area, "expose-event", G_CALLBACK(draw_expose), state);
    143 	g_signal_connect(area, "button-press-event", G_CALLBACK(button_press), state);
    144 	g_signal_connect(area, "button-release-event", G_CALLBACK(button_release), state);
    145 	g_signal_connect(area, "motion-notify-event", G_CALLBACK(drag_motion), state);
    146 	g_signal_connect(window, "configure-event", G_CALLBACK(configure_event), state);
    147 	g_signal_connect(window, "key-press-event", G_CALLBACK(key_press), state);
    148 
    149 	gtk_widget_show_all(window);
    150 
    151 	GdkColormap *cmap = gdk_drawable_get_colormap(area->window);
    152 	gdk_colormap_alloc_color(cmap, &state->col1, FALSE, TRUE);
    153 	gdk_color_parse(SELECTION_COLOR1, &state->col1);
    154 	gdk_colormap_alloc_color(cmap, &state->col2, FALSE, TRUE);
    155 	gdk_color_parse(SELECTION_COLOR2, &state->col2);
    156 	g_object_unref(cmap);
    157 
    158 	gtk_main();
    159 
    160 	for (int i = 0; i < argc; i++) {
    161 		if (state->selections[i]) {
    162 			print_selection(state->selections[i], argv[i]);
    163 			free(state->selections[i]);
    164 		}
    165 	}
    166 	if (state->cur_pixbuf)
    167 		g_object_unref(G_OBJECT(state->cur_pixbuf));
    168 	free(state->selections);
    169 	free(state);
    170 
    171 	return 0;
    172 }
    173 
    174 static void
    175 swap(int *a, int *b) {
    176 	int tmp = *a;
    177 	*a = *b;
    178 	*b = tmp;
    179 }
    180 
    181 static void
    182 sort_coordinates(int *x0, int *y0, int *x1, int *y1) {
    183 	if (*x0 > *x1)
    184 		swap(x0, x1);
    185 	if(*y0 > *y1)
    186 		swap(y0, y1);
    187 }
    188 
    189 static void
    190 print_selection(struct Selection *sel, const char *filename) {
    191 	/* The box was never actually used */
    192 	if (sel->rect.x0 == -200)
    193 		return;
    194 	double scale = (double)sel->orig_w / sel->scaled_w;
    195 	int x0 = sel->rect.x0, y0 = sel->rect.y0;
    196 	int x1 = sel->rect.x1, y1 = sel->rect.y1;
    197 	sort_coordinates(&x0, &y0, &x1, &y1);
    198 	x0 = round(x0 * scale);
    199 	y0 = round(y0 * scale);
    200 	x1 = round(x1 * scale);
    201 	y1 = round(y1 * scale);
    202 	/* The box is completely outside of the picture. */
    203 	if (x0 >= sel->orig_w || y0 >= sel->orig_h)
    204 		return;
    205 	/* Cut the bounding box if it goes past the end of the picture. */
    206 	x0 = x0 < 0 ? 0 : x0;
    207 	y0 = y0 < 0 ? 0 : y0;
    208 	x1 = x1 > sel->orig_w ? sel->orig_w : x1;
    209 	y1 = y1 > sel->orig_h ? sel->orig_h : y1;
    210 	print_cmd(filename, x0, y0, x1 - x0, y1 - y0);
    211 }
    212 
    213 static GdkPixbuf *
    214 load_pixbuf(char *filename, int w, int h, int *actual_w, int *actual_h) {
    215 	(void)gdk_pixbuf_get_file_info(filename, actual_w, actual_h);
    216 	/* *actual_w and *actual_h can be garbage if the file doesn't exist */
    217 	w = w < *actual_w || *actual_w < 0 ? w : *actual_w;
    218 	h = h < *actual_h || *actual_h < 0 ? h : *actual_h;
    219 	GError *err = NULL;
    220 	GdkPixbuf *pix = gdk_pixbuf_new_from_file_at_size(filename, w, h, &err);
    221 	if (err) {
    222 		fprintf(stderr, "%s\n", err->message);
    223 		g_error_free(err);
    224 		return NULL;
    225 	}
    226 	return pix;
    227 }
    228 
    229 static void
    230 destroy(GtkWidget *widget, gpointer data) {
    231 	gtk_main_quit();
    232 }
    233 
    234 static int
    235 collide_point(int x, int y, int x_point, int y_point) {
    236 	return (abs(x - x_point) <= COLLISION_PADDING) &&
    237 		(abs(y - y_point) <= COLLISION_PADDING);
    238 }
    239 
    240 static int
    241 collide_line(int x, int y, int x0, int y0, int x1, int y1) {
    242 	sort_coordinates(&x0, &y0, &x1, &y1);
    243 	/* this expects a valid line */
    244 	if (x0 == x1) {
    245 		return (abs(x - x0) <= COLLISION_PADDING) &&
    246 			(y0 <= y) && (y <= y1);
    247 	} else {
    248 		return (abs(y - y0) <= COLLISION_PADDING) &&
    249 			(x0 <= x) && (x <= x1);
    250 	}
    251 }
    252 
    253 static int
    254 collide_rect(int x, int y, struct Rect rect) {
    255 	int x0 = rect.x0, x1 = rect.x1;
    256 	int y0 = rect.y0, y1 = rect.y1;
    257 	sort_coordinates(&x0, &y0, &x1, &y1);
    258 	return (x0 <= x) && (x <= x1) && (y0 <= y) && (y <= y1);
    259 }
    260 
    261 static gboolean
    262 button_press(GtkWidget *area, GdkEventButton *event, gpointer data) {
    263 	struct State *state = (struct State *)data;
    264 	if (state->cur_selection < 0 || !state->selections[state->cur_selection])
    265 		return FALSE;
    266 	struct Rect *rect = &state->selections[state->cur_selection]->rect;
    267 	gint x = event->x;
    268 	gint y = event->y;
    269 	int x0 = rect->x0, x1 = rect->x1;
    270 	int y0 = rect->y0, y1 = rect->y1;
    271 	if (collide_point(x, y, x0, y0)) {
    272 		rect->x0 = x1;
    273 		rect->y0 = y1;
    274 		rect->x1 = x;
    275 		rect->y1 = y;
    276 	} else if (collide_point(x, y, x1, y1)) {
    277 		rect->x1 = x;
    278 		rect->y1 = y;
    279 	} else if (collide_point(x, y, x0, y1)) {
    280 		rect->x0 = rect->x1;
    281 		rect->x1 = x;
    282 		rect->y1 = y;
    283 	} else if (collide_point(x, y, x1, y0)) {
    284 		rect->y0 = y1;
    285 		rect->x1 = x;
    286 		rect->y1 = y;
    287 	} else if (collide_line(x, y, x0, y0, x1, y0)) {
    288 		state->lock_y = TRUE;
    289 		swap(&rect->x0, &rect->x1);
    290 		rect->y0 = rect->y1;
    291 		rect->y1 = y;
    292 	} else if (collide_line(x, y, x0, y0, x0, y1)) {
    293 		state->lock_x = TRUE;
    294 		swap(&rect->y0, &rect->y1);
    295 		rect->x0 = rect->x1;
    296 		rect->x1 = x;
    297 	} else if (collide_line(x, y, x1, y1, x0, y1)) {
    298 		state->lock_y = TRUE;
    299 		rect->y1 = y;
    300 	} else if (collide_line(x, y, x1, y1, x1, y0)) {
    301 		state->lock_x = TRUE;
    302 		rect->x1 = x;
    303 	} else if (collide_rect(x, y, *rect)) {
    304 		state->moving = TRUE;
    305 		state->move_handle.x = x;
    306 		state->move_handle.y = y;
    307 	} else {
    308 		rect->x0 = x;
    309 		rect->y0 = y;
    310 		rect->x1 = x;
    311 		rect->y1 = y;
    312 	}
    313 	state->resizing = TRUE;
    314 	return FALSE;
    315 }
    316 
    317 static gboolean
    318 button_release(GtkWidget *area, GdkEventButton *event, gpointer data) {
    319 	struct State *state = (struct State *)data;
    320 	state->moving = FALSE;
    321 	state->resizing = FALSE;
    322 	state->lock_x = FALSE;
    323 	state->lock_y = FALSE;
    324 	return FALSE;
    325 }
    326 
    327 static void
    328 redraw(GtkWidget *area, struct State *state) {
    329 	if (!state->cur_pixbuf)
    330 		return;
    331 	cairo_t *cr;
    332 	cr = gdk_cairo_create(area->window);
    333 
    334 	gdk_cairo_set_source_pixbuf(cr, state->cur_pixbuf, 0, 0);
    335 	cairo_paint(cr);
    336 
    337 	GdkColor col = state->cur_col == 1 ? state->col1 : state->col2;
    338 	if (state->selections[state->cur_selection]) {
    339 		struct Rect rect = state->selections[state->cur_selection]->rect;
    340 		gdk_cairo_set_source_color(cr, &col);
    341 		cairo_move_to(cr, rect.x0, rect.y0);
    342 		cairo_line_to(cr, rect.x1, rect.y0);
    343 		cairo_line_to(cr, rect.x1, rect.y1);
    344 		cairo_line_to(cr, rect.x0, rect.y1);
    345 		cairo_line_to(cr, rect.x0, rect.y0);
    346 		cairo_stroke(cr);
    347 	}
    348 
    349 	cairo_destroy(cr);
    350 }
    351 
    352 static gboolean
    353 configure_event(GtkWidget *area, GdkEvent *event, gpointer data) {
    354 	struct State *state = (struct State *)data;
    355 	state->window_w = event->configure.width;
    356 	state->window_h = event->configure.height;
    357 	if (state->cur_selection == -1 && state->window_w > 0 && state->window_h > 0) {
    358 		next_picture(area, state, FALSE);
    359 	}
    360 	return FALSE;
    361 }
    362 
    363 static gboolean
    364 draw_expose(GtkWidget *area, GdkEvent *event, gpointer data) {
    365 	struct State *state = (struct State *)data;
    366 	if (state->cur_selection < 0)
    367 		return FALSE;
    368 	redraw(area, state);
    369 	return FALSE;
    370 }
    371 
    372 static gboolean
    373 drag_motion(GtkWidget *area, GdkEventMotion *event, gpointer data) {
    374 	struct State *state = (struct State *)data;
    375 	if (state->cur_selection < 0 || !state->selections[state->cur_selection])
    376 		return FALSE;
    377 	struct Rect *rect = &state->selections[state->cur_selection]->rect;
    378 	gint x = event->x;
    379 	gint y = event->y;
    380 	if (state->moving == TRUE) {
    381 		int x_delta = x - state->move_handle.x;
    382 		int y_delta = y - state->move_handle.y;
    383 		rect->x0 += x_delta;
    384 		rect->y0 += y_delta;
    385 		rect->x1 += x_delta;
    386 		rect->y1 += y_delta;
    387 		state->move_handle.x = x;
    388 		state->move_handle.y = y;
    389 	} else if (state->resizing == TRUE) {
    390 		if (state->lock_y != TRUE)
    391 			rect->x1 = x;
    392 		if (state->lock_x != TRUE)
    393 			rect->y1 = y;
    394 	} else {
    395 		int x0 = rect->x0, x1 = rect->x1;
    396 		int y0 = rect->y0, y1 = rect->y1;
    397 		sort_coordinates(&x0, &y0, &x1, &y1);
    398 		GdkCursor *c = NULL;
    399 		GdkCursor *old = gdk_window_get_cursor(area->window);
    400 		if (old)
    401 			gdk_cursor_unref(old);
    402 		if (collide_point(x, y, x0, y0)) {
    403 			c = gdk_cursor_new(GDK_TOP_LEFT_CORNER);
    404 		} else if (collide_point(x, y, x1, y0)) {
    405 			c = gdk_cursor_new(GDK_TOP_RIGHT_CORNER);
    406 		} else if (collide_point(x, y, x0, y1)) {
    407 			c = gdk_cursor_new(GDK_BOTTOM_LEFT_CORNER);
    408 		} else if (collide_point(x, y, x1, y1)) {
    409 			c = gdk_cursor_new(GDK_BOTTOM_RIGHT_CORNER);
    410 		} else if (collide_line(x, y, x0, y0, x1, y0)) {
    411 			c = gdk_cursor_new(GDK_TOP_SIDE);
    412 		} else if (collide_line(x, y, x1, y1, x0, y1)) {
    413 			c = gdk_cursor_new(GDK_BOTTOM_SIDE);
    414 		} else if (collide_line(x, y, x1, y1, x1, y0)) {
    415 			c = gdk_cursor_new(GDK_RIGHT_SIDE);
    416 		} else if (collide_line(x, y, x0, y0, x0, y1)) {
    417 			c = gdk_cursor_new(GDK_LEFT_SIDE);
    418 		} else if (collide_rect(x, y, *rect)) {
    419 			c = gdk_cursor_new(GDK_FLEUR);
    420 		}
    421 		gdk_window_set_cursor(area->window, c);
    422 		return FALSE;
    423 	}
    424 
    425 	gtk_widget_queue_draw(area);
    426 	return FALSE;
    427 }
    428 
    429 static struct Selection *
    430 create_selection(
    431 	int rect_x0, int rect_y0, int rect_x1, int rect_y1,
    432 	int orig_w, int orig_h, int scaled_w, int scaled_h) {
    433 
    434 	struct Selection *sel = malloc(sizeof(struct Selection));
    435 	sel->rect.x0 = rect_x0;
    436 	sel->rect.y0 = rect_y0;
    437 	sel->rect.x1 = rect_x1;
    438 	sel->rect.y1 = rect_y1;
    439 	sel->orig_w = orig_w;
    440 	sel->orig_h = orig_h;
    441 	sel->scaled_w = scaled_w;
    442 	sel->scaled_h = scaled_h;
    443 	return sel;
    444 }
    445 
    446 static void
    447 change_picture(
    448 	GtkWidget *area, GdkPixbuf *new_pixbuf,
    449 	int new_selection, int orig_w, int orig_h,
    450 	struct State *state, gboolean copy_box) {
    451 
    452 	if (state->cur_pixbuf) {
    453 		g_object_unref(G_OBJECT(state->cur_pixbuf));
    454 		state->cur_pixbuf = NULL;
    455 	}
    456 	state->cur_pixbuf = new_pixbuf;
    457 	int old_selection = state->cur_selection;
    458 	state->cur_selection = new_selection;
    459 
    460 	struct Selection *sel = state->selections[state->cur_selection];
    461 	int actual_w = gdk_pixbuf_get_width(state->cur_pixbuf);
    462 	int actual_h = gdk_pixbuf_get_height(state->cur_pixbuf);
    463 	if (copy_box == TRUE && old_selection >= 0 && old_selection < state->num_files) {
    464 		struct Selection *old = state->selections[old_selection];
    465 		if (sel)
    466 			free(sel);
    467 		sel = create_selection(old->rect.x0, old->rect.y0, old->rect.x1, old->rect.y1,
    468 			orig_w, orig_h, actual_w, actual_h);
    469 	} else if (!sel) {
    470 		/* Just fill it with -200 so we can check later if it has been used yet */
    471 		sel = create_selection(-200, -200, -200, -200, orig_w, orig_h, actual_w, actual_h);
    472 	} else if (sel->rect.x0 != -200 && actual_w != sel->scaled_w) {
    473 		/* If there is a selection, we need to convert it to the new scale.
    474 		 * This only takes width into account because the aspect ratio
    475 		 * should have been preserved anyways */
    476 		double scale = (double)actual_w / sel->scaled_w;
    477 		sel->rect.x0 = round(sel->rect.x0 * scale);
    478 		sel->rect.y0 = round(sel->rect.y0 * scale);
    479 		sel->rect.x1 = round(sel->rect.x1 * scale);
    480 		sel->rect.y1 = round(sel->rect.y1 * scale);
    481 	}
    482 	sel->scaled_w = actual_w;
    483 	sel->scaled_h = actual_h;
    484 	state->selections[state->cur_selection] = sel;
    485 	gtk_widget_queue_draw(area);
    486 }
    487 
    488 static void
    489 next_picture(GtkWidget *area, struct State *state, gboolean copy_box) {
    490 	if (state->cur_selection + 1 >= state->num_files)
    491 		return;
    492 	GdkPixbuf *tmp_pixbuf = NULL;
    493 	int tmp_cur_selection = state->cur_selection;
    494 	int orig_w, orig_h;
    495 	/* loop until we find a loadable file */
    496 	while (!tmp_pixbuf && tmp_cur_selection + 1 < state->num_files) {
    497 		tmp_cur_selection++;
    498 		tmp_pixbuf = load_pixbuf(
    499 		    state->filenames[tmp_cur_selection],
    500 		    state->window_w, state->window_h, &orig_w, &orig_h);
    501 	}
    502 	if (!tmp_pixbuf)
    503 		return;
    504 	change_picture(area, tmp_pixbuf, tmp_cur_selection, orig_w, orig_h, state, copy_box);
    505 }
    506 
    507 static void
    508 last_picture(GtkWidget *area, struct State *state) {
    509 	if (state->cur_selection <= 0)
    510 		return;
    511 	GdkPixbuf *tmp_pixbuf = NULL;
    512 	int tmp_cur_selection = state->cur_selection;
    513 	int orig_w, orig_h;
    514 	/* loop until we find a loadable file */
    515 	while (!tmp_pixbuf && tmp_cur_selection > 0) {
    516 		tmp_cur_selection--;
    517 		tmp_pixbuf = load_pixbuf(
    518 		    state->filenames[tmp_cur_selection],
    519 		    state->window_w, state->window_h, &orig_w, &orig_h);
    520 	}
    521 
    522 	if (!tmp_pixbuf)
    523 		return;
    524 	change_picture(area, tmp_pixbuf, tmp_cur_selection, orig_w, orig_h, state, FALSE);
    525 }
    526 
    527 static void
    528 clear_selection(GtkWidget *area, struct State *state) {
    529 	if (state->cur_selection < 0 || !state->selections[state->cur_selection])
    530 		return;
    531 	struct Selection *sel = state->selections[state->cur_selection];
    532 	sel->rect.x0 = sel->rect.x1 = sel->rect.y0 = sel->rect.y1 = -200;
    533 	gtk_widget_queue_draw(area);
    534 }
    535 
    536 static void
    537 resize_manual(GtkWidget *area, struct State *state) {
    538 	if (state->cur_selection < 0 || !state->selections[state->cur_selection])
    539 		return;
    540 	int orig_w, orig_h;
    541 	GdkPixbuf *tmp_pixbuf = load_pixbuf(
    542 	    state->filenames[state->cur_selection],
    543 	    state->window_w, state->window_h, &orig_w, &orig_h);
    544 	if (!tmp_pixbuf)
    545 		return;
    546 	change_picture(area, tmp_pixbuf, state->cur_selection, orig_w, orig_h, state, FALSE);
    547 }
    548 
    549 static void
    550 switch_color(GtkWidget *area, struct State *state) {
    551 	if (state->cur_selection < 0 || !state->selections[state->cur_selection])
    552 		return;
    553 	state->cur_col = state->cur_col == 1 ? 2 : 1;
    554 	gtk_widget_queue_draw(area);
    555 }
    556 
    557 static gboolean
    558 key_press(GtkWidget *area, GdkEventKey *event, gpointer data) {
    559 	struct State *state = (struct State *)data;
    560 	switch (event->keyval) {
    561 	case GDK_KEY_Left:
    562 		last_picture(area, state);
    563 		break;
    564 	case GDK_KEY_Right:
    565 		next_picture(area, state, FALSE);
    566 		break;
    567 	case GDK_KEY_Return:
    568 		next_picture(area, state, TRUE);
    569 		break;
    570 	case GDK_KEY_Delete:
    571 		clear_selection(area, state);
    572 		break;
    573 	case GDK_KEY_space:
    574 		resize_manual(area, state);
    575 		break;
    576 	case GDK_KEY_Tab:
    577 		switch_color(area, state);
    578 		break;
    579 	}
    580 	return FALSE;
    581 }