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

common.c (9480B)


      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 <stdio.h>
     18 #include <errno.h>
     19 #include <string.h>
     20 #include <stdlib.h>
     21 #include <limits.h>
     22 
     23 #include <X11/X.h>
     24 #include <X11/Xlib.h>
     25 #include <X11/Xutil.h>
     26 #ifndef NODB
     27 #include <X11/extensions/Xdbe.h>
     28 #endif
     29 
     30 #include <Imlib2.h>
     31 
     32 #include "common.h"
     33 
     34 void
     35 setup_x(GraphicsContext *ctx, int window_w, int window_h, int line_width, int cache_size) {
     36 	XSetWindowAttributes attrs;
     37 	XGCValues gcv;
     38 
     39 	ctx->dpy = XOpenDisplay(NULL);
     40 	ctx->screen = DefaultScreen(ctx->dpy);
     41 	ctx->vis = DefaultVisual(ctx->dpy, ctx->screen);
     42 	ctx->depth = DefaultDepth(ctx->dpy, ctx->screen);
     43 	ctx->cm = DefaultColormap(ctx->dpy, ctx->screen);
     44 	ctx->dirty = 1;
     45 
     46 	#ifndef NODB
     47 	ctx->db_enabled = 0;
     48 	/* based on http://wili.cc/blog/xdbe.html */
     49 	int major, minor;
     50 	if (XdbeQueryExtension(ctx->dpy, &major, &minor)) {
     51 		int num_screens = 1;
     52 		Drawable screens[] = { DefaultRootWindow(ctx->dpy) };
     53 		XdbeScreenVisualInfo *info = XdbeGetVisualInfo(
     54 		    ctx->dpy, screens, &num_screens
     55 		);
     56 		if (!info || num_screens < 1 || info->count < 1) {
     57 			fprintf(stderr,
     58 			    "Warning: No visuals support Xdbe, "
     59 			    "double buffering disabled.\n"
     60 			);
     61 		} else {
     62 			XVisualInfo xvisinfo_templ;
     63 			xvisinfo_templ.visualid = info->visinfo[0].visual;
     64 			xvisinfo_templ.screen = 0;
     65 			xvisinfo_templ.depth = info->visinfo[0].depth;
     66 			int matches;
     67 			XVisualInfo *xvisinfo_match = XGetVisualInfo(
     68 			    ctx->dpy,
     69 			    VisualIDMask | VisualScreenMask | VisualDepthMask,
     70 			    &xvisinfo_templ, &matches
     71 			);
     72 			if (!xvisinfo_match || matches < 1) {
     73 				fprintf(stderr,
     74 				    "Warning: Couldn't match a Visual with "
     75 				    "double buffering, double buffering disabled.\n"
     76 				);
     77 			} else {
     78 				ctx->vis = xvisinfo_match->visual;
     79 				ctx->depth = xvisinfo_match->depth;
     80 				ctx->db_enabled = 1;
     81 			}
     82 			XFree(xvisinfo_match);
     83 		}
     84 		XdbeFreeVisualInfo(info);
     85 	} else {
     86 		fprintf(stderr, "Warning: No Xdbe support, double buffering disabled.\n");
     87 	}
     88 	#endif
     89 
     90 	memset(&attrs, 0, sizeof(attrs));
     91 	attrs.background_pixel = BlackPixel(ctx->dpy, ctx->screen);
     92 	attrs.colormap = ctx->cm;
     93 	/* this causes the window contents to be kept
     94 	 * when it is resized, leading to less flicker */
     95 	attrs.bit_gravity = NorthWestGravity;
     96 	ctx->win = XCreateWindow(ctx->dpy, DefaultRootWindow(ctx->dpy), 0, 0,
     97 	    window_w, window_h, 0, ctx->depth,
     98 	    InputOutput, ctx->vis, CWBackPixel | CWColormap | CWBitGravity, &attrs);
     99 
    100 	#ifndef NODB
    101 	if (ctx->db_enabled) {
    102 		ctx->back_buf = XdbeAllocateBackBufferName(
    103 		    ctx->dpy, ctx->win, XdbeCopied
    104 		);
    105 		ctx->drawable = ctx->back_buf;
    106 	} else {
    107 		ctx->drawable = ctx->win;
    108 	}
    109 	#else
    110 	ctx->drawable = ctx->win;
    111 	#endif
    112 
    113 	memset(&gcv, 0, sizeof(gcv));
    114 	gcv.line_width = line_width;
    115 	ctx->gc = XCreateGC(ctx->dpy, ctx->win, GCLineWidth, &gcv);
    116 
    117 	XSelectInput(
    118 	    ctx->dpy, ctx->win,
    119 	    StructureNotifyMask | KeyPressMask | ButtonPressMask |
    120 	    ButtonReleaseMask | PointerMotionMask | ExposureMask
    121 	);
    122 
    123 	ctx->wm_delete_msg = XInternAtom(ctx->dpy, "WM_DELETE_WINDOW", False);
    124 	XSetWMProtocols(ctx->dpy, ctx->win, &ctx->wm_delete_msg, 1);
    125 
    126 	/* note: since cache_size is <= 1024, this definitely fits in long */
    127 	long cs = (long)cache_size * 1024 * 1024;
    128 	if (cs > INT_MAX) {
    129 		fprintf(stderr, "Cache size would cause integer overflow.\n");
    130 		exit(1);
    131 	}
    132 	imlib_set_cache_size((int)cs);
    133 	imlib_set_color_usage(128);
    134 	imlib_context_set_dither(1);
    135 	imlib_context_set_display(ctx->dpy);
    136 	imlib_context_set_visual(ctx->vis);
    137 	imlib_context_set_colormap(ctx->cm);
    138 	imlib_context_set_drawable(ctx->drawable);
    139 	ctx->updates = imlib_updates_init();
    140 	ctx->cur_image = NULL;
    141 }
    142 
    143 void
    144 cleanup_x(GraphicsContext *ctx) {
    145 	if (ctx->cur_image) {
    146 		imlib_context_set_image(ctx->cur_image);
    147 		imlib_free_image();
    148 	}
    149 	XDestroyWindow(ctx->dpy, ctx->win);
    150 	XCloseDisplay(ctx->dpy);
    151 }
    152 
    153 int
    154 parse_int(const char *str, int min, int max, int *value) {
    155 	char *end;
    156 	long l = strtol(str, &end, 10);
    157 	if (min > max)
    158 		return 1;
    159 	if (str == end || *end != '\0') {
    160 		return 1;
    161 	} else if (l < min || l > max || ((l == LONG_MIN ||
    162 	    l == LONG_MAX) && errno == ERANGE)) {
    163 		return 1;
    164 	}
    165 	*value = (int)l;
    166 
    167 	return 0;
    168 }
    169 
    170 void
    171 queue_area_update(GraphicsContext *ctx, ImageSize *sz, int x, int y, int w, int h) {
    172 	if (x > sz->scaled_w || y > sz->scaled_h)
    173 		return;
    174 	ctx->updates = imlib_update_append_rect(
    175 	    ctx->updates, x, y,
    176 	    w + x > sz->scaled_w ? sz->scaled_w - x : w,
    177 	    h + y > sz->scaled_h ? sz->scaled_h - y : h
    178 	);
    179 	ctx->dirty = 1;
    180 }
    181 
    182 void
    183 clear_screen(GraphicsContext *ctx) {
    184 
    185 	/* clear the window completely */
    186 	XSetForeground(ctx->dpy, ctx->gc, BlackPixel(ctx->dpy, ctx->screen));
    187 	XFillRectangle(
    188 	    ctx->dpy, ctx->drawable, ctx->gc,
    189 	    0, 0, ctx->window_w, ctx->window_h
    190 	);
    191 }
    192 
    193 void
    194 draw_image_updates(GraphicsContext *ctx, ImageSize *sz) {
    195 	Imlib_Image buffer;
    196 	Imlib_Updates current_update;
    197 
    198 	/* draw the parts of the image that need to be redrawn */
    199 	ctx->updates = imlib_updates_merge_for_rendering(
    200 	    ctx->updates, sz->scaled_w, sz->scaled_h
    201 	);
    202 	/* FIXME: check since when imlib_render_image_updates_on_drawable supported, also maybe just render_on_drawable part scaled */
    203 	for (current_update = ctx->updates; current_update;
    204 	    current_update = imlib_updates_get_next(current_update)) {
    205 		int up_x, up_y, up_w, up_h;
    206 		imlib_updates_get_coordinates(current_update, &up_x, &up_y, &up_w, &up_h);
    207 		buffer = imlib_create_image(up_w, up_h);
    208 		imlib_context_set_blend(0);
    209 		imlib_context_set_image(buffer);
    210 		imlib_blend_image_onto_image(
    211 		    ctx->cur_image, 0, 0, 0,
    212 		    sz->orig_w, sz->orig_h,
    213 		    -up_x, -up_y,
    214 		    sz->scaled_w, sz->scaled_h);
    215 		imlib_render_image_on_drawable(up_x, up_y);
    216 		imlib_free_image();
    217 	}
    218 	if (ctx->updates)
    219 		imlib_updates_free(ctx->updates);
    220 	ctx->updates = imlib_updates_init();
    221 }
    222 
    223 void
    224 wipe_around_image(GraphicsContext *ctx, ImageSize *sz) {
    225 
    226 	/* wipe the black area around the image */
    227 	XSetForeground(ctx->dpy, ctx->gc, BlackPixel(ctx->dpy, ctx->screen));
    228 	XFillRectangle(
    229 	    ctx->dpy, ctx->drawable, ctx->gc,
    230 	    0, sz->scaled_h, sz->scaled_w, ctx->window_h - sz->scaled_h
    231 	);
    232 	XFillRectangle(
    233 	    ctx->dpy, ctx->drawable, ctx->gc,
    234 	    sz->scaled_w, 0, ctx->window_w - sz->scaled_w, ctx->window_h
    235 	);
    236 }
    237 
    238 void
    239 swap_buffers(GraphicsContext *ctx) {
    240 	#ifndef NODB
    241 	if (ctx->db_enabled) {
    242 		XdbeSwapInfo swap_info;
    243 		swap_info.swap_window = ctx->win;
    244 		swap_info.swap_action = XdbeCopied;
    245 
    246 		if (!XdbeSwapBuffers(ctx->dpy, &swap_info, 1))
    247 			fprintf(stderr, "Warning: Unable to swap buffers.\n");
    248 	}
    249 	#endif
    250 	ctx->dirty = 0;
    251 }
    252 
    253 /* get the scaled size of an image based on the current window size */
    254 void
    255 get_scaled_size(GraphicsContext *ctx, int orig_w, int orig_h, int *scaled_w, int *scaled_h) {
    256 	double scale_w, scale_h;
    257 	scale_w = (double)ctx->window_w / (double)orig_w;
    258 	scale_h = (double)ctx->window_h / (double)orig_h;
    259 	if (orig_w <= ctx->window_w && orig_h <= ctx->window_h) {
    260 		*scaled_w = orig_w;
    261 		*scaled_h = orig_h;
    262 	} else if (scale_w * orig_h > ctx->window_h) {
    263 		*scaled_w = (int)(scale_h * orig_w);
    264 		*scaled_h = ctx->window_h;
    265 	} else {
    266 		*scaled_w = ctx->window_w;
    267 		*scaled_h = (int)(scale_w * orig_h);
    268 	}
    269 }
    270 
    271 void
    272 next_picture(int cur_selection, char **filenames, int num_files, int copy_box) {
    273 	if (cur_selection + 1 >= num_files)
    274 		return;
    275 	Imlib_Image tmp_image = NULL;
    276 	int tmp_cur_selection = cur_selection;
    277 	/* loop until we find a loadable file */
    278 	while (!tmp_image && tmp_cur_selection + 1 < num_files) {
    279 		tmp_cur_selection++;
    280 		if (!filenames[tmp_cur_selection])
    281 			continue;
    282 		tmp_image = imlib_load_image_immediately(
    283 		    filenames[tmp_cur_selection]
    284 		);
    285 		if (!tmp_image) {
    286 			fprintf(
    287 				stderr, "Warning: Unable to load image '%s'.\n",
    288 				filenames[tmp_cur_selection]
    289 			);
    290 			filenames[tmp_cur_selection] = NULL;
    291 		}
    292 	}
    293 	/* immediately exit program if no loadable image is found on startup */
    294 	if (cur_selection < 0 && !tmp_image) {
    295 		fprintf(stderr, "No loadable images found.\n");
    296 		cleanup();
    297 		exit(1);
    298 	}
    299 	if (!tmp_image)
    300 		return;
    301 
    302 	change_picture(tmp_image, tmp_cur_selection, copy_box);
    303 }
    304 
    305 void
    306 last_picture(int cur_selection, char **filenames, int copy_box) {
    307 	if (cur_selection <= 0)
    308 		return;
    309 	Imlib_Image tmp_image = NULL;
    310 	int tmp_cur_selection = cur_selection;
    311 	/* loop until we find a loadable file */
    312 	while (!tmp_image && tmp_cur_selection > 0) {
    313 		tmp_cur_selection--;
    314 		if (!filenames[tmp_cur_selection])
    315 			continue;
    316 		tmp_image = imlib_load_image_immediately(
    317 		    filenames[tmp_cur_selection]
    318 		);
    319 		if (!tmp_image) {
    320 			fprintf(
    321 				stderr, "Warning: Unable to load image '%s'.\n",
    322 				filenames[tmp_cur_selection]
    323 			);
    324 			filenames[tmp_cur_selection] = NULL;
    325 		}
    326 	}
    327 
    328 	if (!tmp_image)
    329 		return;
    330 
    331 	change_picture(tmp_image, tmp_cur_selection, copy_box);
    332 }