Krolyxon
Commit 0353e6e

Branches

Refs →

nsxivimage.c

791 lines · 16.1 KB · C

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