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, ¤tFrame))
+ {
+ 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(¤tFrame);
+ WebPDemuxDelete(demuxer);
+ return nullptr;
+ }
+
+ WebPDemuxReleaseIterator(¤tFrame);
+ 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