div

X11 image viewer
git clone https://git.porkepik.fr/div
Log | Files | Refs | README | LICENSE

div.c (9284B)


      1 /*
      2    TODO
      3    - set window title
      4 */
      5 
      6 #include <dirent.h>
      7 #include <err.h>
      8 #include <stdio.h>
      9 #include <stdlib.h>
     10 #include <string.h>
     11 #include <sys/stat.h>
     12 
     13 #include <X11/Xlib.h>
     14 #include <X11/Xutil.h>
     15 #include <X11/XKBlib.h>
     16 
     17 #define STB_IMAGE_IMPLEMENTATION
     18 #include "stb_image.h"
     19 
     20 #define STB_IMAGE_RESIZE_IMPLEMENTATION
     21 #include "stb_image_resize.h"
     22 
     23 #define RGBA 4
     24 
     25 
     26 enum zoom {
     27 	NO_ZOOM,
     28 	FILL,
     29 	MAX
     30 };
     31 
     32 struct app {
     33 	Window win;
     34 	int width;
     35 	int height;
     36 	char *bg_color;
     37 };
     38 
     39 struct image {
     40 	int filepath;
     41 	int width;
     42 	int height;
     43 	int dpy_width;
     44 	int dpy_height;
     45 	unsigned char *data;
     46 	unsigned char *dpy_data;
     47 	int src_x;
     48 	int src_y;
     49 	int dest_x;
     50 	int dest_y;
     51 	enum zoom zoom_mode;
     52 };
     53 
     54 
     55 static Display *dpy;
     56 static struct app app;
     57 static char **filepaths;
     58 static int nimgs;
     59 static struct image img;
     60 
     61 
     62 static void
     63 usage(void)
     64 {
     65 	printf("Usage: div [--bg-color \"#ffffff\"] [FILES]\n");
     66 	exit(0);
     67 }
     68 
     69 static void
     70 check_file(char *path)
     71 {
     72 	struct stat path_stat;
     73 	stat(path, &path_stat);
     74 
     75 	if (S_ISREG(path_stat.st_mode)) {
     76 		filepaths = realloc(filepaths, sizeof(char *) * nimgs+1);
     77 		filepaths[nimgs++] = path;
     78 	} else if (S_ISDIR(path_stat.st_mode)) {
     79 		struct dirent *dir;
     80 		DIR *d;
     81 		if (!(d = opendir(path))) {
     82 			printf("Could not open directory: %s\n", path);
     83 			return;
     84 		}
     85 		while ((dir = readdir(d))) {
     86 			char *filepath = malloc(strlen(path) +
     87 			    strlen(dir->d_name) + 2);
     88 			strcpy(filepath, path);
     89 			strcat(filepath, "/");
     90 			strcat(filepath, dir->d_name);
     91 			stat(filepath, &path_stat);
     92 			if (!S_ISREG(path_stat.st_mode))
     93 				continue;
     94 			filepaths = realloc(filepaths, sizeof(char *)*nimgs+1);
     95 			filepaths[nimgs++] = filepath;
     96 		}
     97 		closedir(d);
     98 	}
     99 }
    100 
    101 static void
    102 check_args(int argc, char **argv)
    103 {
    104 	app.bg_color = NULL;
    105 
    106 	for (int i = 1; i <= argc - 1; i++) {
    107 		if (strcmp(argv[i], "--bg-color") == 0 && i+1 != argc)
    108 			app.bg_color = argv[++i];
    109 		else if (strcmp(argv[i], "-h") == 0)
    110 			usage();
    111 		else
    112 			check_file(argv[i]);
    113 	}
    114 }
    115 
    116 static int
    117 filenamecmp(const void *a, const void *b)
    118 {
    119 	const char *arg1 = *(const char **)a;
    120 	const char *arg2 = *(const char **)b;
    121 	return strcmp(arg1, arg2);
    122 }
    123 
    124 static void
    125 rgb_to_bgr(void)
    126 {
    127 	unsigned char red, blue;
    128 
    129 	for (long i = 0; i < img.width * img.height * RGBA; i += RGBA) {
    130 		red = img.data[i+2];
    131 		blue = img.data[i];
    132 		img.data[i] = red;
    133 		img.data[i+2] = blue;
    134 	}
    135 }
    136 
    137 static int
    138 init_img(char *filepath)
    139 {
    140 	img.data = stbi_load(filepath, &img.width, &img.height, NULL, RGBA);
    141 	if (img.data == NULL) {
    142 		printf("Could not open %s\n", filepath);
    143 		return 1;
    144 	}
    145 	printf("Opened: %s, %dx%d\n", filepath, img.width, img.height);
    146 	rgb_to_bgr();
    147 
    148 	img.dpy_data = img.data;
    149 	img.dpy_width = img.width;
    150 	img.dpy_height = img.height;
    151 
    152 	return 0;
    153 }
    154 
    155 static void
    156 free_img(void)
    157 {
    158 	if (img.dpy_data != img.data)
    159 		free(img.dpy_data);
    160 	stbi_image_free(img.data);
    161 }
    162 
    163 static unsigned char *
    164 resize(int new_w, int new_h)
    165 {
    166 	unsigned char *new_data = malloc(new_h * new_w * RGBA);
    167 	stbir_resize_uint8(img.data, img.width, img.height, 0,
    168 	    new_data, new_w, new_h, 0, RGBA);
    169 
    170 	return new_data;
    171 }
    172 
    173 static void
    174 set_zoom(enum zoom zoom_mode)
    175 {
    176 	int scaled_w, scaled_h, new_w, new_h, tmp;
    177 
    178 	scaled_w = (float)app.height * ((float)img.width / (float)img.height);
    179 	scaled_h = (float)app.width / ((float)img.width / (float)img.height);
    180 
    181 	if ((zoom_mode == FILL && scaled_w < app.width) ||
    182 	    (zoom_mode == MAX && scaled_h <= app.height)) {
    183 		new_w = app.width;
    184 		new_h = scaled_h;
    185 	} else {
    186 		new_w = scaled_w;
    187 		new_h = app.height;
    188 	}
    189 
    190 	if (img.dpy_data != img.data)
    191 		free(img.dpy_data);
    192 	if (zoom_mode == FILL || zoom_mode == MAX) {
    193 		img.dpy_data = resize(new_w, new_h);
    194 		img.dpy_width = new_w;
    195 		img.dpy_height = new_h;
    196 	} else if (zoom_mode == NO_ZOOM) {
    197 		img.dpy_data = img.data;
    198 		img.dpy_width = img.width;
    199 		img.dpy_height = img.height;
    200 	}
    201 
    202 	if (zoom_mode == FILL) {
    203 		img.src_x = (img.dpy_width - app.width) / 2;
    204 		img.src_y = (img.dpy_height - app.height) / 2;
    205 		img.dest_x = 0;
    206 		img.dest_y = 0;
    207 	} else if (zoom_mode == MAX) {
    208 		img.dest_x = (app.width - img.dpy_width) / 2;
    209 		img.dest_y = (app.height - img.dpy_height) / 2;
    210 		img.src_x = 0;
    211 		img.src_y = 0;
    212 	} else if (zoom_mode == NO_ZOOM) {
    213 		tmp = (img.dpy_width - app.width) / 2;
    214 		img.src_x = tmp < 0 ? 0 : tmp;
    215 		tmp = (img.dpy_height - app.height) / 2;
    216 		img.src_y = tmp < 0 ? 0 : tmp;
    217 		tmp = (app.width - img.dpy_width) / 2;
    218 		img.dest_x = tmp < 0 ? 0 : tmp;
    219 		tmp = (app.height - img.dpy_height) / 2;
    220 		img.dest_y = tmp < 0 ? 0 : tmp;
    221 	}
    222 }
    223 
    224 static Pixmap
    225 create_pixmap(void)
    226 {
    227 	XColor color;
    228 	GC gc;
    229 	XGCValues gcval;
    230 
    231 	Pixmap pixmap = XCreatePixmap(dpy, app.win, app.width, app.height,
    232 	    DefaultDepth(dpy, 0));
    233 	Colormap cmap = DefaultColormap(dpy, DefaultScreen(dpy));
    234 
    235 	if (app.bg_color == NULL)
    236 		XAllocNamedColor(dpy, cmap, "black", &color, &color);
    237 	else
    238 		XAllocNamedColor(dpy, cmap, app.bg_color, &color, &color);
    239 	gcval.foreground = color.pixel;
    240 	gc = XCreateGC(dpy, app.win, GCForeground, &gcval);
    241 	XFillRectangle(dpy, pixmap, gc, 0, 0, app.width, app.height);
    242 
    243 	XFreeGC(dpy, gc);
    244 	
    245 	return pixmap;
    246 }
    247 
    248 static void
    249 apply_image(Pixmap pixmap)
    250 {
    251 	XImage *ximg = XCreateImage(dpy, DefaultVisual(dpy, 0), 24, ZPixmap, 0,
    252 	    (char *)img.dpy_data, img.dpy_width, img.dpy_height, 8, 0);
    253 
    254 	XSetWindowBackgroundPixmap(dpy, app.win, pixmap);
    255 	XPutImage(dpy, pixmap, DefaultGC(dpy, 0), ximg, img.src_x, img.src_y,
    256 	    img.dest_x, img.dest_y, img.dpy_width, img.dpy_height);
    257 	XClearWindow(dpy, app.win);
    258 
    259 	XFree(ximg);
    260 }
    261 
    262 static void
    263 event_loop()
    264 {
    265 	Pixmap pixmap;
    266 	XEvent ev;
    267 	KeySym keysym;
    268 	XConfigureEvent xce;
    269 	int tmp_x, tmp_y, left_mouse_hold, hold_x, hold_y;
    270 	int imgid, status;
    271 	char gotobuf[10];
    272 
    273 	memset(gotobuf, 0, 10);
    274 	left_mouse_hold = imgid = 0;
    275 
    276 	set_zoom(NO_ZOOM);
    277 	pixmap = create_pixmap();
    278 	apply_image(pixmap);
    279 
    280 	while (1) {
    281 		XNextEvent(dpy, &ev);
    282 		switch (ev.type) {
    283 		case ConfigureNotify:
    284 			xce = ev.xconfigure;
    285 			if (xce.width != app.width ||
    286 			    xce.height != app.height) {
    287 				app.width = xce.width;
    288 				app.height = xce.height;
    289 				set_zoom(NO_ZOOM);
    290 				pixmap = create_pixmap();
    291 				apply_image(pixmap);
    292 			}
    293 			break;
    294 		case KeyPress:
    295 			keysym = XkbKeycodeToKeysym(dpy, ev.xkey.keycode, 0,
    296 			    ev.xkey.state & ShiftMask ? 1 : 0);
    297 			if (keysym == XK_q || keysym == XK_Escape) {
    298 				goto quit;
    299 			} else if (keysym >= XK_0 && keysym <= XK_9) {
    300 				if (gotobuf[9] == '\0') {
    301 					char tmp[2];
    302 					sprintf(tmp, "%lu", keysym - 48);
    303 					strcat(gotobuf, tmp);
    304 				}
    305 			} else if (keysym == XK_g) {
    306 				int newid = atoi(gotobuf) - 1;
    307 				memset(gotobuf, 0, 10);
    308 				if (newid < 0 || newid > nimgs - 1)
    309 					break;
    310 				free_img();
    311 				if (init_img(filepaths[newid]) != 0)
    312 					init_img(filepaths[imgid]);
    313 				imgid = newid;
    314 				set_zoom(NO_ZOOM);
    315 			} else if (keysym == XK_m) {
    316 				set_zoom(MAX);
    317 			} else if (keysym == XK_l) {
    318 				set_zoom(FILL);
    319 			} else if (keysym == XK_o) {
    320 				set_zoom(NO_ZOOM);
    321 			} else if (keysym == XK_p) {
    322 				if (imgid == 0)
    323 					break;
    324 				free_img();
    325 				do {
    326 					imgid--;
    327 					status = init_img(filepaths[imgid]);
    328 					set_zoom(NO_ZOOM);
    329 				} while (status != 0 && imgid > 0);
    330 				if (status != 0)
    331 					goto quit;
    332 			} else if (keysym == XK_n) {
    333 				if (imgid == nimgs - 1)
    334 					break;
    335 				free_img();
    336 				do {
    337 					imgid++;
    338 					status = init_img(filepaths[imgid]);
    339 					set_zoom(NO_ZOOM);
    340 				} while (status != 0 && imgid < nimgs - 1);
    341 				if (status != 0)
    342 					goto quit;
    343 			} else {
    344 				break;
    345 			}
    346 			pixmap = create_pixmap();
    347 			apply_image(pixmap);
    348 			break;
    349 		case ButtonPress:
    350 			if (ev.xbutton.button == 1) {
    351 				left_mouse_hold = 1;
    352 				hold_x = ev.xmotion.x;
    353 				hold_y = ev.xmotion.y;
    354 			}
    355 			break;
    356 		case ButtonRelease:
    357 			if (ev.xbutton.button == 1)
    358 				left_mouse_hold = 0;
    359 		case MotionNotify:
    360 			if (!left_mouse_hold)
    361 				break;
    362 			tmp_x = img.src_x - ev.xmotion.x + hold_x;
    363 			tmp_y = img.src_y - ev.xmotion.y + hold_y;
    364 
    365 			if (img.dpy_width > app.width) {
    366 				if (tmp_x < 0)
    367 					tmp_x = 0;
    368 				if (app.width + tmp_x > img.dpy_width)
    369 					tmp_x = img.dpy_width - app.width;
    370 				img.src_x = tmp_x;
    371 			}
    372 			if (img.dpy_height > app.height) {
    373 				if (tmp_y < 0)
    374 					tmp_y = 0;
    375 				if (app.height + tmp_y > img.dpy_height)
    376 					tmp_y = img.dpy_height - app.height;
    377 
    378 				img.src_y = tmp_y;
    379 			}
    380 			if (img.src_x == tmp_x || img.src_y == tmp_y)
    381 				apply_image(pixmap);
    382 
    383 			hold_x = ev.xmotion.x;
    384 			hold_y = ev.xmotion.y;
    385 			break;
    386 		}
    387 	}
    388 quit:
    389 	XFreePixmap(dpy, pixmap);
    390 }
    391 
    392 int
    393 main(int argc, char **argv)
    394 {
    395 	int screen, status, i;
    396 
    397 	dpy = XOpenDisplay(NULL);
    398 	if (dpy == NULL)
    399 		errx(1, "Failed opening DISPLAY.");
    400 
    401 	if (argc < 2)
    402 		usage();
    403 	check_args(argc, argv);
    404 	qsort(filepaths, nimgs, sizeof(char *), filenamecmp);
    405 
    406 	for (i = 0; i <= nimgs - 1; i++) {
    407 		status = init_img(filepaths[i]);
    408 		if (status == 0)
    409 			break;
    410 	}
    411 	if (status != 0)
    412 		return 0;
    413 	app.width = img.width;
    414 	app.height = img.height;
    415 
    416 	screen = DefaultScreen(dpy);
    417 	app.win = XCreateSimpleWindow(dpy, RootWindow(dpy, screen), 0, 0,
    418 	    app.width, app.height, 0, 0, WhitePixel(dpy, screen));
    419 	XSelectInput(dpy, app.win, ExposureMask | KeyPressMask |
    420 	    ButtonPressMask | StructureNotifyMask | PointerMotionMask |
    421 	    ButtonReleaseMask);
    422 	XMapRaised(dpy, app.win);
    423 
    424 	event_loop();
    425 
    426 	XDestroyWindow(dpy, app.win);
    427 	XCloseDisplay(dpy);
    428 	free_img();
    429 	free(filepaths);
    430 
    431 	return 0;
    432 }