Skip to content

Commit

Permalink
Add webp decoder.
Browse files Browse the repository at this point in the history
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 <[email protected]>
Co-authored-by: rivessamr <[email protected]>
  • Loading branch information
3 people committed Aug 22, 2024
1 parent a8b7f00 commit 6acee5a
Show file tree
Hide file tree
Showing 10 changed files with 259 additions and 9 deletions.
2 changes: 1 addition & 1 deletion .rive_head
Original file line number Diff line number Diff line change
@@ -1 +1 @@
e992059d6354434e91cde562e463f51bff7eac58
160d9eefb4d3e42b620053987fcc8654e98d40a6
10 changes: 8 additions & 2 deletions decoders/premake5_v2.lua
Original file line number Diff line number Diff line change
@@ -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' })

Expand All @@ -32,6 +37,7 @@ do
do
files({
'src/bitmap_decoder_thirdparty.cpp',
'src/decode_webp.cpp',
'src/decode_jpeg.cpp',
'src/decode_png.cpp',
})
Expand Down
2 changes: 1 addition & 1 deletion decoders/src/bitmap_decoder_thirdparty.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
68 changes: 68 additions & 0 deletions decoders/src/decode_webp.cpp
Original file line number Diff line number Diff line change
@@ -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));
}
4 changes: 0 additions & 4 deletions dependencies/premake5_libjpeg_v2.lua
Original file line number Diff line number Diff line change
@@ -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')

Expand Down
166 changes: 166 additions & 0 deletions dependencies/premake5_libwebp_v2.lua
Original file line number Diff line number Diff line change
@@ -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
1 change: 1 addition & 0 deletions dev/test/premake5.lua
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ do
'libpng',
'zlib',
'libjpeg',
'libwebp',
})

files({
Expand Down
Binary file added test/assets/1.webp
Binary file not shown.
13 changes: 13 additions & 0 deletions test/image_decoders_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
2 changes: 1 addition & 1 deletion viewer/build/premake5_viewer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 6acee5a

Please sign in to comment.