dotfiles

personal configuration files and scripts
git clone https://tongong.net/git/dotfiles.git
Log | Files | Refs | README

image.c (17332B)


      1 /* Copyright 2011, 2012 Bert Muennich
      2  *
      3  * This file is part of sxiv.
      4  *
      5  * sxiv is free software; you can redistribute it and/or modify
      6  * it under the terms of the GNU General Public License as published
      7  * by the Free Software Foundation; either version 2 of the License,
      8  * or (at your option) any later version.
      9  *
     10  * sxiv is distributed in the hope that it will be useful,
     11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13  * GNU General Public License for more details.
     14  *
     15  * You should have received a copy of the GNU General Public License
     16  * along with sxiv.  If not, see <http://www.gnu.org/licenses/>.
     17  */
     18 
     19 #include "sxiv.h"
     20 #define _IMAGE_CONFIG
     21 #include "config.h"
     22 
     23 #include <errno.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <sys/stat.h>
     27 #include <sys/types.h>
     28 #include <unistd.h>
     29 
     30 #if HAVE_LIBEXIF
     31 #include <libexif/exif-data.h>
     32 #endif
     33 
     34 #if HAVE_GIFLIB
     35 #include <gif_lib.h>
     36 enum { DEF_GIF_DELAY = 75 };
     37 #endif
     38 
     39 float zoom_min;
     40 float zoom_max;
     41 
     42 static int zoomdiff(img_t *img, float z)
     43 {
     44 	return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom));
     45 }
     46 
     47 void img_init(img_t *img, win_t *win)
     48 {
     49 	zoom_min = zoom_levels[0] / 100.0;
     50 	zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0;
     51 
     52 	imlib_context_set_display(win->env.dpy);
     53 	imlib_context_set_visual(win->env.vis);
     54 	imlib_context_set_colormap(win->env.cmap);
     55 
     56 	img->im = NULL;
     57 	img->win = win;
     58 	img->scalemode = options->scalemode;
     59 	img->zoom = options->zoom;
     60 	img->zoom = MAX(img->zoom, zoom_min);
     61 	img->zoom = MIN(img->zoom, zoom_max);
     62 	img->checkpan = false;
     63 	img->dirty = false;
     64 	img->aa = ANTI_ALIAS;
     65 	img->alpha = ALPHA_LAYER;
     66 	img->multi.cap = img->multi.cnt = 0;
     67 	img->multi.animate = options->animate;
     68 	img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0;
     69 	img->multi.length = 0;
     70 
     71 	img->cmod = imlib_create_color_modifier();
     72 	imlib_context_set_color_modifier(img->cmod);
     73 	img->gamma = MIN(MAX(options->gamma, -GAMMA_RANGE), GAMMA_RANGE);
     74 
     75 	img->ss.on = options->slideshow > 0;
     76 	img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10;
     77 }
     78 
     79 #if HAVE_LIBEXIF
     80 void exif_auto_orientate(const fileinfo_t *file)
     81 {
     82 	ExifData *ed;
     83 	ExifEntry *entry;
     84 	int byte_order, orientation = 0;
     85 
     86 	if ((ed = exif_data_new_from_file(file->path)) == NULL)
     87 		return;
     88 	byte_order = exif_data_get_byte_order(ed);
     89 	entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
     90 	if (entry != NULL)
     91 		orientation = exif_get_short(entry->data, byte_order);
     92 	exif_data_unref(ed);
     93 
     94 	switch (orientation) {
     95 		case 5:
     96 			imlib_image_orientate(1);
     97 		case 2:
     98 			imlib_image_flip_vertical();
     99 			break;
    100 		case 3:
    101 			imlib_image_orientate(2);
    102 			break;
    103 		case 7:
    104 			imlib_image_orientate(1);
    105 		case 4:
    106 			imlib_image_flip_horizontal();
    107 			break;
    108 		case 6:
    109 			imlib_image_orientate(1);
    110 			break;
    111 		case 8:
    112 			imlib_image_orientate(3);
    113 			break;
    114 	}
    115 }
    116 #endif
    117 
    118 #if HAVE_GIFLIB
    119 bool img_load_gif(img_t *img, const fileinfo_t *file)
    120 {
    121 	GifFileType *gif;
    122 	GifRowType *rows = NULL;
    123 	GifRecordType rec;
    124 	ColorMapObject *cmap;
    125 	DATA32 bgpixel, *data, *ptr;
    126 	DATA32 *prev_frame = NULL;
    127 	Imlib_Image im;
    128 	int i, j, bg, r, g, b;
    129 	int x, y, w, h, sw, sh;
    130 	int px, py, pw, ph;
    131 	int intoffset[] = { 0, 4, 2, 1 };
    132 	int intjump[] = { 8, 8, 4, 2 };
    133 	int transp = -1;
    134 	unsigned int disposal = 0, prev_disposal = 0;
    135 	unsigned int delay = 0;
    136 	bool err = false;
    137 
    138 	if (img->multi.cap == 0) {
    139 		img->multi.cap = 8;
    140 		img->multi.frames = (img_frame_t*)
    141 		                    emalloc(sizeof(img_frame_t) * img->multi.cap);
    142 	}
    143 	img->multi.cnt = img->multi.sel = 0;
    144 	img->multi.length = 0;
    145 
    146 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
    147 	gif = DGifOpenFileName(file->path, NULL);
    148 #else
    149 	gif = DGifOpenFileName(file->path);
    150 #endif
    151 	if (gif == NULL) {
    152 		error(0, 0, "%s: Error opening gif image", file->name);
    153 		return false;
    154 	}
    155 	bg = gif->SBackGroundColor;
    156 	sw = gif->SWidth;
    157 	sh = gif->SHeight;
    158 	px = py = pw = ph = 0;
    159 
    160 	do {
    161 		if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
    162 			err = true;
    163 			break;
    164 		}
    165 		if (rec == EXTENSION_RECORD_TYPE) {
    166 			int ext_code;
    167 			GifByteType *ext = NULL;
    168 
    169 			DGifGetExtension(gif, &ext_code, &ext);
    170 			while (ext) {
    171 				if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
    172 					if (ext[1] & 1)
    173 						transp = (int) ext[4];
    174 					else
    175 						transp = -1;
    176 
    177 					delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
    178 					disposal = (unsigned int) ext[1] >> 2 & 0x7;
    179 				}
    180 				ext = NULL;
    181 				DGifGetExtensionNext(gif, &ext);
    182 			}
    183 		} else if (rec == IMAGE_DESC_RECORD_TYPE) {
    184 			if (DGifGetImageDesc(gif) == GIF_ERROR) {
    185 				err = true;
    186 				break;
    187 			}
    188 			x = gif->Image.Left;
    189 			y = gif->Image.Top;
    190 			w = gif->Image.Width;
    191 			h = gif->Image.Height;
    192 
    193 			rows = (GifRowType*) emalloc(h * sizeof(GifRowType));
    194 			for (i = 0; i < h; i++)
    195 				rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType));
    196 			if (gif->Image.Interlace) {
    197 				for (i = 0; i < 4; i++) {
    198 					for (j = intoffset[i]; j < h; j += intjump[i])
    199 						DGifGetLine(gif, rows[j], w);
    200 				}
    201 			} else {
    202 				for (i = 0; i < h; i++)
    203 					DGifGetLine(gif, rows[i], w);
    204 			}
    205 
    206 			ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh);
    207 			cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
    208 			r = cmap->Colors[bg].Red;
    209 			g = cmap->Colors[bg].Green;
    210 			b = cmap->Colors[bg].Blue;
    211 			bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
    212 
    213 			for (i = 0; i < sh; i++) {
    214 				for (j = 0; j < sw; j++) {
    215 					if (i < y || i >= y + h || j < x || j >= x + w ||
    216 					    rows[i-y][j-x] == transp)
    217 					{
    218 						if (prev_frame != NULL && (prev_disposal != 2 ||
    219 						    i < py || i >= py + ph || j < px || j >= px + pw))
    220 						{
    221 							*ptr = prev_frame[i * sw + j];
    222 						} else {
    223 							*ptr = bgpixel;
    224 						}
    225 					} else {
    226 						r = cmap->Colors[rows[i-y][j-x]].Red;
    227 						g = cmap->Colors[rows[i-y][j-x]].Green;
    228 						b = cmap->Colors[rows[i-y][j-x]].Blue;
    229 						*ptr = 0xffu << 24 | r << 16 | g << 8 | b;
    230 					}
    231 					ptr++;
    232 				}
    233 			}
    234 
    235 			im = imlib_create_image_using_copied_data(sw, sh, data);
    236 
    237 			for (i = 0; i < h; i++)
    238 				free(rows[i]);
    239 			free(rows);
    240 			free(data);
    241 
    242 			if (im == NULL) {
    243 				err = true;
    244 				break;
    245 			}
    246 
    247 			imlib_context_set_image(im);
    248 			imlib_image_set_format("gif");
    249 			if (transp >= 0)
    250 				imlib_image_set_has_alpha(1);
    251 
    252 			if (disposal != 3)
    253 				prev_frame = imlib_image_get_data_for_reading_only();
    254 			prev_disposal = disposal;
    255 			px = x, py = y, pw = w, ph = h;
    256 
    257 			if (img->multi.cnt == img->multi.cap) {
    258 				img->multi.cap *= 2;
    259 				img->multi.frames = (img_frame_t*)
    260 				                    erealloc(img->multi.frames,
    261 				                             img->multi.cap * sizeof(img_frame_t));
    262 			}
    263 			img->multi.frames[img->multi.cnt].im = im;
    264 			delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay;
    265 			img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY;
    266 			img->multi.length += img->multi.frames[img->multi.cnt].delay;
    267 			img->multi.cnt++;
    268 		}
    269 	} while (rec != TERMINATE_RECORD_TYPE);
    270 
    271 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
    272 	DGifCloseFile(gif, NULL);
    273 #else
    274 	DGifCloseFile(gif);
    275 #endif
    276 
    277 	if (err && (file->flags & FF_WARN))
    278 		error(0, 0, "%s: Corrupted gif file", file->name);
    279 
    280 	if (img->multi.cnt > 1) {
    281 		imlib_context_set_image(img->im);
    282 		imlib_free_image();
    283 		img->im = img->multi.frames[0].im;
    284 	} else if (img->multi.cnt == 1) {
    285 		imlib_context_set_image(img->multi.frames[0].im);
    286 		imlib_free_image();
    287 		img->multi.cnt = 0;
    288 	}
    289 
    290 	imlib_context_set_image(img->im);
    291 
    292 	return !err;
    293 }
    294 #endif /* HAVE_GIFLIB */
    295 
    296 Imlib_Image img_open(const fileinfo_t *file)
    297 {
    298 	struct stat st;
    299 	Imlib_Image im = NULL;
    300 
    301 	if (access(file->path, R_OK) == 0 &&
    302 	    stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
    303 	{
    304 		im = imlib_load_image(file->path);
    305 		if (im != NULL) {
    306 			imlib_context_set_image(im);
    307 			if (imlib_image_get_data_for_reading_only() == NULL) {
    308 				imlib_free_image();
    309 				im = NULL;
    310 			}
    311 		}
    312 	}
    313 	if (im == NULL && (file->flags & FF_WARN))
    314 		error(0, 0, "%s: Error opening image", file->name);
    315 	return im;
    316 }
    317 
    318 bool img_load(img_t *img, const fileinfo_t *file)
    319 {
    320 	const char *fmt;
    321 
    322 	if ((img->im = img_open(file)) == NULL)
    323 		return false;
    324 
    325 	imlib_image_set_changes_on_disk();
    326 
    327 #if HAVE_LIBEXIF
    328 	exif_auto_orientate(file);
    329 #endif
    330 
    331 	if ((fmt = imlib_image_format()) != NULL) {
    332 #if HAVE_GIFLIB
    333 		if (STREQ(fmt, "gif"))
    334 			img_load_gif(img, file);
    335 #endif
    336 	}
    337 	img->w = imlib_image_get_width();
    338 	img->h = imlib_image_get_height();
    339 	img->checkpan = true;
    340 	img->dirty = true;
    341 
    342 	return true;
    343 }
    344 
    345 CLEANUP void img_close(img_t *img, bool decache)
    346 {
    347 	int i;
    348 
    349 	if (img->multi.cnt > 0) {
    350 		for (i = 0; i < img->multi.cnt; i++) {
    351 			imlib_context_set_image(img->multi.frames[i].im);
    352 			imlib_free_image();
    353 		}
    354 		img->multi.cnt = 0;
    355 		img->im = NULL;
    356 	} else if (img->im != NULL) {
    357 		imlib_context_set_image(img->im);
    358 		if (decache)
    359 			imlib_free_image_and_decache();
    360 		else
    361 			imlib_free_image();
    362 		img->im = NULL;
    363 	}
    364 }
    365 
    366 void img_check_pan(img_t *img, bool moved)
    367 {
    368 	win_t *win;
    369 	float w, h, ox, oy;
    370 
    371 	win = img->win;
    372 	w = img->w * img->zoom;
    373 	h = img->h * img->zoom;
    374 	ox = img->x;
    375 	oy = img->y;
    376 
    377 	if (w < win->w)
    378 		img->x = (win->w - w) / 2;
    379 	else if (img->x > 0)
    380 		img->x = 0;
    381 	else if (img->x + w < win->w)
    382 		img->x = win->w - w;
    383 	if (h < win->h)
    384 		img->y = (win->h - h) / 2;
    385 	else if (img->y > 0)
    386 		img->y = 0;
    387 	else if (img->y + h < win->h)
    388 		img->y = win->h - h;
    389 
    390 	if (!moved && (ox != img->x || oy != img->y))
    391 		img->dirty = true;
    392 }
    393 
    394 bool img_fit(img_t *img)
    395 {
    396 	float z, zw, zh;
    397 
    398 	if (img->scalemode == SCALE_ZOOM)
    399 		return false;
    400 
    401 	zw = (float) img->win->w / (float) img->w;
    402 	zh = (float) img->win->h / (float) img->h;
    403 
    404 	switch (img->scalemode) {
    405 		case SCALE_WIDTH:
    406 			z = zw;
    407 			break;
    408 		case SCALE_HEIGHT:
    409 			z = zh;
    410 			break;
    411 		default:
    412 			z = MIN(zw, zh);
    413 			break;
    414 	}
    415 	z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max);
    416 
    417 	if (zoomdiff(img, z) != 0) {
    418 		img->zoom = z;
    419 		img->dirty = true;
    420 		return true;
    421 	} else {
    422 		return false;
    423 	}
    424 }
    425 
    426 void img_render(img_t *img)
    427 {
    428 	win_t *win;
    429 	int sx, sy, sw, sh;
    430 	int dx, dy, dw, dh;
    431 	Imlib_Image bg;
    432 	unsigned long c;
    433 
    434 	win = img->win;
    435 	img_fit(img);
    436 
    437 	if (img->checkpan) {
    438 		img_check_pan(img, false);
    439 		img->checkpan = false;
    440 	}
    441 
    442 	if (!img->dirty)
    443 		return;
    444 
    445 	/* calculate source and destination offsets:
    446 	 *   - part of image drawn on full window, or
    447 	 *   - full image drawn on part of window
    448 	 */
    449 	if (img->x <= 0) {
    450 		sx = -img->x / img->zoom + 0.5;
    451 		sw = win->w / img->zoom;
    452 		dx = 0;
    453 		dw = win->w;
    454 	} else {
    455 		sx = 0;
    456 		sw = img->w;
    457 		dx = img->x;
    458 		dw = img->w * img->zoom;
    459 	}
    460 	if (img->y <= 0) {
    461 		sy = -img->y / img->zoom + 0.5;
    462 		sh = win->h / img->zoom;
    463 		dy = 0;
    464 		dh = win->h;
    465 	} else {
    466 		sy = 0;
    467 		sh = img->h;
    468 		dy = img->y;
    469 		dh = img->h * img->zoom;
    470 	}
    471 
    472 	win_clear(win);
    473 
    474 	imlib_context_set_image(img->im);
    475 	imlib_context_set_anti_alias(img->aa);
    476 	imlib_context_set_drawable(win->buf.pm);
    477 
    478 	if (imlib_image_has_alpha()) {
    479 		if ((bg = imlib_create_image(dw, dh)) == NULL)
    480 			error(EXIT_FAILURE, ENOMEM, NULL);
    481 		imlib_context_set_image(bg);
    482 		imlib_image_set_has_alpha(0);
    483 
    484 		if (img->alpha) {
    485 			int i, c, r;
    486 			DATA32 col[2] = { 0xFF666666, 0xFF999999 };
    487 			DATA32 * data = imlib_image_get_data();
    488 
    489 			for (r = 0; r < dh; r++) {
    490 				i = r * dw;
    491 				if (r == 0 || r == 8) {
    492 					for (c = 0; c < dw; c++)
    493 						data[i++] = col[!(c & 8) ^ !r];
    494 				} else {
    495 					memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0]));
    496 				}
    497 			}
    498 			imlib_image_put_back_data(data);
    499 		} else {
    500 			c = win->bg.pixel;
    501 			imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF);
    502 			imlib_image_fill_rectangle(0, 0, dw, dh);
    503 		}
    504 		imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
    505 		imlib_context_set_color_modifier(NULL);
    506 		imlib_render_image_on_drawable(dx, dy);
    507 		imlib_free_image();
    508 		imlib_context_set_color_modifier(img->cmod);
    509 	} else {
    510 		imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
    511 	}
    512 	img->dirty = false;
    513 }
    514 
    515 bool img_fit_win(img_t *img, scalemode_t sm)
    516 {
    517 	float oz;
    518 
    519 	oz = img->zoom;
    520 	img->scalemode = sm;
    521 
    522 	if (img_fit(img)) {
    523 		img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz;
    524 		img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz;
    525 		img->checkpan = true;
    526 		return true;
    527 	} else {
    528 		return false;
    529 	}
    530 }
    531 
    532 bool img_zoom(img_t *img, float z)
    533 {
    534 	z = MAX(z, zoom_min);
    535 	z = MIN(z, zoom_max);
    536 
    537 	img->scalemode = SCALE_ZOOM;
    538 
    539 	if (zoomdiff(img, z) != 0) {
    540 		int x, y;
    541 
    542 		win_cursor_pos(img->win, &x, &y);
    543 		if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) {
    544 			x = img->win->w / 2;
    545 			y = img->win->h / 2;
    546 		}
    547 		img->x = x - (x - img->x) * z / img->zoom;
    548 		img->y = y - (y - img->y) * z / img->zoom;
    549 		img->zoom = z;
    550 		img->checkpan = true;
    551 		img->dirty = true;
    552 		return true;
    553 	} else {
    554 		return false;
    555 	}
    556 }
    557 
    558 bool img_zoom_in(img_t *img)
    559 {
    560 	int i;
    561 	float z;
    562 
    563 	for (i = 0; i < ARRLEN(zoom_levels); i++) {
    564 		z = zoom_levels[i] / 100.0;
    565 		if (zoomdiff(img, z) > 0)
    566 			return img_zoom(img, z);
    567 	}
    568 	return false;
    569 }
    570 
    571 bool img_zoom_out(img_t *img)
    572 {
    573 	int i;
    574 	float z;
    575 
    576 	for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) {
    577 		z = zoom_levels[i] / 100.0;
    578 		if (zoomdiff(img, z) < 0)
    579 			return img_zoom(img, z);
    580 	}
    581 	return false;
    582 }
    583 
    584 bool img_pos(img_t *img, float x, float y)
    585 {
    586 	float ox, oy;
    587 
    588 	ox = img->x;
    589 	oy = img->y;
    590 
    591 	img->x = x;
    592 	img->y = y;
    593 
    594 	img_check_pan(img, true);
    595 
    596 	if (ox != img->x || oy != img->y) {
    597 		img->dirty = true;
    598 		return true;
    599 	} else {
    600 		return false;
    601 	}
    602 }
    603 
    604 bool img_move(img_t *img, float dx, float dy)
    605 {
    606 	return img_pos(img, img->x + dx, img->y + dy);
    607 }
    608 
    609 bool img_pan(img_t *img, direction_t dir, int d)
    610 {
    611 	/* d < 0: screen-wise
    612 	 * d = 0: 1/PAN_FRACTION of screen
    613 	 * d > 0: num of pixels
    614 	 */
    615 	float x, y;
    616 
    617 	if (d > 0) {
    618 		x = y = MAX(1, (float) d * img->zoom);
    619 	} else {
    620 		x = img->win->w / (d < 0 ? 1 : PAN_FRACTION);
    621 		y = img->win->h / (d < 0 ? 1 : PAN_FRACTION);
    622 	}
    623 
    624 	switch (dir) {
    625 		case DIR_LEFT:
    626 			return img_move(img, x, 0.0);
    627 		case DIR_RIGHT:
    628 			return img_move(img, -x, 0.0);
    629 		case DIR_UP:
    630 			return img_move(img, 0.0, y);
    631 		case DIR_DOWN:
    632 			return img_move(img, 0.0, -y);
    633 	}
    634 	return false;
    635 }
    636 
    637 bool img_pan_edge(img_t *img, direction_t dir)
    638 {
    639 	float ox, oy;
    640 
    641 	ox = img->x;
    642 	oy = img->y;
    643 
    644 	if (dir & DIR_LEFT)
    645 		img->x = 0;
    646 	if (dir & DIR_RIGHT)
    647 		img->x = img->win->w - img->w * img->zoom;
    648 	if (dir & DIR_UP)
    649 		img->y = 0;
    650 	if (dir & DIR_DOWN)
    651 		img->y = img->win->h - img->h * img->zoom;
    652 
    653 	img_check_pan(img, true);
    654 
    655 	if (ox != img->x || oy != img->y) {
    656 		img->dirty = true;
    657 		return true;
    658 	} else {
    659 		return false;
    660 	}
    661 }
    662 
    663 void img_rotate(img_t *img, degree_t d)
    664 {
    665 	int i, tmp;
    666 	float ox, oy;
    667 
    668 	imlib_context_set_image(img->im);
    669 	imlib_image_orientate(d);
    670 
    671 	for (i = 0; i < img->multi.cnt; i++) {
    672 		if (i != img->multi.sel) {
    673 			imlib_context_set_image(img->multi.frames[i].im);
    674 			imlib_image_orientate(d);
    675 		}
    676 	}
    677 	if (d == DEGREE_90 || d == DEGREE_270) {
    678 		ox = d == DEGREE_90  ? img->x : img->win->w - img->x - img->w * img->zoom;
    679 		oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom;
    680 
    681 		img->x = oy + (img->win->w - img->win->h) / 2;
    682 		img->y = ox + (img->win->h - img->win->w) / 2;
    683 
    684 		tmp = img->w;
    685 		img->w = img->h;
    686 		img->h = tmp;
    687 		img->checkpan = true;
    688 	}
    689 	img->dirty = true;
    690 }
    691 
    692 void img_flip(img_t *img, flipdir_t d)
    693 {
    694 	int i;
    695 	void (*imlib_flip_op[3])(void) = {
    696 		imlib_image_flip_horizontal,
    697 		imlib_image_flip_vertical,
    698 		imlib_image_flip_diagonal
    699 	};
    700 
    701 	d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1;
    702 
    703 	if (d < 0 || d >= ARRLEN(imlib_flip_op))
    704 		return;
    705 
    706 	imlib_context_set_image(img->im);
    707 	imlib_flip_op[d]();
    708 
    709 	for (i = 0; i < img->multi.cnt; i++) {
    710 		if (i != img->multi.sel) {
    711 			imlib_context_set_image(img->multi.frames[i].im);
    712 			imlib_flip_op[d]();
    713 		}
    714 	}
    715 	img->dirty = true;
    716 }
    717 
    718 void img_toggle_antialias(img_t *img)
    719 {
    720 	img->aa = !img->aa;
    721 	imlib_context_set_image(img->im);
    722 	imlib_context_set_anti_alias(img->aa);
    723 	img->dirty = true;
    724 }
    725 
    726 bool img_change_gamma(img_t *img, int d)
    727 {
    728 	/* d < 0: decrease gamma
    729 	 * d = 0: reset gamma
    730 	 * d > 0: increase gamma
    731 	 */
    732 	int gamma;
    733 	double range;
    734 
    735 	if (d == 0)
    736 		gamma = 0;
    737 	else
    738 		gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE);
    739 
    740 	if (img->gamma != gamma) {
    741 		imlib_reset_color_modifier();
    742 		if (gamma != 0) {
    743 			range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0;
    744 			imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE));
    745 		}
    746 		img->gamma = gamma;
    747 		img->dirty = true;
    748 		return true;
    749 	} else {
    750 		return false;
    751 	}
    752 }
    753 
    754 bool img_frame_goto(img_t *img, int n)
    755 {
    756 	if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
    757 		return false;
    758 
    759 	img->multi.sel = n;
    760 	img->im = img->multi.frames[n].im;
    761 
    762 	imlib_context_set_image(img->im);
    763 	img->w = imlib_image_get_width();
    764 	img->h = imlib_image_get_height();
    765 	img->checkpan = true;
    766 	img->dirty = true;
    767 
    768 	return true;
    769 }
    770 
    771 bool img_frame_navigate(img_t *img, int d)
    772 {
    773 	if (img->multi.cnt == 0 || d == 0)
    774 		return false;
    775 
    776 	d += img->multi.sel;
    777 	if (d < 0)
    778 		d = 0;
    779 	else if (d >= img->multi.cnt)
    780 		d = img->multi.cnt - 1;
    781 
    782 	return img_frame_goto(img, d);
    783 }
    784 
    785 bool img_frame_animate(img_t *img)
    786 {
    787 	if (img->multi.cnt == 0)
    788 		return false;
    789 
    790 	if (img->multi.sel + 1 >= img->multi.cnt)
    791 		img_frame_goto(img, 0);
    792 	else
    793 		img_frame_goto(img, img->multi.sel + 1);
    794 	img->dirty = true;
    795 	return true;
    796 }
    797