From b9e801cb940b45cfd9a4cf95bf5af68b084156b4 Mon Sep 17 00:00:00 2001 From: Rodrigo Arias Mallo Date: Sun, 24 Nov 2024 18:15:23 +0100 Subject: Add WebP image support See: https://www.toomanyatoms.com/software/mobilized_dillo.html Authored-By: dogma --- configure.ac | 31 +++++++++ src/IO/mime.c | 3 + src/IO/mime.h | 2 + src/Makefile.am | 4 +- src/dicache.c | 16 ++++- src/dwebp.h | 19 ++++++ src/webp.c | 201 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 274 insertions(+), 2 deletions(-) create mode 100644 src/dwebp.h create mode 100644 src/webp.c diff --git a/configure.ac b/configure.ac index 268e24ba..03715e1b 100644 --- a/configure.ac +++ b/configure.ac @@ -51,6 +51,11 @@ AC_ARG_ENABLE([png], [enable_png=$enableval], [enable_png=yes]) +AC_ARG_ENABLE([webp], + [AS_HELP_STRING([--disable-webp], [Disable support for WEBP images])], + [enable_webp=$enableval], + [enable_webp=yes]) + AC_ARG_ENABLE([jpeg], [AS_HELP_STRING([--disable-jpeg], [Disable support for JPEG images])], [enable_jpeg=$enableval], @@ -284,6 +289,30 @@ if test "x$jpeg_ok" = "xyes"; then AC_DEFINE([ENABLE_JPEG], [1], [Enable JPEG images]) fi +dnl ---------------- +dnl Test for libwebp +dnl ---------------- +dnl +if test "x$enable_webp" = "xyes"; then + AC_CHECK_HEADER(webp/decode.h, webp_ok=yes, webp_ok=no) + + if test "x$webp_ok" = "xyes"; then + old_libs="$LIBS" + AC_CHECK_LIB(webp, WebPGetInfo, webp_ok=yes, webp_ok=no) + LIBS="$old_libs" + fi + + if test "x$webp_ok" = "xyes"; then + LIBWEBP_LIBS="-lwebp" + else + AC_MSG_WARN([*** No libwebp found. Disabling webp images.***]) + fi +fi + +if test "x$webp_ok" = "xyes"; then + AC_DEFINE([ENABLE_WEBP], [1], [Enable webp images]) +fi + dnl ------------- dnl Test for zlib dnl ------------- @@ -688,6 +717,7 @@ AC_SUBST(LIBJPEG_LDFLAGS) AC_SUBST(LIBJPEG_CPPFLAGS) AC_SUBST(LIBPNG_LIBS) AC_SUBST(LIBPNG_CFLAGS) +AC_SUBST(LIBWEBP_LIBS) AC_SUBST(LIBZ_LIBS) AC_SUBST(LIBSSL_LIBS) AC_SUBST(LIBPTHREAD_LIBS) @@ -739,6 +769,7 @@ _AS_ECHO([ JPEG enabled : ${jpeg_ok}]) _AS_ECHO([ PNG enabled : ${png_ok}]) _AS_ECHO([ GIF enabled : ${enable_gif}]) _AS_ECHO([ SVG enabled : ${enable_svg}]) +_AS_ECHO([ WEBP enabled : ${enable_webp}]) _AS_ECHO([]) _AS_ECHO([ HTML tests : ${html_tests_ok}]) _AS_ECHO([]) diff --git a/src/IO/mime.c b/src/IO/mime.c index 1f6a3d47..3d88429e 100644 --- a/src/IO/mime.c +++ b/src/IO/mime.c @@ -108,6 +108,9 @@ void a_Mime_init(void) Mime_add_minor_type("image/png", a_Dicache_png_image); Mime_add_minor_type("image/x-png", a_Dicache_png_image); /* deprecated */ #endif +#ifdef ENABLE_WEBP + Mime_add_minor_type("image/webp", a_Dicache_webp_image); +#endif #ifdef ENABLE_SVG Mime_add_minor_type("image/svg+xml", a_Dicache_svg_image); #endif diff --git a/src/IO/mime.h b/src/IO/mime.h index 47bbf0ba..341e2ace 100644 --- a/src/IO/mime.h +++ b/src/IO/mime.h @@ -32,6 +32,8 @@ void *a_Plain_text(const char *Type,void *web, CA_Callback_t *Call, void **Data); void *a_Dicache_png_image (const char *Type,void *web, CA_Callback_t *Call, void **Data); +void *a_Dicache_webp_image (const char *Type,void *web, CA_Callback_t *Call, + void **Data); void *a_Dicache_gif_image(const char *Type, void *Ptr, CA_Callback_t *Call, void **Data); void *a_Dicache_jpeg_image(const char *Type, void *Ptr, CA_Callback_t *Call, diff --git a/src/Makefile.am b/src/Makefile.am index bb1f6fc3..34c05e30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -20,7 +20,7 @@ dillo_LDADD = \ $(top_builddir)/dw/libDw-fltk.a \ $(top_builddir)/dw/libDw-core.a \ $(top_builddir)/lout/liblout.a \ - @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ \ + @LIBJPEG_LIBS@ @LIBPNG_LIBS@ @LIBWEBP_LIBS@ @LIBFLTK_LIBS@ @LIBZ_LIBS@ \ @LIBICONV_LIBS@ @LIBPTHREAD_LIBS@ @LIBX11_LIBS@ @LIBSSL_LIBS@ dillo_SOURCES = \ @@ -118,6 +118,8 @@ dillo_SOURCES = \ djpeg.h \ png.c \ dpng.h \ + webp.c \ + dwebp.h \ svg.c \ nanosvg.h \ nanosvgrast.h \ diff --git a/src/dicache.c b/src/dicache.c index c9f4067b..e86f5655 100644 --- a/src/dicache.c +++ b/src/dicache.c @@ -19,14 +19,15 @@ #include "web.hh" #include "dicache.h" #include "dpng.h" +#include "dwebp.h" #include "dgif.h" #include "djpeg.h" #include "dsvg.h" - enum { DIC_Gif, DIC_Png, + DIC_Webp, DIC_Jpeg, DIC_Svg }; @@ -390,6 +391,10 @@ static void *Dicache_image(int ImgType, const char *MimeType, void *Ptr, DicEntry->Decoder = (CA_Callback_t)a_Gif_callback; DicEntry->DecoderData = a_Gif_new(web->Image, DicEntry->url, DicEntry->version); + } else if (ImgType == DIC_Webp) { + DicEntry->Decoder = (CA_Callback_t)a_Webp_callback; + DicEntry->DecoderData = + a_Webp_new(web->Image, DicEntry->url, DicEntry->version); } else if (ImgType == DIC_Png) { DicEntry->Decoder = (CA_Callback_t)a_Png_callback; DicEntry->DecoderData = @@ -421,6 +426,15 @@ void *a_Dicache_png_image(const char *Type, void *Ptr, CA_Callback_t *Call, return Dicache_image(DIC_Png, Type, Ptr, Call, Data); } +/** + * WEBP wrapper for Dicache_image() + */ +void *a_Dicache_webp_image(const char *Type, void *Ptr, CA_Callback_t *Call, + void **Data) +{ + return Dicache_image(DIC_Webp, Type, Ptr, Call, Data); +} + /** * GIF wrapper for Dicache_image() */ diff --git a/src/dwebp.h b/src/dwebp.h new file mode 100644 index 00000000..930380b0 --- /dev/null +++ b/src/dwebp.h @@ -0,0 +1,19 @@ +#ifndef __WEBP_H__ +#define __WEBP_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include "url.h" +#include "image.hh" + + +void *a_Webp_new(DilloImage *Image, DilloUrl *url, int version); +void a_Webp_callback(int Op, void *data); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ +#endif /* !__WEBP_H__ */ diff --git a/src/webp.c b/src/webp.c new file mode 100644 index 00000000..ebcc238a --- /dev/null +++ b/src/webp.c @@ -0,0 +1,201 @@ +/* + * File: webp.c + * + * Copyright (C) 2024 dogma + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + */ + +#include +#ifdef ENABLE_WEBP + +#include /* For abort() */ + +#include + +#include "msg.h" +#include "image.hh" +#include "cache.h" +#include "dicache.h" + +enum prog_state { + IS_finished, IS_init, IS_nextdata +}; + +typedef struct { + DilloImage *Image; /* Image meta data */ + DilloUrl *url; /* Primary Key for the dicache */ + int version; /* Secondary Key for the dicache */ + int bgcolor; /* Parent widget background color */ + int state; + int y; + WebPIDecoder* idec; + WebPDecBuffer output_buffer; /* for RGBA */ +} DilloWebp; + + +/* + * Free up the resources for this image. + */ +static void Webp_free(DilloWebp *webp) +{ + _MSG("Webp_free: webp=%p\n", webp); + + WebPFreeDecBuffer(&webp->output_buffer); + if (webp->idec) + WebPIDelete(webp->idec); + dFree(webp); +} + +/* + * Finish the decoding process (and free the memory) + */ +static void Webp_close(DilloWebp *webp, CacheClient_t *Client) +{ + _MSG("Webp_close\n"); + /* Let dicache know decoding is over */ + a_Dicache_close(webp->url, webp->version, Client); + Webp_free(webp); +} + +/* + * Op: Operation to perform. + * If (Op == 0) + * start or continue processing an image if image data exists. + * else + * terminate processing, cleanup any allocated memory, + * close down the decoding process. + * + * Client->CbData : pointer to previously allocated work area. + * This holds the current state of the image processing and is kept + * across calls to this routine. + * Client->Buf : Pointer to data start. + * Client->BufSize : the size of the data buffer. + * + * You have to keep track of where you are in the image data and + * how much has been processed. + * + * It's entirely possible that you will not see the end of the data. The + * user may terminate transfer via a Stop button or there may be a network + * failure. This means that you can't just wait for all the data to be + * presented before starting conversion and display. + */ +void a_Webp_callback(int Op, void *data) +{ + CacheClient_t *Client = data; + + if (Op == CA_Send) { + uint8_t* output; + VP8StatusCode ret; + + DilloWebp *webp = (DilloWebp *)Client->CbData; + + if (webp->state == IS_init) { + WebPBitstreamFeatures features; + + ret = WebPGetFeatures(Client->Buf, Client->BufSize, &features); + if (ret != VP8_STATUS_OK) { + MSG("features ret is %d\n", ret); + return; + } + if (features.has_alpha) { + _MSG("WEBP: Alpha!\n"); + } + webp->output_buffer.colorspace = features.has_alpha ? MODE_RGBA : MODE_RGB; + + a_Dicache_set_parms(webp->url, webp->version, webp->Image, + features.width, features.height, + DILLO_IMG_TYPE_RGB, 1 / 2.2); + + webp->idec = WebPINewDecoder(&webp->output_buffer); + webp->state = IS_nextdata; + } + + ret = WebPIUpdate(webp->idec, Client->Buf, Client->BufSize); + /* SUSPENDED is a success state that means you don't have the entire file yet */ + if (ret == VP8_STATUS_SUSPENDED || ret == VP8_STATUS_OK) { + /* last_y seems 1-based, which would be kind of crazy, but I would expect + * crazy idiocy from google. + */ + int last_y, width, height, stride; + + _MSG("webp completed. status: %s\n", ret == VP8_STATUS_SUSPENDED ? "suspended" : "ok (done)"); + output = WebPIDecGetRGB(webp->idec, &last_y, &width, &height, &stride); + if (!output) { + _MSG("webp decoding no output\n"); + } else { + unsigned char *line; + int row = webp->y; + + if (webp->output_buffer.colorspace == MODE_RGBA) + line = dNew(unsigned char, width * 3); + else + line = output + row * stride; + + for (; row < last_y; row++) { + + if (webp->output_buffer.colorspace == MODE_RGBA) { + int j; + + uint_t bg_blue = (webp->bgcolor) & 0xFF; + uint_t bg_green = (webp->bgcolor>>8) & 0xFF; + uint_t bg_red = (webp->bgcolor>>16) & 0xFF; + for (j = 0; j < width; j++) { + uchar_t alpha = output[row * stride + 4 * j + 3]; + uchar_t r = output[row * stride + 4 * j]; + uchar_t g = output[row * stride + 4 * j + 1]; + uchar_t b = output[row * stride + 4 * j + 2]; + line[3 * j] = (r * alpha + (bg_red * (0xFF - alpha))) / 0xFF; + line[3 * j + 1] = (g * alpha + (bg_green * (0xFF - alpha))) / 0xFF; + line[3 * j + 2] = (b * alpha + (bg_blue * (0xFF - alpha))) / 0xFF; + } + } else { + line = output + row * stride; + } + a_Dicache_write(webp->url, webp->version, line, row); + } + webp->y = last_y; + + if (webp->output_buffer.colorspace == MODE_RGBA) + dFree(line); + } + } else { + MSG("webp WebPIUpdate failed with %d\n", ret); + } + } else if (Op == CA_Close) { + Webp_close(Client->CbData, Client); + } else if (Op == CA_Abort) { + Webp_free(data); + } +} + +/* + * Create the image state data that must be kept between calls + */ +void *a_Webp_new(DilloImage *Image, DilloUrl *url, int version) +{ + DilloWebp *webp = dNew0(DilloWebp, 1); + _MSG("a_Webp_new: webp=%p\n", webp); + + webp->Image = Image; + webp->url = url; + webp->version = version; + + webp->bgcolor = Image->bg_color; + webp->state = IS_init; + webp->y = 0; + webp->idec = NULL; + WebPInitDecBuffer(&webp->output_buffer); + + return webp; +} + +#else /* ENABLE_WEBP */ + +void *a_Webp_new() { return 0; } +void a_Webp_callback() { return; } + +#endif /* ENABLE_WEBP */ -- cgit v1.2.3