Add webp decoder.

We've been needing this for a while for the game engines but we also need it to support decoding webp images for use in the editor with the Rive Renderer.

I haven't instrumented the build properly to use SIMD extensions, but I left some notes for how to do so. This PR unblocks the use of WebP, let's do some perf improvements in a follow up that perhaps the runtime team can own?

Diffs=
160d9eefb Add webp decoder. (#7883)

Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
Co-authored-by: rivessamr <suki@rive.app>
diff --git a/.rive_head b/.rive_head
index f67274a..dce3e71 100644
--- a/.rive_head
+++ b/.rive_head
@@ -1 +1 @@
-e992059d6354434e91cde562e463f51bff7eac58
+160d9eefb4d3e42b620053987fcc8654e98d40a6
diff --git a/decoders/premake5_v2.lua b/decoders/premake5_v2.lua
index 7f7c37b..6c90d95 100644
--- a/decoders/premake5_v2.lua
+++ b/decoders/premake5_v2.lua
@@ -1,17 +1,22 @@
 dofile('rive_build_config.lua')
 
+if _OPTIONS['no-rive-decoders'] then
+    return
+end
+
 rive = path.getabsolute('../')
 
 dofile(rive .. '/dependencies/premake5_libpng_v2.lua')
 dofile(rive .. '/dependencies/premake5_libjpeg_v2.lua')
+dofile(rive .. '/dependencies/premake5_libwebp_v2.lua')
 
 project('rive_decoders')
 do
-    dependson('libpng', 'zlib', 'libjpeg')
+    dependson('libpng', 'zlib', 'libjpeg', 'libwebp')
     kind('StaticLib')
     flags({ 'FatalWarnings' })
 
-    includedirs({ 'include', '../include', libpng, libjpeg })
+    includedirs({ 'include', '../include', libpng, libjpeg, libwebp .. '/src' })
 
     files({ 'src/bitmap_decoder.cpp' })
 
@@ -32,6 +37,7 @@
     do
         files({
             'src/bitmap_decoder_thirdparty.cpp',
+            'src/decode_webp.cpp',
             'src/decode_jpeg.cpp',
             'src/decode_png.cpp',
         })
diff --git a/decoders/src/bitmap_decoder_thirdparty.cpp b/decoders/src/bitmap_decoder_thirdparty.cpp
index 6dc5a91..37c5a66 100644
--- a/decoders/src/bitmap_decoder_thirdparty.cpp
+++ b/decoders/src/bitmap_decoder_thirdparty.cpp
@@ -10,7 +10,7 @@
 
 std::unique_ptr<Bitmap> DecodePng(const uint8_t bytes[], size_t byteCount);
 std::unique_ptr<Bitmap> DecodeJpeg(const uint8_t bytes[], size_t byteCount);
-std::unique_ptr<Bitmap> DecodeWebP(const uint8_t bytes[], size_t byteCount) { return nullptr; }
+std::unique_ptr<Bitmap> DecodeWebP(const uint8_t bytes[], size_t byteCount);
 
 using BitmapDecoder = std::unique_ptr<Bitmap> (*)(const uint8_t bytes[], size_t byteCount);
 struct ImageFormat
diff --git a/decoders/src/decode_webp.cpp b/decoders/src/decode_webp.cpp
new file mode 100644
index 0000000..f261798
--- /dev/null
+++ b/decoders/src/decode_webp.cpp
@@ -0,0 +1,68 @@
+#include "rive/decoders/bitmap_decoder.hpp"
+#include "webp/decode.h"
+#include "webp/demux.h"
+#include <stdio.h>
+#include <vector>
+#include <memory>
+
+std::unique_ptr<Bitmap> DecodeWebP(const uint8_t bytes[], size_t byteCount)
+{
+    WebPDecoderConfig config;
+    if (!WebPInitDecoderConfig(&config))
+    {
+        fprintf(stderr, "DecodeWebP - Library version mismatch!\n");
+        return nullptr;
+    }
+    config.options.dithering_strength = 50;
+    config.options.alpha_dithering_strength = 100;
+
+    if (!WebPGetInfo(bytes, byteCount, nullptr, nullptr))
+    {
+        fprintf(stderr, "DecodeWebP - Input file doesn't appear to be WebP format.\n");
+    }
+
+    WebPData data = {bytes, byteCount};
+    WebPDemuxer* demuxer = WebPDemux(&data);
+    if (demuxer == nullptr)
+    {
+        fprintf(stderr, "DecodeWebP - Could not create demuxer.\n");
+    }
+
+    WebPIterator currentFrame;
+    if (!WebPDemuxGetFrame(demuxer, 1, &currentFrame))
+    {
+        fprintf(stderr, "DecodeWebP - WebPDemuxGetFrame couldn't get frame.\n");
+        WebPDemuxDelete(demuxer);
+        return nullptr;
+    }
+    config.output.colorspace = MODE_RGBA;
+
+    uint32_t width = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
+    uint32_t height = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
+
+    size_t pixelBufferSize =
+        static_cast<size_t>(width) * static_cast<size_t>(height) * static_cast<size_t>(4);
+    std::unique_ptr<uint8_t[]> pixelBuffer = std::make_unique<uint8_t[]>(pixelBufferSize);
+
+    config.output.u.RGBA.rgba = (uint8_t*)pixelBuffer.get();
+    config.output.u.RGBA.stride = (int)(width * 4);
+    config.output.u.RGBA.size = pixelBufferSize;
+    config.output.is_external_memory = 1;
+
+    if (WebPDecode(currentFrame.fragment.bytes, currentFrame.fragment.size, &config) !=
+        VP8_STATUS_OK)
+    {
+        fprintf(stderr, "DecodeWebP - WebPDemuxGetFrame couldn't decode.\n");
+        WebPDemuxReleaseIterator(&currentFrame);
+        WebPDemuxDelete(demuxer);
+        return nullptr;
+    }
+
+    WebPDemuxReleaseIterator(&currentFrame);
+    WebPDemuxDelete(demuxer);
+
+    return std::make_unique<Bitmap>(width,
+                                    height,
+                                    Bitmap::PixelFormat::RGBA,
+                                    std::move(pixelBuffer));
+}
\ No newline at end of file
diff --git a/dependencies/premake5_libjpeg_v2.lua b/dependencies/premake5_libjpeg_v2.lua
index 34ba354..3ed2ecc 100644
--- a/dependencies/premake5_libjpeg_v2.lua
+++ b/dependencies/premake5_libjpeg_v2.lua
@@ -1,9 +1,5 @@
 dofile('rive_build_config.lua')
 
-if _OPTIONS['no-rive-decoders'] then
-    return
-end
-
 local dependency = require('dependency')
 libjpeg = dependency.github('rive-app/libjpeg', 'v9f')
 
diff --git a/dependencies/premake5_libwebp_v2.lua b/dependencies/premake5_libwebp_v2.lua
new file mode 100644
index 0000000..00eb5f1
--- /dev/null
+++ b/dependencies/premake5_libwebp_v2.lua
@@ -0,0 +1,166 @@
+dofile('rive_build_config.lua')
+
+local dependency = require('dependency')
+libwebp = dependency.github('webmproject/libwebp', 'v1.4.0')
+
+project('libwebp')
+do
+    kind('StaticLib')
+    optimize('Speed') -- Always optimize image encoding/decoding, even in debug builds.
+
+    includedirs({ libwebp })
+
+    -- Leaving some notes here for future perf improvements. Define these when
+    -- we can determine we're on a compatible platform/perf gain is worth it.
+    --
+    -- Some extra details about each of these:
+    -- https://github.com/webmproject/libwebp/blob/main/cmake/config.h.in
+    defines({
+        -- 'WEBP_USE_NEON=1',
+        -- 'WEBP_HAVE_NEON_RTCD=1', -- runtime detection of NEON extensions
+        -- 'WEBP_HAVE_SSE41=1',
+        -- 'WEBP_USE_THREAD=1'
+    })
+
+    files({
+        -- common dsp
+        libwebp .. '/src/dsp/alpha_processing.c',
+        libwebp .. '/src/dsp/cpu.c',
+        libwebp .. '/src/dsp/dec.c',
+        libwebp .. '/src/dsp/dec_clip_tables.c',
+        libwebp .. '/src/dsp/filters.c',
+        libwebp .. '/src/dsp/lossless.c',
+        libwebp .. '/src/dsp/rescaler.c',
+        libwebp .. '/src/dsp/upsampling.c',
+        libwebp .. '/src/dsp/yuv.c',
+
+        -- encoder dsp
+        libwebp .. '/src/dsp/cost.c',
+        libwebp .. '/src/dsp/enc.c',
+        libwebp .. '/src/dsp/lossless_enc.c',
+        libwebp .. '/src/dsp/ssim.c',
+
+        -- decoder
+        libwebp .. '/src/dec/alpha_dec.c',
+        libwebp .. '/src/dec/buffer_dec.c',
+        libwebp .. '/src/dec/frame_dec.c',
+        libwebp .. '/src/dec/idec_dec.c',
+        libwebp .. '/src/dec/io_dec.c',
+        libwebp .. '/src/dec/quant_dec.c',
+        libwebp .. '/src/dec/tree_dec.c',
+        libwebp .. '/src/dec/vp8_dec.c',
+        libwebp .. '/src/dec/vp8l_dec.c',
+        libwebp .. '/src/dec/webp_dec.c',
+
+        -- libwebpdspdecode_sse41_la_SOURCES =
+        libwebp .. '/src/dsp/alpha_processing_sse41.c',
+        libwebp .. '/src/dsp/dec_sse41.c',
+        libwebp .. '/src/dsp/lossless_sse41.c',
+        libwebp .. '/src/dsp/upsampling_sse41.c',
+        libwebp .. '/src/dsp/yuv_sse41.c',
+
+        -- libwebpdspdecode_sse2_la_SOURCES =
+        libwebp .. '/src/dsp/alpha_processing_sse2.c',
+        libwebp .. '/src/dsp/common_sse2.h',
+        libwebp .. '/src/dsp/dec_sse2.c',
+        libwebp .. '/src/dsp/filters_sse2.c',
+        libwebp .. '/src/dsp/lossless_sse2.c',
+        libwebp .. '/src/dsp/rescaler_sse2.c',
+        libwebp .. '/src/dsp/upsampling_sse2.c',
+        libwebp .. '/src/dsp/yuv_sse2.c',
+
+        -- neon sources
+        -- TODO: define WEBP_HAVE_NEON when we're on a platform that supports it.
+        libwebp .. '/src/dsp/alpha_processing_neon.c',
+        libwebp .. '/src/dsp/dec_neon.c',
+        libwebp .. '/src/dsp/filters_neon.c',
+        libwebp .. '/src/dsp/lossless_neon.c',
+        libwebp .. '/src/dsp/neon.h',
+        libwebp .. '/src/dsp/rescaler_neon.c',
+        libwebp .. '/src/dsp/upsampling_neon.c',
+        libwebp .. '/src/dsp/yuv_neon.c',
+
+        -- libwebpdspdecode_msa_la_SOURCES =
+        libwebp .. '/src/dsp/dec_msa.c',
+        libwebp .. '/src/dsp/filters_msa.c',
+        libwebp .. '/src/dsp/lossless_msa.c',
+        libwebp .. '/src/dsp/msa_macro.h',
+        libwebp .. '/src/dsp/rescaler_msa.c',
+        libwebp .. '/src/dsp/upsampling_msa.c',
+
+        -- libwebpdspdecode_mips32_la_SOURCES =
+        libwebp .. '/src/dsp/dec_mips32.c',
+        libwebp .. '/src/dsp/mips_macro.h',
+        libwebp .. '/src/dsp/rescaler_mips32.c',
+        libwebp .. '/src/dsp/yuv_mips32.c',
+
+        -- libwebpdspdecode_mips_dsp_r2_la_SOURCES =
+        libwebp .. '/src/dsp/alpha_processing_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/dec_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/filters_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/lossless_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/mips_macro.h',
+        libwebp .. '/src/dsp/rescaler_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/upsampling_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/yuv_mips_dsp_r2.c',
+
+        -- libwebpdsp_sse2_la_SOURCES =
+        libwebp .. '/src/dsp/cost_sse2.c',
+        libwebp .. '/src/dsp/enc_sse2.c',
+        libwebp .. '/src/dsp/lossless_enc_sse2.c',
+        libwebp .. '/src/dsp/ssim_sse2.c',
+
+        -- libwebpdsp_sse41_la_SOURCES =
+        libwebp .. '/src/dsp/enc_sse41.c',
+        libwebp .. '/src/dsp/lossless_enc_sse41.c',
+
+        -- libwebpdsp_neon_la_SOURCES =
+        libwebp .. '/src/dsp/cost_neon.c',
+        libwebp .. '/src/dsp/enc_neon.c',
+        libwebp .. '/src/dsp/lossless_enc_neon.c',
+
+        -- libwebpdsp_msa_la_SOURCES =
+        libwebp .. '/src/dsp/enc_msa.c',
+        libwebp .. '/src/dsp/lossless_enc_msa.c',
+
+        -- libwebpdsp_mips32_la_SOURCES =
+        libwebp .. '/src/dsp/cost_mips32.c',
+        libwebp .. '/src/dsp/enc_mips32.c',
+        libwebp .. '/src/dsp/lossless_enc_mips32.c',
+
+        -- libwebpdsp_mips_dsp_r2_la_SOURCES =
+        libwebp .. '/src/dsp/cost_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/enc_mips_dsp_r2.c',
+        libwebp .. '/src/dsp/lossless_enc_mips_dsp_r2.c',
+
+        -- COMMON_SOURCES =
+        libwebp .. '/src/utils/bit_reader_utils.c',
+        libwebp .. '/src/utils/bit_reader_utils.h',
+        libwebp .. '/src/utils/color_cache_utils.c',
+        libwebp .. '/src/utils/filters_utils.c',
+        libwebp .. '/src/utils/huffman_utils.c',
+        libwebp .. '/src/utils/palette.c',
+        libwebp .. '/src/utils/quant_levels_dec_utils.c',
+        libwebp .. '/src/utils/rescaler_utils.c',
+        libwebp .. '/src/utils/random_utils.c',
+        libwebp .. '/src/utils/thread_utils.c',
+        libwebp .. '/src/utils/utils.c',
+
+        -- ENC_SOURCES =
+        libwebp .. '/src/utils/bit_writer_utils.c',
+        libwebp .. '/src/utils/huffman_encode_utils.c',
+        libwebp .. '/src/utils/quant_levels_utils.c',
+
+        -- libwebpdemux_la_SOURCES =
+        libwebp .. '/src/demux/anim_decode.c',
+        libwebp .. '/src/demux/demux.c',
+    })
+
+    filter({ 'system:windows', 'toolset:clang' })
+    do
+        -- https://github.com/webmproject/libwebp/blob/233e86b91f4e0af7833d50013e3b978f825f73f5/src/dsp/cpu.h#L57
+        -- webp automaticall enables these for windows so we need to compile
+        -- with the correct settings or we get an error.
+        buildoptions({ '-mssse3', '-msse4.1' })
+    end
+end
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua
index dea984e..e6e2cc7 100644
--- a/dev/test/premake5.lua
+++ b/dev/test/premake5.lua
@@ -36,6 +36,7 @@
         'libpng',
         'zlib',
         'libjpeg',
+        'libwebp',
     })
 
     files({
diff --git a/test/assets/1.webp b/test/assets/1.webp
new file mode 100644
index 0000000..122741b
--- /dev/null
+++ b/test/assets/1.webp
Binary files differ
diff --git a/test/image_decoders_test.cpp b/test/image_decoders_test.cpp
index 4d6a821..024115b 100644
--- a/test/image_decoders_test.cpp
+++ b/test/image_decoders_test.cpp
@@ -64,3 +64,16 @@
     REQUIRE(bitmap == nullptr);
 #endif
 }
+
+TEST_CASE("webp file decodes correctly", "[image-decoder]")
+{
+    auto file = ReadFile("../../test/assets/1.webp");
+    REQUIRE(file.size() == 30320);
+
+    auto bitmap = Bitmap::decode(file.data(), file.size());
+
+    REQUIRE(bitmap != nullptr);
+
+    REQUIRE(bitmap->width() == 550);
+    REQUIRE(bitmap->height() == 368);
+}
\ No newline at end of file
diff --git a/viewer/build/premake5_viewer.lua b/viewer/build/premake5_viewer.lua
index 9418d8c..725da54 100644
--- a/viewer/build/premake5_viewer.lua
+++ b/viewer/build/premake5_viewer.lua
@@ -79,7 +79,7 @@
     do
         includedirs({ rive_tess .. '/include', rive .. '/decoders/include' })
         defines({ 'RIVE_RENDERER_TESS' })
-        links({ 'rive_tess_renderer', 'rive_decoders', 'libpng', 'zlib', 'libjpeg' })
+        links({ 'rive_tess_renderer', 'rive_decoders', 'libpng', 'zlib', 'libjpeg', 'libwebp' })
         libdirs({ rive_tess .. '/build/%{cfg.system}/bin/%{cfg.buildcfg}' })
     end