diff --git a/Makefile b/Makefile index 18e75258..f435bff1 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 -lwebpdemux 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 diff --git a/image.c b/image.c index 94cfc2d3..19997bfe 100644 --- a/image.c +++ b/image.c @@ -36,6 +36,11 @@ enum { DEF_GIF_DELAY = 75 }; #endif +#if HAVE_LIBWEBP +#include +#include +#endif + float zoom_min; float zoom_max; @@ -76,6 +81,13 @@ void img_init(img_t *img, win_t *win) img->ss.delay = options->slideshow > 0 ? options->slideshow : SLIDESHOW_DELAY * 10; } +bool img_can_access(const fileinfo_t *file) +{ + struct stat st; + return (access(file->path, R_OK) == 0 && + stat(file->path, &st) == 0 && S_ISREG(st.st_mode)); +} + #if HAVE_LIBEXIF void exif_auto_orientate(const fileinfo_t *file) { @@ -293,13 +305,155 @@ bool img_load_gif(img_t *img, const fileinfo_t *file) } #endif /* HAVE_GIFLIB */ +#ifdef HAVE_LIBWEBP +bool img_is_webp(const fileinfo_t *file) +{ + const int len_ext = 5; + const char *webp_ext = ".webp"; + size_t path_len; + + path_len = strlen(file->path); + if (path_len < len_ext) + return false; + + return strcasecmp(file->path + (path_len - len_ext), webp_ext) == 0; +} + +bool img_load_webp_raw(WebPData *webp_data, const fileinfo_t *file) +{ + FILE* img_file; + uint8_t *img_data; + size_t img_size; + size_t chunks_read; + + img_file = fopen(file->path, "rb"); + if (img_file == NULL) + return false; + + fseek(img_file, 0L, SEEK_END); + img_size = ftell(img_file); + fseek(img_file, 0L, SEEK_SET); + if (img_size < 0) + return false; + + img_data = emalloc(img_size); + chunks_read = fread(img_data, img_size, 1, img_file); + fclose(img_file); + + if (chunks_read != 1) + return false; + + webp_data->bytes = img_data; + webp_data->size = img_size; + + return true; +} + +bool img_load_webp(img_t *img, const fileinfo_t *file) +{ + WebPData webp_data; + WebPAnimDecoder *dec; + WebPAnimInfo anim_info; + WebPAnimDecoderOptions dec_opt; + Imlib_Image im; + int w, h, prev_timestamp = 0; + bool err = false; + + if (!img_can_access(file)) + return false; + + if (img->multi.cap == 0) { + img->multi.cap = 8; + img->multi.frames = (img_frame_t*) + emalloc(sizeof(img_frame_t) * img->multi.cap); + } + img->multi.cnt = img->multi.sel = 0; + img->multi.length = 0; + + WebPDataInit(&webp_data); + + if (!img_load_webp_raw(&webp_data, file)) + return false; + + if (WebPAnimDecoderOptionsInit(&dec_opt) != 1) + return false; + dec_opt.color_mode = MODE_BGRA; + + dec = WebPAnimDecoderNew(&webp_data, &dec_opt); + if (dec == NULL) + return false; + + if (WebPAnimDecoderGetInfo(dec, &anim_info)) + { + w = anim_info.canvas_width; + h = anim_info.canvas_height; + } + else + { + err = true; + } + + while (!err && WebPAnimDecoderHasMoreFrames(dec)) + { + uint8_t *bgra_data; + DATA32 *curr_img_data; + int timestamp, delay; + + im = NULL; + if (!WebPAnimDecoderGetNext(dec, &bgra_data, ×tamp)) + { + err = true; + break; + } + + curr_img_data = (DATA32 *)bgra_data; + im = imlib_create_image_using_copied_data(w, h, curr_img_data); + + if (im == NULL) { + err = true; + break; + } + + imlib_context_set_image(im); + imlib_image_set_format("webp"); + imlib_image_set_has_alpha(1); + + 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)); + } + img->multi.frames[img->multi.cnt].im = im; + delay = timestamp - prev_timestamp; + + img->multi.frames[img->multi.cnt].delay = delay > 0 ? delay : DEF_GIF_DELAY; + img->multi.length += img->multi.frames[img->multi.cnt].delay; + img->multi.cnt++; + + prev_timestamp = timestamp; + } + + WebPAnimDecoderDelete(dec); + + if (img->multi.cnt > 1) { + img->im = img->multi.frames[0].im; + } else if (img->multi.cnt == 1) { + img->im = img->multi.frames[0].im; + img->multi.cnt = 0; + } + + 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)) + if (img_can_access(file)) { im = imlib_load_image(file->path); if (im != NULL) { @@ -319,10 +473,26 @@ bool img_load(img_t *img, const fileinfo_t *file) { const char *fmt; +#if HAVE_LIBWEBP + if (img_is_webp(file)) + { + if (!img_load_webp(img, file)) + { + if (file->flags & FF_WARN) + error(0, 0, "%s: Corrupted webp file", file->name); + return false; + } + } + else + { +#endif if ((img->im = img_open(file)) == NULL) return false; imlib_image_set_changes_on_disk(); +#if HAVE_LIBWEBP + } +#endif #if HAVE_LIBEXIF exif_auto_orientate(file); diff --git a/sxiv.desktop b/sxiv.desktop index 2940b326..46826bef 100644 --- a/sxiv.desktop +++ b/sxiv.desktop @@ -3,6 +3,6 @@ Type=Application Name=sxiv GenericName=Image Viewer Exec=sxiv %F -MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap; +MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/png;image/tiff;image/x-bmp;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-tga;image/x-xpixmap;image/webp; NoDisplay=true Icon=sxiv