Krolyxon
Commit 4780ec5

Branches

Refs →

nsxivimage.c

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