Krolyxon
Commit 4ec8fd5

Branches

Refs →

nsxivimage.c

955 lines · 21.5 KB · C

1/* Copyright 2011, 2012 Bert Muennich
2 *
3 * This file is a part of nsxiv.
4 *
5 * nsxiv 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 * nsxiv 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 nsxiv. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "nsxiv.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_LIBGIF
35#include <gif_lib.h>
36enum { DEF_GIF_DELAY = 75 };
37#endif
38
39#if HAVE_LIBWEBP
40#include <webp/decode.h>
41#include <webp/demux.h>
42enum { DEF_WEBP_DELAY = 75 };
43#endif
44
45float zoom_min;
46float zoom_max;
47
48static int zoomdiff(img_t *img, float z)
49{
50 return (int) ((img->w * z - img->w * img->zoom) + (img->h * z - img->h * img->zoom));
51}
52
53void img_init(img_t *img, win_t *win)
54{
55 zoom_min = zoom_levels[0] / 100.0;
56 zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0;
57
58 imlib_context_set_display(win->env.dpy);
59 imlib_context_set_visual(win->env.vis);
60 imlib_context_set_colormap(win->env.cmap);
61
62 img->im = NULL;
63 img->win = win;
64 img->scalemode = options->scalemode;
65 img->zoom = options->zoom;
66 img->zoom = MAX(img->zoom, zoom_min);
67 img->zoom = MIN(img->zoom, zoom_max);
68 img->checkpan = false;
69 img->dirty = false;
70 img->aa = ANTI_ALIAS;
71 img->alpha = ALPHA_LAYER;
72 img->multi.cap = img->multi.cnt = 0;
73 img->multi.animate = options->animate;
74 img->multi.framedelay = options->framerate > 0 ? 1000 / options->framerate : 0;
75 img->multi.length = 0;
76
77 img->cmod = imlib_create_color_modifier();
78 imlib_context_set_color_modifier(img->cmod);
79 img_change_gamma(img, options->gamma);
80
81 img->ss.on = options->slideshow > 0;
82 img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10;
83}
84
85#if HAVE_LIBEXIF
86void exif_auto_orientate(const fileinfo_t *file)
87{
88 ExifData *ed;
89 ExifEntry *entry;
90 int byte_order, orientation = 0;
91
92 if ((ed = exif_data_new_from_file(file->path)) == NULL)
93 return;
94 byte_order = exif_data_get_byte_order(ed);
95 entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
96 if (entry != NULL)
97 orientation = exif_get_short(entry->data, byte_order);
98 exif_data_unref(ed);
99
100 switch (orientation) {
101 case 5:
102 imlib_image_orientate(1);
103 case 2:
104 imlib_image_flip_vertical();
105 break;
106 case 3:
107 imlib_image_orientate(2);
108 break;
109 case 7:
110 imlib_image_orientate(1);
111 case 4:
112 imlib_image_flip_horizontal();
113 break;
114 case 6:
115 imlib_image_orientate(1);
116 break;
117 case 8:
118 imlib_image_orientate(3);
119 break;
120 }
121}
122#endif
123
124#if HAVE_LIBGIF
125bool img_load_gif(img_t *img, const fileinfo_t *file)
126{
127 GifFileType *gif;
128 GifRowType *rows = NULL;
129 GifRecordType rec;
130 ColorMapObject *cmap;
131 DATA32 bgpixel, *data, *ptr;
132 DATA32 *prev_frame = NULL;
133 Imlib_Image im;
134 int i, j, bg, r, g, b;
135 int x, y, w, h, sw, sh;
136 int px, py, pw, ph;
137 int intoffset[] = { 0, 4, 2, 1 };
138 int intjump[] = { 8, 8, 4, 2 };
139 int transp = -1;
140 unsigned int disposal = 0, prev_disposal = 0;
141 unsigned int delay = 0;
142 bool err = false;
143
144 if (img->multi.cap == 0) {
145 img->multi.cap = 8;
146 img->multi.frames = (img_frame_t*)
147 emalloc(sizeof(img_frame_t) * img->multi.cap);
148 }
149 img->multi.cnt = img->multi.sel = 0;
150 img->multi.length = 0;
151
152#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
153 gif = DGifOpenFileName(file->path, NULL);
154#else
155 gif = DGifOpenFileName(file->path);
156#endif
157 if (gif == NULL) {
158 error(0, 0, "%s: Error opening gif image", file->name);
159 return false;
160 }
161 bg = gif->SBackGroundColor;
162 sw = gif->SWidth;
163 sh = gif->SHeight;
164 px = py = pw = ph = 0;
165
166 do {
167 if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
168 err = true;
169 break;
170 }
171 if (rec == EXTENSION_RECORD_TYPE) {
172 int ext_code;
173 GifByteType *ext = NULL;
174
175 DGifGetExtension(gif, &ext_code, &ext);
176 while (ext) {
177 if (ext_code == GRAPHICS_EXT_FUNC_CODE) {
178 if (ext[1] & 1)
179 transp = (int) ext[4];
180 else
181 transp = -1;
182
183 delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
184 disposal = (unsigned int) ext[1] >> 2 & 0x7;
185 }
186 ext = NULL;
187 DGifGetExtensionNext(gif, &ext);
188 }
189 } else if (rec == IMAGE_DESC_RECORD_TYPE) {
190 if (DGifGetImageDesc(gif) == GIF_ERROR) {
191 err = true;
192 break;
193 }
194 x = gif->Image.Left;
195 y = gif->Image.Top;
196 w = gif->Image.Width;
197 h = gif->Image.Height;
198
199 rows = (GifRowType*) emalloc(h * sizeof(GifRowType));
200 for (i = 0; i < h; i++)
201 rows[i] = (GifRowType) emalloc(w * sizeof(GifPixelType));
202 if (gif->Image.Interlace) {
203 for (i = 0; i < 4; i++) {
204 for (j = intoffset[i]; j < h; j += intjump[i])
205 DGifGetLine(gif, rows[j], w);
206 }
207 } else {
208 for (i = 0; i < h; i++)
209 DGifGetLine(gif, rows[i], w);
210 }
211
212 ptr = data = (DATA32*) emalloc(sizeof(DATA32) * sw * sh);
213 cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
214 if (bg >= cmap->ColorCount) {
215 err = true;
216 break;
217 }
218 r = cmap->Colors[bg].Red;
219 g = cmap->Colors[bg].Green;
220 b = cmap->Colors[bg].Blue;
221 bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
222
223 for (i = 0; i < sh; i++) {
224 for (j = 0; j < sw; j++) {
225 if (i < y || i >= y + h || j < x || j >= x + w ||
226 rows[i-y][j-x] == transp)
227 {
228 if (prev_frame != NULL && (prev_disposal != 2 ||
229 i < py || i >= py + ph || j < px || j >= px + pw))
230 {
231 *ptr = prev_frame[i * sw + j];
232 } else {
233 *ptr = bgpixel;
234 }
235 } else {
236 r = cmap->Colors[rows[i-y][j-x]].Red;
237 g = cmap->Colors[rows[i-y][j-x]].Green;
238 b = cmap->Colors[rows[i-y][j-x]].Blue;
239 *ptr = 0xffu << 24 | r << 16 | g << 8 | b;
240 }
241 ptr++;
242 }
243 }
244
245 im = imlib_create_image_using_copied_data(sw, sh, data);
246
247 for (i = 0; i < h; i++)
248 free(rows[i]);
249 free(rows);
250 free(data);
251
252 if (im == NULL) {
253 err = true;
254 break;
255 }
256
257 imlib_context_set_image(im);
258 imlib_image_set_format("gif");
259 if (transp >= 0)
260 imlib_image_set_has_alpha(1);
261
262 if (disposal != 3)
263 prev_frame = imlib_image_get_data_for_reading_only();
264 prev_disposal = disposal;
265 px = x, py = y, pw = w, ph = h;
266
267 if (img->multi.cnt == img->multi.cap) {
268 img->multi.cap *= 2;
269 img->multi.frames = (img_frame_t*)
270 erealloc(img->multi.frames,
271 img->multi.cap * sizeof(img_frame_t));
272 }
273 img->multi.frames[img->multi.cnt].im = im;
274 delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay;
275 img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY;
276 img->multi.length += img->multi.frames[img->multi.cnt].delay;
277 img->multi.cnt++;
278 }
279 } while (rec != TERMINATE_RECORD_TYPE);
280
281#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
282 DGifCloseFile(gif, NULL);
283#else
284 DGifCloseFile(gif);
285#endif
286
287 if (err && (file->flags & FF_WARN))
288 error(0, 0, "%s: Corrupted gif file", file->name);
289
290 if (img->multi.cnt > 1) {
291 imlib_context_set_image(img->im);
292 imlib_free_image();
293 img->im = img->multi.frames[0].im;
294 } else if (img->multi.cnt == 1) {
295 imlib_context_set_image(img->multi.frames[0].im);
296 imlib_free_image();
297 img->multi.cnt = 0;
298 }
299
300 imlib_context_set_image(img->im);
301
302 return !err;
303}
304#endif /* HAVE_LIBGIF */
305
306
307#if HAVE_LIBWEBP
308bool is_webp(const char *path)
309{
310 /* The size (in bytes) of the largest amount of data required to verify a WebP image. */
311 enum { max = 30 };
312 const unsigned char fmt[max];
313 bool ret = false;
314 FILE *f;
315
316 if ((f = fopen(path, "rb")) != NULL) {
317 if (fread((unsigned char *) fmt, 1, max, f) == max)
318 ret = WebPGetInfo(fmt, max, NULL, NULL);
319 fclose(f);
320 }
321 return ret;
322}
323
324/* fframe img
325 * NULL NULL = do nothing
326 * x NULL = load the first frame as an Imlib_Image
327 * NULL x = load all frames into img->multi.
328 */
329bool img_load_webp(const fileinfo_t *file, Imlib_Image *fframe, img_t *img)
330{
331 FILE *webp_file;
332 WebPData data;
333 data.bytes = NULL;
334
335 Imlib_Image im = NULL;
336 struct WebPAnimDecoderOptions opts;
337 WebPAnimDecoder *dec = NULL;
338 struct WebPAnimInfo info;
339 unsigned char *buf = NULL;
340 int ts;
341 const WebPDemuxer *demux;
342 WebPIterator iter;
343 unsigned long flags;
344 unsigned int delay;
345 bool err = false;
346
347 if ((err = fframe == NULL && img == NULL))
348 goto fail;
349
350 if ((err = (webp_file = fopen(file->path, "rb")) == NULL)) {
351 error(0, 0, "%s: Error opening webp image", file->name);
352 goto fail;
353 }
354 fseek(webp_file, 0L, SEEK_END);
355 data.size = ftell(webp_file);
356 rewind(webp_file);
357 data.bytes = emalloc(data.size);
358 if ((err = fread((unsigned char *)data.bytes, 1, data.size, webp_file) != data.size)) {
359 error(0, 0, "%s: Error reading webp image", file->name);
360 goto fail;
361 }
362
363 /* Setup the WebP Animation Decoder */
364 if ((err = !WebPAnimDecoderOptionsInit(&opts))) {
365 error(0, 0, "%s: WebP library version mismatch", file->name);
366 goto fail;
367 }
368 opts.color_mode = MODE_BGRA;
369 /* NOTE: Multi-threaded decoding may cause problems on some system */
370 opts.use_threads = true;
371 dec = WebPAnimDecoderNew(&data, &opts);
372 if ((err = (dec == NULL) || !WebPAnimDecoderGetInfo(dec, &info))) {
373 error(0, 0, "%s: WebP parsing or memory error (file is corrupt?)", file->name);
374 goto fail;
375 }
376 demux = WebPAnimDecoderGetDemuxer(dec);
377
378 if (img == NULL) { /* Only get the first frame and put it into fframe. */
379 if ((err = !WebPAnimDecoderGetNext(dec, &buf, &ts))) {
380 error(0, 0, "%s: Error loading first frame", file->name);
381 goto fail;
382 }
383 *fframe = imlib_create_image_using_copied_data(
384 info.canvas_width, info.canvas_height, (DATA32*)buf);
385 imlib_context_set_image(*fframe);
386 imlib_image_set_has_alpha(WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS) & ALPHA_FLAG);
387 } else {
388 /* Get global information for the image */
389 flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
390 img->w = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
391 img->h = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
392 img->multi.cap = info.frame_count;
393 img->multi.sel = 0;
394 img->multi.frames = emalloc(info.frame_count * sizeof(img_frame_t));
395
396 /* Load and decode frames (also works on images with only 1 frame) */
397 img->multi.cnt = 0;
398 while (WebPAnimDecoderGetNext(dec, &buf, &ts)) {
399 im = imlib_create_image_using_copied_data(
400 info.canvas_width, info.canvas_height, (DATA32*)buf);
401 imlib_context_set_image(im);
402 imlib_image_set_format("webp");
403 /* Get an iterator of this frame - used for frame info (duration, etc.) */
404 WebPDemuxGetFrame(demux, img->multi.cnt+1, &iter);
405 imlib_image_set_has_alpha((flags & ALPHA_FLAG) == ALPHA_FLAG);
406 /* Store info for this frame */
407 img->multi.frames[img->multi.cnt].im = im;
408 delay = iter.duration > 0 ? iter.duration : DEF_WEBP_DELAY;
409 img->multi.frames[img->multi.cnt].delay = delay;
410 img->multi.length += img->multi.frames[img->multi.cnt].delay;
411 img->multi.cnt++;
412 }
413 WebPDemuxReleaseIterator(&iter);
414
415 if (img->multi.cnt > 1) {
416 imlib_context_set_image(img->im);
417 imlib_free_image();
418 img->im = img->multi.frames[0].im;
419 } else if (img->multi.cnt == 1) {
420 imlib_context_set_image(img->multi.frames[0].im);
421 imlib_free_image();
422 img->multi.cnt = 0;
423 }
424 imlib_context_set_image(img->im);
425 }
426 imlib_image_set_format("webp");
427fail:
428 if (dec != NULL)
429 WebPAnimDecoderDelete(dec);
430 free((unsigned char *)data.bytes);
431 return !err;
432}
433#endif /* HAVE_LIBWEBP */
434
435Imlib_Image img_open(const fileinfo_t *file)
436{
437 struct stat st;
438 Imlib_Image im = NULL;
439
440 if (access(file->path, R_OK) == 0 &&
441 stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
442 {
443#if HAVE_LIBWEBP
444 if (is_webp(file->path))
445 img_load_webp(file, &im, NULL);
446 else
447#endif
448 im = imlib_load_image(file->path);
449 if (im != NULL) {
450 imlib_context_set_image(im);
451#if HAVE_LIBWEBP
452 const char *fmt;
453 if ((fmt = imlib_image_format()) != NULL && !STREQ(fmt, "webp") &&
454 imlib_image_get_data_for_reading_only() == NULL) {
455#else
456 if (imlib_image_get_data_for_reading_only() == NULL) {
457#endif
458 imlib_free_image();
459 im = NULL;
460 }
461 }
462 }
463 if (im == NULL && (file->flags & FF_WARN))
464 error(0, 0, "%s: Error opening image", file->name);
465 return im;
466}
467
468bool img_load(img_t *img, const fileinfo_t *file)
469{
470 const char *fmt;
471
472 if ((img->im = img_open(file)) == NULL)
473 return false;
474
475 imlib_image_set_changes_on_disk();
476
477#if HAVE_LIBEXIF
478 exif_auto_orientate(file);
479#endif
480
481 if ((fmt = imlib_image_format()) != NULL) {
482#if HAVE_LIBGIF
483 if (STREQ(fmt, "gif"))
484 img_load_gif(img, file);
485#endif
486#if HAVE_LIBWEBP
487 if (STREQ(fmt, "webp"))
488 img_load_webp(file, NULL, img);
489#endif
490 }
491 img->w = imlib_image_get_width();
492 img->h = imlib_image_get_height();
493 img->checkpan = true;
494 img->dirty = true;
495
496 return true;
497}
498
499CLEANUP void img_close(img_t *img, bool decache)
500{
501 int i;
502
503 if (img->multi.cnt > 0) {
504 for (i = 0; i < img->multi.cnt; i++) {
505 imlib_context_set_image(img->multi.frames[i].im);
506 imlib_free_image();
507 }
508 img->multi.cnt = 0;
509 img->im = NULL;
510 } else if (img->im != NULL) {
511 imlib_context_set_image(img->im);
512 if (decache)
513 imlib_free_image_and_decache();
514 else
515 imlib_free_image();
516 img->im = NULL;
517 }
518}
519
520void img_check_pan(img_t *img, bool moved)
521{
522 win_t *win;
523 float w, h, ox, oy;
524
525 win = img->win;
526 w = img->w * img->zoom;
527 h = img->h * img->zoom;
528 ox = img->x;
529 oy = img->y;
530
531 if (w < win->w)
532 img->x = (win->w - w) / 2;
533 else if (img->x > 0)
534 img->x = 0;
535 else if (img->x + w < win->w)
536 img->x = win->w - w;
537 if (h < win->h)
538 img->y = (win->h - h) / 2;
539 else if (img->y > 0)
540 img->y = 0;
541 else if (img->y + h < win->h)
542 img->y = win->h - h;
543
544 if (!moved && (ox != img->x || oy != img->y))
545 img->dirty = true;
546}
547
548bool img_fit(img_t *img)
549{
550 float z, zw, zh;
551
552 if (img->scalemode == SCALE_ZOOM)
553 return false;
554
555 zw = (float) img->win->w / (float) img->w;
556 zh = (float) img->win->h / (float) img->h;
557
558 switch (img->scalemode) {
559 case SCALE_FILL:
560 z = MAX(zw, zh);
561 break;
562 case SCALE_WIDTH:
563 z = zw;
564 break;
565 case SCALE_HEIGHT:
566 z = zh;
567 break;
568 default:
569 z = MIN(zw, zh);
570 break;
571 }
572 z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max);
573
574 if (zoomdiff(img, z) != 0) {
575 img->zoom = z;
576 img->dirty = true;
577 return true;
578 } else {
579 return false;
580 }
581}
582
583void img_render(img_t *img)
584{
585 win_t *win;
586 int sx, sy, sw, sh;
587 int dx, dy, dw, dh;
588 Imlib_Image bg;
589 unsigned long c;
590
591 win = img->win;
592 img_fit(img);
593
594 if (img->checkpan) {
595 img_check_pan(img, false);
596 img->checkpan = false;
597 }
598
599 if (!img->dirty)
600 return;
601
602 /* calculate source and destination offsets:
603 * - part of image drawn on full window, or
604 * - full image drawn on part of window
605 */
606 if (img->x <= 0) {
607 sx = -img->x / img->zoom + 0.5;
608 sw = win->w / img->zoom;
609 dx = 0;
610 dw = win->w;
611 } else {
612 sx = 0;
613 sw = img->w;
614 dx = img->x;
615 dw = img->w * img->zoom;
616 }
617 if (img->y <= 0) {
618 sy = -img->y / img->zoom + 0.5;
619 sh = win->h / img->zoom;
620 dy = 0;
621 dh = win->h;
622 } else {
623 sy = 0;
624 sh = img->h;
625 dy = img->y;
626 dh = img->h * img->zoom;
627 }
628
629 win_clear(win);
630
631 imlib_context_set_image(img->im);
632 imlib_context_set_anti_alias(img->aa);
633 imlib_context_set_drawable(win->buf.pm);
634
635 if (imlib_image_has_alpha()) {
636 if ((bg = imlib_create_image(dw, dh)) == NULL)
637 error(EXIT_FAILURE, ENOMEM, NULL);
638 imlib_context_set_image(bg);
639 imlib_image_set_has_alpha(1);
640
641 if (img->alpha) {
642 int i, c, r;
643 DATA32 col[2] = { 0xFF666666, 0xFF999999 };
644 DATA32 * data = imlib_image_get_data();
645
646 for (r = 0; r < dh; r++) {
647 i = r * dw;
648 if (r == 0 || r == 8) {
649 for (c = 0; c < dw; c++)
650 data[i++] = col[!(c & 8) ^ !r];
651 } else {
652 memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0]));
653 }
654 }
655 imlib_image_put_back_data(data);
656 } else {
657 c = win->win_bg.pixel;
658 imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF);
659 imlib_image_fill_rectangle(0, 0, dw, dh);
660 }
661 imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
662 imlib_context_set_color_modifier(NULL);
663 imlib_render_image_on_drawable(dx, dy);
664 imlib_free_image();
665 imlib_context_set_color_modifier(img->cmod);
666 } else {
667 imlib_image_set_has_alpha(1);
668 imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
669 }
670 img->dirty = false;
671}
672
673bool img_fit_win(img_t *img, scalemode_t sm)
674{
675 float oz;
676
677 oz = img->zoom;
678 img->scalemode = sm;
679
680 if (img_fit(img)) {
681 img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz;
682 img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz;
683 img->checkpan = true;
684 return true;
685 } else {
686 return false;
687 }
688}
689
690bool img_zoom(img_t *img, float z)
691{
692 z = MAX(z, zoom_min);
693 z = MIN(z, zoom_max);
694
695 img->scalemode = SCALE_ZOOM;
696
697 if (zoomdiff(img, z) != 0) {
698 int x, y;
699
700 win_cursor_pos(img->win, &x, &y);
701 if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) {
702 x = img->win->w / 2;
703 y = img->win->h / 2;
704 }
705 img->x = x - (x - img->x) * z / img->zoom;
706 img->y = y - (y - img->y) * z / img->zoom;
707 img->zoom = z;
708 img->checkpan = true;
709 img->dirty = true;
710 return true;
711 } else {
712 return false;
713 }
714}
715
716bool img_zoom_in(img_t *img)
717{
718 int i;
719 float z;
720
721 for (i = 0; i < ARRLEN(zoom_levels); i++) {
722 z = zoom_levels[i] / 100.0;
723 if (zoomdiff(img, z) > 0)
724 return img_zoom(img, z);
725 }
726 return false;
727}
728
729bool img_zoom_out(img_t *img)
730{
731 int i;
732 float z;
733
734 for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) {
735 z = zoom_levels[i] / 100.0;
736 if (zoomdiff(img, z) < 0)
737 return img_zoom(img, z);
738 }
739 return false;
740}
741
742bool img_pos(img_t *img, float x, float y)
743{
744 float ox, oy;
745
746 ox = img->x;
747 oy = img->y;
748
749 img->x = x;
750 img->y = y;
751
752 img_check_pan(img, true);
753
754 if (ox != img->x || oy != img->y) {
755 img->dirty = true;
756 return true;
757 } else {
758 return false;
759 }
760}
761
762bool img_move(img_t *img, float dx, float dy)
763{
764 return img_pos(img, img->x + dx, img->y + dy);
765}
766
767bool img_pan(img_t *img, direction_t dir, int d)
768{
769 /* d < 0: screen-wise
770 * d = 0: 1/PAN_FRACTION of screen
771 * d > 0: num of pixels
772 */
773 float x, y;
774
775 if (d > 0) {
776 x = y = MAX(1, (float) d * img->zoom);
777 } else {
778 x = img->win->w / (d < 0 ? 1 : PAN_FRACTION);
779 y = img->win->h / (d < 0 ? 1 : PAN_FRACTION);
780 }
781
782 switch (dir) {
783 case DIR_LEFT:
784 return img_move(img, x, 0.0);
785 case DIR_RIGHT:
786 return img_move(img, -x, 0.0);
787 case DIR_UP:
788 return img_move(img, 0.0, y);
789 case DIR_DOWN:
790 return img_move(img, 0.0, -y);
791 }
792 return false;
793}
794
795bool img_pan_edge(img_t *img, direction_t dir)
796{
797 float ox, oy;
798
799 ox = img->x;
800 oy = img->y;
801
802 if (dir & DIR_LEFT)
803 img->x = 0;
804 if (dir & DIR_RIGHT)
805 img->x = img->win->w - img->w * img->zoom;
806 if (dir & DIR_UP)
807 img->y = 0;
808 if (dir & DIR_DOWN)
809 img->y = img->win->h - img->h * img->zoom;
810
811 img_check_pan(img, true);
812
813 if (ox != img->x || oy != img->y) {
814 img->dirty = true;
815 return true;
816 } else {
817 return false;
818 }
819}
820
821void img_rotate(img_t *img, degree_t d)
822{
823 int i, tmp;
824 float ox, oy;
825
826 imlib_context_set_image(img->im);
827 imlib_image_orientate(d);
828
829 for (i = 0; i < img->multi.cnt; i++) {
830 if (i != img->multi.sel) {
831 imlib_context_set_image(img->multi.frames[i].im);
832 imlib_image_orientate(d);
833 }
834 }
835 if (d == DEGREE_90 || d == DEGREE_270) {
836 ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom;
837 oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom;
838
839 img->x = oy + (img->win->w - img->win->h) / 2;
840 img->y = ox + (img->win->h - img->win->w) / 2;
841
842 tmp = img->w;
843 img->w = img->h;
844 img->h = tmp;
845 img->checkpan = true;
846 }
847 img->dirty = true;
848}
849
850void img_flip(img_t *img, flipdir_t d)
851{
852 int i;
853 void (*imlib_flip_op[3])(void) = {
854 imlib_image_flip_horizontal,
855 imlib_image_flip_vertical,
856 imlib_image_flip_diagonal
857 };
858
859 d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1;
860
861 if (d < 0 || d >= ARRLEN(imlib_flip_op))
862 return;
863
864 imlib_context_set_image(img->im);
865 imlib_flip_op[d]();
866
867 for (i = 0; i < img->multi.cnt; i++) {
868 if (i != img->multi.sel) {
869 imlib_context_set_image(img->multi.frames[i].im);
870 imlib_flip_op[d]();
871 }
872 }
873 img->dirty = true;
874}
875
876void img_toggle_antialias(img_t *img)
877{
878 img->aa = !img->aa;
879 imlib_context_set_image(img->im);
880 imlib_context_set_anti_alias(img->aa);
881 img->dirty = true;
882}
883
884bool img_change_gamma(img_t *img, int d)
885{
886 /* d < 0: decrease gamma
887 * d = 0: reset gamma
888 * d > 0: increase gamma
889 */
890 int gamma;
891 double range;
892
893 if (d == 0)
894 gamma = 0;
895 else
896 gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE);
897
898 if (img->gamma != gamma) {
899 imlib_reset_color_modifier();
900 if (gamma != 0) {
901 range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0;
902 imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE));
903 }
904 img->gamma = gamma;
905 img->dirty = true;
906 return true;
907 } else {
908 return false;
909 }
910}
911
912bool img_frame_goto(img_t *img, int n)
913{
914 if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
915 return false;
916
917 img->multi.sel = n;
918 img->im = img->multi.frames[n].im;
919
920 imlib_context_set_image(img->im);
921 img->w = imlib_image_get_width();
922 img->h = imlib_image_get_height();
923 img->checkpan = true;
924 img->dirty = true;
925
926 return true;
927}
928
929bool img_frame_navigate(img_t *img, int d)
930{
931 if (img->multi.cnt == 0 || d == 0)
932 return false;
933
934 d += img->multi.sel;
935 if (d < 0)
936 d = 0;
937 else if (d >= img->multi.cnt)
938 d = img->multi.cnt - 1;
939
940 return img_frame_goto(img, d);
941}
942
943bool img_frame_animate(img_t *img)
944{
945 if (img->multi.cnt == 0)
946 return false;
947
948 if (img->multi.sel + 1 >= img->multi.cnt)
949 img_frame_goto(img, 0);
950 else
951 img_frame_goto(img, img->multi.sel + 1);
952 img->dirty = true;
953 return true;
954}
955