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