From 59a13d91ea441bed479feff14f4614db1a81e449 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Wed, 3 Apr 2024 22:53:35 +1000 Subject: [PATCH] Image: Don't use libjpeg stdio functions Fixes I/O in debug builds. --- src/common/types.h | 3 + src/util/image.cpp | 101 +++++++++++++++++++++++++++++++++- src/util/imgui_fullscreen.cpp | 38 +++++++++---- 3 files changed, 128 insertions(+), 14 deletions(-) diff --git a/src/common/types.h b/src/common/types.h index 6295006b4..600a06c8c 100644 --- a/src/common/types.h +++ b/src/common/types.h @@ -224,3 +224,6 @@ static constexpr u32 HOST_PAGE_SHIFT = 12; static_cast::type>(rhs)); \ return lhs; \ } + +// Compute the address of a base type given a field offset. +#define BASE_FROM_RECORD_FIELD(ptr, base_type, field) ((base_type*)(((char*)ptr) - offsetof(base_type, field))) diff --git a/src/util/image.cpp b/src/util/image.cpp index e5af1007d..8cc05cf1c 100644 --- a/src/util/image.cpp +++ b/src/util/image.cpp @@ -518,7 +518,62 @@ bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size) bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp) { - return WrapJPEGDecompress(image, [fp](jpeg_decompress_struct& info) { jpeg_stdio_src(&info, fp); }); + static constexpr u32 BUFFER_SIZE = 16384; + + struct FileCallback + { + jpeg_source_mgr mgr; + + std::FILE* fp; + std::unique_ptr buffer; + bool end_of_file; + }; + + FileCallback cb = { + .mgr = { + .init_source = [](j_decompress_ptr cinfo) {}, + .fill_input_buffer = [](j_decompress_ptr cinfo) -> boolean { + FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr); + cb->mgr.next_input_byte = cb->buffer.get(); + if (cb->end_of_file) + { + cb->buffer[0] = 0xFF; + cb->buffer[1] = JPEG_EOI; + cb->mgr.bytes_in_buffer = 2; + return TRUE; + } + + const size_t r = std::fread(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp); + cb->end_of_file |= (std::feof(cb->fp) != 0); + cb->mgr.bytes_in_buffer = r; + return TRUE; + }, + .skip_input_data = + [](j_decompress_ptr cinfo, long num_bytes) { + FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->src, FileCallback, mgr); + const size_t skip_in_buffer = std::min(cb->mgr.bytes_in_buffer, static_cast(num_bytes)); + cb->mgr.next_input_byte += skip_in_buffer; + cb->mgr.bytes_in_buffer -= skip_in_buffer; + + const size_t seek_cur = static_cast(num_bytes) - skip_in_buffer; + if (seek_cur > 0) + { + if (FileSystem::FSeek64(cb->fp, static_cast(seek_cur), SEEK_CUR) != 0) + { + cb->end_of_file = true; + return; + } + } + }, + .resync_to_restart = jpeg_resync_to_restart, + .term_source = [](j_decompress_ptr cinfo) {}, + }, + .fp = fp, + .buffer = std::make_unique(BUFFER_SIZE), + .end_of_file = false, + }; + + return WrapJPEGDecompress(image, [&cb](jpeg_decompress_struct& info) { info.src = &cb.mgr; }); } template @@ -613,7 +668,49 @@ bool JPEGBufferSaver(const RGBA8Image& image, std::vector* buffer, u8 qualit bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality) { - return WrapJPEGCompress(image, quality, [fp](jpeg_compress_struct& info) { jpeg_stdio_dest(&info, fp); }); + static constexpr u32 BUFFER_SIZE = 16384; + + struct FileCallback + { + jpeg_destination_mgr mgr; + + std::FILE* fp; + std::unique_ptr buffer; + bool write_error; + }; + + FileCallback cb = { + .mgr = { + .init_destination = + [](j_compress_ptr cinfo) { + FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr); + cb->mgr.next_output_byte = cb->buffer.get(); + cb->mgr.free_in_buffer = BUFFER_SIZE; + }, + .empty_output_buffer = [](j_compress_ptr cinfo) -> boolean { + FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr); + if (!cb->write_error) + cb->write_error |= (std::fwrite(cb->buffer.get(), 1, BUFFER_SIZE, cb->fp) != BUFFER_SIZE); + + cb->mgr.next_output_byte = cb->buffer.get(); + cb->mgr.free_in_buffer = BUFFER_SIZE; + return TRUE; + }, + .term_destination = + [](j_compress_ptr cinfo) { + FileCallback* cb = BASE_FROM_RECORD_FIELD(cinfo->dest, FileCallback, mgr); + const size_t left = BUFFER_SIZE - cb->mgr.free_in_buffer; + if (left > 0 && !cb->write_error) + cb->write_error |= (std::fwrite(cb->buffer.get(), 1, left, cb->fp) != left); + }, + }, + .fp = fp, + .buffer = std::make_unique(BUFFER_SIZE), + .write_error = false, + }; + + return (WrapJPEGCompress(image, quality, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; }) && + !cb.write_error); } bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size) diff --git a/src/util/imgui_fullscreen.cpp b/src/util/imgui_fullscreen.cpp index 6efa48df6..ebe9a4fbb 100644 --- a/src/util/imgui_fullscreen.cpp +++ b/src/util/imgui_fullscreen.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #define IMGUI_DEFINE_MATH_OPERATORS @@ -10,6 +10,7 @@ #include "common/assert.h" #include "common/easing.h" +#include "common/error.h" #include "common/file_system.h" #include "common/log.h" #include "common/lru_cache.h" @@ -271,24 +272,37 @@ const std::shared_ptr& ImGuiFullscreen::GetPlaceholderTexture() std::optional ImGuiFullscreen::LoadTextureImage(const char* path) { std::optional image; - - std::optional> data; if (Path::IsAbsolute(path)) - data = FileSystem::ReadBinaryFile(path); - else - data = Host::ReadResourceFile(path, true); - if (data.has_value()) { - image = RGBA8Image(); - if (!image->LoadFromBuffer(path, data->data(), data->size())) + Error error; + auto fp = FileSystem::OpenManagedCFile(path, "rb", &error); + if (fp) { - Log_ErrorPrintf("Failed to read texture resource '%s'", path); - image.reset(); + image = RGBA8Image(); + if (!image->LoadFromFile(path, fp.get())) + Log_ErrorFmt("Failed to read texture file '{}'", path); + } + else + { + Log_ErrorFmt("Failed to open texture file '{}': {}", path, error.GetDescription()); } } else { - Log_ErrorPrintf("Failed to open texture resource '%s'", path); + std::optional> data = Host::ReadResourceFile(path, true); + if (data.has_value()) + { + image = RGBA8Image(); + if (!image->LoadFromBuffer(path, data->data(), data->size())) + { + Log_ErrorFmt("Failed to read texture resource '{}'", path); + image.reset(); + } + } + else + { + Log_ErrorFmt("Failed to open texture resource '{}'", path); + } } return image;