From af98249b6886defcd9d456bd5a23082f9ec7f75f Mon Sep 17 00:00:00 2001
From: Sam Whitehead <sam.everythingcomputers@gmail.com>
Date: Fri, 24 Sep 2021 05:20:55 -0600
Subject: Add animated webp support (#20)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: NRK <nrk@disroot.org>
Co-authored-by: Stein Gunnar Bakkeby <bakkeby@gmail.com>
Co-authored-by: Berke Kocaoğlu <berke.kocaoglu@metu.edu.tr>
---
 Makefile  |   8 +++-
 README.md |   1 +
 image.c   | 152 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 159 insertions(+), 2 deletions(-)

diff --git a/Makefile b/Makefile
index 3b4ba0a..1bae33b 100644
--- a/Makefile
+++ b/Makefile
@@ -30,9 +30,15 @@ ifeq ($(HAVE_LIBGIF), 1)
 else
 	HAVE_LIBGIF = 0
 endif
+ifeq ($(HAVE_LIBWEBP), 1)
+	OPTIONAL_LIBS += -lwebpdemux -lwebp
+else
+	HAVE_LIBWEBP = 0
+endif
 
 CPPFLAGS = -D_XOPEN_SOURCE=700 \
   -DHAVE_LIBGIF=$(HAVE_LIBGIF) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \
+  -DHAVE_LIBWEBP=$(HAVE_LIBWEBP) \
   -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2
 
 LDLIBS = -lImlib2 -lX11 -lXft -lfontconfig $(OPTIONAL_LIBS)
@@ -61,7 +67,7 @@ window.o: icon/data.h
 config.mk:
 	@echo "GEN $@"
 	@echo "# 0 = disable, 1 = enable" > config.mk
-	@for lib in exif gif; do \
+	@for lib in exif gif webp; do \
 		if echo "int main(){}" | $(CC) "-l$$lib" -o /dev/null -x c - 2>/dev/null ; then \
 			echo "HAVE_LIB$$lib=1" | tr '[:lower:]' '[:upper:]' >> config.mk ; \
 		fi \
diff --git a/README.md b/README.md
index 1d90110..ff5bb4c 100644
--- a/README.md
+++ b/README.md
@@ -53,6 +53,7 @@ The following libraries are optional. They are automatically enabled if installe
 
   * giflib : Used for animated gif playback.
   * libexif : Used for auto-orientation and exif thumbnails.
+  * libwebp : Used for animated webp playback.
 
 Please make sure to install the corresponding development packages in case that
 you want to build nsxiv on a distribution with separate runtime and development
diff --git a/image.c b/image.c
index 7689c75..5e778d4 100644
--- a/image.c
+++ b/image.c
@@ -36,6 +36,12 @@
 enum { DEF_GIF_DELAY = 75 };
 #endif
 
+#if HAVE_LIBWEBP
+#include <webp/decode.h>
+#include <webp/demux.h>
+enum { DEF_WEBP_DELAY = 75 };
+#endif
+
 float zoom_min;
 float zoom_max;
 
@@ -297,6 +303,135 @@ bool img_load_gif(img_t *img, const fileinfo_t *file)
 }
 #endif /* HAVE_LIBGIF */
 
