URI: 
       croptool.c - croptool - Image cropping tool
  HTML git clone git://lumidify.org/croptool.git (fast, but not encrypted)
  HTML git clone https://lumidify.org/croptool.git (encrypted, but very slow)
  HTML git clone git://4kcetb7mo7hj6grozzybxtotsub5bempzo4lirzc3437amof2c2impyd.onion/croptool.git (over tor)
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       croptool.c (23657B)
       ---
            1 /*
            2  * Copyright (c) 2021-2024 lumidify <nobody@lumidify.org>
            3  *
            4  * Permission to use, copy, modify, and/or 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 <math.h>
           18 #include <stdio.h>
           19 #include <stdlib.h>
           20 #include <unistd.h>
           21 
           22 #include <X11/X.h>
           23 #include <X11/Xlib.h>
           24 #include <X11/Xutil.h>
           25 #include <X11/keysym.h>
           26 #include <X11/cursorfont.h>
           27 
           28 #include <Imlib2.h>
           29 
           30 #include "common.h"
           31 
           32 /* The number of pixels to check on each side when checking
           33  * if a corner or edge of the selection box was clicked 
           34  * (in order to change the size of the box) */
           35 static int COLLISION_PADDING = 10;
           36 /* The color of the selection box */
           37 static const char *SELECTION_COLOR1 = "#000000";
           38 /* The second selection color - when tab is pressed */
           39 static const char *SELECTION_COLOR2 = "#FFFFFF";
           40 /* The width of the selection line */
           41 static int LINE_WIDTH = 2;
           42 /* When set to 1, the display is redrawn on window resize */
           43 static short RESIZE_REDRAW = 1;
           44 /* When set to 1, the selection is redrawn continually,
           45    not just when the mouse button is released */
           46 static short SELECTION_REDRAW = 1;
           47 /*
           48   The command printed for each image.
           49   %w: Width of cropped area.
           50   %h: Height of cropped area.
           51   %l: Left side of cropped area.
           52   %r: Right side of cropped area.
           53   %t: Top side of cropped area.
           54   %b: Bottom side of cropped area.
           55   %f: Filename of image.
           56 */
           57 static const char *CMD_FORMAT = "croptool_crop %wx%h+%l+%t '%f'";
           58 /* Size of Imlib2 in-memory cache in MiB */
           59 static int CACHE_SIZE = 4;
           60 
           61 extern char *optarg;
           62 extern int optind;
           63 
           64 struct Rect {
           65         int x0;
           66         int y0;
           67         int x1;
           68         int y1;
           69 };
           70 
           71 struct Point {
           72         int x;
           73         int y;
           74 };
           75 
           76 struct Selection {
           77         struct Rect rect;
           78         ImageSize sz;
           79         char valid;
           80 };
           81 
           82 static struct {
           83         GraphicsContext ctx;
           84 
           85         struct Selection *selections;
           86         char **filenames;
           87         int cur_selection;
           88         int num_files;
           89         int cursor_x;
           90         int cursor_y;
           91         struct Point move_handle;
           92         XColor col1;
           93         XColor col2;
           94         int cur_col;
           95         char moving;
           96         char resizing;
           97         char lock_x;
           98         char lock_y;
           99         char print_on_exit;
          100 } state;
          101 
          102 static struct {
          103         Cursor top;
          104         Cursor bottom;
          105         Cursor left;
          106         Cursor right;
          107         Cursor topleft;
          108         Cursor topright;
          109         Cursor bottomleft;
          110         Cursor bottomright;
          111         Cursor grab;
          112 } cursors;
          113 
          114 static void usage(void);
          115 static void mainloop(void);
          116 static void setup(int argc, char *argv[]);
          117 static void sort_coordinates(int *x0, int *y0, int *x1, int *y1);
          118 static void swap(int *a, int *b);
          119 static void redraw(void);
          120 static void print_cmd(const char *filename, int x, int y, int w, int h, int dry_run);
          121 static void print_selection(struct Selection *sel, const char *filename);
          122 static int collide_point(int x, int y, int x_point, int y_point);
          123 static int collide_line(int x, int y, int x0, int y0, int x1, int y1);
          124 static int collide_rect(int x, int y, struct Rect rect);
          125 static void switch_color(void);
          126 static void clear_selection(void);
          127 static void set_selection(
          128     struct Selection *sel, int rect_x0, int rect_y0, int rect_x1,
          129     int rect_y1, int orig_w, int orig_h, int scaled_w, int scaled_h
          130 );
          131 static void queue_update(int x, int y, int w, int h);
          132 static void queue_rectangle_redraw(int x0, int y0, int x1, int y1);
          133 static void set_cursor(struct Rect rect);
          134 static void drag_motion(XEvent event);
          135 static void resize_window(int w, int h);
          136 static void button_release(void);
          137 static void button_press(XEvent event);
          138 static int key_press(XEvent event);
          139 
          140 static void
          141 usage(void) {
          142         fprintf(stderr, "USAGE: croptool [-mr] [-f format] "
          143             "[-w width] [-c padding] [-p color] [-s color] "
          144             "[-z size] file ...\n");
          145 }
          146 
          147 int 
          148 main(int argc, char *argv[]) {
          149         char c;
          150 
          151         while ((c = getopt(argc, argv, "f:w:c:mrp:s:z:")) != -1) {
          152                 switch (c) {
          153                 case 'f':
          154                         CMD_FORMAT = optarg;
          155                         break;
          156                 case 'm':
          157                         RESIZE_REDRAW = 0;
          158                         break;
          159                 case 'r':
          160                         SELECTION_REDRAW = 0;
          161                         break;
          162                 case 'p':
          163                         SELECTION_COLOR1 = optarg;
          164                         break;
          165                 case 's':
          166                         SELECTION_COLOR2 = optarg;
          167                         break;
          168                 case 'c':
          169                         if (parse_int(optarg, 1, 99, &COLLISION_PADDING)) {
          170                                 fprintf(stderr, "Invalid collision padding.\n");
          171                                 exit(1);
          172                         }
          173                         break;
          174                 case 'w':
          175                         if (parse_int(optarg, 1, 99, &LINE_WIDTH)) {
          176                                 fprintf(stderr, "Invalid line width.\n");
          177                                 exit(1);
          178                         }
          179                         break;
          180                 case 'z':
          181                         if (parse_int(optarg, 0, 1024, &CACHE_SIZE)) {
          182                                 fprintf(stderr, "Invalid cache size.\n");
          183                                 exit(1);
          184                         }
          185                         break;
          186                 default:
          187                         usage();
          188                         exit(1);
          189                         break;
          190                 }
          191         }
          192         /* print warning if command format is invalid */
          193         print_cmd("", 0, 0, 0, 0, 1);
          194 
          195         argc -= optind;
          196         argv += optind;
          197         if (argc < 1) {
          198                 usage();
          199                 exit(1);
          200         }
          201         setup(argc, argv);
          202 
          203         mainloop();
          204 
          205         if (state.print_on_exit) {
          206                 for (int i = 0; i < argc; i++) {
          207                         if (state.selections[i].valid) {
          208                                 print_selection(&state.selections[i], state.filenames[i]);
          209                         }
          210                 }
          211         }
          212 
          213         cleanup();
          214 
          215         return 0;
          216 }
          217 
          218 static void
          219 mainloop(void) {
          220         XEvent event;
          221         int running = 1;
          222 
          223         while (running) {
          224                 do {
          225                         XNextEvent(state.ctx.dpy, &event);
          226                         switch (event.type) {
          227                         case Expose:
          228                                 if (RESIZE_REDRAW)
          229                                         queue_update(event.xexpose.x, event.xexpose.y,
          230                                             event.xexpose.width, event.xexpose.height);
          231                                 break;
          232                         case ConfigureNotify:
          233                                 if (RESIZE_REDRAW)
          234                                         resize_window(
          235                                             event.xconfigure.width,
          236                                             event.xconfigure.height
          237                                         );
          238                                 break;
          239                         case ButtonPress:
          240                                 if (event.xbutton.button == Button1)
          241                                         button_press(event);
          242                                 break;
          243                         case ButtonRelease:
          244                                 if (event.xbutton.button == Button1)
          245                                         button_release();
          246                                 break;
          247                         case MotionNotify:
          248                                 drag_motion(event);
          249                                 break;
          250                         case KeyPress:
          251                                 running = key_press(event);
          252                                 break;
          253                         case ClientMessage:
          254                                 if ((Atom)event.xclient.data.l[0] == state.ctx.wm_delete_msg)
          255                                         running = 0;
          256                         default:
          257                                 break;
          258                         }
          259                 } while (XPending(state.ctx.dpy));
          260 
          261                 redraw();
          262         }
          263 }
          264 
          265 static void
          266 setup(int argc, char *argv[]) {
          267         state.selections = malloc(argc * sizeof(struct Selection));
          268         if (!state.selections) {
          269                 fprintf(stderr, "Unable to allocate memory.\n");
          270                 exit(1);
          271         }
          272         state.num_files = argc;
          273         state.filenames = argv;
          274         state.cur_selection = -1;
          275         state.moving = 0;
          276         state.resizing = 0;
          277         state.lock_x = 0;
          278         state.lock_y = 0;
          279         state.cursor_x = 0;
          280         state.cursor_y = 0;
          281         state.cur_col = 1;
          282         state.print_on_exit = 0;
          283 
          284         for (int i = 0; i < argc; i++) {
          285                 state.selections[i].valid = 0;
          286         }
          287 
          288         setup_x(&state.ctx, 500, 500, LINE_WIDTH, CACHE_SIZE);
          289 
          290         if (!XParseColor(state.ctx.dpy, state.ctx.cm, SELECTION_COLOR1, &state.col1)) {
          291                 fprintf(stderr, "Primary color invalid.\n");
          292                 exit(1);
          293         }
          294         XAllocColor(state.ctx.dpy, state.ctx.cm, &state.col1);
          295         if (!XParseColor(state.ctx.dpy, state.ctx.cm, SELECTION_COLOR2, &state.col2)) {
          296                 fprintf(stderr, "Secondary color invalid.\n");
          297                 exit(1);
          298         }
          299         XAllocColor(state.ctx.dpy, state.ctx.cm, &state.col2);
          300 
          301         cursors.top = XCreateFontCursor(state.ctx.dpy, XC_top_side);
          302         cursors.bottom = XCreateFontCursor(state.ctx.dpy, XC_bottom_side);
          303         cursors.left = XCreateFontCursor(state.ctx.dpy, XC_left_side);
          304         cursors.right = XCreateFontCursor(state.ctx.dpy, XC_right_side);
          305         cursors.topleft = XCreateFontCursor(state.ctx.dpy, XC_top_left_corner);
          306         cursors.topright = XCreateFontCursor(state.ctx.dpy, XC_top_right_corner);
          307         cursors.bottomleft = XCreateFontCursor(state.ctx.dpy, XC_bottom_left_corner);
          308         cursors.bottomright = XCreateFontCursor(state.ctx.dpy, XC_bottom_right_corner);
          309         cursors.grab = XCreateFontCursor(state.ctx.dpy, XC_fleur);
          310 
          311         next_picture(state.cur_selection, state.filenames, state.num_files, 0);
          312         /* Only map window here so the program exits immediately if
          313            there are no loadable images, without first opening the
          314            window and closing it again immediately */
          315         XMapWindow(state.ctx.dpy, state.ctx.win);
          316         redraw();
          317 }
          318 
          319 void
          320 cleanup(void) {
          321         free(state.selections);
          322         cleanup_x(&state.ctx);
          323 }
          324 
          325 /* TODO: Escape filename properly
          326  * -> But how? Since the format can be set by the user,
          327  * it isn't really clear *what* needs to be escaped. */
          328 static void
          329 print_cmd(const char *filename, int x, int y, int w, int h, int dry_run) {
          330         short percent = 0;
          331         const char *c;
          332         int length = 0;
          333         int start_index = 0;
          334         /* FIXME: just use putc instead of this complex printf dance */
          335         for (c = CMD_FORMAT; *c != '\0'; c++) {
          336                 if (percent)
          337                         start_index++;
          338                 if (*c == '%') {
          339                         if (length) {
          340                                 if (!dry_run)
          341                                         printf("%.*s", length, CMD_FORMAT + start_index);
          342                                 start_index += length;
          343                                 length = 0;
          344                         }
          345                         if (percent && !dry_run)
          346                                 printf("%%");
          347                         percent++;
          348                         percent %= 2;
          349                         start_index++;
          350                 } else if (percent && *c == 'w') {
          351                         if (!dry_run)
          352                                 printf("%d", w);
          353                         percent = 0;
          354                 } else if (percent && *c == 'h') {
          355                         if (!dry_run)
          356                                 printf("%d", h);
          357                         percent = 0;
          358                 } else if (percent && *c == 'l') {
          359                         if (!dry_run)
          360                                 printf("%d", x);
          361                         percent = 0;
          362                 } else if (percent && *c == 't') {
          363                         if (!dry_run)
          364                                 printf("%d", y);
          365                         percent = 0;
          366                 } else if (percent && *c == 'r') {
          367                         if (!dry_run)
          368                                 printf("%d", x + w);
          369                         percent = 0;
          370                 } else if (percent && *c == 'b') {
          371                         if (!dry_run)
          372                                 printf("%d", y + h);
          373                         percent = 0;
          374                 } else if (percent && *c == 'f') {
          375                         if (!dry_run)
          376                                 printf("%s", filename);
          377                         percent = 0;
          378                 } else if (percent) {
          379                         if (dry_run) {
          380                                 fprintf(stderr,
          381                                     "Warning: Unknown substitution '%c' "
          382                                     "in format string.\n", *c
          383                                 );
          384                         } else {
          385                                 printf("%%%c", *c);
          386                         }
          387                         percent = 0;
          388                 } else {
          389                         length++;
          390                 }
          391         }
          392         if (!dry_run) {
          393                 if (length)
          394                         printf("%.*s", length, CMD_FORMAT + start_index);
          395                 printf("\n");
          396         }
          397 }
          398 
          399 static void
          400 redraw(void) {
          401         if (!state.ctx.dirty)
          402                 return;
          403         if (!state.ctx.cur_image || state.cur_selection < 0) {
          404                 clear_screen(&state.ctx);
          405                 swap_buffers(&state.ctx);
          406                 return;
          407         }
          408 
          409         /* draw the parts of the image that need to be redrawn */
          410         struct Selection *sel = &state.selections[state.cur_selection];
          411         draw_image_updates(&state.ctx, &sel->sz);
          412 
          413         wipe_around_image(&state.ctx, &sel->sz);
          414 
          415         /* draw the rectangle */
          416         struct Rect rect = sel->rect;
          417         if (rect.x0 != -200) {
          418                 XColor col = state.cur_col == 1 ? state.col1 : state.col2;
          419                 XSetForeground(state.ctx.dpy, state.ctx.gc, col.pixel);
          420                 sort_coordinates(&rect.x0, &rect.y0, &rect.x1, &rect.y1);
          421                 XDrawRectangle(
          422                     state.ctx.dpy, state.ctx.drawable, state.ctx.gc,
          423                     rect.x0, rect.y0, rect.x1 - rect.x0, rect.y1 - rect.y0
          424                 );
          425         }
          426         swap_buffers(&state.ctx);
          427 }
          428 
          429 static void
          430 swap(int *a, int *b) {
          431         int tmp = *a;
          432         *a = *b;
          433         *b = tmp;
          434 }
          435 
          436 /* sort rectangle coordinates into their canonical
          437  * form so *x1 - *x0 >= 0 and *y1 - *y0 >= 0 */
          438 static void
          439 sort_coordinates(int *x0, int *y0, int *x1, int *y1) {
          440         if (*x0 > *x1)
          441                 swap(x0, x1);
          442         if(*y0 > *y1)
          443                 swap(y0, y1);
          444 }
          445 
          446 static void
          447 print_selection(struct Selection *sel, const char *filename) {
          448         /* The box was never actually used */
          449         if (sel->rect.x0 == -200)
          450                 return;
          451         double scale = (double)sel->sz.orig_w / sel->sz.scaled_w;
          452         int x0 = sel->rect.x0, y0 = sel->rect.y0;
          453         int x1 = sel->rect.x1, y1 = sel->rect.y1;
          454         sort_coordinates(&x0, &y0, &x1, &y1);
          455         x0 = round(x0 * scale);
          456         y0 = round(y0 * scale);
          457         x1 = round(x1 * scale);
          458         y1 = round(y1 * scale);
          459         /* The box is completely outside of the picture. */
          460         if (x0 >= sel->sz.orig_w || y0 >= sel->sz.orig_h)
          461                 return;
          462         /* Cut the bounding box if it goes past the end of the picture. */
          463         x0 = x0 < 0 ? 0 : x0;
          464         y0 = y0 < 0 ? 0 : y0;
          465         x1 = x1 > sel->sz.orig_w ? sel->sz.orig_w : x1;
          466         y1 = y1 > sel->sz.orig_h ? sel->sz.orig_h : y1;
          467         print_cmd(filename, x0, y0, x1 - x0, y1 - y0, 0);
          468 }
          469 
          470 static int
          471 collide_point(int x, int y, int x_point, int y_point) {
          472         return (abs(x - x_point) <= COLLISION_PADDING) &&
          473                 (abs(y - y_point) <= COLLISION_PADDING);
          474 }
          475 
          476 static int
          477 collide_line(int x, int y, int x0, int y0, int x1, int y1) {
          478         sort_coordinates(&x0, &y0, &x1, &y1);
          479         /* this expects a valid line */
          480         if (x0 == x1) {
          481                 return (abs(x - x0) <= COLLISION_PADDING) &&
          482                         (y0 <= y) && (y <= y1);
          483         } else {
          484                 return (abs(y - y0) <= COLLISION_PADDING) &&
          485                         (x0 <= x) && (x <= x1);
          486         }
          487 }
          488 
          489 static int
          490 collide_rect(int x, int y, struct Rect rect) {
          491         int x0 = rect.x0, x1 = rect.x1;
          492         int y0 = rect.y0, y1 = rect.y1;
          493         sort_coordinates(&x0, &y0, &x1, &y1);
          494         return (x0 <= x) && (x <= x1) && (y0 <= y) && (y <= y1);
          495 }
          496 
          497 static void
          498 button_press(XEvent event) {
          499         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          500                 return;
          501         struct Rect *rect = &state.selections[state.cur_selection].rect;
          502         int x = event.xbutton.x;
          503         int y = event.xbutton.y;
          504         int x0 = rect->x0, x1 = rect->x1;
          505         int y0 = rect->y0, y1 = rect->y1;
          506         /* erase old rectangle */
          507         queue_rectangle_redraw(x0, y0, x1, y1);
          508         if (collide_point(x, y, x0, y0)) {
          509                 rect->x0 = x1;
          510                 rect->y0 = y1;
          511                 rect->x1 = x;
          512                 rect->y1 = y;
          513         } else if (collide_point(x, y, x1, y1)) {
          514                 rect->x1 = x;
          515                 rect->y1 = y;
          516         } else if (collide_point(x, y, x0, y1)) {
          517                 rect->x0 = rect->x1;
          518                 rect->x1 = x;
          519                 rect->y1 = y;
          520         } else if (collide_point(x, y, x1, y0)) {
          521                 rect->y0 = y1;
          522                 rect->x1 = x;
          523                 rect->y1 = y;
          524         } else if (collide_line(x, y, x0, y0, x1, y0)) {
          525                 state.lock_y = 1;
          526                 swap(&rect->x0, &rect->x1);
          527                 rect->y0 = rect->y1;
          528                 rect->y1 = y;
          529         } else if (collide_line(x, y, x0, y0, x0, y1)) {
          530                 state.lock_x = 1;
          531                 swap(&rect->y0, &rect->y1);
          532                 rect->x0 = rect->x1;
          533                 rect->x1 = x;
          534         } else if (collide_line(x, y, x1, y1, x0, y1)) {
          535                 state.lock_y = 1;
          536                 rect->y1 = y;
          537         } else if (collide_line(x, y, x1, y1, x1, y0)) {
          538                 state.lock_x = 1;
          539                 rect->x1 = x;
          540         } else if (collide_rect(x, y, *rect)) {
          541                 state.moving = 1;
          542                 state.move_handle.x = x;
          543                 state.move_handle.y = y;
          544         } else {
          545                 rect->x0 = x;
          546                 rect->y0 = y;
          547                 rect->x1 = x;
          548                 rect->y1 = y;
          549         }
          550         state.resizing = 1;
          551 }
          552 
          553 static void
          554 queue_update(int x, int y, int w, int h) {
          555         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          556                 return;
          557         struct Selection *sel = &state.selections[state.cur_selection];
          558         queue_area_update(&state.ctx, &sel->sz, x, y, w, h);
          559 }
          560 
          561 static void
          562 button_release(void) {
          563         state.moving = 0;
          564         state.resizing = 0;
          565         state.lock_x = 0;
          566         state.lock_y = 0;
          567         /* redraw everything if automatic redrawing of the rectangle
          568            is disabled (so it's redrawn when the mouse is released) */
          569         if (!SELECTION_REDRAW)
          570                 queue_update(0, 0, state.ctx.window_w, state.ctx.window_h);
          571 }
          572 
          573 static void
          574 resize_window(int w, int h) {
          575         int actual_w, actual_h;
          576         struct Selection *sel;
          577         state.ctx.window_w = w;
          578         state.ctx.window_h = h;
          579 
          580         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          581                 return;
          582         sel = &state.selections[state.cur_selection];
          583         get_scaled_size(&state.ctx, sel->sz.orig_w, sel->sz.orig_h, &actual_w, &actual_h);
          584         if (actual_w != sel->sz.scaled_w) {
          585                 if (sel->rect.x0 != -200) {
          586                         /* If there is a selection, we need to convert it to
          587                          * the new scale. This only takes width into account
          588                          * because the aspect ratio should have been preserved
          589                          * anyways */
          590                         double scale = (double)actual_w / sel->sz.scaled_w;
          591                         sel->rect.x0 = round(sel->rect.x0 * scale);
          592                         sel->rect.y0 = round(sel->rect.y0 * scale);
          593                         sel->rect.x1 = round(sel->rect.x1 * scale);
          594                         sel->rect.y1 = round(sel->rect.y1 * scale);
          595                 }
          596                 sel->sz.scaled_w = actual_w;
          597                 sel->sz.scaled_h = actual_h;
          598                 queue_update(0, 0, sel->sz.scaled_w, sel->sz.scaled_h);
          599         }
          600 }
          601 
          602 /* queue the redrawing of a rectangular area on the image -
          603  * this queues four updates, one for each side of the rectangle,
          604  * with the width or height (depending on which side) of the
          605  * rectangle being determined by the configured line width */
          606 static void
          607 queue_rectangle_redraw(int x0, int y0, int x1, int y1) {
          608         sort_coordinates(&x0, &y0, &x1, &y1);
          609         queue_update(
          610             x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
          611             y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
          612             x1 - x0 + LINE_WIDTH * 2, LINE_WIDTH * 2);
          613         queue_update(
          614             x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
          615             y1 - LINE_WIDTH > 0 ? y1 - LINE_WIDTH : 0,
          616             x1 - x0 + LINE_WIDTH * 2, LINE_WIDTH * 2);
          617         queue_update(
          618             x0 - LINE_WIDTH > 0 ? x0 - LINE_WIDTH : 0,
          619             y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
          620             LINE_WIDTH * 2, y1 - y0 + LINE_WIDTH * 2);
          621         queue_update(
          622             x1 - LINE_WIDTH > 0 ? x1 - LINE_WIDTH : 0,
          623             y0 - LINE_WIDTH > 0 ? y0 - LINE_WIDTH : 0,
          624             LINE_WIDTH * 2, y1 - y0 + LINE_WIDTH * 2);
          625 }
          626 
          627 /* set the appropriate cursor based on the
          628  * current mouse position and a cropping rectangle */
          629 static void
          630 set_cursor(struct Rect rect) {
          631         Cursor c = None;
          632         sort_coordinates(&rect.x0, &rect.y0, &rect.x1, &rect.y1);
          633         if (collide_point(
          634             state.cursor_x, state.cursor_y,
          635             rect.x0, rect.y0)) {
          636                 c = cursors.topleft;
          637         } else if (collide_point(
          638             state.cursor_x, state.cursor_y,
          639             rect.x1, rect.y0)) {
          640                 c = cursors.topright;
          641         } else if (collide_point(
          642             state.cursor_x, state.cursor_y,
          643             rect.x0, rect.y1)) {
          644                 c = cursors.bottomleft;
          645         } else if (collide_point(
          646             state.cursor_x, state.cursor_y,
          647             rect.x1, rect.y1)) {
          648                 c = cursors.bottomright;
          649         } else if (collide_line(
          650             state.cursor_x, state.cursor_y,
          651             rect.x0, rect.y0, rect.x1, rect.y0)) {
          652                 c = cursors.top;
          653         } else if (collide_line(
          654             state.cursor_x, state.cursor_y,
          655             rect.x1, rect.y1, rect.x0, rect.y1)) {
          656                 c = cursors.bottom;
          657         } else if (collide_line(
          658             state.cursor_x, state.cursor_y,
          659             rect.x1, rect.y1, rect.x1, rect.y0)) {
          660                 c = cursors.right;
          661         } else if (collide_line(
          662             state.cursor_x, state.cursor_y,
          663             rect.x0, rect.y0, rect.x0, rect.y1)) {
          664                 c = cursors.left;
          665         } else if (collide_rect(state.cursor_x, state.cursor_y, rect)) {
          666                 c = cursors.grab;
          667         }
          668         XDefineCursor(state.ctx.dpy, state.ctx.win, c);
          669 }
          670 
          671 static void
          672 drag_motion(XEvent event) {
          673         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          674                 return;
          675         struct Selection *sel = &state.selections[state.cur_selection];
          676         struct Rect *rect = &sel->rect;
          677 
          678         /* don't allow coordinates to go below 0 */
          679         if (event.xbutton.x >= 0)
          680                 state.cursor_x = event.xbutton.x;
          681         else
          682                 state.cursor_x = 0;
          683         if (event.xbutton.y >= 0)
          684                 state.cursor_y = event.xbutton.y;
          685         else
          686                 state.cursor_y = 0;
          687 
          688         int x0 = rect->x0, x1 = rect->x1;
          689         int y0 = rect->y0, y1 = rect->y1;
          690         sort_coordinates(&x0, &y0, &x1, &y1);
          691         /* redraw the old rectangle */
          692         if (SELECTION_REDRAW && (state.moving || state.resizing))
          693                 queue_rectangle_redraw(x0, y0, x1, y1);
          694         if (state.moving) {
          695                 int x_delta = state.cursor_x - state.move_handle.x;
          696                 int y_delta = state.cursor_y - state.move_handle.y;
          697                 /* don't allow coordinates to go below 0 */
          698                 int x_realdelta = x0 + x_delta >= 0 ? x_delta : -x0;
          699                 int y_realdelta = y0 + y_delta >= 0 ? y_delta : -y0;
          700                 rect->x0 += x_realdelta;
          701                 rect->x1 += x_realdelta;
          702                 rect->y0 += y_realdelta;
          703                 rect->y1 += y_realdelta;
          704                 state.move_handle.x = state.cursor_x;
          705                 state.move_handle.y = state.cursor_y;
          706         } else if (state.resizing) {
          707                 if (!state.lock_y)
          708                         rect->x1 = state.cursor_x;
          709                 if (!state.lock_x)
          710                         rect->y1 = state.cursor_y;
          711         } else {
          712                 set_cursor(*rect);
          713                 return;
          714         }
          715         set_cursor(*rect);
          716 
          717         /* redraw the new rectangle */
          718         if (SELECTION_REDRAW)
          719                 queue_rectangle_redraw(rect->x0, rect->y0, rect->x1, rect->y1);
          720 }
          721 
          722 static void
          723 set_selection(
          724         struct Selection *sel, int rect_x0, int rect_y0, int rect_x1,
          725         int rect_y1, int orig_w, int orig_h, int scaled_w, int scaled_h) {
          726 
          727         sel->rect.x0 = rect_x0;
          728         sel->rect.y0 = rect_y0;
          729         sel->rect.x1 = rect_x1;
          730         sel->rect.y1 = rect_y1;
          731         sel->sz.orig_w = orig_w;
          732         sel->sz.orig_h = orig_h;
          733         sel->sz.scaled_w = scaled_w;
          734         sel->sz.scaled_h = scaled_h;
          735 }
          736 
          737 /* change the shown image
          738  * new_selection is the index of the new selection
          739  * copy_box determines whether the cropping rectangle of the current
          740  * selection should be copied (i.e. this is a true value when return
          741  * is pressed) */
          742 void
          743 change_picture(Imlib_Image new_image, int new_selection, int copy_box) {
          744         int orig_w, orig_h, actual_w, actual_h;
          745         /* set window title to filename */
          746         XSetStandardProperties(
          747             state.ctx.dpy, state.ctx.win,
          748             state.filenames[new_selection],
          749             NULL, None, NULL, 0, NULL
          750         );
          751         if (state.ctx.cur_image) {
          752                 imlib_context_set_image(state.ctx.cur_image);
          753                 imlib_free_image();
          754         }
          755         state.ctx.cur_image = new_image;
          756         imlib_context_set_image(state.ctx.cur_image);
          757         int old_selection = state.cur_selection;
          758         state.cur_selection = new_selection;
          759 
          760         orig_w = imlib_image_get_width();
          761         orig_h = imlib_image_get_height();
          762         get_scaled_size(&state.ctx, orig_w, orig_h, &actual_w, &actual_h);
          763 
          764         struct Selection *sel = &state.selections[state.cur_selection];
          765         if (copy_box && old_selection >= 0 && old_selection < state.num_files) {
          766                 struct Selection *old = &state.selections[old_selection];
          767                 set_selection(
          768                     sel,
          769                     old->rect.x0, old->rect.y0, old->rect.x1, old->rect.y1,
          770                     orig_w, orig_h, actual_w, actual_h
          771                 );
          772         } else if (!sel->valid) {
          773                 /* Just fill it with -200 so we can check
          774                  * later if it has been used yet */
          775                 set_selection(
          776                     sel,
          777                     -200, -200, -200, -200,
          778                     orig_w, orig_h, actual_w, actual_h
          779                 );
          780         } else if (sel->rect.x0 != -200 && actual_w != sel->sz.scaled_w) {
          781                 /* If there is a selection, we need to convert it to the
          782                  * new scale. This only takes width into account because
          783                  * the aspect ratio should have been preserved anyways */
          784                 double scale = (double)actual_w / sel->sz.scaled_w;
          785                 sel->rect.x0 = round(sel->rect.x0 * scale);
          786                 sel->rect.y0 = round(sel->rect.y0 * scale);
          787                 sel->rect.x1 = round(sel->rect.x1 * scale);
          788                 sel->rect.y1 = round(sel->rect.y1 * scale);
          789         }
          790         sel->sz.scaled_w = actual_w;
          791         sel->sz.scaled_h = actual_h;
          792         sel->valid = 1;
          793         queue_update(0, 0, sel->sz.scaled_w, sel->sz.scaled_h);
          794 
          795         /* set the cursor since the cropping rectangle may have changed */
          796         set_cursor(sel->rect);
          797 }
          798 
          799 static void
          800 clear_selection(void) {
          801         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          802                 return;
          803         struct Selection *sel = &state.selections[state.cur_selection];
          804         sel->rect.x0 = sel->rect.x1 = sel->rect.y0 = sel->rect.y1 = -200;
          805         queue_update(0, 0, sel->sz.scaled_w, sel->sz.scaled_h);
          806 }
          807 
          808 static void
          809 switch_color(void) {
          810         if (state.cur_selection < 0 || !state.selections[state.cur_selection].valid)
          811                 return;
          812         state.cur_col = state.cur_col == 1 ? 2 : 1;
          813         queue_update(0, 0, state.ctx.window_w, state.ctx.window_h);
          814 }
          815 
          816 static int
          817 key_press(XEvent event) {
          818         XWindowAttributes attrs;
          819         char buf[32];
          820         KeySym sym;
          821         XLookupString(&event.xkey, buf, sizeof(buf), &sym, NULL);
          822         switch (sym) {
          823         case XK_Left:
          824                 last_picture(state.cur_selection, state.filenames, 0);
          825                 break;
          826         case XK_Right:
          827                 next_picture(state.cur_selection, state.filenames, state.num_files, 0);
          828                 break;
          829         case XK_Return:
          830                 if (event.xkey.state & ShiftMask)
          831                         last_picture(state.cur_selection, state.filenames, 1);
          832                 else
          833                         next_picture(state.cur_selection, state.filenames, state.num_files, 1);
          834                 break;
          835         case XK_Delete:
          836                 clear_selection();
          837                 break;
          838         case XK_Tab:
          839                 switch_color();
          840                 break;
          841         case XK_space:
          842                 XGetWindowAttributes(state.ctx.dpy, state.ctx.win, &attrs);
          843                 resize_window(attrs.width, attrs.height);
          844                 /* queue update separately so it also redraws when
          845                    size didn't change */
          846                 queue_update(0, 0, state.ctx.window_w, state.ctx.window_h);
          847                 break;
          848         case XK_q:
          849                 state.print_on_exit = 1;
          850                 return 0;
          851         default:
          852                 break;
          853         }
          854         return 1;
          855 }