Krolyxon
Commit 4864f04

Branches

Refs →

nsxivimage.c

723 lines · 15.5 KB · C

1/* sxiv: image.c
2 * Copyright (c) 2011 Bert Muennich <muennich at informatik.hu-berlin.de>
3 *
4 * This program is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU General Public License as published by the
6 * Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License along
15 * with this program; if not, write to the Free Software Foundation, Inc.,
16 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17 */
18
19#define _POSIX_C_SOURCE 200112L
20#define _FEATURE_CONFIG
21#define _IMAGE_CONFIG
22
23#include <string.h>
24#include <unistd.h>
25
26#include "image.h"
27#include "options.h"
28#include "util.h"
29#include "config.h"
30
31#if EXIF_SUPPORT
32#include <libexif/exif-data.h>
33#endif
34
35#if GIF_SUPPORT
36#include <stdlib.h>
37#include <sys/types.h>
38#include <gif_lib.h>
39#endif
40
41enum { MIN_GIF_DELAY = 50 };
42
43float zoom_min;
44float zoom_max;
45
46static inline
47bool zoomdiff(float z1, float z2) {
48 const float mindelta = 0.001;
49 return (z1 - z2 > mindelta) || (z1 - z2 < mindelta);
50}
51
52void img_init(img_t *img, win_t *win) {
53 zoom_min = zoom_levels[0] / 100.0;
54 zoom_max = zoom_levels[ARRLEN(zoom_levels) - 1] / 100.0;
55
56 if (img == NULL || win == NULL)
57 return;
58
59 imlib_context_set_display(win->env.dpy);
60 imlib_context_set_visual(win->env.vis);
61 imlib_context_set_colormap(win->env.cmap);
62
63 img->im = NULL;
64 img->win = win;
65 img->zoom = options->zoom;
66 img->zoom = MAX(img->zoom, zoom_min);
67 img->zoom = MIN(img->zoom, zoom_max);
68 img->checkpan = false;
69 img->dirty = false;
70 img->aa = options->aa;
71 img->alpha = true;
72 img->slideshow = false;
73 img->ss_delay = SLIDESHOW_DELAY * 1000;
74 img->multi.cap = img->multi.cnt = 0;
75 img->multi.animate = false;
76}
77
78#if EXIF_SUPPORT
79void exif_auto_orientate(const fileinfo_t *file) {
80 ExifData *ed;
81 ExifEntry *entry;
82 int byte_order, orientation;
83
84 if ((ed = exif_data_new_from_file(file->path)) == NULL)
85 return;
86 entry = exif_content_get_entry(ed->ifd[EXIF_IFD_0], EXIF_TAG_ORIENTATION);
87 if (entry != NULL) {
88 byte_order = exif_data_get_byte_order(ed);
89 orientation = exif_get_short(entry->data, byte_order);
90 }
91 exif_data_unref(ed);
92 if (entry == NULL)
93 return;
94
95 switch (orientation) {
96 case 5:
97 imlib_image_orientate(1);
98 case 2:
99 imlib_image_flip_vertical();
100 break;
101
102 case 3:
103 imlib_image_orientate(2);
104 break;
105
106 case 7:
107 imlib_image_orientate(1);
108 case 4:
109 imlib_image_flip_horizontal();
110 break;
111
112 case 6:
113 imlib_image_orientate(1);
114 break;
115
116 case 8:
117 imlib_image_orientate(3);
118 break;
119 }
120}
121#endif /* EXIF_SUPPORT */
122
123#if GIF_SUPPORT
124/* Originally based on, but in its current form merely inspired by Imlib2's
125 * src/modules/loaders/loader_gif.c:load(), written by Carsten Haitzler.
126 */
127bool img_load_gif(img_t *img, const fileinfo_t *file) {
128 GifFileType *gif;
129 GifRowType *rows = NULL;
130 GifRecordType rec;
131 ColorMapObject *cmap;
132 DATA32 bgpixel, *data, *ptr;
133 DATA32 *prev_frame = NULL;
134 Imlib_Image *im;
135 int i, j, bg, r, g, b;
136 int x, y, w, h, sw, sh;
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 = 0;
150 img->multi.sel = 0;
151
152 gif = DGifOpenFileName(file->path);
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
161 do {
162 if (DGifGetRecordType(gif, &rec) == GIF_ERROR) {
163 err = true;
164 break;
165 }
166 if (rec == EXTENSION_RECORD_TYPE) {
167 int ext_code;
168 GifByteType *ext = NULL;
169
170 DGifGetExtension(gif, &ext_code, &ext);
171 while (ext) {
172 if (ext_code == 0xf9) {
173 if (ext[1] & 1)
174 transp = (int) ext[4];
175 else
176 transp = -1;
177
178 delay = 10 * ((unsigned int) ext[3] << 8 | (unsigned int) ext[2]);
179 if (delay)
180 delay = MAX(delay, MIN_GIF_DELAY);
181
182 disposal = (unsigned int) ext[1] >> 2 & 0x7;
183 }
184 ext = NULL;
185 DGifGetExtensionNext(gif, &ext);
186 }
187 } else if (rec == IMAGE_DESC_RECORD_TYPE) {
188 if (DGifGetImageDesc(gif) == GIF_ERROR) {
189 err = true;
190 break;
191 }
192 x = gif->Image.Left;
193 y = gif->Image.Top;
194 w = gif->Image.Width;
195 h = gif->Image.Height;
196
197 rows = (GifRowType*) s_malloc(h * sizeof(GifRowType));
198 for (i = 0; i < h; i++)
199 rows[i] = (GifRowType) s_malloc(w * sizeof(GifPixelType));
200 if (gif->Image.Interlace) {
201 for (i = 0; i < 4; i++) {
202 for (j = intoffset[i]; j < h; j += intjump[i])
203 DGifGetLine(gif, rows[j], w);
204 }
205 } else {
206 for (i = 0; i < h; i++)
207 DGifGetLine(gif, rows[i], w);
208 }
209
210 ptr = data = (DATA32*) s_malloc(sizeof(DATA32) * sw * sh);
211 cmap = gif->Image.ColorMap ? gif->Image.ColorMap : gif->SColorMap;
212 r = cmap->Colors[bg].Red;
213 g = cmap->Colors[bg].Green;
214 b = cmap->Colors[bg].Blue;
215 bgpixel = 0x00ffffff & (r << 16 | g << 8 | b);
216
217 for (i = 0; i < sh; i++) {
218 for (j = 0; j < sw; j++) {
219 if (i < y || i >= y + h || j < x || j >= x + w ||
220 rows[i-y][j-x] == transp)
221 {
222 if (prev_disposal != 2 && prev_frame != NULL)
223 *ptr = prev_frame[i * sw + j];
224 else
225 *ptr = bgpixel;
226 } else {
227 r = cmap->Colors[rows[i-y][j-x]].Red;
228 g = cmap->Colors[rows[i-y][j-x]].Green;
229 b = cmap->Colors[rows[i-y][j-x]].Blue;
230 *ptr = 0xff << 24 | r << 16 | g << 8 | b;
231 }
232 ptr++;
233 }
234 }
235
236 im = imlib_create_image_using_copied_data(sw, sh, data);
237
238 for (i = 0; i < h; i++)
239 free(rows[i]);
240 free(rows);
241 free(data);
242
243 if (im == NULL) {
244 err = true;
245 break;
246 }
247
248 imlib_context_set_image(im);
249 imlib_image_set_format("gif");
250 if (transp >= 0)
251 imlib_image_set_has_alpha(1);
252
253 if (disposal != 3)
254 prev_frame = imlib_image_get_data_for_reading_only();
255 prev_disposal = disposal;
256
257 if (img->multi.cnt == img->multi.cap) {
258 img->multi.cap *= 2;
259 img->multi.frames = (img_frame_t*)
260 s_realloc(img->multi.frames,
261 img->multi.cap * sizeof(img_frame_t));
262 }
263 img->multi.frames[img->multi.cnt].im = im;
264 img->multi.frames[img->multi.cnt].delay = delay ? delay : GIF_DELAY;
265 img->multi.cnt++;
266 }
267 } while (rec != TERMINATE_RECORD_TYPE);
268
269 DGifCloseFile(gif);
270
271 if (err && !file->loaded)
272 warn("corrupted gif file: %s", file->name);
273
274 if (img->multi.cnt > 1) {
275 imlib_context_set_image(img->im);
276 imlib_free_image();
277 img->im = img->multi.frames[0].im;
278 img->multi.animate = GIF_AUTOPLAY;
279 } else if (img->multi.cnt == 1) {
280 imlib_context_set_image(img->multi.frames[0].im);
281 imlib_free_image();
282 img->multi.cnt = 0;
283 img->multi.animate = false;
284 }
285
286 imlib_context_set_image(img->im);
287
288 return !err;
289}
290#endif /* GIF_SUPPORT */
291
292bool img_load(img_t *img, const fileinfo_t *file) {
293 const char *fmt;
294
295 if (img == NULL || file == NULL || file->name == NULL || file->path == NULL)
296 return false;
297
298 if (access(file->path, R_OK) < 0 ||
299 (img->im = imlib_load_image(file->path)) == NULL)
300 {
301 warn("could not open image: %s", file->name);
302 return false;
303 }
304
305 imlib_context_set_image(img->im);
306 imlib_image_set_changes_on_disk();
307 imlib_context_set_anti_alias(img->aa);
308
309 fmt = imlib_image_format();
310 /* avoid unused-but-set-variable warning */
311 (void) fmt;
312
313#if EXIF_SUPPORT
314 if (streq(fmt, "jpeg"))
315 exif_auto_orientate(file);
316#endif
317#if GIF_SUPPORT
318 if (streq(fmt, "gif"))
319 img_load_gif(img, file);
320#endif
321
322 img->w = imlib_image_get_width();
323 img->h = imlib_image_get_height();
324 img->scalemode = options->scalemode;
325 img->re = false;
326 img->checkpan = false;
327 img->dirty = true;
328
329 return true;
330}
331
332void img_close(img_t *img, bool decache) {
333 int i;
334
335 if (img == NULL)
336 return;
337
338 if (img->multi.cnt > 0) {
339 for (i = 0; i < img->multi.cnt; i++) {
340 imlib_context_set_image(img->multi.frames[i].im);
341 imlib_free_image();
342 }
343 img->multi.cnt = 0;
344 img->im = NULL;
345 } else if (img->im != NULL) {
346 imlib_context_set_image(img->im);
347 if (decache)
348 imlib_free_image_and_decache();
349 else
350 imlib_free_image();
351 img->im = NULL;
352 }
353}
354
355void img_check_pan(img_t *img, bool moved) {
356 win_t *win;
357 int ox, oy;
358
359 if (img == NULL || img->im == NULL || img->win == NULL)
360 return;
361
362 win = img->win;
363 ox = img->x;
364 oy = img->y;
365
366 if (img->w * img->zoom > win->w) {
367 if (img->x > 0 && img->x + img->w * img->zoom > win->w)
368 img->x = 0;
369 if (img->x < 0 && img->x + img->w * img->zoom < win->w)
370 img->x = win->w - img->w * img->zoom;
371 } else {
372 img->x = (win->w - img->w * img->zoom) / 2;
373 }
374 if (img->h * img->zoom > win->h) {
375 if (img->y > 0 && img->y + img->h * img->zoom > win->h)
376 img->y = 0;
377 if (img->y < 0 && img->y + img->h * img->zoom < win->h)
378 img->y = win->h - img->h * img->zoom;
379 } else {
380 img->y = (win->h - img->h * img->zoom) / 2;
381 }
382
383 if (!moved && (ox != img->x || oy != img->y))
384 img->dirty = true;
385}
386
387bool img_fit(img_t *img) {
388 float z, zmax, zw, zh;
389
390 if (img == NULL || img->im == NULL || img->win == NULL)
391 return false;
392 if (img->scalemode == SCALE_ZOOM)
393 return false;
394
395 zmax = img->scalemode == SCALE_DOWN ? 1.0 : zoom_max;
396 zw = (float) img->win->w / (float) img->w;
397 zh = (float) img->win->h / (float) img->h;
398
399 z = MIN(zw, zh);
400 z = MAX(z, zoom_min);
401 z = MIN(z, zmax);
402
403 if (zoomdiff(z, img->zoom)) {
404 img->zoom = z;
405 img->dirty = true;
406 return true;
407 } else {
408 return false;
409 }
410}
411
412void img_render(img_t *img) {
413 win_t *win;
414 int sx, sy, sw, sh;
415 int dx, dy, dw, dh;
416
417 if (img == NULL || img->im == NULL || img->win == NULL)
418 return;
419
420 win = img->win;
421 img_fit(img);
422
423 if (!img->re) {
424 /* rendered for the first time */
425 img->re = true;
426 if (img->zoom * img->w <= win->w)
427 img->x = (win->w - img->w * img->zoom) / 2;
428 else
429 img->x = 0;
430 if (img->zoom * img->h <= win->h)
431 img->y = (win->h - img->h * img->zoom) / 2;
432 else
433 img->y = 0;
434 }
435
436 if (img->checkpan) {
437 img_check_pan(img, false);
438 img->checkpan = false;
439 }
440
441 if (!img->dirty)
442 return;
443
444 /* calculate source and destination offsets */
445 if (img->x < 0) {
446 sx = -img->x / img->zoom;
447 sw = win->w / img->zoom;
448 dx = 0;
449 dw = win->w;
450 } else {
451 sx = 0;
452 sw = img->w;
453 dx = img->x;
454 dw = img->w * img->zoom;
455 }
456 if (img->y < 0) {
457 sy = -img->y / img->zoom;
458 sh = win->h / img->zoom;
459 dy = 0;
460 dh = win->h;
461 } else {
462 sy = 0;
463 sh = img->h;
464 dy = img->y;
465 dh = img->h * img->zoom;
466 }
467
468 win_clear(win);
469
470 imlib_context_set_image(img->im);
471
472 if (!img->alpha && imlib_image_has_alpha())
473 win_draw_rect(win, win->pm, dx, dy, dw, dh, True, 0, win->white);
474
475 imlib_context_set_drawable(win->pm);
476 imlib_render_image_part_on_drawable_at_size(sx, sy, sw, sh, dx, dy, dw, dh);
477
478 win_draw(win);
479
480 img->dirty = false;
481}
482
483bool img_fit_win(img_t *img) {
484 if (img == NULL || img->im == NULL)
485 return false;
486
487 img->scalemode = SCALE_FIT;
488 return img_fit(img);
489}
490
491bool img_center(img_t *img) {
492 int ox, oy;
493
494 if (img == NULL || img->im == NULL || img->win == NULL)
495 return false;
496
497 ox = img->x;
498 oy = img->y;
499
500 img->x = (img->win->w - img->w * img->zoom) / 2;
501 img->y = (img->win->h - img->h * img->zoom) / 2;
502
503 if (ox != img->x || oy != img->y) {
504 img->dirty = true;
505 return true;
506 } else {
507 return false;
508 }
509}
510
511bool img_zoom(img_t *img, float z) {
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)) {
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 int i;
534
535 if (img == NULL || img->im == NULL)
536 return false;
537
538 for (i = 1; i < ARRLEN(zoom_levels); i++) {
539 if (zoom_levels[i] > img->zoom * 100.0)
540 return img_zoom(img, zoom_levels[i] / 100.0);
541 }
542 return false;
543}
544
545bool img_zoom_out(img_t *img) {
546 int i;
547
548 if (img == NULL || img->im == NULL)
549 return false;
550
551 for (i = ARRLEN(zoom_levels) - 2; i >= 0; i--) {
552 if (zoom_levels[i] < img->zoom * 100.0)
553 return img_zoom(img, zoom_levels[i] / 100.0);
554 }
555 return false;
556}
557
558bool img_move(img_t *img, int dx, int dy) {
559 int ox, oy;
560
561 if (img == NULL || img->im == NULL)
562 return false;
563
564 ox = img->x;
565 oy = img->y;
566
567 img->x += dx;
568 img->y += dy;
569
570 img_check_pan(img, true);
571
572 if (ox != img->x || oy != img->y) {
573 img->dirty = true;
574 return true;
575 } else {
576 return false;
577 }
578}
579
580bool img_pan(img_t *img, direction_t dir, bool screen) {
581 if (img == NULL || img->im == NULL || img->win == NULL)
582 return false;
583
584 switch (dir) {
585 case DIR_LEFT:
586 return img_move(img, img->win->w / (screen ? 1 : 5), 0);
587 case DIR_RIGHT:
588 return img_move(img, img->win->w / (screen ? 1 : 5) * -1, 0);
589 case DIR_UP:
590 return img_move(img, 0, img->win->h / (screen ? 1 : 5));
591 case DIR_DOWN:
592 return img_move(img, 0, img->win->h / (screen ? 1 : 5) * -1);
593 }
594 return false;
595}
596
597bool img_pan_edge(img_t *img, direction_t dir) {
598 int ox, oy;
599
600 if (img == NULL || img->im == NULL || img->win == NULL)
601 return false;
602
603 ox = img->x;
604 oy = img->y;
605
606 switch (dir) {
607 case DIR_LEFT:
608 img->x = 0;
609 break;
610 case DIR_RIGHT:
611 img->x = img->win->w - img->w * img->zoom;
612 break;
613 case DIR_UP:
614 img->y = 0;
615 break;
616 case DIR_DOWN:
617 img->y = img->win->h - img->h * img->zoom;
618 break;
619 }
620
621 img_check_pan(img, true);
622
623 if (ox != img->x || oy != img->y) {
624 img->dirty = true;
625 return true;
626 } else {
627 return false;
628 }
629}
630
631void img_rotate(img_t *img, int d) {
632 win_t *win;
633 int ox, oy, tmp;
634
635 if (img == NULL || img->im == NULL || img->win == NULL)
636 return;
637
638 win = img->win;
639 ox = d == 1 ? img->x : win->w - img->x - img->w * img->zoom;
640 oy = d == 3 ? img->y : win->h - img->y - img->h * img->zoom;
641
642 imlib_context_set_image(img->im);
643 imlib_image_orientate(d);
644
645 img->x = oy + (win->w - win->h) / 2;
646 img->y = ox + (win->h - win->w) / 2;
647
648 tmp = img->w;
649 img->w = img->h;
650 img->h = tmp;
651
652 img->checkpan = true;
653 img->dirty = true;
654}
655
656void img_rotate_left(img_t *img) {
657 img_rotate(img, 3);
658}
659
660void img_rotate_right(img_t *img) {
661 img_rotate(img, 1);
662}
663
664void img_toggle_antialias(img_t *img) {
665 if (img == NULL || img->im == NULL)
666 return;
667
668 img->aa = !img->aa;
669 imlib_context_set_image(img->im);
670 imlib_context_set_anti_alias(img->aa);
671 img->dirty = true;
672}
673
674bool img_frame_goto(img_t *img, int n) {
675 if (img == NULL || img->im == NULL)
676 return false;
677 if (n < 0 || n >= img->multi.cnt || n == img->multi.sel)
678 return false;
679
680 img->multi.sel = n;
681 img->im = img->multi.frames[n].im;
682
683 imlib_context_set_image(img->im);
684 img->w = imlib_image_get_width();
685 img->h = imlib_image_get_height();
686 img->checkpan = true;
687 img->dirty = true;
688
689 return true;
690}
691
692bool img_frame_navigate(img_t *img, int d) {
693 if (img == NULL|| img->im == NULL || img->multi.cnt == 0 || d == 0)
694 return false;
695
696 d += img->multi.sel;
697 if (d < 0)
698 d = 0;
699 else if (d >= img->multi.cnt)
700 d = img->multi.cnt - 1;
701
702 return img_frame_goto(img, d);
703}
704
705bool img_frame_animate(img_t *img, bool restart) {
706 if (img == NULL || img->im == NULL || img->multi.cnt == 0)
707 return false;
708
709 if (img->multi.sel + 1 >= img->multi.cnt) {
710 if (restart || (GIF_LOOP && !img->slideshow)) {
711 img_frame_goto(img, 0);
712 } else {
713 img->multi.animate = false;
714 return false;
715 }
716 } else if (!restart) {
717 img_frame_goto(img, img->multi.sel + 1);
718 }
719 img->multi.animate = true;
720 img->dirty = true;
721
722 return true;
723}