Image: Load libpng/libjpeg/libwebp dynamically

pull/3794/head
Stenzek 3 weeks ago
parent 3f44ac2e9c
commit f39feb9d8f
No known key found for this signature in database

@ -30,6 +30,7 @@ add_library(util
cue_parser.h cue_parser.h
dyn_shaderc.h dyn_shaderc.h
dyn_spirv_cross.h dyn_spirv_cross.h
dyn_libpng.h
dyn_sqlite.h dyn_sqlite.h
elf_file.cpp elf_file.cpp
elf_file.h elf_file.h
@ -100,8 +101,12 @@ add_library(util
target_precompile_headers(util PRIVATE "pch.h") target_precompile_headers(util PRIVATE "pch.h")
target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..") target_include_directories(util PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/..")
get_target_property(JPEG_INCLUDE_DIRS libjpeg-turbo::jpeg INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(PNG_INCLUDE_DIRS PNG::png_shared INTERFACE_INCLUDE_DIRECTORIES)
get_target_property(WEBP_INCLUDE_DIRS WebP::webp INTERFACE_INCLUDE_DIRECTORIES)
target_include_directories(util PRIVATE ${JPEG_INCLUDE_DIRS} ${PNG_INCLUDE_DIRS} ${WEBP_INCLUDE_DIRS})
target_link_libraries(util PUBLIC common imgui) target_link_libraries(util PUBLIC common imgui)
target_link_libraries(util PRIVATE libchdr lzma libjpeg-turbo::jpeg PNG::png_shared WebP::webp target_link_libraries(util PRIVATE libchdr lzma
plutosvg::plutosvg ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash plutosvg::plutosvg ZLIB::ZLIB SoundTouch::SoundTouchDLL xxhash
libzip::zip zstd::libzstd_shared reshadefx) libzip::zip zstd::libzstd_shared reshadefx)
@ -274,8 +279,10 @@ function(add_util_resources target)
endforeach() endforeach()
endif() endif()
# Copy dynamically-loaded libraries (harfbuzz/shaderc/spirv-cross) into the bundle. # Copy dynamically-loaded libraries into the bundle.
bundle_libraries(${target} spirv-cross-c-shared Shaderc::shaderc_shared SQLite3::sqlite3-shared) bundle_libraries(${target} spirv-cross-c-shared Shaderc::shaderc_shared SQLite3::sqlite3-shared
libjpeg-turbo::jpeg PNG::png_shared WebP::webp WebP::sharpyuv WebP::webpdecoder
WebP::webpdemux WebP::libwebpmux)
if(APPLE) if(APPLE)
bundle_libraries(${target} harfbuzz::harfbuzz) bundle_libraries(${target} harfbuzz::harfbuzz)
endif() endif()

@ -2,6 +2,7 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "animated_image.h" #include "animated_image.h"
#include "dyn_libpng.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/bitutils.h" #include "common/bitutils.h"
@ -13,7 +14,6 @@
#include "common/string_util.h" #include "common/string_util.h"
#include <limits> #include <limits>
#include <png.h>
// clang-format off // clang-format off
#ifdef _MSC_VER #ifdef _MSC_VER
@ -321,89 +321,89 @@ std::optional<DynamicHeapArray<u8>> AnimatedImage::SaveToBuffer(std::string_view
static void PNGSetErrorFunction(png_structp png_ptr, Error* error) static void PNGSetErrorFunction(png_structp png_ptr, Error* error)
{ {
png_set_error_fn( g_dyn_libpng.png_set_error_fn(
png_ptr, error, png_ptr, error,
[](png_structp png_ptr, png_const_charp message) { [](png_structp png_ptr, png_const_charp message) {
Error::SetStringView(static_cast<Error*>(png_get_error_ptr(png_ptr)), message); Error::SetStringView(static_cast<Error*>(g_dyn_libpng.png_get_error_ptr(png_ptr)), message);
png_longjmp(png_ptr, 1); g_dyn_libpng.png_longjmp(png_ptr, 1);
}, },
[](png_structp png_ptr, png_const_charp message) { WARNING_LOG("libpng warning: {}", message); }); [](png_structp png_ptr, png_const_charp message) { WARNING_LOG("libpng warning: {}", message); });
} }
static bool PNGCommonLoader(AnimatedImage* image, png_structp png_ptr, png_infop info_ptr) static bool PNGCommonLoader(AnimatedImage* image, png_structp png_ptr, png_infop info_ptr)
{ {
png_read_info(png_ptr, info_ptr); g_dyn_libpng.png_read_info(png_ptr, info_ptr);
const u32 width = png_get_image_width(png_ptr, info_ptr); const u32 width = g_dyn_libpng.png_get_image_width(png_ptr, info_ptr);
const u32 height = png_get_image_height(png_ptr, info_ptr); const u32 height = g_dyn_libpng.png_get_image_height(png_ptr, info_ptr);
const u32 num_frames = png_get_num_frames(png_ptr, info_ptr); const u32 num_frames = g_dyn_libpng.png_get_num_frames(png_ptr, info_ptr);
const png_byte color_type = png_get_color_type(png_ptr, info_ptr); const png_byte color_type = g_dyn_libpng.png_get_color_type(png_ptr, info_ptr);
const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); const png_byte bit_depth = g_dyn_libpng.png_get_bit_depth(png_ptr, info_ptr);
if (num_frames == 0) if (num_frames == 0)
png_error(png_ptr, "Image has zero frames"); g_dyn_libpng.png_error(png_ptr, "Image has zero frames");
// Read any color_type into 8bit depth, RGBA format. // Read any color_type into 8bit depth, RGBA format.
// See http://www.libpng.org/pub/png/libpng-manual.txt // See http://www.libpng.org/pub/png/libpng-manual.txt
if (bit_depth == 16) if (bit_depth == 16)
png_set_strip_16(png_ptr); g_dyn_libpng.png_set_strip_16(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr); g_dyn_libpng.png_set_palette_to_rgb(png_ptr);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr); g_dyn_libpng.png_set_expand_gray_1_2_4_to_8(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) if (g_dyn_libpng.png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr); g_dyn_libpng.png_set_tRNS_to_alpha(png_ptr);
// These color_type don't have an alpha channel then fill it with 0xff. // These color_type don't have an alpha channel then fill it with 0xff.
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); g_dyn_libpng.png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr); g_dyn_libpng.png_set_gray_to_rgb(png_ptr);
png_read_update_info(png_ptr, info_ptr); g_dyn_libpng.png_read_update_info(png_ptr, info_ptr);
DebugAssert(num_frames > 0); DebugAssert(num_frames > 0);
image->Resize(width, height, num_frames, {1, 10}, false); image->Resize(width, height, num_frames, {1, 10}, false);
if (!image->IsValid()) if (!image->IsValid())
png_error(png_ptr, "Image dimensions are too large"); g_dyn_libpng.png_error(png_ptr, "Image dimensions are too large");
if (num_frames > 1) if (num_frames > 1)
{ {
for (u32 i = 0; i < num_frames; i++) for (u32 i = 0; i < num_frames; i++)
{ {
png_read_frame_head(png_ptr, info_ptr); g_dyn_libpng.png_read_frame_head(png_ptr, info_ptr);
const u32 frame_width = png_get_next_frame_width(png_ptr, info_ptr); const u32 frame_width = g_dyn_libpng.png_get_next_frame_width(png_ptr, info_ptr);
const u32 frame_height = png_get_next_frame_height(png_ptr, info_ptr); const u32 frame_height = g_dyn_libpng.png_get_next_frame_height(png_ptr, info_ptr);
if (frame_width != width || frame_height != height) if (frame_width != width || frame_height != height)
png_error(png_ptr, "Frame size does not match image size"); g_dyn_libpng.png_error(png_ptr, "Frame size does not match image size");
const u16 delay_num = static_cast<u16>(png_get_next_frame_delay_num(png_ptr, info_ptr)); const u16 delay_num = static_cast<u16>(g_dyn_libpng.png_get_next_frame_delay_num(png_ptr, info_ptr));
const u16 delay_den = static_cast<u16>(png_get_next_frame_delay_den(png_ptr, info_ptr)); const u16 delay_den = static_cast<u16>(g_dyn_libpng.png_get_next_frame_delay_den(png_ptr, info_ptr));
image->SetDelay(i, {delay_num, std::max<u16>(delay_den, 1)}); image->SetDelay(i, {delay_num, std::max<u16>(delay_den, 1)});
// TODO: blending/compose/etc. // TODO: blending/compose/etc.
const int num_passes = png_set_interlace_handling(png_ptr); const int num_passes = g_dyn_libpng.png_set_interlace_handling(png_ptr);
for (int pass = 0; pass < num_passes; pass++) for (int pass = 0; pass < num_passes; pass++)
{ {
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(i, y)), nullptr); g_dyn_libpng.png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(i, y)), nullptr);
} }
} }
} }
else else
{ {
const int num_passes = png_set_interlace_handling(png_ptr); const int num_passes = g_dyn_libpng.png_set_interlace_handling(png_ptr);
for (int pass = 0; pass < num_passes; pass++) for (int pass = 0; pass < num_passes; pass++)
{ {
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(0, y)), nullptr); g_dyn_libpng.png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(0, y)), nullptr);
} }
} }
@ -412,34 +412,37 @@ static bool PNGCommonLoader(AnimatedImage* image, png_structp png_ptr, png_infop
bool PNGFileLoader(AnimatedImage* image, std::string_view filename, std::FILE* fp, Error* error) bool PNGFileLoader(AnimatedImage* image, std::string_view filename, std::FILE* fp, Error* error)
{ {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) if (!png_ptr)
{ {
Error::SetStringView(error, "png_create_read_struct() failed."); Error::SetStringView(error, "png_create_read_struct() failed.");
return false; return false;
} }
png_infop info_ptr = png_create_info_struct(png_ptr); png_infop info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
png_destroy_read_struct(&png_ptr, nullptr, nullptr); g_dyn_libpng.png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false; return false;
} }
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { g_dyn_libpng.png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
{ {
image->Invalidate(); image->Invalidate();
return false; return false;
} }
png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { g_dyn_libpng.png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
std::FILE* fp = static_cast<std::FILE*>(png_get_io_ptr(png_ptr)); std::FILE* fp = static_cast<std::FILE*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
if (std::fread(data_ptr, size, 1, fp) != 1) if (std::fread(data_ptr, size, 1, fp) != 1)
png_error(png_ptr, "fread() failed"); g_dyn_libpng.png_error(png_ptr, "fread() failed");
}); });
return PNGCommonLoader(image, png_ptr, info_ptr); return PNGCommonLoader(image, png_ptr, info_ptr);
@ -447,27 +450,30 @@ bool PNGFileLoader(AnimatedImage* image, std::string_view filename, std::FILE* f
bool PNGBufferLoader(AnimatedImage* image, std::span<const u8> data, Error* error) bool PNGBufferLoader(AnimatedImage* image, std::span<const u8> data, Error* error)
{ {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) if (!png_ptr)
{ {
Error::SetStringView(error, "png_create_read_struct() failed."); Error::SetStringView(error, "png_create_read_struct() failed.");
return false; return false;
} }
png_infop info_ptr = png_create_info_struct(png_ptr); png_infop info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
png_destroy_read_struct(&png_ptr, nullptr, nullptr); g_dyn_libpng.png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false; return false;
} }
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { g_dyn_libpng.png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
std::vector<png_bytep> row_pointers; std::vector<png_bytep> row_pointers;
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
{ {
image->Invalidate(); image->Invalidate();
return false; return false;
@ -480,10 +486,10 @@ bool PNGBufferLoader(AnimatedImage* image, std::span<const u8> data, Error* erro
}; };
IOData iodata = {data, 0}; IOData iodata = {data, 0};
png_set_read_fn(png_ptr, &iodata, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { g_dyn_libpng.png_set_read_fn(png_ptr, &iodata, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
IOData* data = static_cast<IOData*>(png_get_io_ptr(png_ptr)); IOData* data = static_cast<IOData*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
if (data->buffer_pos > data->buffer.size() || size > (data->buffer.size() - data->buffer_pos)) if (data->buffer_pos > data->buffer.size() || size > (data->buffer.size() - data->buffer_pos))
png_error(png_ptr, "Unexpected end of PNG data"); g_dyn_libpng.png_error(png_ptr, "Unexpected end of PNG data");
std::memcpy(data_ptr, data->buffer.data() + data->buffer_pos, size); std::memcpy(data_ptr, data->buffer.data() + data->buffer_pos, size);
data->buffer_pos += size; data->buffer_pos += size;
@ -494,46 +500,49 @@ bool PNGBufferLoader(AnimatedImage* image, std::span<const u8> data, Error* erro
static void PNGSaveCommon(const AnimatedImage& image, png_structp png_ptr, png_infop info_ptr, u8 quality) static void PNGSaveCommon(const AnimatedImage& image, png_structp png_ptr, png_infop info_ptr, u8 quality)
{ {
png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9)); g_dyn_libpng.png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9));
png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, g_dyn_libpng.png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
const u32 width = image.GetWidth(); const u32 width = image.GetWidth();
const u32 height = image.GetHeight(); const u32 height = image.GetHeight();
const u32 frames = image.GetFrames(); const u32 frames = image.GetFrames();
if (frames > 1) if (frames > 1)
{ {
if (!png_set_acTL(png_ptr, info_ptr, frames, 0)) if (!g_dyn_libpng.png_set_acTL(png_ptr, info_ptr, frames, 0))
png_error(png_ptr, "png_set_acTL() failed"); g_dyn_libpng.png_error(png_ptr, "png_set_acTL() failed");
png_write_info(png_ptr, info_ptr); g_dyn_libpng.png_write_info(png_ptr, info_ptr);
for (u32 i = 0; i < frames; i++) for (u32 i = 0; i < frames; i++)
{ {
const AnimatedImage::FrameDelay& fd = image.GetFrameDelay(i); const AnimatedImage::FrameDelay& fd = image.GetFrameDelay(i);
png_write_frame_head(png_ptr, info_ptr, width, height, 0, 0, fd.numerator, fd.denominator, PNG_DISPOSE_OP_NONE, g_dyn_libpng.png_write_frame_head(png_ptr, info_ptr, width, height, 0, 0, fd.numerator, fd.denominator,
PNG_BLEND_OP_SOURCE); PNG_DISPOSE_OP_NONE, PNG_BLEND_OP_SOURCE);
for (u32 y = 0; y < height; ++y) for (u32 y = 0; y < height; ++y)
png_write_row(png_ptr, (png_bytep)image.GetRowPixels(i, y)); g_dyn_libpng.png_write_row(png_ptr, (png_bytep)image.GetRowPixels(i, y));
png_write_frame_tail(png_ptr, info_ptr); g_dyn_libpng.png_write_frame_tail(png_ptr, info_ptr);
} }
} }
else else
{ {
// only one frame // only one frame
png_write_info(png_ptr, info_ptr); g_dyn_libpng.png_write_info(png_ptr, info_ptr);
for (u32 y = 0; y < height; ++y) for (u32 y = 0; y < height; ++y)
png_write_row(png_ptr, (png_bytep)image.GetRowPixels(0, y)); g_dyn_libpng.png_write_row(png_ptr, (png_bytep)image.GetRowPixels(0, y));
} }
png_write_end(png_ptr, nullptr); g_dyn_libpng.png_write_end(png_ptr, nullptr);
} }
bool PNGFileSaver(const AnimatedImage& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) bool PNGFileSaver(const AnimatedImage& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error)
{ {
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
if (!png_ptr) if (!png_ptr)
{ {
@ -543,10 +552,10 @@ bool PNGFileSaver(const AnimatedImage& image, std::string_view filename, std::FI
ScopedGuard cleanup([&png_ptr, &info_ptr]() { ScopedGuard cleanup([&png_ptr, &info_ptr]() {
if (png_ptr) if (png_ptr)
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); g_dyn_libpng.png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
}); });
info_ptr = png_create_info_struct(png_ptr); info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
@ -554,14 +563,14 @@ bool PNGFileSaver(const AnimatedImage& image, std::string_view filename, std::FI
} }
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
return false; return false;
png_set_write_fn( g_dyn_libpng.png_set_write_fn(
png_ptr, fp, png_ptr, fp,
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1) if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(g_dyn_libpng.png_get_io_ptr(png_ptr))) != 1)
png_error(png_ptr, "fwrite() failed"); g_dyn_libpng.png_error(png_ptr, "fwrite() failed");
}, },
[](png_structp png_ptr) {}); [](png_structp png_ptr) {});
@ -571,7 +580,10 @@ bool PNGFileSaver(const AnimatedImage& image, std::string_view filename, std::FI
bool PNGBufferSaver(const AnimatedImage& image, DynamicHeapArray<u8>* data, u8 quality, Error* error) bool PNGBufferSaver(const AnimatedImage& image, DynamicHeapArray<u8>* data, u8 quality, Error* error)
{ {
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
if (!png_ptr) if (!png_ptr)
{ {
@ -581,10 +593,10 @@ bool PNGBufferSaver(const AnimatedImage& image, DynamicHeapArray<u8>* data, u8 q
ScopedGuard cleanup([&png_ptr, &info_ptr]() { ScopedGuard cleanup([&png_ptr, &info_ptr]() {
if (png_ptr) if (png_ptr)
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); g_dyn_libpng.png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
}); });
info_ptr = png_create_info_struct(png_ptr); info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
@ -601,15 +613,15 @@ bool PNGBufferSaver(const AnimatedImage& image, DynamicHeapArray<u8>* data, u8 q
data->resize(static_cast<size_t>(image.GetFrameSize()) * 2); data->resize(static_cast<size_t>(image.GetFrameSize()) * 2);
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
return false; return false;
png_set_write_fn( g_dyn_libpng.png_set_write_fn(
png_ptr, &iodata, png_ptr, &iodata,
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
IOData* iodata = static_cast<IOData*>(png_get_io_ptr(png_ptr)); IOData* iodata = static_cast<IOData*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
if (size > (std::numeric_limits<size_t>::max() - iodata->buffer_pos)) if (size > (std::numeric_limits<size_t>::max() - iodata->buffer_pos))
png_error(png_ptr, "PNG output is too large"); g_dyn_libpng.png_error(png_ptr, "PNG output is too large");
const size_t new_pos = iodata->buffer_pos + size; const size_t new_pos = iodata->buffer_pos + size;
if (new_pos > iodata->buffer->size()) if (new_pos > iodata->buffer->size())
@ -620,7 +632,7 @@ bool PNGBufferSaver(const AnimatedImage& image, DynamicHeapArray<u8>* data, u8 q
iodata->buffer->resize(std::max(new_pos, current_size + growth)); iodata->buffer->resize(std::max(new_pos, current_size + growth));
} }
if (iodata->buffer_pos > iodata->buffer->size() || size > (iodata->buffer->size() - iodata->buffer_pos)) if (iodata->buffer_pos > iodata->buffer->size() || size > (iodata->buffer->size() - iodata->buffer_pos))
png_error(png_ptr, "PNG output buffer is too small"); g_dyn_libpng.png_error(png_ptr, "PNG output buffer is too small");
std::memcpy(iodata->buffer->data() + iodata->buffer_pos, data_ptr, size); std::memcpy(iodata->buffer->data() + iodata->buffer_pos, data_ptr, size);
iodata->buffer_pos = new_pos; iodata->buffer_pos = new_pos;

@ -0,0 +1,63 @@
// SPDX-FileCopyrightText: 2019-2026 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include <png.h>
class Error;
#define DYN_LIBPNG_FUNCTIONS(X) \
X(png_create_read_struct) /* ordinal 4 */ \
X(png_create_write_struct) /* ordinal 5 */ \
X(png_set_longjmp_fn) /* ordinal 8 */ \
X(png_longjmp) /* ordinal 9 */ \
X(png_create_info_struct) /* ordinal 18 */ \
X(png_write_info) /* ordinal 21 */ \
X(png_read_info) /* ordinal 22 */ \
X(png_set_expand_gray_1_2_4_to_8) /* ordinal 27 */ \
X(png_set_palette_to_rgb) /* ordinal 28 */ \
X(png_set_tRNS_to_alpha) /* ordinal 29 */ \
X(png_set_gray_to_rgb) /* ordinal 31 */ \
X(png_set_filler) /* ordinal 39 */ \
X(png_set_interlace_handling) /* ordinal 45 */ \
X(png_set_strip_16) /* ordinal 48 */ \
X(png_read_update_info) /* ordinal 54 */ \
X(png_read_row) /* ordinal 56 */ \
X(png_write_row) /* ordinal 58 */ \
X(png_write_end) /* ordinal 61 */ \
X(png_destroy_read_struct) /* ordinal 64 */ \
X(png_destroy_write_struct) /* ordinal 65 */ \
X(png_set_compression_level) /* ordinal 69 */ \
X(png_set_error_fn) /* ordinal 75 */ \
X(png_get_error_ptr) /* ordinal 76 */ \
X(png_set_write_fn) /* ordinal 77 */ \
X(png_set_read_fn) /* ordinal 78 */ \
X(png_get_io_ptr) /* ordinal 79 */ \
X(png_error) /* ordinal 102 */ \
X(png_get_valid) /* ordinal 110 */ \
X(png_set_IHDR) /* ordinal 144 */ \
X(png_get_image_width) /* ordinal 115 */ \
X(png_get_image_height) /* ordinal 116 */ \
X(png_get_bit_depth) /* ordinal 117 */ \
X(png_get_color_type) /* ordinal 118 */ \
X(png_set_acTL) /* ordinal 261 */ \
X(png_get_num_frames) /* ordinal 262 */ \
X(png_get_next_frame_width) /* ordinal 266 */ \
X(png_get_next_frame_height) /* ordinal 267 */ \
X(png_get_next_frame_delay_num) /* ordinal 270 */ \
X(png_get_next_frame_delay_den) /* ordinal 271 */ \
X(png_read_frame_head) /* ordinal 276 */ \
X(png_write_frame_head) /* ordinal 278 */ \
X(png_write_frame_tail) /* ordinal 279 */
struct DynLibPNG
{
#define ADD_FUNC(F) decltype(&::F) F;
DYN_LIBPNG_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
bool Open(Error* error);
};
extern DynLibPNG g_dyn_libpng;

@ -2,10 +2,12 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0 // SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "image.h" #include "image.h"
#include "dyn_libpng.h"
#include "texture_decompress.h" #include "texture_decompress.h"
#include "common/assert.h" #include "common/assert.h"
#include "common/bitutils.h" #include "common/bitutils.h"
#include "common/dynamic_library.h"
#include "common/error.h" #include "common/error.h"
#include "common/fastjmp.h" #include "common/fastjmp.h"
#include "common/file_system.h" #include "common/file_system.h"
@ -21,10 +23,11 @@
#include <jpeglib.h> #include <jpeglib.h>
#include <limits> #include <limits>
#include <plutosvg.h> #include <plutosvg.h>
#include <png.h>
#include <webp/decode.h> #include <webp/decode.h>
#include <webp/encode.h> #include <webp/encode.h>
#include <mutex>
// clang-format off // clang-format off
#ifdef _MSC_VER #ifdef _MSC_VER
#pragma warning(disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable #pragma warning(disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable
@ -33,6 +36,178 @@
LOG_CHANNEL(Image); LOG_CHANNEL(Image);
#define DYN_LIBJPEG_FUNCTIONS(X) \
X(jpeg_CreateDecompress) \
X(jpeg_CreateCompress) \
X(jpeg_destroy_decompress) \
X(jpeg_destroy_compress) \
X(jpeg_std_error) \
X(jpeg_read_header) \
X(jpeg_start_decompress) \
X(jpeg_read_scanlines) \
X(jpeg_finish_decompress) \
X(jpeg_mem_src) \
X(jpeg_resync_to_restart) \
X(jpeg_set_defaults) \
X(jpeg_set_quality) \
X(jpeg_start_compress) \
X(jpeg_write_scanlines) \
X(jpeg_finish_compress)
#define DYN_LIBWEBP_FUNCTIONS(X) \
X(WebPGetInfo) \
X(WebPDecodeRGBAInto) \
X(WebPEncodeRGBA) \
X(WebPFree)
namespace {
struct DynLibJPEG
{
DynamicLibrary library;
std::once_flag init_flag;
#define ADD_FUNC(F) decltype(&::F) F;
DYN_LIBJPEG_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
bool Open(Error* error);
};
struct DynLibWebP
{
DynamicLibrary library;
std::once_flag init_flag;
#define ADD_FUNC(F) decltype(&::F) F;
DYN_LIBWEBP_FUNCTIONS(ADD_FUNC)
#undef ADD_FUNC
bool Open(Error* error);
};
struct Locals
{
DynamicLibrary png_library;
std::once_flag png_init_flag;
};
} // namespace
static Locals s_locals;
static DynLibJPEG s_dyn_libjpeg;
static DynLibWebP s_dyn_libwebp;
DynLibPNG g_dyn_libpng;
bool DynLibPNG::Open(Error* error)
{
if (s_locals.png_library.IsOpen())
return true;
std::call_once(s_locals.png_init_flag, [&error]() {
Error lerror;
DynamicLibrary lib;
#ifdef _WIN32
static constexpr int lib_major_version = -1;
#else
static constexpr int lib_major_version = 16;
#endif
if (!lib.Open(DynamicLibrary::GetVersionedFilename("libpng16", lib_major_version).c_str(), &lerror))
{
ERROR_LOG("Failed to load libpng: {}", lerror.GetDescription());
Error::SetStringFmt(error, "Failed to load libpng: {}", lerror.GetDescription());
return;
}
// clang-format off
static const DynamicLibrary::SymbolTable png_symbols[] = {
#define PNG_SYMBOL(F) {#F, (void**)&g_dyn_libpng.F},
DYN_LIBPNG_FUNCTIONS(PNG_SYMBOL)
#undef PNG_SYMBOL
};
// clang-format on
if (!lib.ResolveSymbols(png_symbols, std::size(png_symbols), error))
return;
s_locals.png_library = std::move(lib);
});
return s_locals.png_library.IsOpen();
}
bool DynLibJPEG::Open(Error* error)
{
if (library.IsOpen())
return true;
std::call_once(init_flag, [&error]() {
Error lerror;
DynamicLibrary lib;
#ifdef _WIN32
static constexpr int lib_major_version = -1;
#else
static constexpr int lib_major_version = 62;
#endif
if (!lib.Open(DynamicLibrary::GetVersionedFilename("jpeg", lib_major_version).c_str(), &lerror))
{
ERROR_LOG("Failed to load libjpeg: {}", lerror.GetDescription());
Error::SetStringFmt(error, "Failed to load libjpeg: {}", lerror.GetDescription());
return;
}
// clang-format off
static const DynamicLibrary::SymbolTable jpeg_symbols[] = {
#define JPEG_SYMBOL(F) {#F, (void**)&s_dyn_libjpeg.F},
DYN_LIBJPEG_FUNCTIONS(JPEG_SYMBOL)
#undef JPEG_SYMBOL
};
// clang-format on
if (!lib.ResolveSymbols(jpeg_symbols, std::size(jpeg_symbols), error))
return;
s_dyn_libjpeg.library = std::move(lib);
});
return library.IsOpen();
}
bool DynLibWebP::Open(Error* error)
{
if (library.IsOpen())
return true;
std::call_once(init_flag, [&error]() {
Error lerror;
DynamicLibrary lib;
#ifdef _WIN32
static constexpr int lib_major_version = -1;
#else
static constexpr int lib_major_version = 7;
#endif
if (!lib.Open(DynamicLibrary::GetVersionedFilename("webp", lib_major_version).c_str(), &lerror))
{
ERROR_LOG("Failed to load libwebp: {}", lerror.GetDescription());
Error::SetStringFmt(error, "Failed to load libwebp: {}", lerror.GetDescription());
return;
}
// clang-format off
static const DynamicLibrary::SymbolTable webp_symbols[] = {
#define WEBP_SYMBOL(F) {#F, (void**)&s_dyn_libwebp.F},
DYN_LIBWEBP_FUNCTIONS(WEBP_SYMBOL)
#undef WEBP_SYMBOL
};
// clang-format on
if (!lib.ResolveSymbols(webp_symbols, std::size(webp_symbols), error))
return;
s_dyn_libwebp.library = std::move(lib);
});
return library.IsOpen();
}
static bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error); static bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error);
static bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error); static bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error);
static bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error); static bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error);
@ -948,61 +1123,61 @@ void Image::FlipY()
static void PNGSetErrorFunction(png_structp png_ptr, Error* error) static void PNGSetErrorFunction(png_structp png_ptr, Error* error)
{ {
png_set_error_fn( g_dyn_libpng.png_set_error_fn(
png_ptr, error, png_ptr, error,
[](png_structp png_ptr, png_const_charp message) { [](png_structp png_ptr, png_const_charp message) {
Error::SetStringView(static_cast<Error*>(png_get_error_ptr(png_ptr)), message); Error::SetStringView(static_cast<Error*>(g_dyn_libpng.png_get_error_ptr(png_ptr)), message);
png_longjmp(png_ptr, 1); g_dyn_libpng.png_longjmp(png_ptr, 1);
}, },
[](png_structp png_ptr, png_const_charp message) { WARNING_LOG("libpng warning: {}", message); }); [](png_structp png_ptr, png_const_charp message) { WARNING_LOG("libpng warning: {}", message); });
} }
static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_ptr) static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_ptr)
{ {
png_read_info(png_ptr, info_ptr); g_dyn_libpng.png_read_info(png_ptr, info_ptr);
const u32 width = png_get_image_width(png_ptr, info_ptr); const u32 width = g_dyn_libpng.png_get_image_width(png_ptr, info_ptr);
const u32 height = png_get_image_height(png_ptr, info_ptr); const u32 height = g_dyn_libpng.png_get_image_height(png_ptr, info_ptr);
const png_byte color_type = png_get_color_type(png_ptr, info_ptr); const png_byte color_type = g_dyn_libpng.png_get_color_type(png_ptr, info_ptr);
const png_byte bit_depth = png_get_bit_depth(png_ptr, info_ptr); const png_byte bit_depth = g_dyn_libpng.png_get_bit_depth(png_ptr, info_ptr);
// Read any color_type into 8bit depth, RGBA format. // Read any color_type into 8bit depth, RGBA format.
// See http://www.libpng.org/pub/png/libpng-manual.txt // See http://www.libpng.org/pub/png/libpng-manual.txt
if (bit_depth == 16) if (bit_depth == 16)
png_set_strip_16(png_ptr); g_dyn_libpng.png_set_strip_16(png_ptr);
if (color_type == PNG_COLOR_TYPE_PALETTE) if (color_type == PNG_COLOR_TYPE_PALETTE)
png_set_palette_to_rgb(png_ptr); g_dyn_libpng.png_set_palette_to_rgb(png_ptr);
// PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth. // PNG_COLOR_TYPE_GRAY_ALPHA is always 8 or 16bit depth.
if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
png_set_expand_gray_1_2_4_to_8(png_ptr); g_dyn_libpng.png_set_expand_gray_1_2_4_to_8(png_ptr);
if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS)) if (g_dyn_libpng.png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
png_set_tRNS_to_alpha(png_ptr); g_dyn_libpng.png_set_tRNS_to_alpha(png_ptr);
// These color_type don't have an alpha channel then fill it with 0xff. // These color_type don't have an alpha channel then fill it with 0xff.
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE) if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER); g_dyn_libpng.png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
png_set_gray_to_rgb(png_ptr); g_dyn_libpng.png_set_gray_to_rgb(png_ptr);
png_read_update_info(png_ptr, info_ptr); g_dyn_libpng.png_read_update_info(png_ptr, info_ptr);
image->Resize(width, height, ImageFormat::RGBA8, false); image->Resize(width, height, ImageFormat::RGBA8, false);
if (!image->IsValid()) if (!image->IsValid())
{ {
png_error(png_ptr, "Image dimensions are too large"); g_dyn_libpng.png_error(png_ptr, "Image dimensions are too large");
return false; return false;
} }
const int passes = png_set_interlace_handling(png_ptr); const int passes = g_dyn_libpng.png_set_interlace_handling(png_ptr);
for (int pass = 0; pass < passes; pass++) for (int pass = 0; pass < passes; pass++)
{ {
for (u32 y = 0; y < height; y++) for (u32 y = 0; y < height; y++)
png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(y)), nullptr); g_dyn_libpng.png_read_row(png_ptr, reinterpret_cast<png_bytep>(image->GetRowPixels(y)), nullptr);
} }
return true; return true;
@ -1010,34 +1185,37 @@ static bool PNGCommonLoader(Image* image, png_structp png_ptr, png_infop info_pt
bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error) bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error* error)
{ {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) if (!png_ptr)
{ {
Error::SetStringView(error, "png_create_read_struct() failed."); Error::SetStringView(error, "png_create_read_struct() failed.");
return false; return false;
} }
png_infop info_ptr = png_create_info_struct(png_ptr); png_infop info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
png_destroy_read_struct(&png_ptr, nullptr, nullptr); g_dyn_libpng.png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false; return false;
} }
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { g_dyn_libpng.png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
{ {
image->Invalidate(); image->Invalidate();
return false; return false;
} }
png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { g_dyn_libpng.png_set_read_fn(png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
std::FILE* fp = static_cast<std::FILE*>(png_get_io_ptr(png_ptr)); std::FILE* fp = static_cast<std::FILE*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
if (std::fread(data_ptr, size, 1, fp) != 1) if (std::fread(data_ptr, size, 1, fp) != 1)
png_error(png_ptr, "fread() failed"); g_dyn_libpng.png_error(png_ptr, "fread() failed");
}); });
return PNGCommonLoader(image, png_ptr, info_ptr); return PNGCommonLoader(image, png_ptr, info_ptr);
@ -1045,25 +1223,28 @@ bool PNGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Error
bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error) bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_read_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
if (!png_ptr) if (!png_ptr)
{ {
Error::SetStringView(error, "png_create_read_struct() failed."); Error::SetStringView(error, "png_create_read_struct() failed.");
return false; return false;
} }
png_infop info_ptr = png_create_info_struct(png_ptr); png_infop info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
png_destroy_read_struct(&png_ptr, nullptr, nullptr); g_dyn_libpng.png_destroy_read_struct(&png_ptr, nullptr, nullptr);
return false; return false;
} }
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); }); ScopedGuard cleanup([&png_ptr, &info_ptr]() { g_dyn_libpng.png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
{ {
image->Invalidate(); image->Invalidate();
return false; return false;
@ -1076,8 +1257,8 @@ bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error)
}; };
IOData iodata = {data, 0}; IOData iodata = {data, 0};
png_set_read_fn(png_ptr, &iodata, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { g_dyn_libpng.png_set_read_fn(png_ptr, &iodata, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
IOData* data = static_cast<IOData*>(png_get_io_ptr(png_ptr)); IOData* data = static_cast<IOData*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
const size_t read_size = std::min<size_t>(data->buffer.size() - data->buffer_pos, size); const size_t read_size = std::min<size_t>(data->buffer.size() - data->buffer_pos, size);
if (read_size > 0) if (read_size > 0)
{ {
@ -1091,20 +1272,23 @@ bool PNGBufferLoader(Image* image, std::span<const u8> data, Error* error)
static void PNGSaveCommon(const Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality) static void PNGSaveCommon(const Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality)
{ {
png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9)); g_dyn_libpng.png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9));
png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE, g_dyn_libpng.png_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA,
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
png_write_info(png_ptr, info_ptr); g_dyn_libpng.png_write_info(png_ptr, info_ptr);
for (u32 y = 0; y < image.GetHeight(); ++y) for (u32 y = 0; y < image.GetHeight(); ++y)
png_write_row(png_ptr, (png_bytep)image.GetRowPixels(y)); g_dyn_libpng.png_write_row(png_ptr, (png_bytep)image.GetRowPixels(y));
png_write_end(png_ptr, nullptr); g_dyn_libpng.png_write_end(png_ptr, nullptr);
} }
bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error) bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp, u8 quality, Error* error)
{ {
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
if (!png_ptr) if (!png_ptr)
{ {
@ -1114,10 +1298,10 @@ bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp,
ScopedGuard cleanup([&png_ptr, &info_ptr]() { ScopedGuard cleanup([&png_ptr, &info_ptr]() {
if (png_ptr) if (png_ptr)
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); g_dyn_libpng.png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
}); });
info_ptr = png_create_info_struct(png_ptr); info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
@ -1125,14 +1309,14 @@ bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp,
} }
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
return false; return false;
png_set_write_fn( g_dyn_libpng.png_set_write_fn(
png_ptr, fp, png_ptr, fp,
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1) if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(g_dyn_libpng.png_get_io_ptr(png_ptr))) != 1)
png_error(png_ptr, "fwrite() failed"); g_dyn_libpng.png_error(png_ptr, "fwrite() failed");
}, },
[](png_structp png_ptr) {}); [](png_structp png_ptr) {});
@ -1142,7 +1326,10 @@ bool PNGFileSaver(const Image& image, std::string_view filename, std::FILE* fp,
bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error) bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error)
{ {
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); if (!g_dyn_libpng.Open(error))
return false;
png_structp png_ptr = g_dyn_libpng.png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
png_infop info_ptr = nullptr; png_infop info_ptr = nullptr;
if (!png_ptr) if (!png_ptr)
{ {
@ -1152,10 +1339,10 @@ bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality,
ScopedGuard cleanup([&png_ptr, &info_ptr]() { ScopedGuard cleanup([&png_ptr, &info_ptr]() {
if (png_ptr) if (png_ptr)
png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr); g_dyn_libpng.png_destroy_write_struct(&png_ptr, info_ptr ? &info_ptr : nullptr);
}); });
info_ptr = png_create_info_struct(png_ptr); info_ptr = g_dyn_libpng.png_create_info_struct(png_ptr);
if (!info_ptr) if (!info_ptr)
{ {
Error::SetStringView(error, "png_create_info_struct() failed."); Error::SetStringView(error, "png_create_info_struct() failed.");
@ -1172,13 +1359,13 @@ bool PNGBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality,
data->resize(image.GetStorageSize() / 2); data->resize(image.GetStorageSize() / 2);
PNGSetErrorFunction(png_ptr, error); PNGSetErrorFunction(png_ptr, error);
if (setjmp(png_jmpbuf(png_ptr))) if (setjmp(*g_dyn_libpng.png_set_longjmp_fn(png_ptr, longjmp, sizeof(jmp_buf))) != 0)
return false; return false;
png_set_write_fn( g_dyn_libpng.png_set_write_fn(
png_ptr, &iodata, png_ptr, &iodata,
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) { [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
IOData* iodata = static_cast<IOData*>(png_get_io_ptr(png_ptr)); IOData* iodata = static_cast<IOData*>(g_dyn_libpng.png_get_io_ptr(png_ptr));
const size_t new_pos = iodata->buffer_pos + size; const size_t new_pos = iodata->buffer_pos + size;
if (new_pos > iodata->buffer->size()) if (new_pos > iodata->buffer->size())
iodata->buffer->resize(std::max(new_pos, iodata->buffer->size() * 2)); iodata->buffer->resize(std::max(new_pos, iodata->buffer->size() * 2));
@ -1201,7 +1388,7 @@ struct JPEGErrorHandler
JPEGErrorHandler(Error* errptr_) JPEGErrorHandler(Error* errptr_)
{ {
jpeg_std_error(&err); s_dyn_libjpeg.jpeg_std_error(&err);
err.error_exit = &ErrorExit; err.error_exit = &ErrorExit;
errptr = errptr_; errptr = errptr_;
} }
@ -1220,6 +1407,9 @@ struct JPEGErrorHandler
template<typename T> template<typename T>
static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func) static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
{ {
if (!s_dyn_libjpeg.Open(error))
return false;
std::vector<u8> scanline; std::vector<u8> scanline;
jpeg_decompress_struct info = {}; jpeg_decompress_struct info = {};
@ -1228,15 +1418,15 @@ static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
JPEGErrorHandler errhandler(error); JPEGErrorHandler errhandler(error);
if (fastjmp_set(&errhandler.jbuf) != 0) if (fastjmp_set(&errhandler.jbuf) != 0)
{ {
jpeg_destroy_decompress(&info); s_dyn_libjpeg.jpeg_destroy_decompress(&info);
return false; return false;
} }
info.err = &errhandler.err; info.err = &errhandler.err;
jpeg_create_decompress(&info); s_dyn_libjpeg.jpeg_CreateDecompress(&info, JPEG_LIB_VERSION, sizeof(info));
setup_func(info); setup_func(info);
const int herr = jpeg_read_header(&info, TRUE); const int herr = s_dyn_libjpeg.jpeg_read_header(&info, TRUE);
if (herr != JPEG_HEADER_OK) if (herr != JPEG_HEADER_OK)
{ {
Error::SetStringFmt(error, "jpeg_read_header() returned {}", herr); Error::SetStringFmt(error, "jpeg_read_header() returned {}", herr);
@ -1253,7 +1443,7 @@ static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
info.out_color_space = JCS_RGB; info.out_color_space = JCS_RGB;
info.out_color_components = 3; info.out_color_components = 3;
if (!jpeg_start_decompress(&info)) if (!s_dyn_libjpeg.jpeg_start_decompress(&info))
{ {
Error::SetStringFmt(error, "jpeg_start_decompress() returned failure"); Error::SetStringFmt(error, "jpeg_start_decompress() returned failure");
return false; return false;
@ -1263,7 +1453,7 @@ static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
if (!image->IsValid()) if (!image->IsValid())
{ {
Error::SetStringFmt(error, "Image dimensions are too large: {}x{}", info.image_width, info.image_height); Error::SetStringFmt(error, "Image dimensions are too large: {}x{}", info.image_width, info.image_height);
jpeg_destroy_decompress(&info); s_dyn_libjpeg.jpeg_destroy_decompress(&info);
return false; return false;
} }
scanline.resize(static_cast<size_t>(info.image_width) * 3); scanline.resize(static_cast<size_t>(info.image_width) * 3);
@ -1272,7 +1462,7 @@ static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
bool result = true; bool result = true;
for (u32 y = 0; y < info.image_height; y++) for (u32 y = 0; y < info.image_height; y++)
{ {
if (jpeg_read_scanlines(&info, scanline_buffer, 1) != 1) if (s_dyn_libjpeg.jpeg_read_scanlines(&info, scanline_buffer, 1) != 1)
{ {
Error::SetStringFmt(error, "jpeg_read_scanlines() failed at row {}", y); Error::SetStringFmt(error, "jpeg_read_scanlines() failed at row {}", y);
result = false; result = false;
@ -1292,15 +1482,16 @@ static bool WrapJPEGDecompress(Image* image, Error* error, T setup_func)
} }
} }
jpeg_finish_decompress(&info); s_dyn_libjpeg.jpeg_finish_decompress(&info);
jpeg_destroy_decompress(&info); s_dyn_libjpeg.jpeg_destroy_decompress(&info);
return result; return result;
} }
bool JPEGBufferLoader(Image* image, std::span<const u8> data, Error* error) bool JPEGBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
return WrapJPEGDecompress(image, error, [data](jpeg_decompress_struct& info) { return WrapJPEGDecompress(image, error, [data](jpeg_decompress_struct& info) {
jpeg_mem_src(&info, static_cast<const unsigned char*>(data.data()), static_cast<unsigned long>(data.size())); s_dyn_libjpeg.jpeg_mem_src(&info, static_cast<const unsigned char*>(data.data()),
static_cast<unsigned long>(data.size()));
}); });
} }
@ -1356,7 +1547,7 @@ bool JPEGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Erro
} }
} }
}, },
.resync_to_restart = jpeg_resync_to_restart, .resync_to_restart = s_dyn_libjpeg.jpeg_resync_to_restart,
.term_source = [](j_decompress_ptr cinfo) {}, .term_source = [](j_decompress_ptr cinfo) {},
}, },
.fp = fp, .fp = fp,
@ -1371,6 +1562,9 @@ bool JPEGFileLoader(Image* image, std::string_view filename, std::FILE* fp, Erro
template<typename T> template<typename T>
static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T setup_func) static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T setup_func)
{ {
if (!s_dyn_libjpeg.Open(error))
return false;
std::vector<u8> scanline; std::vector<u8> scanline;
jpeg_compress_struct info = {}; jpeg_compress_struct info = {};
@ -1379,12 +1573,12 @@ static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T set
JPEGErrorHandler errhandler(error); JPEGErrorHandler errhandler(error);
if (fastjmp_set(&errhandler.jbuf) != 0) if (fastjmp_set(&errhandler.jbuf) != 0)
{ {
jpeg_destroy_compress(&info); s_dyn_libjpeg.jpeg_destroy_compress(&info);
return false; return false;
} }
info.err = &errhandler.err; info.err = &errhandler.err;
jpeg_create_compress(&info); s_dyn_libjpeg.jpeg_CreateCompress(&info, JPEG_LIB_VERSION, sizeof(info));
setup_func(info); setup_func(info);
info.image_width = image.GetWidth(); info.image_width = image.GetWidth();
@ -1392,9 +1586,9 @@ static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T set
info.in_color_space = JCS_RGB; info.in_color_space = JCS_RGB;
info.input_components = 3; info.input_components = 3;
jpeg_set_defaults(&info); s_dyn_libjpeg.jpeg_set_defaults(&info);
jpeg_set_quality(&info, quality, TRUE); s_dyn_libjpeg.jpeg_set_quality(&info, quality, TRUE);
jpeg_start_compress(&info, TRUE); s_dyn_libjpeg.jpeg_start_compress(&info, TRUE);
scanline.resize(static_cast<size_t>(image.GetWidth()) * 3); scanline.resize(static_cast<size_t>(image.GetWidth()) * 3);
u8* scanline_buffer[1] = {scanline.data()}; u8* scanline_buffer[1] = {scanline.data()};
@ -1414,7 +1608,7 @@ static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T set
*(dst_ptr++) = Truncate8(rgba >> 16); *(dst_ptr++) = Truncate8(rgba >> 16);
} }
if (jpeg_write_scanlines(&info, scanline_buffer, 1) != 1) if (s_dyn_libjpeg.jpeg_write_scanlines(&info, scanline_buffer, 1) != 1)
{ {
Error::SetStringFmt(error, "jpeg_write_scanlines() failed at row {}", y); Error::SetStringFmt(error, "jpeg_write_scanlines() failed at row {}", y);
result = false; result = false;
@ -1422,8 +1616,8 @@ static bool WrapJPEGCompress(const Image& image, u8 quality, Error* error, T set
} }
} }
jpeg_finish_compress(&info); s_dyn_libjpeg.jpeg_finish_compress(&info);
jpeg_destroy_compress(&info); s_dyn_libjpeg.jpeg_destroy_compress(&info);
return result; return result;
} }
@ -1524,8 +1718,11 @@ bool JPEGFileSaver(const Image& image, std::string_view filename, std::FILE* fp,
bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error) bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error)
{ {
if (!s_dyn_libwebp.Open(error))
return false;
int width, height; int width, height;
if (!WebPGetInfo(data.data(), data.size(), &width, &height) || width <= 0 || height <= 0) if (!s_dyn_libwebp.WebPGetInfo(data.data(), data.size(), &width, &height) || width <= 0 || height <= 0)
{ {
Error::SetStringView(error, "WebPGetInfo() failed"); Error::SetStringView(error, "WebPGetInfo() failed");
return false; return false;
@ -1538,7 +1735,8 @@ bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error)
return false; return false;
} }
if (!WebPDecodeRGBAInto(data.data(), data.size(), image->GetPixels(), image->GetStorageSize(), image->GetPitch())) if (!s_dyn_libwebp.WebPDecodeRGBAInto(data.data(), data.size(), image->GetPixels(), image->GetStorageSize(),
image->GetPitch()))
{ {
Error::SetStringView(error, "WebPDecodeRGBAInto() failed"); Error::SetStringView(error, "WebPDecodeRGBAInto() failed");
image->Invalidate(); image->Invalidate();
@ -1550,10 +1748,13 @@ bool WebPBufferLoader(Image* image, std::span<const u8> data, Error* error)
bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error) bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality, Error* error)
{ {
if (!s_dyn_libwebp.Open(error))
return false;
u8* encoded_data; u8* encoded_data;
const size_t encoded_size = const size_t encoded_size =
WebPEncodeRGBA(reinterpret_cast<const u8*>(image.GetPixels()), image.GetWidth(), image.GetHeight(), s_dyn_libwebp.WebPEncodeRGBA(reinterpret_cast<const u8*>(image.GetPixels()), image.GetWidth(), image.GetHeight(),
image.GetPitch(), static_cast<float>(quality), &encoded_data); image.GetPitch(), static_cast<float>(quality), &encoded_data);
if (encoded_size == 0) if (encoded_size == 0)
{ {
Error::SetStringFmt(error, "WebPEncodeRGBA() for {}x{} failed.", image.GetWidth(), image.GetHeight()); Error::SetStringFmt(error, "WebPEncodeRGBA() for {}x{} failed.", image.GetWidth(), image.GetHeight());
@ -1562,7 +1763,7 @@ bool WebPBufferSaver(const Image& image, DynamicHeapArray<u8>* data, u8 quality,
data->resize(encoded_size); data->resize(encoded_size);
std::memcpy(data->data(), encoded_data, encoded_size); std::memcpy(data->data(), encoded_data, encoded_size);
WebPFree(encoded_data); s_dyn_libwebp.WebPFree(encoded_data);
return true; return true;
} }

@ -25,7 +25,7 @@
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)spirv_cross</AdditionalIncludeDirectories> <AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(DepsIncludeDir)spirv_cross</AdditionalIncludeDirectories>
</ClCompile> </ClCompile>
<Link> <Link>
<AdditionalDependencies>%(AdditionalDependencies);freetype.lib;jpeg.lib;libpng16.lib;libwebp.lib;plutosvg.lib;SDL3.lib;soundtouch.lib;zip.lib;zlib.lib;zstd.lib</AdditionalDependencies> <AdditionalDependencies>%(AdditionalDependencies);freetype.lib;plutosvg.lib;SDL3.lib;soundtouch.lib;zip.lib;zlib.lib;zstd.lib</AdditionalDependencies>
</Link> </Link>
</ItemDefinitionGroup> </ItemDefinitionGroup>
<ItemGroup> <ItemGroup>

@ -10,6 +10,7 @@
<ClInclude Include="core_audio_stream.h" /> <ClInclude Include="core_audio_stream.h" />
<ClInclude Include="dyn_shaderc.h" /> <ClInclude Include="dyn_shaderc.h" />
<ClInclude Include="dyn_spirv_cross.h" /> <ClInclude Include="dyn_spirv_cross.h" />
<ClInclude Include="dyn_libpng.h" />
<ClInclude Include="elf_file.h" /> <ClInclude Include="elf_file.h" />
<ClInclude Include="gpu_types.h" /> <ClInclude Include="gpu_types.h" />
<ClInclude Include="image.h" /> <ClInclude Include="image.h" />

@ -84,6 +84,7 @@
<ClInclude Include="object_archive.h" /> <ClInclude Include="object_archive.h" />
<ClInclude Include="http_cache.h" /> <ClInclude Include="http_cache.h" />
<ClInclude Include="dyn_sqlite.h" /> <ClInclude Include="dyn_sqlite.h" />
<ClInclude Include="dyn_libpng.h" />
<ClInclude Include="sqlite_helpers.h" /> <ClInclude Include="sqlite_helpers.h" />
<ClInclude Include="zip_helpers.h" /> <ClInclude Include="zip_helpers.h" />
</ItemGroup> </ItemGroup>

Loading…
Cancel
Save