Krolyxon
Commit 9574150

Branches

Refs →

nsxivimage.c

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