From 0bfc65cab18363fe7574966388b5309337d399a7 Mon Sep 17 00:00:00 2001 From: Sam Whitehead Date: Sun, 31 Jan 2021 01:09:07 +0000 Subject: [PATCH 1/4] Add webp compiler flags to Makefile --- Makefile | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index da6c209f..eb82010c 100644 --- a/Makefile +++ b/Makefile @@ -15,17 +15,24 @@ HAVE_GIFLIB = 1 # enable features requiring libexif (-lexif) HAVE_LIBEXIF = 1 +# enable features requiring libwebp (-lwebp) +HAVE_LIBWEBP = 1 + cflags = -std=c99 -Wall -pedantic $(CFLAGS) cppflags = -I. $(CPPFLAGS) -D_XOPEN_SOURCE=700 \ -DHAVE_GIFLIB=$(HAVE_GIFLIB) -DHAVE_LIBEXIF=$(HAVE_LIBEXIF) \ + -DHAVE_LIBWEBP=$(HAVE_LIBWEBP) \ -I/usr/include/freetype2 -I$(PREFIX)/include/freetype2 lib_exif_0 = lib_exif_1 = -lexif lib_gif_0 = lib_gif_1 = -lgif +lib_webp_0 = +lib_webp_1 = -lwebp ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ - $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) + $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) \ + $(lib_webp_$(HAVE_LIBWEBP)) objs = autoreload_$(AUTORELOAD).o commands.o image.o main.o options.o \ thumbs.o util.o window.o From 678b63542a4162bb27f635a0312fdbf238cdf0ba Mon Sep 17 00:00:00 2001 From: Sam Whitehead Date: Sun, 31 Jan 2021 15:36:51 +0000 Subject: [PATCH 2/4] Introduce a multiframe webp loader Created a function `img_load_webp()` which does the same thing as the function `img_load_gif()`, but for webp images. Added a small amount of code to make sxiv call this function when the file given is a webp image file (using file extension to check at first, then doing proper tests in the function). --- Makefile | 2 +- image.c | 143 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 136 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index eb82010c..8dc59f7b 100644 --- a/Makefile +++ b/Makefile @@ -29,7 +29,7 @@ lib_exif_1 = -lexif lib_gif_0 = lib_gif_1 = -lgif lib_webp_0 = -lib_webp_1 = -lwebp +lib_webp_1 = -lwebpdemux -lwebp ldlibs = $(LDLIBS) -lImlib2 -lX11 -lXft -lfontconfig \ $(lib_exif_$(HAVE_LIBEXIF)) $(lib_gif_$(HAVE_GIFLIB)) \ $(lib_webp_$(HAVE_LIBWEBP)) diff --git a/image.c b/image.c index 9a9c531b..37624053 100644 --- a/image.c +++ b/image.c @@ -36,6 +36,12 @@ enum { DEF_GIF_DELAY = 75 }; #endif +#if HAVE_LIBWEBP +#include +#include +enum { DEF_WEBP_DELAY = 75 }; +#endif + float zoom_min; float zoom_max; @@ -138,7 +144,7 @@ bool img_load_gif(img_t *img, const fileinfo_t *file) if (img->multi.cap == 0) { img->multi.cap = 8; img->multi.frames = (img_frame_t*) - emalloc(sizeof(img_frame_t) * img->multi.cap); + emalloc(sizeof(img_frame_t) * img->multi.cap); } img->multi.cnt = img->multi.sel = 0; img->multi.length = 0; @@ -213,10 +219,10 @@ bool img_load_gif(img_t *img, const fileinfo_t *file) for (i = 0; i < sh; i++) { for (j = 0; j < sw; j++) { if (i < y || i >= y + h || j < x || j >= x + w || - rows[i-y][j-x] == transp) + rows[i-y][j-x] == transp) { if (prev_frame != NULL && (prev_disposal != 2 || - i < py || i >= py + ph || j < px || j >= px + pw)) + i < py || i >= py + ph || j < px || j >= px + pw)) { *ptr = prev_frame[i * sw + j]; } else { @@ -257,8 +263,8 @@ bool img_load_gif(img_t *img, const fileinfo_t *file) if (img->multi.cnt == img->multi.cap) { img->multi.cap *= 2; img->multi.frames = (img_frame_t*) - erealloc(img->multi.frames, - img->multi.cap * sizeof(img_frame_t)); + erealloc(img->multi.frames, + img->multi.cap * sizeof(img_frame_t)); } img->multi.frames[img->multi.cnt].im = im; delay = img->multi.framedelay > 0 ? img->multi.framedelay : delay; @@ -293,13 +299,123 @@ bool img_load_gif(img_t *img, const fileinfo_t *file) } #endif /* HAVE_GIFLIB */ + +#if HAVE_LIBWEBP +bool img_load_webp(img_t* img, const fileinfo_t* file) +{ + bool err = false; + FILE *webp_file; + size_t file_size; + WebPData data; + + Imlib_Image im = NULL; + struct WebPAnimDecoderOptions opts; + WebPAnimDecoder *dec; + struct WebPAnimInfo info; + uint8_t *buf = NULL; + int ts; + const WebPDemuxer *demux; + WebPIterator iter; + uint32_t flags; + uint32_t delay; + + // Check if file exists (and we are able to read it) + if (access(file->path, R_OK) == -1) { + error(0, 0, "%s: Error opening webp image", file->name); + return false; + } + // Open the file + if ((webp_file = fopen(file->path, "rb")) == NULL) { + error(0, 0, "%s: Error opening webp image", file->name); + return false; + } + // Get the file size + fseek(webp_file, 0L, SEEK_END); + file_size = ftell(webp_file); + rewind(webp_file); + // Read the whole file into memory + data.bytes = emalloc(file_size); + if (fread((uint8_t*)data.bytes, 1, file_size, webp_file) != file_size) { + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return false; + } + data.size = file_size; + + // Setup the WebP Animation Decoder + if (!WebPAnimDecoderOptionsInit(&opts)) { + // Version mismatch in WebP library + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return false; + } + opts.color_mode = MODE_BGRA; + // This could cause problems on some systems and will require more testing + // than I can do (multithreaded decoding) + opts.use_threads = true; + if ((dec = WebPAnimDecoderNew(&data, &opts)) == NULL) { + // Parsing error, invalid operation, or memory error + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return false; + } + if (!WebPAnimDecoderGetInfo(dec, &info)) { + error(0, 0, "%s: Error opening webp image", file->name); + WebPAnimDecoderDelete(dec); + free((uint8_t*)data.bytes); + return false; + } + demux = WebPAnimDecoderGetDemuxer(dec); + + // 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->alpha = (flags & ALPHA_FLAG) > 0; + img->multi.cap = info.frame_count; + img->multi.sel = 0; + img->multi.frames = emalloc(info.frame_count * sizeof(img_frame_t)); + + // Load (and decode) the frames of the image (also works with images that + // have 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); + // TODO: iter refers to the sub-frame, not the total frame. im is the + // total frame, entirely reconstructed. How can we see if the total + // frame has transparency? + if (iter.has_alpha) imlib_image_set_has_alpha(1); + // 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++; + } + WebPAnimDecoderDelete(dec); + WebPDemuxReleaseIterator(&iter); + + if (img->multi.cnt == 1) img->multi.cnt = 0; + img->im = img->multi.frames[0].im; + + imlib_context_set_image(img->im); + + return !err; +} +#endif /* HAVE_LIBWEBP */ + Imlib_Image img_open(const fileinfo_t *file) { struct stat st; Imlib_Image im = NULL; 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 (im != NULL) { @@ -319,8 +435,15 @@ bool img_load(img_t *img, const fileinfo_t *file) { const char *fmt; +#if HAVE_LIBWEBP + if (STREQ(strrchr(file->path, '.')+1, "webp")) + img_load_webp(img, file); + else if ((img->im = img_open(file)) == NULL) + return false; +#else if ((img->im = img_open(file)) == NULL) return false; +#endif imlib_image_set_changes_on_disk(); @@ -332,6 +455,10 @@ bool img_load(img_t *img, const fileinfo_t *file) #if HAVE_GIFLIB if (STREQ(fmt, "gif")) img_load_gif(img, file); +#endif +#if HAVE_LIBWEBP + if (STREQ(fmt, "webp")) + img_load_webp(img, file); #endif } img->w = imlib_image_get_width(); @@ -443,8 +570,8 @@ void img_render(img_t *img) return; /* calculate source and destination offsets: - * - part of image drawn on full window, or - * - full image drawn on part of window + * - part of image drawn on full window, or + * - full image drawn on part of window */ if (img->x <= 0) { sx = -img->x / img->zoom + 0.5; From 75c820253c3a4fd65afe8ccd85a515bc7732574d Mon Sep 17 00:00:00 2001 From: Sam Whitehead Date: Sun, 31 Jan 2021 22:58:20 +0000 Subject: [PATCH 3/4] Fix memory leaks from previous commit --- image.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 105 insertions(+), 10 deletions(-) diff --git a/image.c b/image.c index 37624053..4e96d64c 100644 --- a/image.c +++ b/image.c @@ -40,6 +40,7 @@ enum { DEF_GIF_DELAY = 75 }; #include #include enum { DEF_WEBP_DELAY = 75 }; +Imlib_Image load_webp_firstframe_im(const fileinfo_t *file); #endif float zoom_min; @@ -335,12 +336,12 @@ bool img_load_webp(img_t* img, const fileinfo_t* file) rewind(webp_file); // Read the whole file into memory data.bytes = emalloc(file_size); + data.size = file_size; if (fread((uint8_t*)data.bytes, 1, file_size, webp_file) != file_size) { error(0, 0, "%s: Error opening webp image", file->name); free((uint8_t*)data.bytes); return false; } - data.size = file_size; // Setup the WebP Animation Decoder if (!WebPAnimDecoderOptionsInit(&opts)) { @@ -400,13 +401,104 @@ bool img_load_webp(img_t* img, const fileinfo_t* file) WebPAnimDecoderDelete(dec); WebPDemuxReleaseIterator(&iter); - if (img->multi.cnt == 1) img->multi.cnt = 0; - img->im = img->multi.frames[0].im; + 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"); + free((uint8_t*)data.bytes); return !err; } + +Imlib_Image load_webp_firstframe_im(const fileinfo_t *file) +{ + struct stat st; + + if (!file->name || !file->path) + return NULL; + if (!STREQ(strrchr(file->path, '.'), ".webp")) + return NULL; + if (access(file->path, R_OK) != 0 || stat(file->path, &st) != 0 || !S_ISREG(st.st_mode)) + return NULL; + + FILE *webp_file; + size_t file_size; + WebPData data; + + Imlib_Image im = NULL; + struct WebPAnimDecoderOptions opts; + WebPAnimDecoder *dec; + struct WebPAnimInfo info; + uint8_t *buf = NULL; + int ts; + + // Open the file + if ((webp_file = fopen(file->path, "rb")) == NULL) { + error(0, 0, "%s: Error opening webp image", file->name); + return NULL; + } + // Get the file size + fseek(webp_file, 0L, SEEK_END); + file_size = ftell(webp_file); + rewind(webp_file); + // Read the whole file into memory + data.bytes = emalloc(file_size); + if (fread((uint8_t*)data.bytes, 1, file_size, webp_file) != file_size) { + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return NULL; + } + data.size = file_size; + + // Setup the WebP Animation Decoder + if (!WebPAnimDecoderOptionsInit(&opts)) { + // Version mismatch in WebP library + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return NULL; + } + opts.color_mode = MODE_BGRA; + // This could cause problems on some systems and will require more testing + // than I can do (multithreaded decoding) + opts.use_threads = true; + if ((dec = WebPAnimDecoderNew(&data, &opts)) == NULL) { + // Parsing error, invalid operation, or memory error + error(0, 0, "%s: Error opening webp image", file->name); + free((uint8_t*)data.bytes); + return NULL; + } + if (!WebPAnimDecoderGetInfo(dec, &info)) { + error(0, 0, "%s: Error opening webp image", file->name); + WebPAnimDecoderDelete(dec); + free((uint8_t*)data.bytes); + return NULL; + } + + // Get the first frame + if (!WebPAnimDecoderGetNext(dec, &buf, &ts)) { + error(0, 0, "%s: Error opening webp image", file->name); + WebPAnimDecoderDelete(dec); + free((uint8_t*)data.bytes); + return NULL; + } + + 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"); + + WebPAnimDecoderDelete(dec); + free((uint8_t*)data.bytes); + return im; +} #endif /* HAVE_LIBWEBP */ Imlib_Image img_open(const fileinfo_t *file) @@ -417,6 +509,16 @@ 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)) { +#if HAVE_LIBWEBP + im = load_webp_firstframe_im(file); + if (im != NULL) { + if (imlib_image_get_data_for_reading_only() == NULL) { + imlib_free_image(); + im = NULL; + } + } + if (im != NULL) return im; +#endif im = imlib_load_image(file->path); if (im != NULL) { imlib_context_set_image(im); @@ -435,15 +537,8 @@ bool img_load(img_t *img, const fileinfo_t *file) { const char *fmt; -#if HAVE_LIBWEBP - if (STREQ(strrchr(file->path, '.')+1, "webp")) - img_load_webp(img, file); - else if ((img->im = img_open(file)) == NULL) - return false; -#else if ((img->im = img_open(file)) == NULL) return false; -#endif imlib_image_set_changes_on_disk(); From 3df10f5852b696ac4b09c05a356780ebf00cc546 Mon Sep 17 00:00:00 2001 From: Sam Whitehead Date: Thu, 26 Aug 2021 19:03:45 +0100 Subject: [PATCH 4/4] Fix transparency issue with WebP images. Credit for this fix goes to @bakkeby on github. --- image.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/image.c b/image.c index 4e96d64c..27e55839 100644 --- a/image.c +++ b/image.c @@ -372,7 +372,7 @@ bool img_load_webp(img_t* img, const fileinfo_t* file) flags = WebPDemuxGetI(demux, WEBP_FF_FORMAT_FLAGS); img->w = WebPDemuxGetI(demux, WEBP_FF_CANVAS_WIDTH); img->h = WebPDemuxGetI(demux, WEBP_FF_CANVAS_HEIGHT); - img->alpha = (flags & ALPHA_FLAG) > 0; + img->alpha = ALPHA_LAYER; img->multi.cap = info.frame_count; img->multi.sel = 0; img->multi.frames = emalloc(info.frame_count * sizeof(img_frame_t)); @@ -387,10 +387,7 @@ bool img_load_webp(img_t* img, const fileinfo_t* file) imlib_image_set_format("webp"); // Get an iterator of this frame - used for frame info (duration, etc.) WebPDemuxGetFrame(demux, img->multi.cnt+1, &iter); - // TODO: iter refers to the sub-frame, not the total frame. im is the - // total frame, entirely reconstructed. How can we see if the total - // frame has transparency? - if (iter.has_alpha) imlib_image_set_has_alpha(1); + imlib_image_set_has_alpha((flags & ALPHA_FLAG) > 0); // Store info for this frame img->multi.frames[img->multi.cnt].im = im; delay = iter.duration > 0 ? iter.duration : DEF_WEBP_DELAY; @@ -494,6 +491,7 @@ Imlib_Image load_webp_firstframe_im(const fileinfo_t *file) info.canvas_width, info.canvas_height, (DATA32*)buf); imlib_context_set_image(im); imlib_image_set_format("webp"); + imlib_image_set_has_alpha(1); WebPAnimDecoderDelete(dec); free((uint8_t*)data.bytes);