diff --git a/.rive_head b/.rive_head index f67274a7..dce3e71a 100644 --- a/.rive_head +++ b/.rive_head @@ -1 +1 @@ -e992059d6354434e91cde562e463f51bff7eac58 +160d9eefb4d3e42b620053987fcc8654e98d40a6 diff --git a/decoders/premake5_v2.lua b/decoders/premake5_v2.lua index 7f7c37b7..6c90d953 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 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 6dc5a91e..37c5a667 100644 --- a/decoders/src/bitmap_decoder_thirdparty.cpp +++ b/decoders/src/bitmap_decoder_thirdparty.cpp @@ -10,7 +10,7 @@ std::unique_ptr DecodePng(const uint8_t bytes[], size_t byteCount); std::unique_ptr DecodeJpeg(const uint8_t bytes[], size_t byteCount); -std::unique_ptr DecodeWebP(const uint8_t bytes[], size_t byteCount) { return nullptr; } +std::unique_ptr DecodeWebP(const uint8_t bytes[], size_t byteCount); using BitmapDecoder = std::unique_ptr (*)(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 00000000..f2617982 --- /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 +#include +#include + +std::unique_ptr 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(width) * static_cast(height) * static_cast(4); + std::unique_ptr pixelBuffer = std::make_unique(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(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 34ba3547..3ed2ecc0 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 00000000..00eb5f18 --- /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 dea984ef..e6e2cc73 100644 --- a/dev/test/premake5.lua +++ b/dev/test/premake5.lua @@ -36,6 +36,7 @@ do 'libpng', 'zlib', 'libjpeg', + 'libwebp', }) files({ diff --git a/test/assets/1.webp b/test/assets/1.webp new file mode 100644 index 00000000..122741b6 Binary files /dev/null and b/test/assets/1.webp differ diff --git a/test/image_decoders_test.cpp b/test/image_decoders_test.cpp index 4d6a8216..024115b8 100644 --- a/test/image_decoders_test.cpp +++ b/test/image_decoders_test.cpp @@ -64,3 +64,16 @@ TEST_CASE("bad png file doesn't cause an overflow", "[image-decoder]") 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 9418d8c3..725da540 100644 --- a/viewer/build/premake5_viewer.lua +++ b/viewer/build/premake5_viewer.lua @@ -79,7 +79,7 @@ do 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