#include "SDL_internal.h"
#include "SDL_stb_c.h"
#include "SDL_surface_c.h"
#include "SDL_yuv_c.h"
#ifndef SDL_DISABLE_STB
#define SDL_HAVE_STB
#endif
#ifdef SDL_HAVE_STB
#define malloc SDL_malloc
#define realloc SDL_realloc
#define free SDL_free
#undef memcpy
#define memcpy SDL_memcpy
#undef memset
#define memset SDL_memset
#undef strcmp
#define strcmp SDL_strcmp
#undef strncmp
#define strncmp SDL_strncmp
#define strtol SDL_strtol
#define abs SDL_abs
#define pow SDL_pow
#define ldexp SDL_scalbn
#define STB_IMAGE_STATIC
#define STBI_NO_THREAD_LOCALS
#define STBI_FAILURE_USERMSG
#if defined(SDL_NEON_INTRINSICS)
#define STBI_NEON
#endif
#define STBI_ONLY_PNG
#define STBI_ONLY_JPEG
#define STBI_NO_GIF
#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STBI_NO_STDIO
#define STBI_ASSERT SDL_assert
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define MZ_ASSERT(x) SDL_assert(x)
#if SDL_BYTEORDER == SDL_LIL_ENDIAN
#define MINIZ_LITTLE_ENDIAN 1
#else
#define MINIZ_LITTLE_ENDIAN 0
#endif
#define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 0
#define MINIZ_SDL_NOUNUSED
#include "miniz.h"
#undef memset
#endif
#ifdef SDL_HAVE_STB
static bool SDL_ConvertPixels_MJPG_to_NV12(int width, int height, const void *src, int src_pitch, void *dst, int dst_pitch)
{
int w = 0, h = 0, format = 0;
stbi__context s;
stbi__start_mem(&s, src, src_pitch);
stbi__result_info ri;
SDL_zero(ri);
ri.bits_per_channel = 8;
ri.channel_order = STBI_ORDER_RGB;
ri.num_channels = 0;
stbi__nv12 nv12;
nv12.w = width;
nv12.h = height;
nv12.pitch = dst_pitch;
nv12.y = (stbi_uc *)dst;
nv12.uv = nv12.y + (nv12.h * nv12.pitch);
void *pixels = stbi__jpeg_load(&s, &w, &h, &format, 4, &nv12, &ri);
if (!pixels) {
return false;
}
return true;
}
#endif
bool SDL_ConvertPixels_STB(int width, int height,
SDL_PixelFormat src_format, SDL_Colorspace src_colorspace, SDL_PropertiesID src_properties, const void *src, int src_pitch,
SDL_PixelFormat dst_format, SDL_Colorspace dst_colorspace, SDL_PropertiesID dst_properties, void *dst, int dst_pitch)
{
#ifdef SDL_HAVE_STB
if (src_format == SDL_PIXELFORMAT_MJPG) {
if (dst_format == SDL_PIXELFORMAT_NV12) {
return SDL_ConvertPixels_MJPG_to_NV12(width, height, src, src_pitch, dst, dst_pitch);
} else if (
dst_format == SDL_PIXELFORMAT_YV12 ||
dst_format == SDL_PIXELFORMAT_IYUV ||
dst_format == SDL_PIXELFORMAT_YUY2 ||
dst_format == SDL_PIXELFORMAT_UYVY ||
dst_format == SDL_PIXELFORMAT_YVYU ||
dst_format == SDL_PIXELFORMAT_NV21 ||
dst_format == SDL_PIXELFORMAT_P010
) {
size_t temp_size = 0;
size_t temp_pitch = 0;
if (!SDL_CalculateYUVSize(dst_format, width, height, &temp_size, &temp_pitch)) {
return false;
}
void *temp_pixels = SDL_malloc(temp_size);
if (!temp_pixels) {
return false;
}
if (!SDL_ConvertPixels_MJPG_to_NV12(width, height, src, src_pitch, temp_pixels, (int)temp_pitch)) {
SDL_free(temp_pixels);
return false;
}
bool result = SDL_ConvertPixelsAndColorspace(width, height, SDL_PIXELFORMAT_NV12, src_colorspace, 0 , temp_pixels, (int)temp_pitch, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
SDL_free(temp_pixels);
return result;
}
}
bool result;
int w = 0, h = 0, format = 0;
int len = (src_format == SDL_PIXELFORMAT_MJPG) ? src_pitch : (height * src_pitch);
void *pixels = stbi_load_from_memory(src, len, &w, &h, &format, 4);
if (!pixels) {
return false;
}
if (w == width && h == height) {
result = SDL_ConvertPixelsAndColorspace(w, h, SDL_PIXELFORMAT_RGBA32, SDL_COLORSPACE_SRGB, 0, pixels, width * 4, dst_format, dst_colorspace, dst_properties, dst, dst_pitch);
} else {
result = SDL_SetError("Expected image size %dx%d, actual size %dx%d", width, height, w, h);
}
stbi_image_free(pixels);
return result;
#else
return SDL_SetError("SDL not built with STB image support");
#endif
}
#ifdef SDL_HAVE_STB
static int IMG_LoadSTB_IO_read(void *user, char *data, int size)
{
size_t amount = SDL_ReadIO((SDL_IOStream*)user, data, size);
return (int)amount;
}
static void IMG_LoadSTB_IO_skip(void *user, int n)
{
SDL_SeekIO((SDL_IOStream*)user, n, SDL_IO_SEEK_CUR);
}
static int IMG_LoadSTB_IO_eof(void *user)
{
SDL_IOStream *src = (SDL_IOStream*)user;
return SDL_GetIOStatus(src) == SDL_IO_STATUS_EOF;
}
static SDL_Surface *SDL_LoadSTB_IO(SDL_IOStream *src)
{
Sint64 start;
Uint8 magic[26];
int w, h, format;
stbi_uc *pixels;
stbi_io_callbacks rw_callbacks;
SDL_Surface *surface = NULL;
bool use_palette = false;
unsigned int palette_colors[256];
start = SDL_TellIO(src);
if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) {
const Uint8 PNG_COLOR_INDEXED = 3;
if (magic[0] == 0x89 &&
magic[1] == 'P' &&
magic[2] == 'N' &&
magic[3] == 'G' &&
magic[12] == 'I' &&
magic[13] == 'H' &&
magic[14] == 'D' &&
magic[15] == 'R' &&
magic[25] == PNG_COLOR_INDEXED) {
use_palette = true;
}
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
rw_callbacks.read = IMG_LoadSTB_IO_read;
rw_callbacks.skip = IMG_LoadSTB_IO_skip;
rw_callbacks.eof = IMG_LoadSTB_IO_eof;
w = h = format = 0;
if (use_palette) {
SDL_memset(palette_colors, 0xff, sizeof(palette_colors));
pixels = stbi_load_from_callbacks_with_palette(
&rw_callbacks,
src,
&w,
&h,
palette_colors,
SDL_arraysize(palette_colors)
);
} else {
pixels = stbi_load_from_callbacks(
&rw_callbacks,
src,
&w,
&h,
&format,
STBI_default
);
}
if (!pixels) {
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
return NULL;
}
if (use_palette) {
surface = SDL_CreateSurfaceFrom(
w,
h,
SDL_PIXELFORMAT_INDEX8,
pixels,
w
);
if (surface) {
bool has_colorkey = false;
int colorkey_index = -1;
bool has_alpha = false;
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
if (palette) {
int i;
Uint8 *palette_bytes = (Uint8 *)palette_colors;
for (i = 0; i < palette->ncolors; i++) {
palette->colors[i].r = *palette_bytes++;
palette->colors[i].g = *palette_bytes++;
palette->colors[i].b = *palette_bytes++;
palette->colors[i].a = *palette_bytes++;
if (palette->colors[i].a != SDL_ALPHA_OPAQUE) {
if (palette->colors[i].a == SDL_ALPHA_TRANSPARENT && !has_colorkey) {
has_colorkey = true;
colorkey_index = i;
} else {
has_alpha = true;
}
}
}
}
if (has_alpha) {
SDL_SetSurfaceBlendMode(surface, SDL_BLENDMODE_BLEND);
} else if (has_colorkey) {
SDL_SetSurfaceColorKey(surface, true, colorkey_index);
}
surface->flags &= ~SDL_SURFACE_PREALLOCATED;
}
} else if (format == STBI_grey || format == STBI_rgb || format == STBI_rgb_alpha) {
surface = SDL_CreateSurfaceFrom(
w,
h,
(format == STBI_rgb_alpha) ? SDL_PIXELFORMAT_RGBA32 :
(format == STBI_rgb) ? SDL_PIXELFORMAT_RGB24 :
SDL_PIXELFORMAT_INDEX8,
pixels,
w * format
);
if (surface) {
if (surface->format == SDL_PIXELFORMAT_INDEX8) {
SDL_Palette *palette = SDL_CreateSurfacePalette(surface);
if (palette) {
int i;
for (i = 0; i < palette->ncolors; i++) {
palette->colors[i].r = (Uint8)i;
palette->colors[i].g = (Uint8)i;
palette->colors[i].b = (Uint8)i;
}
}
}
surface->flags &= ~SDL_SURFACE_PREALLOCATED;
}
} else if (format == STBI_grey_alpha) {
surface = SDL_CreateSurface(w, h, SDL_PIXELFORMAT_RGBA32);
if (surface) {
Uint8 *src_ptr = pixels;
Uint8 *dst = (Uint8 *)surface->pixels;
int skip = surface->pitch - (surface->w * 4);
int row, col;
for (row = 0; row < h; ++row) {
for (col = 0; col < w; ++col) {
Uint8 c = *src_ptr++;
Uint8 a = *src_ptr++;
*dst++ = c;
*dst++ = c;
*dst++ = c;
*dst++ = a;
}
dst += skip;
}
stbi_image_free(pixels);
}
} else {
SDL_SetError("Unknown image format: %d", format);
}
if (!surface) {
stbi_image_free(pixels);
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
}
return surface;
}
#endif
bool SDL_IsPNG(SDL_IOStream *src)
{
Sint64 start;
Uint8 magic[4];
bool is_PNG;
is_PNG = false;
start = SDL_TellIO(src);
if (start >= 0) {
if (SDL_ReadIO(src, magic, sizeof(magic)) == sizeof(magic)) {
if (magic[0] == 0x89 &&
magic[1] == 'P' &&
magic[2] == 'N' &&
magic[3] == 'G') {
is_PNG = true;
}
}
SDL_SeekIO(src, start, SDL_IO_SEEK_SET);
}
return is_PNG;
}
SDL_Surface *SDL_LoadPNG_IO(SDL_IOStream *src, bool closeio)
{
SDL_Surface *surface = NULL;
CHECK_PARAM(!src) {
SDL_InvalidParamError("src");
goto done;
}
if (!SDL_IsPNG(src)) {
SDL_SetError("File is not a PNG file");
goto done;
}
#ifdef SDL_HAVE_STB
surface = SDL_LoadSTB_IO(src);
#else
SDL_SetError("SDL not built with STB image support");
#endif
done:
if (src && closeio) {
SDL_CloseIO(src);
}
return surface;
}
SDL_Surface *SDL_LoadPNG(const char *file)
{
SDL_IOStream *stream = SDL_IOFromFile(file, "rb");
if (!stream) {
return NULL;
}
return SDL_LoadPNG_IO(stream, true);
}
bool SDL_SavePNG_IO(SDL_Surface *surface, SDL_IOStream *dst, bool closeio)
{
bool retval = false;
Uint8 *plte = NULL;
Uint8 *trns = NULL;
bool free_surface = false;
CHECK_PARAM(!SDL_SurfaceValid(surface)) {
SDL_InvalidParamError("surface");
goto done;
}
CHECK_PARAM(!dst) {
SDL_InvalidParamError("dst");
goto done;
}
#ifdef SDL_HAVE_STB
int plte_size = 0;
int trns_size = 0;
if (SDL_ISPIXELFORMAT_INDEXED(surface->format)) {
if (!surface->palette) {
SDL_SetError("Indexed surfaces must have a palette");
goto done;
}
if (surface->format != SDL_PIXELFORMAT_INDEX8) {
surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_INDEX8);
if (!surface) {
goto done;
}
free_surface = true;
}
plte_size = surface->palette->ncolors * 3;
trns_size = surface->palette->ncolors;
plte = (Uint8 *)SDL_malloc(plte_size);
trns = (Uint8 *)SDL_malloc(trns_size);
if (!plte || !trns) {
goto done;
}
SDL_Color *colors = surface->palette->colors;
for (int i = 0; i < surface->palette->ncolors; ++i) {
plte[i * 3 + 0] = colors[i].r;
plte[i * 3 + 1] = colors[i].g;
plte[i * 3 + 2] = colors[i].b;
trns[i] = colors[i].a;
}
} else {
if (surface->format != SDL_PIXELFORMAT_RGBA32) {
surface = SDL_ConvertSurface(surface, SDL_PIXELFORMAT_RGBA32);
if (!surface) {
goto done;
}
free_surface = true;
}
}
size_t size = 0;
void *png = tdefl_write_image_to_png_file_in_memory_ex(surface->pixels, surface->w, surface->h, SDL_BYTESPERPIXEL(surface->format), surface->pitch, &size, 6, MZ_FALSE, plte, plte_size, trns, trns_size);
if (png) {
if (SDL_WriteIO(dst, png, size)) {
retval = true;
}
mz_free(png);
} else {
SDL_SetError("Failed to convert and save image");
}
#else
SDL_SetError("SDL not built with STB image support");
#endif
done:
if (free_surface) {
SDL_DestroySurface(surface);
}
SDL_free(plte);
SDL_free(trns);
if (dst && closeio) {
retval &= SDL_CloseIO(dst);
}
return retval;
}
bool SDL_SavePNG(SDL_Surface *surface, const char *file)
{
#ifdef SDL_HAVE_STB
CHECK_PARAM(!SDL_SurfaceValid(surface)) {
return SDL_InvalidParamError("surface");
}
if (SDL_ISPIXELFORMAT_INDEXED(surface->format) && !surface->palette) {
return SDL_SetError("Indexed surfaces must have a palette");
}
SDL_IOStream *stream = SDL_IOFromFile(file, "wb");
if (!stream) {
return false;
}
return SDL_SavePNG_IO(surface, stream, true);
#else
return SDL_SetError("SDL not built with STB image support");
#endif
}