From a6deca0ad390d29ada6d72584a1be835a3913139 Mon Sep 17 00:00:00 2001
From: Harry Jeffery <harry@exec64.co.uk>
Date: Mon, 2 Sep 2019 22:09:14 +0100
Subject: libnsgif: Add libnsgif backend

---
 .builds/archlinux.yml  |   1 +
 .builds/debian.yml     |   2 +
 .builds/fedora.yml     |   2 +
 .builds/freebsd.yml    |   1 +
 .builds/ubuntu.yml     |   2 +
 Makefile               |   6 ++
 README.md              |   1 +
 config.mk              |   8 +-
 src/backend_libnsgif.c | 210 +++++++++++++++++++++++++++++++++++++++++++++++++
 src/main.c             |   5 ++
 10 files changed, 237 insertions(+), 1 deletion(-)
 create mode 100644 src/backend_libnsgif.c

diff --git a/.builds/archlinux.yml b/.builds/archlinux.yml
index c60bbf7..6186aba 100644
--- a/.builds/archlinux.yml
+++ b/.builds/archlinux.yml
@@ -5,6 +5,7 @@ packages:
   - freeimage
   - icu
   - libjpeg-turbo
+  - libnsgif
   - libpng
   - librsvg
   - libtiff
diff --git a/.builds/debian.yml b/.builds/debian.yml
index 81e2959..aed4546 100644
--- a/.builds/debian.yml
+++ b/.builds/debian.yml
@@ -23,6 +23,8 @@ tasks:
   - configure: |
       cd imv
       sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk
+      # libnsgif isn't packaged by debian
+      sed -i -e 's/BACKEND_LIBNSGIF=yes/BACKEND_LIBNSGIF=no/' config.mk
       cat config.mk
   - gcc: |
       cd imv
diff --git a/.builds/fedora.yml b/.builds/fedora.yml
index e641dde..0d07d5e 100644
--- a/.builds/fedora.yml
+++ b/.builds/fedora.yml
@@ -23,6 +23,8 @@ tasks:
   - configure: |
       cd imv
       sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk
+      # libnsgif isn't packaged by fedora
+      sed -i -e 's/BACKEND_LIBNSGIF=yes/BACKEND_LIBNSGIF=no/' config.mk
       cat config.mk
   - gcc: |
       cd imv
diff --git a/.builds/freebsd.yml b/.builds/freebsd.yml
index 98b62e7..191ee87 100644
--- a/.builds/freebsd.yml
+++ b/.builds/freebsd.yml
@@ -6,6 +6,7 @@ packages:
   - graphics/freeimage
   - graphics/libGLU
   - graphics/libjpeg-turbo
+  - graphics/libnsgif
   - graphics/librsvg2
   - graphics/png
   - graphics/tiff
diff --git a/.builds/ubuntu.yml b/.builds/ubuntu.yml
index 0477de9..be2b734 100644
--- a/.builds/ubuntu.yml
+++ b/.builds/ubuntu.yml
@@ -23,6 +23,8 @@ tasks:
   - configure: |
       cd imv
       sed -i -e 's/BACKEND_\(.*\)=no/BACKEND_\1=yes/' config.mk
+      # libnsgif isn't packaged by ubuntu
+      sed -i -e 's/BACKEND_LIBNSGIF=yes/BACKEND_LIBNSGIF=no/' config.mk
       cat config.mk
   - gcc: |
       cd imv
diff --git a/Makefile b/Makefile
index 2f0ff1d..79fd23a 100644
--- a/Makefile
+++ b/Makefile
@@ -89,6 +89,12 @@ ifeq ($(BACKEND_LIBRSVG),yes)
 	override LIBS += $(shell pkg-config --libs librsvg-2.0)
 endif
 
+ifeq ($(BACKEND_LIBNSGIF),yes)
+	SOURCES += src/backend_libnsgif.c
+	override CPPFLAGS += -DIMV_BACKEND_LIBNSGIF $(shell pkg-config --cflags libnsgif)
+	override LIBS += $(shell pkg-config --libs libnsgif)
+endif
+
 
 TEST_SOURCES := test/list.c test/navigator.c
 
