Krolyxon
Commit 76c2b81

Branches

Refs →

nsxiv76c2b81

add support for multi-frame images via imlib2 (#373)

this will be a massive change compared to the usual stuff. however the
gains will be worth it:

* we gain lots of additional animated image support.
* and we'll gain _even_ more format support as imlib2 adds them, without needing
  any change in our code-base.
* about ~300 LoC will be purged once we remove our internal gif and webp loader.

as for when to remove the internal loaders, a good time might be when debian
upgrades their imlib2, currently it seems to be at v1.7.5, which doesn't support
animated images.

as of now, nsxiv will continue to build with the internal gif/webp loaders
(assuming they were enabled in config.mk) if imlib2 version is below 1.8.0 and
will print out a deprecation notice.

and if imlib2 version supports multi-frame then it will simply ignore the
internal loaders and use the imlib2 one.

in other words, users shouldn't need to do anything on their side. everything
that previously functioned will continue to function regardless of the user's
imlib2 version (though they might see the annoying deprecation notice if the
imlib2 version doesn't support multi-frame images).

known issue:

* image loading performance can be noticeably worse in
  imlib2 versions below 1.9.0
Author
NRK
Date
2023-01-08 10:02 +0000
Parent
95bc9b4
Commit
76c2b81b6077702d3bbd9726b698cd8b6547fc5e

+176 −17

3 files changed

  1. M config.mk +4 −1
  2. M etc/woodpecker/clang-tidy-checks +1 −1
  3. M image.c +171 −15
M config.mk View
@@ -14,8 +14,11 @@ HAVE_INOTIFY = $(OPT_DEP_DEFAULT)
1414
1515 # optional dependencies, see README for more info
1616 HAVE_LIBFONTS = $(OPT_DEP_DEFAULT)
17−HAVE_LIBGIF = $(OPT_DEP_DEFAULT)
1817 HAVE_LIBEXIF = $(OPT_DEP_DEFAULT)
18+
19+# unused if imlib2 version is 1.8.0 or higher.
20+# these options will be removed eventually.
21+HAVE_LIBGIF = $(OPT_DEP_DEFAULT)
1922 HAVE_LIBWEBP = $(OPT_DEP_DEFAULT)
2023
2124 # Compiler and linker
M etc/woodpecker/clang-tidy-checks View
@@ -1,6 +1,6 @@
11 # checks
22 clang-analyzer-*,clang-diagnostic-*,bugprone-*,performance-*,modernize-*
3−misc-*,android-cloexec-*,cert-*,llvm-include-order
3+misc-*,android-cloexec-*,llvm-include-order
44 -readability-*,readability-duplicate-include,readability-misleading-indentation
55
66 # silence
M image.c View
@@ -33,12 +33,21 @@
3333 #include <libexif/exif-data.h>
3434 #endif
3535
36−#if HAVE_LIBGIF
36+#ifdef IMLIB2_VERSION
37+ #if IMLIB2_VERSION >= IMLIB2_VERSION_(1, 8, 0)
38+ #define HAVE_IMLIB2_MULTI_FRAME 1
39+ #endif
40+#endif
41+#ifndef HAVE_IMLIB2_MULTI_FRAME
42+ #define HAVE_IMLIB2_MULTI_FRAME 0
43+#endif
44+
45+#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME
3746 #include <gif_lib.h>
3847 enum { DEF_GIF_DELAY = 75 };
3948 #endif
4049
41−#if HAVE_LIBWEBP
50+#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME
4251 #include <stdio.h>
4352 #include <webp/decode.h>
4453 #include <webp/demux.h>
@@ -139,7 +148,7 @@ void exif_auto_orientate(const fileinfo_t *file)
139148 }
140149 #endif
141150
142−#if HAVE_LIBGIF || HAVE_LIBWEBP
151+#if HAVE_LIBGIF || HAVE_LIBWEBP || HAVE_IMLIB2_MULTI_FRAME
143152 static void img_multiframe_context_set(img_t *img)
144153 {
145154 if (img->multi.cnt > 1) {
@@ -156,7 +165,25 @@ static void img_multiframe_context_set(img_t *img)
156165 }
157166 #endif
158167
159−#if HAVE_LIBGIF
168+#if (HAVE_LIBGIF || HAVE_LIBWEBP) && !HAVE_IMLIB2_MULTI_FRAME
169+static void img_multiframe_deprecation_notice(void)
170+{
171+ static bool warned;
172+ if (!warned) {
173+ error(0, 0, "\n"
174+ "################################################################\n"
175+ "# DEPRECATION NOTICE #\n"
176+ "################################################################\n"
177+ "# Internal multi-frame gif and webp loaders are deprecated and #\n"
178+ "# will be removed soon. Please upgrade to Imlib2 v1.8.0 for #\n"
179+ "# multi-frame/animated image support. #\n"
180+ "################################################################");
181+ warned = true;
182+ }
183+}
184+#endif
185+
186+#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME
160187 static bool img_load_gif(img_t *img, const fileinfo_t *file)
161188 {
162189 GifFileType *gif;
@@ -177,6 +204,8 @@ static bool img_load_gif(img_t *img, const fileinfo_t *file)
177204 bool err = false;
178205 multi_img_t *m = &img->multi;
179206
207+ img_multiframe_deprecation_notice();
208+
180209 #if defined(GIFLIB_MAJOR) && GIFLIB_MAJOR >= 5
181210 gif = DGifOpenFileName(file->path, NULL);
182211 #else
@@ -321,8 +350,7 @@ static bool img_load_gif(img_t *img, const fileinfo_t *file)
321350 }
322351 #endif /* HAVE_LIBGIF */
323352
324−
325−#if HAVE_LIBWEBP
353+#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME
326354 static bool img_load_webp(img_t *img, const fileinfo_t *file)
327355 {
328356 FILE *webp_file;
@@ -340,6 +368,8 @@ static bool img_load_webp(img_t *img, const fileinfo_t *file)
340368 bool err = false;
341369 multi_img_t *m = &img->multi;
342370
371+ img_multiframe_deprecation_notice();
372+
343373 if ((webp_file = fopen(file->path, "rb")) == NULL) {
344374 error(0, errno, "%s: Error opening webp image", file->name);
345375 return false;
@@ -408,6 +438,118 @@ fail:
408438 }
409439 #endif /* HAVE_LIBWEBP */
410440
441+#if HAVE_IMLIB2_MULTI_FRAME
442+static void img_area_clear(int x, int y, int w, int h)
443+{
444+ assert(x >= 0 && y >= 0);
445+ assert(w > 0 && h > 0);
446+ imlib_image_set_has_alpha(1);
447+ imlib_context_set_blend(0);
448+ imlib_context_set_color(0, 0, 0, 0);
449+ imlib_image_fill_rectangle(x, y, w, h);
450+}
451+
452+static bool img_load_multiframe(img_t *img, const fileinfo_t *file)
453+{
454+ unsigned int n, fcnt;
455+ Imlib_Image blank;
456+ Imlib_Frame_Info finfo;
457+ int px, py, pw, ph, pflag;
458+ multi_img_t *m = &img->multi;
459+
460+ imlib_context_set_image(img->im);
461+ imlib_image_get_frame_info(&finfo);
462+ if ((fcnt = finfo.frame_count) <= 1 || !(finfo.frame_flags & IMLIB_IMAGE_ANIMATED))
463+ return false;
464+ img->w = finfo.canvas_w;
465+ img->h = finfo.canvas_h;
466+
467+ if (fcnt > m->cap) {
468+ m->cap = fcnt;
469+ m->frames = erealloc(m->frames, m->cap * sizeof(*m->frames));
470+ }
471+
472+ imlib_context_set_dither(0);
473+ imlib_context_set_anti_alias(0);
474+ imlib_context_set_color_modifier(NULL);
475+ imlib_context_set_operation(IMLIB_OP_COPY);
476+
477+ if ((blank = imlib_create_image(img->w, img->h)) == NULL) {
478+ error(0, 0, "%s: couldn't create image", file->name);
479+ return false;
480+ }
481+ imlib_context_set_image(blank);
482+ img_area_clear(0, 0, img->w, img->h);
483+
484+ /*
485+ * Imlib2 gives back a "raw frame", we need to blend it on top of the
486+ * previous frame ourselves if necessary to get the fully decoded frame.
487+ */
488+ pflag = m->length = m->cnt = m->sel = 0;
489+ px = py = pw = ph = 0;
490+ for (n = 1; n <= fcnt; ++n) {
491+ Imlib_Image frame, canvas;
492+ int sx, sy, sw, sh;
493+ bool has_alpha;
494+
495+ imlib_context_set_image(m->cnt < 1 ? blank : m->frames[m->cnt - 1].im);
496+ if ((canvas = imlib_clone_image()) == NULL ||
497+ (frame = imlib_load_image_frame(file->path, n)) == NULL)
498+ {
499+ if (canvas != NULL) {
500+ imlib_context_set_image(canvas);
501+ imlib_free_image();
502+ }
503+ error(0, 0, "%s: failed to load frame %d", file->name, n);
504+ break;
505+ }
506+
507+ imlib_context_set_image(frame);
508+ imlib_image_get_frame_info(&finfo);
509+ assert(finfo.frame_count == (int)fcnt);
510+ assert(finfo.canvas_w == img->w && finfo.canvas_h == img->h);
511+ sx = finfo.frame_x;
512+ sy = finfo.frame_y;
513+ sw = finfo.frame_w;
514+ sh = finfo.frame_h;
515+ has_alpha = imlib_image_has_alpha();
516+
517+ imlib_context_set_image(canvas);
518+ /* the dispose flags are explained in Imlib2's header */
519+ if (pflag & IMLIB_FRAME_DISPOSE_CLEAR) {
520+ img_area_clear(px, py, pw, ph);
521+ } else if (pflag & IMLIB_FRAME_DISPOSE_PREV) {
522+ Imlib_Image p = m->cnt < 2 ? blank : m->frames[m->cnt - 2].im;
523+ assert(m->cnt > 0);
524+ img_area_clear(0, 0, img->w, img->h);
525+ imlib_blend_image_onto_image(p, 1, px, py, pw, ph, px, py, pw, ph);
526+ }
527+ pflag = finfo.frame_flags;
528+ if (pflag & (IMLIB_FRAME_DISPOSE_CLEAR | IMLIB_FRAME_DISPOSE_PREV)) {
529+ /* remember these so we can "dispose" them before blending next frame */
530+ px = sx;
531+ py = sy;
532+ pw = sw;
533+ ph = sh;
534+ }
535+ assert(imlib_context_get_operation() == IMLIB_OP_COPY);
536+ imlib_image_set_has_alpha(has_alpha);
537+ imlib_context_set_blend(!!(finfo.frame_flags & IMLIB_FRAME_BLEND));
538+ imlib_blend_image_onto_image(frame, has_alpha, 0, 0, sw, sh, sx, sy, sw, sh);
539+ m->frames[m->cnt].im = canvas;
540+ m->frames[m->cnt].delay = finfo.frame_delay;
541+ m->length += m->frames[m->cnt].delay;
542+ m->cnt++;
543+ imlib_context_set_image(frame);
544+ imlib_free_image();
545+ }
546+ imlib_context_set_image(blank);
547+ imlib_free_image();
548+ img_multiframe_context_set(img);
549+ return m->cnt > 0;
550+}
551+#endif /* HAVE_IMLIB2_MULTI_FRAME */
552+
411553 Imlib_Image img_open(const fileinfo_t *file)
412554 {
413555 struct stat st;
@@ -415,7 +557,11 @@ Imlib_Image img_open(const fileinfo_t *file)
415557
416558 if (access(file->path, R_OK) == 0 &&
417559 stat(file->path, &st) == 0 && S_ISREG(st.st_mode) &&
560+#if HAVE_IMLIB2_MULTI_FRAME
561+ (im = imlib_load_image_frame(file->path, 1)) != NULL)
562+#else
418563 (im = imlib_load_image_immediately(file->path)) != NULL)
564+#endif
419565 {
420566 imlib_context_set_image(im);
421567 }
@@ -427,6 +573,7 @@ Imlib_Image img_open(const fileinfo_t *file)
427573 bool img_load(img_t *img, const fileinfo_t *file)
428574 {
429575 const char *fmt;
576+ bool animated = false;
430577
431578 if ((img->im = img_open(file)) == NULL)
432579 return false;
@@ -441,12 +588,16 @@ bool img_load(img_t *img, const fileinfo_t *file)
441588 exif_auto_orientate(file);
442589 #endif
443590
591+#if HAVE_IMLIB2_MULTI_FRAME
592+ animated = img_load_multiframe(img, file);
593+#endif
594+
444595 if ((fmt = imlib_image_format()) != NULL) { /* NOLINT: fmt might be unused, not worth fixing */
445−#if HAVE_LIBGIF
596+#if HAVE_LIBGIF && !HAVE_IMLIB2_MULTI_FRAME
446597 if (STREQ(fmt, "gif"))
447598 img_load_gif(img, file);
448599 #endif
449−#if HAVE_LIBWEBP
600+#if HAVE_LIBWEBP && !HAVE_IMLIB2_MULTI_FRAME
450601 if (STREQ(fmt, "webp"))
451602 img_load_webp(img, file);
452603 #endif
@@ -455,8 +606,13 @@ bool img_load(img_t *img, const fileinfo_t *file)
455606 exif_auto_orientate(file);
456607 #endif
457608 }
458− img->w = imlib_image_get_width();
459− img->h = imlib_image_get_height();
609+ /* for animated images, we want the _canvas_ width/height, which
610+ * img_load_multiframe() sets already.
611+ */
612+ if (!animated) {
613+ img->w = imlib_image_get_width();
614+ img->h = imlib_image_get_height();
615+ }
460616 img->checkpan = true;
461617 img->dirty = true;
462618
@@ -466,20 +622,18 @@ bool img_load(img_t *img, const fileinfo_t *file)
466622 CLEANUP void img_close(img_t *img, bool decache)
467623 {
468624 unsigned int i;
625+ void (*free_img)(void) = decache ? imlib_free_image_and_decache : imlib_free_image;
469626
470627 if (img->multi.cnt > 0) {
471628 for (i = 0; i < img->multi.cnt; i++) {
472629 imlib_context_set_image(img->multi.frames[i].im);
473− imlib_free_image();
630+ free_img();
474631 }
475632 img->multi.cnt = 0;
476633 img->im = NULL;
477634 } else if (img->im != NULL) {
478635 imlib_context_set_image(img->im);
479− if (decache)
480− imlib_free_image_and_decache();
481− else
482− imlib_free_image();
636+ free_img();
483637 img->im = NULL;
484638 }
485639 }
@@ -629,6 +783,8 @@ void img_render(img_t *img)
629783 imlib_context_set_color(c.red >> 8, c.green >> 8, c.blue >> 8, 0xFF);
630784 imlib_image_fill_rectangle(0, 0, dw, dh);
631785 }
786+ imlib_context_set_blend(1);
787+ imlib_context_set_operation(IMLIB_OP_COPY);
632788 imlib_blend_image_onto_image(img->im, 0, sx, sy, sw, sh, 0, 0, dw, dh);
633789 imlib_context_set_color_modifier(NULL);
634790 imlib_render_image_on_drawable(dx, dy);