blob: 2ed8c9ae2b78e7fa09532b320c804b78d367c5f4 [file] [log] [blame]
// 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);
size_t pixelBufferSize = static_cast<size_t>(cinfo.output_width) *
static_cast<size_t>(cinfo.output_height) *
static_cast<size_t>(cinfo.output_components);
pixelBuffer = std::make_unique<uint8_t[]>(pixelBufferSize);
uint8_t* pixelWriteBuffer = (uint8_t*)pixelBuffer.get();
const uint8_t* pixelWriteBufferEnd = pixelWriteBuffer + pixelBufferSize;
// 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);
if (pixelWriteBuffer + row_stride > pixelWriteBufferEnd)
{
// memcpy would cause an overflow.
jpeg_finish_decompress(&cinfo);
jpeg_destroy_decompress(&cinfo);
return nullptr;
}
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));
}