diff --git a/README.md b/README.md
index 2410117..dacabcb 100644
--- a/README.md
+++ b/README.md
@@ -133,6 +133,7 @@ Installation
 | libpng         |          | Optional. Provides PNG support.                |
 | libjpeg-turbo  |          | Optional. Provides JPEG support.               |
 | librsvg        | >=v2.44  | Optional. Provides SVG support.                |
+| libnsgif       |          | Optional. Provides GIF support.                |
 
 Dependencies are determined by which backends and window systems are enabled
 when building `imv`. You can find a summary of which backends are available and
diff --git a/config.mk b/config.mk
index 2889b63..a9c5941 100644
--- a/config.mk
+++ b/config.mk
@@ -16,7 +16,7 @@ BACKEND_FREEIMAGE=yes
 
 # libtiff
 # provides: tiff
-# dependws: libjpeg  zlib  xz  zstd
+# depends: libjpeg  zlib  xz  zstd
 # license: MIT
 BACKEND_LIBTIFF=no
 
@@ -37,3 +37,9 @@ BACKEND_LIBJPEG=no
 # depends: gdk-pixbuf2 pango libcroco
 # license: LGPL
 BACKEND_LIBRSVG=yes
+
+# libnsgif https://www.netsurf-browser.org/projects/libnsgif/
+# provides: animated gif
+# depends: none
+# license: MIT
+BACKEND_LIBNSGIF=no
diff --git a/src/backend_libnsgif.c b/src/backend_libnsgif.c
new file mode 100644
index 0000000..f5fcd3a
--- /dev/null
+++ b/src/backend_libnsgif.c
@@ -0,0 +1,210 @@
+#include "backend.h"
+#include "bitmap.h"
+#include "image.h"
+#include "log.h"
+#include "source.h"
+#include "source_private.h"
+
+#include <fcntl.h>
+#include <libnsgif.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+struct private {
+  int current_frame;
+  gif_animation gif;
+  void *data;
+  size_t len;
+};
+
+static void* bitmap_create(int width, int height)
+{
+  const size_t bytes_per_pixel = 4;
+  return calloc(width * height, bytes_per_pixel);
+}
+
+static void bitmap_destroy(void *bitmap)
+{
+  free(bitmap);
+}
+
+static unsigned char* bitmap_get_buffer(void *bitmap)
+{
+  return bitmap;
+}
+
+static void bitmap_set_opaque(void *bitmap, bool opaque)
+{
+  (void)bitmap;
+  (void)opaque;
+}
+
+static bool bitmap_test_opaque(void *bitmap)
+{
+  (void)bitmap;
+  return false;
+}
+
+static void bitmap_mark_modified(void *bitmap)
+{
+  (void)bitmap;
+}
+
+static gif_bitmap_callback_vt bitmap_callbacks = {
+  bitmap_create,
+  bitmap_destroy,
+  bitmap_get_buffer,
+  bitmap_set_opaque,
+  bitmap_test_opaque,
+  bitmap_mark_modified
+};
+
+
+static void free_private(void *raw_private)
+{
+  if (!raw_private) {
+    return;
+  }
+
+  struct private *private = raw_private;
+  gif_finalise(&private->gif);
+  munmap(private->data, private->len);
+  free(private);
+}
+
+static void push_current_image(struct private *private,
+    struct imv_image **image, int *frametime)
+{
+  struct imv_bitmap *bmp = malloc(sizeof *bmp);
+  bmp->width = private->gif.width;
+  bmp->height = private->gif.height;
+  bmp->format = IMV_ABGR;
+  size_t len = 4 * bmp->width * bmp->height;
+  bmp->data = malloc(len);
+  memcpy(bmp->data, private->gif.frame_image, len);
+
+  *image = imv_image_create_from_bitmap(bmp);
+  *frametime = private->gif.frames[private->current_frame].frame_delay * 10.0;
+}
+
+static void first_frame(void *raw_private, struct imv_image **image, int *frametime)
+{
+  *image = NULL;
+  *frametime = 0;
+
+  struct private *private = raw_private;
+  private->current_frame = 0;
+
+  gif_result code = gif_decode_frame(&private->gif, private->current_frame);
+  if (code != GIF_OK) {
+    imv_log(IMV_DEBUG, "libnsgif: failed to decode first frame\n");
+    return;
+  }
+
+  push_current_image(private, image, frametime);
+}
+
+static void next_frame(void *raw_private, struct imv_image **image, int *frametime)
+{
+  *image = NULL;
+  *frametime = 0;
+
+  struct private *private = raw_private;
+
+  private->current_frame++;
+  private->current_frame %= private->gif.frame_count;
+
+  gif_result code = gif_decode_frame(&private->gif, private->current_frame);
+  if (code != GIF_OK) {
+    imv_log(IMV_DEBUG, "libnsgif: failed to decode first frame\n");
+    return;
+  }
+
+  push_current_image(private, image, frametime);
+}
+
+static const struct imv_source_vtable vtable = {
+  .load_first_frame = first_frame,
+  .load_next_frame = next_frame,
+  .free = free_private
+};
+
+static enum backend_result open_memory(void *data, size_t len, struct imv_source **src)
+{
+  struct private *private = calloc(1, sizeof *private);
+  gif_create(&private->gif, &bitmap_callbacks);
+
+  gif_result code;
+  do {
+    code = gif_initialise(&private->gif, len, data);
+  } while (code == GIF_WORKING);
+
+  if (code != GIF_OK) {
+    gif_finalise(&private->gif);
+    free(private);
+    imv_log(IMV_DEBUG, "libsngif: unsupported file\n");
+    return BACKEND_UNSUPPORTED;
+  }
+
+  *src = imv_source_create(&vtable, private);
+  return BACKEND_SUCCESS;
+}
+
+static enum backend_result open_path(const char *path, struct imv_source **src)
+{
+  imv_log(IMV_DEBUG, "libnsgif: open_path(%s)\n", path);
+
+  int fd = open(path, O_RDONLY);
+  if (fd < 0) {
+    return BACKEND_BAD_PATH;
+  }
+
+  off_t len = lseek(fd, 0, SEEK_END);
+  if (len < 0) {
+    close(fd);
+    return BACKEND_BAD_PATH;
+  }
+
+  void *data = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
+  close(fd);
+  if (data == MAP_FAILED || !data) {
+    return BACKEND_BAD_PATH;
+  }
+
+  struct private *private = calloc(1, sizeof *private);
+  private->data = data;
+  private->len = len;
+  gif_create(&private->gif, &bitmap_callbacks);
+
+  gif_result code;
+  do {
+    code = gif_initialise(&private->gif, private->len, private->data);
+  } while (code == GIF_WORKING);
+
+  if (code != GIF_OK) {
+    gif_finalise(&private->gif);
+    munmap(private->data, private->len);
+    free(private);
+    imv_log(IMV_DEBUG, "libsngif: unsupported file\n");
+    return BACKEND_UNSUPPORTED;
+  }
+
+  imv_log(IMV_DEBUG, "libnsgif: num_frames=%d\n", private->gif.frame_count);
+  imv_log(IMV_DEBUG, "libnsgif: width=%d\n", private->gif.width);
+  imv_log(IMV_DEBUG, "libnsgif: height=%d\n", private->gif.height);
+
+  *src = imv_source_create(&vtable, private);
+  return BACKEND_SUCCESS;
+}
+
+
+const struct imv_backend imv_backend_libnsgif = {
+  .name = "libnsgif",
+  .description = "Tiny GIF decoding library from the NetSurf project",
+  .website = "https://www.netsurf-browser.org/projects/libnsgif/",
+  .license = "MIT",
+  .open_path = &open_path,
+  .open_memory = &open_memory,
+};
diff --git a/src/main.c b/src/main.c
index ca2767e..cb0db83 100644
--- a/src/main.c
+++ b/src/main.c
@@ -7,6 +7,7 @@ extern const struct imv_backend imv_backend_libpng;
 extern const struct imv_backend imv_backend_librsvg;
 extern const struct imv_backend imv_backend_libtiff;
 extern const struct imv_backend imv_backend_libjpeg;
+extern const struct imv_backend imv_backend_libnsgif;
 
 int main(int argc, char **argv)
 {
@@ -32,6 +33,10 @@ int main(int argc, char **argv)
   imv_install_backend(imv, &imv_backend_librsvg);
 #endif
 
+#ifdef IMV_BACKEND_LIBNSGIF
+  imv_install_backend(imv, &imv_backend_libnsgif);
+#endif
+
 #ifdef IMV_BACKEND_FREEIMAGE
   imv_install_backend(imv, &imv_backend_freeimage);
 #endif
-- 
cgit v1.2.3