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