Krolyxon
Commit 7d878bd

Branches

Refs →

nsxivimage.c

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