+
+#if HAVE_LIBWEBP
+bool is_webp(const char *path)
+{
+	/* The size (in bytes) of the largest amount of data required to verify a WebP image. */
+	enum { max = 30 };
+	const unsigned char fmt[max];
+	bool ret = false;
+	FILE *f;
+
+	if ((f = fopen(path, "rb")) != NULL) {
+		if (fread((unsigned char *) fmt, 1, max, f) == max)
+			ret = WebPGetInfo(fmt, max, NULL, NULL);
+		fclose(f);
+	}
+	return ret;
+}
+
+/* fframe   img
+ * NULL     NULL  = do nothing
+ * x        NULL  = load the first frame as an Imlib_Image
+ * NULL     x     = load all frames into img->multi.
+ */
+bool img_load_webp(const fileinfo_t *file, Imlib_Image *fframe, img_t *img)
+{
+	FILE *webp_file;
+	WebPData data;
+	data.bytes = NULL;
+
+	Imlib_Image im = NULL;
+	struct WebPAnimDecoderOptions opts;
+	WebPAnimDecoder *dec = NULL;
+	struct WebPAnimInfo info;
+	unsigned char *buf = NULL;
+	int ts;
+	const WebPDemuxer *demux;
+	WebPIterator iter;
+	unsigned long flags;
+	unsigned int delay;
+	bool err = false;
+
+	if ((err = fframe == NULL && img == NULL))
+		goto fail;
+
+	if ((err = (webp_file = fopen(file->path, "rb")) == NULL)) {
+		error(0, 0, "%s: Error opening webp image", file->name);
+		goto fail;
+	}
+	fseek(webp_file, 0L, SEEK_END);
+	data.size = ftell(webp_file);
+	rewind(webp_file);
+	data.bytes = emalloc(data.size);
+	if ((err = fread((unsigned char *)data.bytes, 1, data.size, webp_file) != data.size)) {
+		error(0, 0, "%s: Error reading webp image", file->name);
+		goto fail;
+	}
+
+	/* Setup the WebP Animation Decoder */
+	if ((err = !WebPAnimDecoderOptionsInit(&opts))) {
+		error(0, 0, "%s: WebP library version mismatch", file->name);
+		goto fail;
+	}
+	opts.color_mode = MODE_BGRA;
+	/* NOTE: Multi-threaded decoding may cause problems on some system */
+	opts.use_threads = true;
+	dec = WebPAnimDecoderNew(&data, &opts);
+	if ((err = (dec == NULL) || !WebPAnimDecoderGetInfo(dec, &info))) {
+		error(0, 0, "%s: WebP parsing or memory error (file is corrupt?)", file->name);
+		goto fail;
+	}
+	demux = WebPAnimDecoderGetDemuxer(dec);
+
+	if (img == NULL) { /* Only get the first frame and put it into fframe. */
+		if ((err = !WebPAnimDecoderGetNext(dec, &buf, &ts))) {
+			error(0, 0, "%s: Error loading first frame", file->name);
+			goto fail;
+		}
+		*fframe = imlib_create_image_using_copied_data(
+		          info.canvas_width, info.canvas_height, (DATA32*)buf);
+		imlib_context_set_image(*fframe);
+		imlib_image_set_has_alpha(WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS) & ALPHA_FLAG);
+	} else {
+		/* Get global information for the image */
+		flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS);
+		img->w = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH);
+		img->h = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT);
+		img->multi.cap = info.frame_count;
+		img->multi.sel = 0;
+		img->multi.frames = emalloc(info.frame_count * sizeof(img_frame_t));
+
+		/* Load and decode frames (also works on images with only 1 frame) */
+		img->multi.cnt = 0;
+		while (WebPAnimDecoderGetNext(dec, &buf, &ts)) {
+			im = imlib_create_image_using_copied_data(
+			     info.canvas_width, info.canvas_height, (DATA32*)buf);
+			imlib_context_set_image(im);
+			imlib_image_set_format("webp");
+			/* Get an iterator of this frame - used for frame info (duration, etc.) */
+			WebPDemuxGetFrame(demux, img->multi.cnt+1, &iter);
+			imlib_image_set_has_alpha((flags & ALPHA_FLAG) == ALPHA_FLAG);
+			/* Store info for this frame */
+			img->multi.frames[img->multi.cnt].im = im;
+			delay = iter.duration > 0 ? iter.duration : DEF_WEBP_DELAY;
+			img->multi.frames[img->multi.cnt].delay = delay;
+			img->multi.length += img->multi.frames[img->multi.cnt].delay;
+			img->multi.cnt++;
+		}
+		WebPDemuxReleaseIterator(&iter);
+
+		if (img->multi.cnt > 1) {
+			imlib_context_set_image(img->im);
+			imlib_free_image();
+			img->im = img->multi.frames[0].im;
+		} else if (img->multi.cnt == 1) {
+			imlib_context_set_image(img->multi.frames[0].im);
+			imlib_free_image();
+			img->multi.cnt = 0;
+		}
+		imlib_context_set_image(img->im);
+	}
+	imlib_image_set_format("webp");
+fail:
+	if (dec != NULL)
+		WebPAnimDecoderDelete(dec);
+	free((unsigned char *)data.bytes);
+	return !err;
+}
+#endif /* HAVE_LIBWEBP */
+
 Imlib_Image img_open(const fileinfo_t *file)
 {
 	struct stat st;
@@ -305,10 +440,21 @@ Imlib_Image img_open(const fileinfo_t *file)
 	if (access(file->path, R_OK) == 0 &&
 	    stat(file->path, &st) == 0 && S_ISREG(st.st_mode))
 	{
-		im = imlib_load_image(file->path);
+#if HAVE_LIBWEBP
+		if (is_webp(file->path))
+			img_load_webp(file, &im, NULL);
+		else
+#endif
+			im = imlib_load_image(file->path);
 		if (im != NULL) {
 			imlib_context_set_image(im);
+#if HAVE_LIBWEBP
+			const char *fmt;
+			if ((fmt = imlib_image_format()) != NULL && !STREQ(fmt, "webp") &&
+			    imlib_image_get_data_for_reading_only() == NULL) {
+#else
 			if (imlib_image_get_data_for_reading_only() == NULL) {
+#endif
 				imlib_free_image();
 				im = NULL;
 			}
@@ -336,6 +482,10 @@ bool img_load(img_t *img, const fileinfo_t *file)
 #if HAVE_LIBGIF
 		if (STREQ(fmt, "gif"))
 			img_load_gif(img, file);
+#endif
+#if HAVE_LIBWEBP
+		if (STREQ(fmt, "webp"))
+			img_load_webp(file, NULL, img);
 #endif
 	}
 	img->w = imlib_image_get_width();
-- 
cgit v1.2.3