Add animated webp support (#20)

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>
This commit is contained in:
Sam Whitehead 2021-09-24 05:20:55 -06:00 committed by NRK
parent 0e7ab7e5c3
commit af98249b68
3 changed files with 159 additions and 2 deletions

View File

@ -30,9 +30,15 @@ ifeq ($(HAVE_LIBGIF), 1)
else else
HAVE_LIBGIF = 0 HAVE_LIBGIF = 0
endif endif
ifeq ($(HAVE_LIBWEBP), 1)
OPTIONAL_LIBS += -lwebpdemux -lwebp
else
HAVE_LIBWEBP = 0
endif
CPPFLAGS = -D_XOPEN_SOURCE=700 \ CPPFLAGS = -D_XOPEN_SOURCE=700 \
-DHAVE_LIBGIF=$(HAVE_LIBGIF) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ -DHAVE_LIBGIF=$(HAVE_LIBGIF) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \
-DHAVE_LIBWEBP=$(HAVE_LIBWEBP) \
-I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2
LDLIBS = -lImlib2 -lX11 -lXft -lfontconfig $(OPTIONAL_LIBS) LDLIBS = -lImlib2 -lX11 -lXft -lfontconfig $(OPTIONAL_LIBS)
@ -61,7 +67,7 @@ window.o: icon/data.h
config.mk: config.mk:
@echo "GEN $@" @echo "GEN $@"
@echo "# 0 = disable, 1 = enable" > config.mk @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 \ 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 ; \ echo "HAVE_LIB$$lib=1" | tr '[:lower:]' '[:upper:]' >> config.mk ; \
fi \ fi \

View File

@ -53,6 +53,7 @@ The following libraries are optional. They are automatically enabled if installe
* giflib : Used for animated gif playback. * giflib : Used for animated gif playback.
* libexif : Used for auto-orientation and exif thumbnails. * 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 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 you want to build nsxiv on a distribution with separate runtime and development

152
image.c
View File

@ -36,6 +36,12 @@
enum { DEF_GIF_DELAY = 75 }; enum { DEF_GIF_DELAY = 75 };
#endif #endif
#if HAVE_LIBWEBP
#include <webp/decode.h>
#include <webp/demux.h>
enum { DEF_WEBP_DELAY = 75 };
#endif
float zoom_min; float zoom_min;
float zoom_max; float zoom_max;
@ -297,6 +303,135 @@ bool img_load_gif(img_t *img, const fileinfo_t *file)
} }
#endif /* HAVE_LIBGIF */ #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) Imlib_Image img_open(const fileinfo_t *file)
{ {
struct stat st; struct stat st;
@ -305,10 +440,21 @@ Imlib_Image img_open(const fileinfo_t *file)
if (access(file->path, R_OK) == 0 && if (access(file->path, R_OK) == 0 &&
stat(file->path, &st) == 0 && S_ISREG(st.st_mode)) 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) { if (im != NULL) {
imlib_context_set_image(im); 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) { if (imlib_image_get_data_for_reading_only() == NULL) {
#endif
imlib_free_image(); imlib_free_image();
im = NULL; im = NULL;
} }
@ -336,6 +482,10 @@ bool img_load(img_t *img, const fileinfo_t *file)
#if HAVE_LIBGIF #if HAVE_LIBGIF
if (STREQ(fmt, "gif")) if (STREQ(fmt, "gif"))
img_load_gif(img, file); img_load_gif(img, file);
#endif
#if HAVE_LIBWEBP
if (STREQ(fmt, "webp"))
img_load_webp(file, NULL, img);
#endif #endif
} }
img->w = imlib_image_get_width(); img->w = imlib_image_get_width();