#include <SDL3_image/SDL_image.h>
#include <stdio.h>
#include <setjmp.h>
#ifndef SAVE_JPG
#define SAVE_JPG 1
#endif
#if defined(USE_STBIMAGE)
#undef WANT_JPEGLIB
#elif defined(SDL_IMAGE_USE_COMMON_BACKEND)
#define WANT_JPEGLIB
#elif defined(SDL_IMAGE_USE_WIC_BACKEND)
#undef WANT_JPEGLIB
#elif defined(__APPLE__) && defined(JPG_USES_IMAGEIO)
#undef WANT_JPEGLIB
#else
#define WANT_JPEGLIB
#endif
#ifdef LOAD_JPG
#ifdef WANT_JPEGLIB
#define USE_JPEGLIB
#if defined(LOAD_JPG_DYNAMIC) && defined(SDL_ELF_NOTE_DLOPEN)
SDL_ELF_NOTE_DLOPEN(
"jpeg",
"Support for JPEG images using libjpg",
SDL_ELF_NOTE_DLOPEN_PRIORITY_SUGGESTED,
LOAD_JPG_DYNAMIC
)
#endif
#include <jpeglib.h>
#ifdef JPEG_TRUE
typedef JPEG_boolean boolean;
#define TRUE JPEG_TRUE
#define FALSE JPEG_FALSE
#endif
#define FAST_IS_JPEG
static struct {
int loaded;
void *handle;
void (*jpeg_calc_output_dimensions) (j_decompress_ptr cinfo);
void (*jpeg_CreateDecompress) (j_decompress_ptr cinfo, int version, size_t structsize);
void (*jpeg_destroy_decompress) (j_decompress_ptr cinfo);
boolean (*jpeg_finish_decompress) (j_decompress_ptr cinfo);
int (*jpeg_read_header) (j_decompress_ptr cinfo, boolean require_image);
JDIMENSION (*jpeg_read_scanlines) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines);
boolean (*jpeg_resync_to_restart) (j_decompress_ptr cinfo, int desired);
boolean (*jpeg_start_decompress) (j_decompress_ptr cinfo);
void (*jpeg_CreateCompress) (j_compress_ptr cinfo, int version, size_t structsize);
void (*jpeg_start_compress) (j_compress_ptr cinfo, boolean write_all_tables);
void (*jpeg_set_quality) (j_compress_ptr cinfo, int quality, boolean force_baseline);
void (*jpeg_set_defaults) (j_compress_ptr cinfo);
JDIMENSION (*jpeg_write_scanlines) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines);
void (*jpeg_finish_compress) (j_compress_ptr cinfo);
void (*jpeg_destroy_compress) (j_compress_ptr cinfo);
struct jpeg_error_mgr * (*jpeg_std_error) (struct jpeg_error_mgr * err);
} lib;
#ifdef LOAD_JPG_DYNAMIC
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = (SIG) SDL_LoadFunction(lib.handle, #FUNC); \
if (lib.FUNC == NULL) { SDL_UnloadObject(lib.handle); return false; }
#else
#define FUNCTION_LOADER(FUNC, SIG) \
lib.FUNC = FUNC;
#endif
static bool IMG_InitJPG(void)
{
if ( lib.loaded == 0 ) {
#ifdef LOAD_JPG_DYNAMIC
lib.handle = SDL_LoadObject(LOAD_JPG_DYNAMIC);
if ( lib.handle == NULL ) {
return false;
}
#endif
FUNCTION_LOADER(jpeg_calc_output_dimensions, void (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_CreateDecompress, void (*) (j_decompress_ptr cinfo, int version, size_t structsize))
FUNCTION_LOADER(jpeg_destroy_decompress, void (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_finish_decompress, boolean (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_read_header, int (*) (j_decompress_ptr cinfo, boolean require_image))
FUNCTION_LOADER(jpeg_read_scanlines, JDIMENSION (*) (j_decompress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION max_lines))
FUNCTION_LOADER(jpeg_resync_to_restart, boolean (*) (j_decompress_ptr cinfo, int desired))
FUNCTION_LOADER(jpeg_start_decompress, boolean (*) (j_decompress_ptr cinfo))
FUNCTION_LOADER(jpeg_CreateCompress, void (*) (j_compress_ptr cinfo, int version, size_t structsize))
FUNCTION_LOADER(jpeg_start_compress, void (*) (j_compress_ptr cinfo, boolean write_all_tables))
FUNCTION_LOADER(jpeg_set_quality, void (*) (j_compress_ptr cinfo, int quality, boolean force_baseline))
FUNCTION_LOADER(jpeg_set_defaults, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_write_scanlines, JDIMENSION (*) (j_compress_ptr cinfo, JSAMPARRAY scanlines, JDIMENSION num_lines))
FUNCTION_LOADER(jpeg_finish_compress, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_destroy_compress, void (*) (j_compress_ptr cinfo))
FUNCTION_LOADER(jpeg_std_error, struct jpeg_error_mgr * (*) (struct jpeg_error_mgr * err))
}
++lib.loaded;
return true;
}
#if 0#endif
bool IMG_isJPG(SDL_IOStream *src)
{
Sint64 start;
bool is_JPG;
bool in_scan;
Uint8 magic[4];
if (!src) {
return false;
}
start = SDL_TellIO(src);
is_JPG = false;
in_scan = false;
if (SDL_ReadIO(src, magic, 2) == 2) {
if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
is_JPG = true;
while (is_JPG) {
if (SDL_ReadIO(src, magic, 2) != 2) {
is_JPG = false;
} else if ( (magic[0] != 0xFF) && !in_scan ) {
is_JPG = false;
} else if ( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
SDL_SeekIO(src, -1, SDL_IO_SEEK_CUR);
} else if (magic[1] == 0xD9) {
break;
} else if ( in_scan && (magic[1] == 0x00) ) {
} else if ( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
} else if (SDL_ReadIO(src, magic+2, 2) != 2) {
is_JPG = false;
} else {
Sint64 innerStart;
Uint32 size;
Sint64 end;
innerStart = SDL_TellIO(src);
size = (magic[2] << 8) + magic[3];
end = SDL_SeekIO(src, size-2, SDL_IO_SEEK_CUR);
if ( end != innerStart + size - 2 ) {
is_JPG = false;
}
if ( magic[1] == 0xDA ) {
#ifdef FAST_IS_JPEG
break;
#else
in_scan = true;
#endif
}
}
}
}
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
return is_JPG;
}
#define INPUT_BUFFER_SIZE 4096
typedef struct {
struct jpeg_source_mgr pub;
SDL_IOStream *ctx;
Uint8 buffer[INPUT_BUFFER_SIZE];
} my_source_mgr;
static void init_source (j_decompress_ptr cinfo)
{
return;
}
static boolean fill_input_buffer (j_decompress_ptr cinfo)
{
my_source_mgr * src = (my_source_mgr *) cinfo->src;
size_t nbytes;
nbytes = SDL_ReadIO(src->ctx, src->buffer, INPUT_BUFFER_SIZE);
if (nbytes == 0) {
src->buffer[0] = (Uint8) 0xFF;
src->buffer[1] = (Uint8) JPEG_EOI;
nbytes = 2;
}
src->pub.next_input_byte = src->buffer;
src->pub.bytes_in_buffer = nbytes;
return TRUE;
}
static void skip_input_data (j_decompress_ptr cinfo, long num_bytes)
{
my_source_mgr * src = (my_source_mgr *) cinfo->src;
if (num_bytes > 0) {
while (num_bytes > (long) src->pub.bytes_in_buffer) {
num_bytes -= (long) src->pub.bytes_in_buffer;
(void) src->pub.fill_input_buffer(cinfo);
}
src->pub.next_input_byte += (size_t) num_bytes;
src->pub.bytes_in_buffer -= (size_t) num_bytes;
}
}
static void term_source (j_decompress_ptr cinfo)
{
return;
}
static void jpeg_SDL_IO_src (j_decompress_ptr cinfo, SDL_IOStream *ctx)
{
my_source_mgr *src;
if (cinfo->src == NULL) {
cinfo->src = (struct jpeg_source_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
sizeof(my_source_mgr));
src = (my_source_mgr *) cinfo->src;
}
src = (my_source_mgr *) cinfo->src;
src->pub.init_source = init_source;
src->pub.fill_input_buffer = fill_input_buffer;
src->pub.skip_input_data = skip_input_data;
src->pub.resync_to_restart = lib.jpeg_resync_to_restart;
src->pub.term_source = term_source;
src->ctx = ctx;
src->pub.bytes_in_buffer = 0;
src->pub.next_input_byte = NULL;
}
struct my_error_mgr {
struct jpeg_error_mgr errmgr;
jmp_buf escape;
};
static void my_error_exit(j_common_ptr cinfo)
{
struct my_error_mgr *err = (struct my_error_mgr *)cinfo->err;
longjmp(err->escape, 1);
}
static void output_no_message(j_common_ptr cinfo)
{
}
struct loadjpeg_vars {
const char *error;
SDL_Surface *surface;
struct jpeg_decompress_struct cinfo;
struct my_error_mgr jerr;
};
static bool LIBJPEG_LoadJPG_IO(SDL_IOStream *src, struct loadjpeg_vars *vars)
{
JSAMPROW rowptr[1];
vars->cinfo.err = lib.jpeg_std_error(&vars->jerr.errmgr);
vars->jerr.errmgr.error_exit = my_error_exit;
vars->jerr.errmgr.output_message = output_no_message;
if (setjmp(vars->jerr.escape)) {
lib.jpeg_destroy_decompress(&vars->cinfo);
vars->error = "JPEG loading error";
return false;
}
lib.jpeg_create_decompress(&vars->cinfo);
jpeg_SDL_IO_src(&vars->cinfo, src);
lib.jpeg_read_header(&vars->cinfo, TRUE);
if (vars->cinfo.num_components == 4) {
vars->cinfo.out_color_space = JCS_CMYK;
vars->cinfo.quantize_colors = FALSE;
lib.jpeg_calc_output_dimensions(&vars->cinfo);
vars->surface = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGBA32);
} else {
vars->cinfo.out_color_space = JCS_RGB;
vars->cinfo.quantize_colors = FALSE;
#ifdef FAST_JPEG
vars->cinfo.scale_num = 1;
vars->cinfo.scale_denom = 1;
vars->cinfo.dct_method = JDCT_FASTEST;
vars->cinfo.do_fancy_upsampling = FALSE;
#endif
lib.jpeg_calc_output_dimensions(&vars->cinfo);
vars->surface = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGB24);
}
if (!vars->surface) {
lib.jpeg_destroy_decompress(&vars->cinfo);
return false;
}
lib.jpeg_start_decompress(&vars->cinfo);
while (vars->cinfo.output_scanline < vars->cinfo.output_height) {
rowptr[0] = (JSAMPROW)(Uint8 *)vars->surface->pixels +
vars->cinfo.output_scanline * vars->surface->pitch;
lib.jpeg_read_scanlines(&vars->cinfo, rowptr, (JDIMENSION) 1);
}
lib.jpeg_finish_decompress(&vars->cinfo);
lib.jpeg_destroy_decompress(&vars->cinfo);
if (vars->cinfo.num_components == 4) {
SDL_Surface *output = SDL_CreateSurface(vars->cinfo.output_width, vars->cinfo.output_height, SDL_PIXELFORMAT_RGB24);
if (!output) {
return false;
}
SDL_BlitSurface(vars->surface, NULL, output, NULL);
SDL_DestroySurface(vars->surface);
vars->surface = output;
}
return true;
}
SDL_Surface *IMG_LoadJPG_IO(SDL_IOStream *src)
{
Sint64 start;
struct loadjpeg_vars vars;
if (!src) {
return NULL;
}
if (!IMG_InitJPG()) {
return NULL;
}
start = SDL_TellIO(src);
SDL_zero(vars);
if (LIBJPEG_LoadJPG_IO(src, &vars)) {
return vars.surface;
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
if (vars.surface) {
SDL_DestroySurface(vars.surface);
}
if (vars.error) {
SDL_SetError("%s", vars.error);
}
return NULL;
}
#define OUTPUT_BUFFER_SIZE 4096
typedef struct {
struct jpeg_destination_mgr pub;
SDL_IOStream *ctx;
Uint8 buffer[OUTPUT_BUFFER_SIZE];
} my_destination_mgr;
static void init_destination(j_compress_ptr cinfo)
{
return;
}
static boolean empty_output_buffer(j_compress_ptr cinfo)
{
my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
SDL_WriteIO(dest->ctx, dest->buffer, OUTPUT_BUFFER_SIZE);
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
return TRUE;
}
static void term_destination(j_compress_ptr cinfo)
{
my_destination_mgr * dest = (my_destination_mgr *)cinfo->dest;
SDL_WriteIO(dest->ctx, dest->buffer, OUTPUT_BUFFER_SIZE - dest->pub.free_in_buffer);
}
static void jpeg_SDL_IO_dest(j_compress_ptr cinfo, SDL_IOStream *ctx)
{
my_destination_mgr *dest;
if (cinfo->dest == NULL) {
cinfo->dest = (struct jpeg_destination_mgr *)
(*cinfo->mem->alloc_small) ((j_common_ptr)cinfo, JPOOL_PERMANENT,
sizeof(my_destination_mgr));
dest = (my_destination_mgr *)cinfo->dest;
}
dest = (my_destination_mgr *)cinfo->dest;
dest->pub.init_destination = init_destination;
dest->pub.empty_output_buffer = empty_output_buffer;
dest->pub.term_destination = term_destination;
dest->ctx = ctx;
dest->pub.next_output_byte = dest->buffer;
dest->pub.free_in_buffer = OUTPUT_BUFFER_SIZE;
}
struct savejpeg_vars
{
struct jpeg_compress_struct cinfo;
struct my_error_mgr jerr;
Sint64 original_offset;
};
static bool JPEG_SaveJPEG_IO(struct savejpeg_vars *vars, SDL_Surface *jpeg_surface, SDL_IOStream *dst, int quality)
{
vars->cinfo.err = lib.jpeg_std_error(&vars->jerr.errmgr);
vars->jerr.errmgr.error_exit = my_error_exit;
vars->jerr.errmgr.output_message = output_no_message;
vars->original_offset = SDL_TellIO(dst);
if (setjmp(vars->jerr.escape)) {
lib.jpeg_destroy_compress(&vars->cinfo);
SDL_SeekIO(dst, vars->original_offset, SDL_IO_SEEK_SET);
return SDL_SetError("Error saving JPEG with libjpeg");
}
lib.jpeg_create_compress(&vars->cinfo);
jpeg_SDL_IO_dest(&vars->cinfo, dst);
vars->cinfo.image_width = jpeg_surface->w;
vars->cinfo.image_height = jpeg_surface->h;
vars->cinfo.in_color_space = JCS_RGB;
vars->cinfo.input_components = 3;
lib.jpeg_set_defaults(&vars->cinfo);
lib.jpeg_set_quality(&vars->cinfo, quality, TRUE);
lib.jpeg_start_compress(&vars->cinfo, TRUE);
while (vars->cinfo.next_scanline < vars->cinfo.image_height) {
JSAMPROW row_pointer[1];
int offset = vars->cinfo.next_scanline * jpeg_surface->pitch;
row_pointer[0] = ((Uint8*)jpeg_surface->pixels) + offset;
lib.jpeg_write_scanlines(&vars->cinfo, row_pointer, 1);
}
lib.jpeg_finish_compress(&vars->cinfo);
lib.jpeg_destroy_compress(&vars->cinfo);
return true;
}
static bool IMG_SaveJPG_IO_jpeglib(SDL_Surface *surface, SDL_IOStream *dst, int quality)
{
struct savejpeg_vars vars;
static const Uint32 jpg_format = SDL_PIXELFORMAT_RGB24;
SDL_Surface* jpeg_surface = surface;
bool result;
if (!IMG_InitJPG()) {
return false;
}
if (surface->format != jpg_format) {
jpeg_surface = SDL_ConvertSurface(surface, jpg_format);
if (!jpeg_surface) {
return false;
}
}
SDL_zero(vars);
result = JPEG_SaveJPEG_IO(&vars, jpeg_surface, dst, quality);
if (jpeg_surface != surface) {
SDL_DestroySurface(jpeg_surface);
}
return result;
}
#elif defined(USE_STBIMAGE)
extern SDL_Surface *IMG_LoadSTB_IO(SDL_IOStream *src);
#define FAST_IS_JPEG
bool IMG_isJPG(SDL_IOStream *src)
{
Sint64 start;
bool is_JPG;
bool in_scan;
Uint8 magic[4];
if (!src) {
return false;
}
start = SDL_TellIO(src);
is_JPG = false;
in_scan = false;
if (SDL_ReadIO(src, magic, 2) == 2) {
if ( (magic[0] == 0xFF) && (magic[1] == 0xD8) ) {
is_JPG = true;
while (is_JPG) {
if (SDL_ReadIO(src, magic, 2) != 2) {
is_JPG = false;
} else if ( (magic[0] != 0xFF) && !in_scan ) {
is_JPG = false;
} else if ( (magic[0] != 0xFF) || (magic[1] == 0xFF) ) {
SDL_SeekIO(src, -1, SDL_IO_SEEK_CUR);
} else if (magic[1] == 0xD9) {
break;
} else if ( in_scan && (magic[1] == 0x00) ) {
} else if ( (magic[1] >= 0xD0) && (magic[1] < 0xD9) ) {
} else if (SDL_ReadIO(src, magic+2, 2) != 2) {
is_JPG = false;
} else {
Sint64 innerStart;
Uint32 size;
Sint64 end;
innerStart = SDL_TellIO(src);
size = (magic[2] << 8) + magic[3];
end = SDL_SeekIO(src, size-2, SDL_IO_SEEK_CUR);
if ( end != innerStart + size - 2 ) {
is_JPG = false;
}
if ( magic[1] == 0xDA ) {
#ifdef FAST_IS_JPEG
break;
#else
in_scan = true;
#endif
}
}
}
}
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
return is_JPG;
}
SDL_Surface *IMG_LoadJPG_IO(SDL_IOStream *src)
{
return IMG_LoadSTB_IO(src);
}
#endif
#else
bool IMG_isJPG(SDL_IOStream *src)
{
return false;
}
SDL_Surface *IMG_LoadJPG_IO(SDL_IOStream *src)
{
SDL_SetError("SDL_image built without JPG support");
return NULL;
}
#endif
#if SAVE_JPG && (defined(LOAD_JPG_DYNAMIC) || !defined(WANT_JPEGLIB))
#ifdef assert
#undef assert
#endif
#ifdef memcpy
#undef memcpy
#endif
#ifdef memset
#undef memset
#endif
#define assert SDL_assert
#define memcpy SDL_memcpy
#define memset SDL_memset
#define ceilf SDL_ceilf
#define floorf SDL_floorf
#define cosf SDL_cosf
#define tje_log SDL_Log
#define TJE_IMPLEMENTATION
#include "tiny_jpeg.h"
static void IMG_SaveJPG_IO_tinyjpeg_callback(void* context, void* data, int size)
{
SDL_WriteIO((SDL_IOStream*) context, data, size);
}
static bool IMG_SaveJPG_IO_tinyjpeg(SDL_Surface *surface, SDL_IOStream *dst, int quality)
{
static const Uint32 jpg_format = SDL_PIXELFORMAT_RGB24;
SDL_Surface* jpeg_surface = surface;
bool result = false;
if (surface->format != jpg_format) {
jpeg_surface = SDL_ConvertSurface(surface, jpg_format);
if (!jpeg_surface) {
return false;
}
}
if (quality < 34) quality = 1;
else if (quality < 67) quality = 2;
else quality = 3;
result = tje_encode_with_func(
IMG_SaveJPG_IO_tinyjpeg_callback,
dst,
quality,
jpeg_surface->w,
jpeg_surface->h,
3,
jpeg_surface->pixels,
jpeg_surface->pitch
);
if (jpeg_surface != surface) {
SDL_DestroySurface(jpeg_surface);
}
if (!result) {
SDL_SetError("tinyjpeg error");
}
return result;
}
#endif
#if SAVE_JPG
bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
{
bool result = false;
if (!surface) {
SDL_InvalidParamError("surface");
goto done;
}
if (!dst) {
SDL_InvalidParamError("dst");
goto done;
}
#ifdef USE_JPEGLIB
if (!result) {
result = IMG_SaveJPG_IO_jpeglib(surface, dst, quality);
}
#endif
#if defined(LOAD_JPG_DYNAMIC) || !defined(WANT_JPEGLIB)
if (!result) {
result = IMG_SaveJPG_IO_tinyjpeg(surface, dst, quality);
}
#endif
done:
if (closeio) {
result &= SDL_CloseIO(dst);
}
return result;
}
bool IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
{
SDL_IOStream *dst = SDL_IOFromFile(file, "wb");
if (dst) {
return IMG_SaveJPG_IO(surface, dst, true, quality);
} else {
return false;
}
}
#else
bool IMG_SaveJPG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio, int quality)
{
return SDL_SetError("SDL_image built without JPG save support");
}
bool IMG_SaveJPG(SDL_Surface *surface, const char *file, int quality)
{
return SDL_SetError("SDL_image built without JPG save support");
}
#endif