Krolyxon
Commit 956faac

Branches

Refs →

nsxivimage.c

805 lines · 17.1 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
39float zoom_min;
40float zoom_max;
41
42static 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
47void 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_change_gamma(img, options->gamma);
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
80void 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_LIBGIF
119bool 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 if (bg >= cmap->ColorCount) {
209 err = true;
210 break;
211 }
212 r = cmap->Colors[bg].Red;
213 g = cmap->Colors[bg].Green;
214 b = cmap->Colors[bg].Blue;
215 bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
216
217 for (i = 0; i < sh; i++) {
218 for (j = 0; j < sw; j++) {
219 if (i < y || i >= y + h || j < x || j >= x + w ||
220 rows[i-y][j-x] == transp)
221 {
222 if (prev_frame != NULL && (prev_disposal != 2 ||
223 i < py || i >= py + ph || j < px || j >= px + pw))
224 {
225 *ptr = prev_frame[i * sw + j];
226 } else {
227 *ptr = bgpixel;
228 }
229 } else {
230 r = cmap->Colors[rows[i-y][j-x]].Red;
231 g = cmap->Colors[rows[i-y][j-x]].Green;
232 b = cmap->Colors[rows[i-y][j-x]].Blue;
233 *ptr = 0xffu << 24 | r << 16 | g << 8 | b;
234 }
235 ptr++;
236 }
237 }
238
239 im = imlib_create_image_using_copied_data(sw, sh, data);
240
241 for (i = 0; i < h; i++)
242 free(rows[i]);
243 free(rows);
244 free(data);
245
246 if (im == NULL) {
247 err = true;
248 break;
249 }
250
251 imlib_context_set_image(im);
252 imlib_image_set_format("gif");
253 if (transp >= 0)
254 imlib_image_set_has_alpha(1);
255
256 if (disposal != 3)
257 prev_frame = imlib_image_get_data_for_reading_only();
258 prev_disposal = disposal;
259 px = x, py = y, pw = w, ph = h;
260
261 if (img->multi.cnt == img->multi.cap) {
262 img->multi.cap *= 2;
263 img->multi.frames = (img_frame_t*)
264 erealloc(img->multi.frames,
265 img->multi.cap * sizeof(img_frame_t));
266 }
267 img->multi.frames[img->multi.cnt].im = im;
268 delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay;
269 img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY;
270 img->multi.length += img->multi.frames[img->multi.cnt].delay;
271 img->multi.cnt++;
272 }
273 } while (rec != TERMINATE_RECORD_TYPE);
274
275#if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5 && GIFLIB_MINOR >= 1
276 DGifCloseFile(gif, NULL);
277#else
278 DGifCloseFile(gif);
279#endif
280
281 if (err && (file->flags & FF_WARN))
282 error(0, 0, "%s: Corrupted gif file", file->name);
283
284 if (img->multi.cnt > 1) {
285 imlib_context_set_image(img->im);
286 imlib_free_image();
287 img->im = img->multi.frames[0].im;
288 } else if (img->multi.cnt == 1) {
289 imlib_context_set_image(img->multi.frames[0].im);
290 imlib_free_image();
291 img->multi.cnt = 0;
292 }
293
294 imlib_context_set_image(img->im);
295
296 return !err;
297}
298#endif /* HAVE_LIBGIF */
299
300Imlib_Image img_open(const fileinfo_t *file)
301{
302 struct stat st;
303 Imlib_Image im = NULL;
304
305 if (access(file->path, R_OK) == 0 &&
306 stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
307 {
308 im = imlib_load_image(file->path);
309 if (im != NULL) {
310 imlib_context_set_image(im);
311 if (imlib_image_get_data_for_reading_only() == NULL) {
312 imlib_free_image();
313 im = NULL;
314 }
315 }
316 }
317 if (im == NULL && (file->flags & FF_WARN))
318 error(0, 0, "%s: Error opening image", file->name);
319 return im;
320}
321
322bool img_load(img_t *img, const fileinfo_t *file)
323{
324 const char *fmt;
325
326 if ((img->im = img_open(file)) == NULL)
327 return false;
328
329 imlib_image_set_changes_on_disk();
330
331#if HAVE_LIBEXIF
332 exif_auto_orientate(file);
333#endif
334
335 if ((fmt = imlib_image_format()) != NULL) {
336#if HAVE_LIBGIF
337 if (STREQ(fmt, "gif"))
338 img_load_gif(img, file);
339#endif
340 }
341 img->w = imlib_image_get_width();
342 img->h = imlib_image_get_height();
343 img->checkpan = true;
344 img->dirty = true;
345
346 return true;
347}
348
349CLEANUP void img_close(img_t *img, bool decache)
350{
351 int i;
352
353 if (img->multi.cnt > 0) {
354 for (i = 0; i < img->multi.cnt; i++) {
355 imlib_context_set_image(img->multi.frames[i].im);
356 imlib_free_image();
357 }
358 img->multi.cnt = 0;
359 img->im = NULL;
360 } else if (img->im != NULL) {
361 imlib_context_set_image(img->im);
362 if (decache)
363 imlib_free_image_and_decache();
364 else
365 imlib_free_image();
366 img->im = NULL;
367 }
368}
369
370void img_check_pan(img_t *img, bool moved)
371{
372 win_t *win;
373 float w, h, ox, oy;
374
375 win = img->win;
376 w = img->w * img->zoom;
377 h = img->h * img->zoom;
378 ox = img->x;
379 oy = img->y;
380
381 if (w < win->w)
382 img->x = (win->w - w) / 2;
383 else if (img->x > 0)
384 img->x = 0;
385 else if (img->x + w < win->w)
386 img->x = win->w - w;
387 if (h < win->h)
388 img->y = (win->h - h) / 2;
389 else if (img->y > 0)
390 img->y = 0;
391 else if (img->y + h < win->h)
392 img->y = win->h - h;
393
394 if (!moved && (ox != img->x || oy != img->y))
395 img->dirty = true;
396}
397
398bool img_fit(img_t *img)
399{
400 float z, zw, zh;
401
402 if (img->scalemode == SCALE_ZOOM)
403 return false;
404
405 zw = (float) img->win->w / (float) img->w;
406 zh = (float) img->win->h / (float) img->h;
407
408 switch (img->scalemode) {
409 case SCALE_FILL:
410 z = MAX(zw, zh);
411 break;
412 case SCALE_WIDTH:
413 z = zw;
414 break;
415 case SCALE_HEIGHT:
416 z = zh;
417 break;
418 default:
419 z = MIN(zw, zh);
420 break;
421 }
422 z = MIN(z, img->scalemode == SCALE_DOWN ? 1.0 : zoom_max);
423
424 if (zoomdiff(img, z) != 0) {
425 img->zoom = z;
426 img->dirty = true;
427 return true;
428 } else {
429 return false;
430 }
431}
432
433void img_render(img_t *img)
434{
435 win_t *win;
436 int sx, sy, sw, sh;
437 int dx, dy, dw, dh;
438 Imlib_Image bg;
439 unsigned long c;
440
441 win = img->win;
442 img_fit(img);
443
444 if (img->checkpan) {
445 img_check_pan(img, false);
446 img->checkpan = false;
447 }
448
449 if (!img->dirty)
450 return;
451
452 /* calculate source and destination offsets:
453 * - part of image drawn on full window, or
454 * - full image drawn on part of window
455 */
456 if (img->x <= 0) {
457 sx = -img->x / img->zoom + 0.5;
458 sw = win->w / img->zoom;
459 dx = 0;
460 dw = win->w;
461 } else {
462 sx = 0;
463 sw = img->w;
464 dx = img->x;
465 dw = img->w * img->zoom;
466 }
467 if (img->y <= 0) {
468 sy = -img->y / img->zoom + 0.5;
469 sh = win->h / img->zoom;
470 dy = 0;
471 dh = win->h;
472 } else {
473 sy = 0;
474 sh = img->h;
475 dy = img->y;
476 dh = img->h * img->zoom;
477 }
478
479 win_clear(win);
480
481 imlib_context_set_image(img->im);
482 imlib_context_set_anti_alias(img->aa);
483 imlib_context_set_drawable(win->buf.pm);
484
485 if (imlib_image_has_alpha()) {
486 if ((bg = imlib_create_image(dw, dh)) == NULL)
487 error(EXIT_FAILURE, ENOMEM, NULL);
488 imlib_context_set_image(bg);
489 imlib_image_set_has_alpha(1);
490
491 if (img->alpha) {
492 int i, c, r;
493 DATA32 col[2] = { 0xFF666666, 0xFF999999 };
494 DATA32 * data = imlib_image_get_data();
495
496 for (r = 0; r < dh; r++) {
497 i = r * dw;
498 if (r == 0 || r == 8) {
499 for (c = 0; c < dw; c++)
500 data[i++] = col[!(c & 8) ^ !r];
501 } else {
502 memcpy(&data[i], &data[(r & 8) * dw], dw * sizeof(data[0]));
503 }
504 }
505 imlib_image_put_back_data(data);
506 } else {
507 c = win->win_bg.pixel;
508 imlib_context_set_color(c >> 16 & 0xFF, c >> 8 & 0xFF, c & 0xFF, 0xFF);
509 imlib_image_fill_rectangle(0, 0, dw, dh);
510 }
511 imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
512 imlib_context_set_color_modifier(NULL);
513 imlib_render_image_on_drawable(dx, dy);
514 imlib_free_image();
515 imlib_context_set_color_modifier(img->cmod);
516 } else {
517 imlib_image_set_has_alpha(1);
518 imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
519 }
520 img->dirty = false;
521}
522
523bool img_fit_win(img_t *img, scalemode_t sm)
524{
525 float oz;
526
527 oz = img->zoom;
528 img->scalemode = sm;
529
530 if (img_fit(img)) {
531 img->x = img->win->w / 2 - (img->win->w / 2 - img->x) * img->zoom / oz;
532 img->y = img->win->h / 2 - (img->win->h / 2 - img->y) * img->zoom / oz;
533 img->checkpan = true;
534 return true;
535 } else {
536 return false;
537 }
538}
539
540bool img_zoom(img_t *img, float z)
541{
542 z = MAX(z, zoom_min);
543 z = MIN(z, zoom_max);
544
545 img->scalemode = SCALE_ZOOM;
546
547 if (zoomdiff(img, z) != 0) {
548 int x, y;
549
550 win_cursor_pos(img->win, &x, &y);
551 if (x < 0 || x >= img->win->w || y < 0 || y >= img->win->h) {
552 x = img->win->w / 2;
553 y = img->win->h / 2;
554 }
555 img->x = x - (x - img->x) * z / img->zoom;
556 img->y = y - (y - img->y) * z / img->zoom;
557 img->zoom = z;
558 img->checkpan = true;
559 img->dirty = true;
560 return true;
561 } else {
562 return false;
563 }
564}
565
566bool img_zoom_in(img_t *img)
567{
568 int i;
569 float z;
570
571 for (i = 0; i < ARRLEN(zoom_levels); i++) {
572 z = zoom_levels[i] / 100.0;
573 if (zoomdiff(img, z) > 0)
574 return img_zoom(img, z);
575 }
576 return false;
577}
578
579bool img_zoom_out(img_t *img)
580{
581 int i;
582 float z;
583
584 for (i = ARRLEN(zoom_levels) - 1; i >= 0; i--) {
585 z = zoom_levels[i] / 100.0;
586 if (zoomdiff(img, z) < 0)
587 return img_zoom(img, z);
588 }
589 return false;
590}
591
592bool img_pos(img_t *img, float x, float y)
593{
594 float ox, oy;
595
596 ox = img->x;
597 oy = img->y;
598
599 img->x = x;
600 img->y = y;
601
602 img_check_pan(img, true);
603
604 if (ox != img->x || oy != img->y) {
605 img->dirty = true;
606 return true;
607 } else {
608 return false;
609 }
610}
611
612bool img_move(img_t *img, float dx, float dy)
613{
614 return img_pos(img, img->x + dx, img->y + dy);
615}
616
617bool img_pan(img_t *img, direction_t dir, int d)
618{
619 /* d < 0: screen-wise
620 * d = 0: 1/PAN_FRACTION of screen
621 * d > 0: num of pixels
622 */
623 float x, y;
624
625 if (d > 0) {
626 x = y = MAX(1, (float) d * img->zoom);
627 } else {
628 x = img->win->w / (d < 0 ? 1 : PAN_FRACTION);
629 y = img->win->h / (d < 0 ? 1 : PAN_FRACTION);
630 }
631
632 switch (dir) {
633 case DIR_LEFT:
634 return img_move(img, x, 0.0);
635 case DIR_RIGHT:
636 return img_move(img, -x, 0.0);
637 case DIR_UP:
638 return img_move(img, 0.0, y);
639 case DIR_DOWN:
640 return img_move(img, 0.0, -y);
641 }
642 return false;
643}
644
645bool img_pan_edge(img_t *img, direction_t dir)
646{
647 float ox, oy;
648
649 ox = img->x;
650 oy = img->y;
651
652 if (dir & DIR_LEFT)
653 img->x = 0;
654 if (dir & DIR_RIGHT)
655 img->x = img->win->w - img->w * img->zoom;
656 if (dir & DIR_UP)
657 img->y = 0;
658 if (dir & DIR_DOWN)
659 img->y = img->win->h - img->h * img->zoom;
660
661 img_check_pan(img, true);
662
663 if (ox != img->x || oy != img->y) {
664 img->dirty = true;
665 return true;
666 } else {
667 return false;
668 }
669}
670
671void img_rotate(img_t *img, degree_t d)
672{
673 int i, tmp;
674 float ox, oy;
675
676 imlib_context_set_image(img->im);
677 imlib_image_orientate(d);
678
679 for (i = 0; i < img->multi.cnt; i++) {
680 if (i != img->multi.sel) {
681 imlib_context_set_image(img->multi.frames[i].im);
682 imlib_image_orientate(d);
683 }
684 }
685 if (d == DEGREE_90 || d == DEGREE_270) {
686 ox = d == DEGREE_90 ? img->x : img->win->w - img->x - img->w * img->zoom;
687 oy = d == DEGREE_270 ? img->y : img->win->h - img->y - img->h * img->zoom;
688
689 img->x = oy + (img->win->w - img->win->h) / 2;
690 img->y = ox + (img->win->h - img->win->w) / 2;
691
692 tmp = img->w;
693 img->w = img->h;
694 img->h = tmp;
695 img->checkpan = true;
696 }
697 img->dirty = true;
698}
699
700void img_flip(img_t *img, flipdir_t d)
701{
702 int i;
703 void (*imlib_flip_op[3])(void) = {
704 imlib_image_flip_horizontal,
705 imlib_image_flip_vertical,
706 imlib_image_flip_diagonal
707 };
708
709 d = (d & (FLIP_HORIZONTAL | FLIP_VERTICAL)) - 1;
710
711 if (d < 0 || d >= ARRLEN(imlib_flip_op))
712 return;
713
714 imlib_context_set_image(img->im);
715 imlib_flip_op[d]();
716
717 for (i = 0; i < img->multi.cnt; i++) {
718 if (i != img->multi.sel) {
719 imlib_context_set_image(img->multi.frames[i].im);
720 imlib_flip_op[d]();
721 }
722 }
723 img->dirty = true;
724}
725
726void img_toggle_antialias(img_t *img)
727{
728 img->aa = !img->aa;
729 imlib_context_set_image(img->im);
730 imlib_context_set_anti_alias(img->aa);
731 img->dirty = true;
732}
733
734bool img_change_gamma(img_t *img, int d)
735{
736 /* d < 0: decrease gamma
737 * d = 0: reset gamma
738 * d > 0: increase gamma
739 */
740 int gamma;
741 double range;
742
743 if (d == 0)
744 gamma = 0;
745 else
746 gamma = MIN(MAX(img->gamma + d, -GAMMA_RANGE), GAMMA_RANGE);
747
748 if (img->gamma != gamma) {
749 imlib_reset_color_modifier();
750 if (gamma != 0) {
751 range = gamma <= 0 ? 1.0 : GAMMA_MAX - 1.0;
752 imlib_modify_color_modifier_gamma(1.0 + gamma * (range / GAMMA_RANGE));
753 }
754 img->gamma = gamma;
755 img->dirty = true;
756 return true;
757 } else {
758 return false;
759 }
760}
761
762bool img_frame_goto(img_t *img, int n)
763{
764 if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
765 return false;
766
767 img->multi.sel = n;
768 img->im = img->multi.frames[n].im;
769
770 imlib_context_set_image(img->im);
771 img->w = imlib_image_get_width();
772 img->h = imlib_image_get_height();
773 img->checkpan = true;
774 img->dirty = true;
775
776 return true;
777}
778
779bool img_frame_navigate(img_t *img, int d)
780{
781 if (img->multi.cnt == 0 || d == 0)
782 return false;
783
784 d += img->multi.sel;
785 if (d < 0)
786 d = 0;
787 else if (d >= img->multi.cnt)
788 d = img->multi.cnt - 1;
789
790 return img_frame_goto(img, d);
791}
792
793bool img_frame_animate(img_t *img)
794{
795 if (img->multi.cnt == 0)
796 return false;
797
798 if (img->multi.sel + 1 >= img->multi.cnt)
799 img_frame_goto(img, 0);
800 else
801 img_frame_goto(img, img->multi.sel + 1);
802 img->dirty = true;
803 return true;
804}
805