Simple libjpeg Original libjpeg (turbo was forked from an older version of this). Note C api is still the same. Diffs= 45b998e68 Simple libjpeg (#7277) Co-authored-by: Luigi Rosso <luigi-rosso@users.noreply.github.com>
diff --git a/.rive_head b/.rive_head index 231082d..1070542 100644 --- a/.rive_head +++ b/.rive_head
@@ -1 +1 @@ -5ccc21fa3a5f726827da601f04cf234138b379df +45b998e68b849e0c8741bf254a2193e31844c405
diff --git a/decoders/premake5_v2.lua b/decoders/premake5_v2.lua index b85d38b..bf0a115 100644 --- a/decoders/premake5_v2.lua +++ b/decoders/premake5_v2.lua
@@ -3,14 +3,15 @@ rive = path.getabsolute('../') dofile(rive .. '/dependencies/premake5_libpng_v2.lua') +dofile(rive .. '/dependencies/premake5_libjpeg_v2.lua') project('rive_decoders') do - dependson('libpng') + dependson('libpng', 'zlib', 'libjpeg') kind('StaticLib') flags({ 'FatalWarnings' }) - includedirs({ 'include', '../include', libpng }) + includedirs({ 'include', '../include', libpng, libjpeg }) files({ 'src/**.cpp' }) end
diff --git a/decoders/src/bitmap_decoder.cpp b/decoders/src/bitmap_decoder.cpp index 243ce29..e497a6e 100644 --- a/decoders/src/bitmap_decoder.cpp +++ b/decoders/src/bitmap_decoder.cpp
@@ -39,7 +39,7 @@ size_t Bitmap::byteSize() const { return byteSize(m_PixelFormat); } std::unique_ptr<Bitmap> DecodePng(const uint8_t bytes[], size_t byteCount); -std::unique_ptr<Bitmap> DecodeJpeg(const uint8_t bytes[], size_t byteCount) { return nullptr; } +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; } using BitmapDecoder = std::unique_ptr<Bitmap> (*)(const uint8_t bytes[], size_t byteCount);
diff --git a/decoders/src/decode_jpeg.cpp b/decoders/src/decode_jpeg.cpp new file mode 100644 index 0000000..0eab5c7 --- /dev/null +++ b/decoders/src/decode_jpeg.cpp
@@ -0,0 +1,117 @@ +// Adapted from libjpeg-turbo's example: +// https://github.com/libjpeg-turbo/libjpeg-turbo/blob/main/example.c +#include "rive/decoders/bitmap_decoder.hpp" + +#include "jpeglib.h" +#include "jerror.h" + +#include <setjmp.h> +#include <algorithm> +#include <cassert> +#include <string.h> + +struct my_error_mgr +{ + struct jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +typedef struct my_error_mgr* my_error_ptr; + +void my_error_exit(j_common_ptr cinfo) +{ + // cinfo.err really points to a my_error_mgr struct, so coerce pointer + my_error_ptr myerr = (my_error_ptr)cinfo->err; + + // Always display the message. + // We could postpone this until after returning, if we chose. + (*cinfo->err->output_message)(cinfo); + + // Return control to the setjmp point + longjmp(myerr->setjmp_buffer, 1); +} + +std::unique_ptr<Bitmap> DecodeJpeg(const uint8_t bytes[], size_t byteCount) +{ + struct jpeg_decompress_struct cinfo; + struct my_error_mgr jerr; + + JSAMPARRAY buffer = nullptr; + std::unique_ptr<const uint8_t[]> pixelBuffer; + int row_stride; + + // Step 1: allocate and initialize JPEG decompression object. + + // We set up the normal JPEG error routines, then override error_exit. + cinfo.err = jpeg_std_error(&jerr.pub); + jerr.pub.error_exit = my_error_exit; + // Establish the setjmp return context for my_error_exit to use. + if (setjmp(jerr.setjmp_buffer)) + { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + jpeg_destroy_decompress(&cinfo); + return nullptr; + } + + // Now we can initialize the JPEG decompression object. + jpeg_create_decompress(&cinfo); + + // Step 2: specify data source + jpeg_mem_src(&cinfo, bytes, byteCount); + + // Step 3: read file parameters with jpeg_read_header() + + jpeg_read_header(&cinfo, TRUE); + + // Step 4: set parameters for decompression + + // always want 8 bit RGB + cinfo.data_precision = 8; + cinfo.out_color_space = JCS_RGB; + + // Step 5: Start decompressor + jpeg_start_decompress(&cinfo); + + /// Api worked as expected and gave us correct format even for jpeg 12 or 16 + assert(cinfo.data_precision == 8); + assert(cinfo.output_components == 3); + + pixelBuffer = std::make_unique<uint8_t[]>(cinfo.output_width * cinfo.output_height * + cinfo.output_components); + + uint8_t* pixelWriteBuffer = (uint8_t*)pixelBuffer.get(); + + // Samples per row in output buffer + row_stride = cinfo.output_width * cinfo.output_components; + // Make a one-row-high sample array that will go away when done with image + buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, JPOOL_IMAGE, row_stride, 1); + + // Step 6: while (scan lines remain to be read) + // jpeg_read_scanlines(...); + + // Here we use the library's state variable cinfo->output_scanline as the + // loop counter, so that we don't have to keep track ourselves. + // + while (cinfo.output_scanline < cinfo.output_height) + { + // jpeg_read_scanlines expects an array of pointers to scanlines. + // Here the array is only one element long, but you could ask for + // more than one scanline at a time if that's more convenient. + jpeg_read_scanlines(&cinfo, buffer, 1); + + memcpy(pixelWriteBuffer, buffer[0], row_stride); + pixelWriteBuffer += row_stride; + } + + // Step 7: Finish decompression + jpeg_finish_decompress(&cinfo); + + // Step 8: Release JPEG decompression object + jpeg_destroy_decompress(&cinfo); + + return std::make_unique<Bitmap>(cinfo.output_width, + cinfo.output_height, + Bitmap::PixelFormat::RGB, + std::move(pixelBuffer)); +} \ No newline at end of file
diff --git a/dependencies/jconfig.h b/dependencies/jconfig.h new file mode 100644 index 0000000..b0daaa1 --- /dev/null +++ b/dependencies/jconfig.h
@@ -0,0 +1,11 @@ +#define HAVE_PROTOTYPES +#define HAVE_UNSIGNED_CHAR +#define HAVE_UNSIGNED_SHORT +#undef CHAR_IS_UNSIGNED +#define HAVE_STDDEF_H +#define HAVE_STDLIB_H +#undef NEED_BSD_STRINGS +#undef NEED_SYS_TYPES_H +#undef NEED_FAR_POINTERS +#undef NEED_SHORT_EXTERNAL_NAMES +#undef INCOMPLETE_TYPES_BROKEN \ No newline at end of file
diff --git a/dependencies/premake5_libjpeg_v2.lua b/dependencies/premake5_libjpeg_v2.lua new file mode 100644 index 0000000..30be966 --- /dev/null +++ b/dependencies/premake5_libjpeg_v2.lua
@@ -0,0 +1,64 @@ +dofile('rive_build_config.lua') + +if _OPTIONS['no-rive-decoders'] then + return +end + +local dependency = require('dependency') +libjpeg = dependency.github('rive-app/libjpeg', 'v9f') + +project('libjpeg') +do + kind('StaticLib') + + includedirs({ libjpeg }) + + files({ + libjpeg .. '/jaricom.c', + libjpeg .. '/jcapimin.c', + libjpeg .. '/jcapistd.c', + libjpeg .. '/jcarith.c', + libjpeg .. '/jccoefct.c', + libjpeg .. '/jccolor.c', + libjpeg .. '/jcdctmgr.c', + libjpeg .. '/jchuff.c', + libjpeg .. '/jcinit.c', + libjpeg .. '/jcmainct.c', + libjpeg .. '/jcmarker.c', + libjpeg .. '/jcmaster.c', + libjpeg .. '/jcomapi.c', + libjpeg .. '/jcparam.c', + libjpeg .. '/jcprepct.c', + libjpeg .. '/jcsample.c', + libjpeg .. '/jctrans.c', + libjpeg .. '/jdapimin.c', + libjpeg .. '/jdapistd.c', + libjpeg .. '/jdarith.c', + libjpeg .. '/jdatadst.c', + libjpeg .. '/jdatasrc.c', + libjpeg .. '/jdcoefct.c', + libjpeg .. '/jdcolor.c', + libjpeg .. '/jddctmgr.c', + libjpeg .. '/jdhuff.c', + libjpeg .. '/jdinput.c', + libjpeg .. '/jdmainct.c', + libjpeg .. '/jdmarker.c', + libjpeg .. '/jdmaster.c', + libjpeg .. '/jdmerge.c', + libjpeg .. '/jdpostct.c', + libjpeg .. '/jdsample.c', + libjpeg .. '/jdtrans.c', + libjpeg .. '/jerror.c', + libjpeg .. '/jfdctflt.c', + libjpeg .. '/jfdctfst.c', + libjpeg .. '/jfdctint.c', + libjpeg .. '/jidctflt.c', + libjpeg .. '/jidctfst.c', + libjpeg .. '/jidctint.c', + libjpeg .. '/jquant1.c', + libjpeg .. '/jquant2.c', + libjpeg .. '/jutils.c', + libjpeg .. '/jmemmgr.c', + libjpeg .. '/jmemansi.c', + }) +end
diff --git a/dev/test/premake5.lua b/dev/test/premake5.lua index bef580b..7fe5e83 100644 --- a/dev/test/premake5.lua +++ b/dev/test/premake5.lua
@@ -10,6 +10,7 @@ }) dofile(path.join(path.getabsolute('../../'), 'premake5_v2.lua')) +dofile(path.join(path.getabsolute('../../decoders/'), 'premake5_v2.lua')) project('tests') do @@ -19,10 +20,19 @@ includedirs({ './include', '../../include', + '../../decoders/include', miniaudio, }) - links({ 'rive', 'rive_harfbuzz', 'rive_sheenbidi' }) + links({ + 'rive', + 'rive_harfbuzz', + 'rive_sheenbidi', + 'rive_decoders', + 'libpng', + 'zlib', + 'libjpeg', + }) files({ '../../test/**.cpp', -- the tests
diff --git a/test/assets/open_source.jpg b/test/assets/open_source.jpg new file mode 100644 index 0000000..710a4cf --- /dev/null +++ b/test/assets/open_source.jpg Binary files differ
diff --git a/test/assets/placeholder.png b/test/assets/placeholder.png new file mode 100644 index 0000000..9c3668f --- /dev/null +++ b/test/assets/placeholder.png Binary files differ
diff --git a/test/image_decoders_test.cpp b/test/image_decoders_test.cpp new file mode 100644 index 0000000..d9fe827 --- /dev/null +++ b/test/image_decoders_test.cpp
@@ -0,0 +1,29 @@ +#include "rive_file_reader.hpp" +#include "rive_testing.hpp" +#include "rive/decoders/bitmap_decoder.hpp" + +TEST_CASE("png file decodes correctly", "[image-decoder]") +{ + auto file = ReadFile("../../test/assets/placeholder.png"); + REQUIRE(file.size() == 1096); + + auto bitmap = Bitmap::decode(file.data(), file.size()); + + REQUIRE(bitmap != nullptr); + + REQUIRE(bitmap->width() == 226); + REQUIRE(bitmap->height() == 128); +} + +TEST_CASE("jpeg file decodes correctly", "[image-decoder]") +{ + auto file = ReadFile("../../test/assets/open_source.jpg"); + REQUIRE(file.size() == 8880); + + auto bitmap = Bitmap::decode(file.data(), file.size()); + + REQUIRE(bitmap != nullptr); + + REQUIRE(bitmap->width() == 350); + REQUIRE(bitmap->height() == 200); +}
diff --git a/test/rive_file_reader.hpp b/test/rive_file_reader.hpp index 6ba4c94..19001f0 100644 --- a/test/rive_file_reader.hpp +++ b/test/rive_file_reader.hpp
@@ -8,6 +8,21 @@ static rive::NoOpFactory gNoOpFactory; +static inline std::vector<uint8_t> ReadFile(const char path[]) +{ + FILE* fp = fopen(path, "rb"); + REQUIRE(fp != nullptr); + + fseek(fp, 0, SEEK_END); + const size_t length = ftell(fp); + fseek(fp, 0, SEEK_SET); + std::vector<uint8_t> bytes(length); + REQUIRE(fread(bytes.data(), 1, length, fp) == length); + fclose(fp); + + return bytes; +} + static inline std::unique_ptr<rive::File> ReadRiveFile(const char path[], rive::Factory* factory = nullptr, rive::FileAssetLoader* loader = nullptr, @@ -18,15 +33,7 @@ factory = &gNoOpFactory; } - FILE* fp = fopen(path, "rb"); - REQUIRE(fp != nullptr); - - fseek(fp, 0, SEEK_END); - const size_t length = ftell(fp); - fseek(fp, 0, SEEK_SET); - std::vector<uint8_t> bytes(length); - REQUIRE(fread(bytes.data(), 1, length, fp) == length); - fclose(fp); + std::vector<uint8_t> bytes = ReadFile(path); rive::ImportResult result; auto file = rive::File::import(bytes, factory, &result, loader);
diff --git a/viewer/build/premake5_viewer.lua b/viewer/build/premake5_viewer.lua index 1f7b2d9..741c497 100644 --- a/viewer/build/premake5_viewer.lua +++ b/viewer/build/premake5_viewer.lua
@@ -78,7 +78,7 @@ do includedirs({ rive_tess .. '/include', rive .. '/decoders/include' }) defines({ 'RIVE_RENDERER_TESS' }) - links({ 'rive_tess_renderer', 'rive_decoders', 'libpng', 'zlib' }) + links({ 'rive_tess_renderer', 'rive_decoders', 'libpng', 'zlib', 'libjpeg' }) libdirs({ rive_tess .. '/build/%{cfg.system}/bin/%{cfg.buildcfg}' }) end