Krolyxon
Commit 851e428

Branches

Refs →

nsxivimage.c

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