mirror of https://github.com/PCSX2/pcsx2.git
Image: Use libjpeg and support WebP saving
Backport from:f3c0c14b2a
c854b8f85e
This commit is contained in:
parent
6808db1cde
commit
590b81a782
|
@ -12,6 +12,7 @@ find_package(Threads REQUIRED)
|
||||||
set(FIND_FRAMEWORK_BACKUP ${CMAKE_FIND_FRAMEWORK})
|
set(FIND_FRAMEWORK_BACKUP ${CMAKE_FIND_FRAMEWORK})
|
||||||
set(CMAKE_FIND_FRAMEWORK NEVER)
|
set(CMAKE_FIND_FRAMEWORK NEVER)
|
||||||
find_package(PNG 1.6.40 REQUIRED)
|
find_package(PNG 1.6.40 REQUIRED)
|
||||||
|
find_package(JPEG REQUIRED) # No version because flatpak uses libjpeg-turbo.
|
||||||
find_package(ZLIB REQUIRED) # v1.3, but Mac uses the SDK version.
|
find_package(ZLIB REQUIRED) # v1.3, but Mac uses the SDK version.
|
||||||
find_package(Zstd 1.5.5 REQUIRED)
|
find_package(Zstd 1.5.5 REQUIRED)
|
||||||
find_package(LZ4 REQUIRED)
|
find_package(LZ4 REQUIRED)
|
||||||
|
|
|
@ -186,8 +186,8 @@ endif()
|
||||||
|
|
||||||
target_link_libraries(common PRIVATE
|
target_link_libraries(common PRIVATE
|
||||||
${LIBC_LIBRARIES}
|
${LIBC_LIBRARIES}
|
||||||
|
JPEG::JPEG
|
||||||
PNG::PNG
|
PNG::PNG
|
||||||
jpgd
|
|
||||||
WebP::libwebp
|
WebP::libwebp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
443
common/Image.cpp
443
common/Image.cpp
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#include "Image.h"
|
#include "Image.h"
|
||||||
|
@ -8,36 +8,33 @@
|
||||||
#include "ScopedGuard.h"
|
#include "ScopedGuard.h"
|
||||||
#include "StringUtil.h"
|
#include "StringUtil.h"
|
||||||
|
|
||||||
#include "jpgd.h"
|
#include <jpeglib.h>
|
||||||
#include "jpge.h"
|
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
#include <webp/decode.h>
|
#include <webp/decode.h>
|
||||||
//#include <webp/encode.h>
|
#include <webp/encode.h>
|
||||||
|
|
||||||
using namespace Common;
|
|
||||||
|
|
||||||
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
static bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||||
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
static bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||||
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
static bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||||
static bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
static bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||||
|
|
||||||
static bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
static bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||||
static bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
static bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||||
static bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
static bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||||
static bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
static bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||||
|
|
||||||
static bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
static bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size);
|
||||||
static bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality);
|
static bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality);
|
||||||
static bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
static bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp);
|
||||||
static bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality);
|
static bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality);
|
||||||
|
|
||||||
struct FormatHandler
|
struct FormatHandler
|
||||||
{
|
{
|
||||||
const char* extension;
|
const char* extension;
|
||||||
bool (*buffer_loader)(RGBA8Image*, const void*, size_t);
|
bool (*buffer_loader)(RGBA8Image*, const void*, size_t);
|
||||||
bool (*buffer_saver)(const RGBA8Image&, std::vector<u8>*, int);
|
bool (*buffer_saver)(const RGBA8Image&, std::vector<u8>*, u8);
|
||||||
bool (*file_loader)(RGBA8Image*, const char*, std::FILE*);
|
bool (*file_loader)(RGBA8Image*, const char*, std::FILE*);
|
||||||
bool (*file_saver)(const RGBA8Image&, const char*, std::FILE*, int);
|
bool (*file_saver)(const RGBA8Image&, const char*, std::FILE*, u8);
|
||||||
};
|
};
|
||||||
|
|
||||||
static constexpr FormatHandler s_format_handlers[] = {
|
static constexpr FormatHandler s_format_handlers[] = {
|
||||||
|
@ -51,7 +48,7 @@ static const FormatHandler* GetFormatHandler(const std::string_view& extension)
|
||||||
{
|
{
|
||||||
for (const FormatHandler& handler : s_format_handlers)
|
for (const FormatHandler& handler : s_format_handlers)
|
||||||
{
|
{
|
||||||
if (StringUtil::compareNoCase(extension, handler.extension))
|
if (StringUtil::Strncasecmp(extension.data(), handler.extension, extension.size()) == 0)
|
||||||
return &handler;
|
return &handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,6 +72,16 @@ RGBA8Image::RGBA8Image(RGBA8Image&& move)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RGBA8Image::RGBA8Image(u32 width, u32 height)
|
||||||
|
: Image(width, height)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RGBA8Image::RGBA8Image(u32 width, u32 height, std::vector<u32> pixels)
|
||||||
|
: Image(width, height, std::move(pixels))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy)
|
RGBA8Image& RGBA8Image::operator=(const RGBA8Image& copy)
|
||||||
{
|
{
|
||||||
Image<u32>::operator=(copy);
|
Image<u32>::operator=(copy);
|
||||||
|
@ -96,7 +103,7 @@ bool RGBA8Image::LoadFromFile(const char* filename)
|
||||||
return LoadFromFile(filename, fp.get());
|
return LoadFromFile(filename, fp.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RGBA8Image::SaveToFile(const char* filename, int quality) const
|
bool RGBA8Image::SaveToFile(const char* filename, u8 quality) const
|
||||||
{
|
{
|
||||||
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
auto fp = FileSystem::OpenManagedCFile(filename, "wb");
|
||||||
if (!fp)
|
if (!fp)
|
||||||
|
@ -117,8 +124,7 @@ bool RGBA8Image::LoadFromFile(const char* filename, std::FILE* fp)
|
||||||
const FormatHandler* handler = GetFormatHandler(extension);
|
const FormatHandler* handler = GetFormatHandler(extension);
|
||||||
if (!handler || !handler->file_loader)
|
if (!handler || !handler->file_loader)
|
||||||
{
|
{
|
||||||
Console.Error("(RGBA8Image::LoadFromFile) Unknown extension '%.*s'",
|
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||||
static_cast<int>(extension.size()), extension.data());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,32 +137,30 @@ bool RGBA8Image::LoadFromBuffer(const char* filename, const void* buffer, size_t
|
||||||
const FormatHandler* handler = GetFormatHandler(extension);
|
const FormatHandler* handler = GetFormatHandler(extension);
|
||||||
if (!handler || !handler->buffer_loader)
|
if (!handler || !handler->buffer_loader)
|
||||||
{
|
{
|
||||||
Console.Error("(RGBA8Image::LoadFromBuffer) Unknown extension '%.*s'",
|
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||||
static_cast<int>(extension.size()), extension.data());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return handler->buffer_loader(this, buffer, buffer_size);
|
return handler->buffer_loader(this, buffer, buffer_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RGBA8Image::SaveToFile(const char* filename, std::FILE* fp, int quality) const
|
bool RGBA8Image::SaveToFile(const char* filename, std::FILE* fp, u8 quality) const
|
||||||
{
|
{
|
||||||
const std::string_view extension(Path::GetExtension(filename));
|
const std::string_view extension(Path::GetExtension(filename));
|
||||||
const FormatHandler* handler = GetFormatHandler(extension);
|
const FormatHandler* handler = GetFormatHandler(extension);
|
||||||
if (!handler || !handler->file_saver)
|
if (!handler || !handler->file_saver)
|
||||||
{
|
{
|
||||||
Console.Error("(RGBA8Image::SaveToFile) Unknown extension '%.*s'",
|
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||||
static_cast<int>(extension.size()), extension.data());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsValid() || !handler->file_saver(*this, filename, fp, quality))
|
if (!handler->file_saver(*this, filename, fp, quality))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return (std::fflush(fp) == 0);
|
return (std::fflush(fp) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(const char* filename, int quality) const
|
std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(const char* filename, u8 quality) const
|
||||||
{
|
{
|
||||||
std::optional<std::vector<u8>> ret;
|
std::optional<std::vector<u8>> ret;
|
||||||
|
|
||||||
|
@ -164,20 +168,19 @@ std::optional<std::vector<u8>> RGBA8Image::SaveToBuffer(const char* filename, in
|
||||||
const FormatHandler* handler = GetFormatHandler(extension);
|
const FormatHandler* handler = GetFormatHandler(extension);
|
||||||
if (!handler || !handler->file_saver)
|
if (!handler || !handler->file_saver)
|
||||||
{
|
{
|
||||||
Console.Error("(RGBA8Image::SaveToBuffer) Unknown extension '%.*s'",
|
Console.ErrorFmt("Unknown extension '{}'", extension);
|
||||||
static_cast<int>(extension.size()), extension.data());
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = std::vector<u8>();
|
ret = std::vector<u8>();
|
||||||
if (!IsValid() || !handler->buffer_saver(*this, &ret.value(), quality))
|
if (!handler->buffer_saver(*this, &ret.value(), quality))
|
||||||
ret.reset();
|
ret.reset();
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr,
|
static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop info_ptr, std::vector<u32>& new_data,
|
||||||
std::vector<u32>& new_data, std::vector<png_bytep>& row_pointers)
|
std::vector<png_bytep>& row_pointers)
|
||||||
{
|
{
|
||||||
png_read_info(png_ptr, info_ptr);
|
png_read_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
|
@ -203,13 +206,10 @@ static bool PNGCommonLoader(RGBA8Image* image, png_structp png_ptr, png_infop in
|
||||||
png_set_tRNS_to_alpha(png_ptr);
|
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 ||
|
if (color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_PALETTE)
|
||||||
color_type == PNG_COLOR_TYPE_GRAY ||
|
|
||||||
color_type == PNG_COLOR_TYPE_PALETTE)
|
|
||||||
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
png_set_filler(png_ptr, 0xFF, PNG_FILLER_AFTER);
|
||||||
|
|
||||||
if (color_type == PNG_COLOR_TYPE_GRAY ||
|
if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
||||||
color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
|
|
||||||
png_set_gray_to_rgb(png_ptr);
|
png_set_gray_to_rgb(png_ptr);
|
||||||
|
|
||||||
png_read_update_info(png_ptr, info_ptr);
|
png_read_update_info(png_ptr, info_ptr);
|
||||||
|
@ -237,9 +237,7 @@ bool PNGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
|
||||||
});
|
|
||||||
|
|
||||||
std::vector<u32> new_data;
|
std::vector<u32> new_data;
|
||||||
std::vector<png_bytep> row_pointers;
|
std::vector<png_bytep> row_pointers;
|
||||||
|
@ -264,9 +262,7 @@ bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
ScopedGuard cleanup([&png_ptr, &info_ptr]() {
|
ScopedGuard cleanup([&png_ptr, &info_ptr]() { png_destroy_read_struct(&png_ptr, &info_ptr, nullptr); });
|
||||||
png_destroy_read_struct(&png_ptr, &info_ptr, nullptr);
|
|
||||||
});
|
|
||||||
|
|
||||||
std::vector<u32> new_data;
|
std::vector<u32> new_data;
|
||||||
std::vector<png_bytep> row_pointers;
|
std::vector<png_bytep> row_pointers;
|
||||||
|
@ -295,11 +291,11 @@ bool PNGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
return PNGCommonLoader(image, png_ptr, info_ptr, new_data, row_pointers);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, int quality)
|
static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_infop info_ptr, u8 quality)
|
||||||
{
|
{
|
||||||
png_set_compression_level(png_ptr, std::clamp(quality / 10, 0, 9));
|
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_set_IHDR(png_ptr, info_ptr, image.GetWidth(), image.GetHeight(), 8, PNG_COLOR_TYPE_RGBA, PNG_INTERLACE_NONE,
|
||||||
PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
|
||||||
png_write_info(png_ptr, info_ptr);
|
png_write_info(png_ptr, info_ptr);
|
||||||
|
|
||||||
for (u32 y = 0; y < image.GetHeight(); ++y)
|
for (u32 y = 0; y < image.GetHeight(); ++y)
|
||||||
|
@ -308,7 +304,7 @@ static void PNGSaveCommon(const RGBA8Image& image, png_structp png_ptr, png_info
|
||||||
png_write_end(png_ptr, nullptr);
|
png_write_end(png_ptr, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||||
{
|
{
|
||||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
png_infop info_ptr = nullptr;
|
png_infop info_ptr = nullptr;
|
||||||
|
@ -328,15 +324,18 @@ bool PNGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp,
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
png_set_write_fn(
|
png_set_write_fn(
|
||||||
png_ptr, fp, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
png_ptr, fp,
|
||||||
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1)
|
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||||
png_error(png_ptr, "file write error"); }, [](png_structp png_ptr) {});
|
if (std::fwrite(data_ptr, size, 1, static_cast<std::FILE*>(png_get_io_ptr(png_ptr))) != 1)
|
||||||
|
png_error(png_ptr, "file write error");
|
||||||
|
},
|
||||||
|
[](png_structp png_ptr) {});
|
||||||
|
|
||||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||||
{
|
{
|
||||||
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr);
|
||||||
png_infop info_ptr = nullptr;
|
png_infop info_ptr = nullptr;
|
||||||
|
@ -358,185 +357,216 @@ bool PNGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int qualit
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
png_set_write_fn(
|
png_set_write_fn(
|
||||||
png_ptr, buffer, [](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
png_ptr, buffer,
|
||||||
std::vector<u8>* buffer = static_cast<std::vector<u8>*>(png_get_io_ptr(png_ptr));
|
[](png_structp png_ptr, png_bytep data_ptr, png_size_t size) {
|
||||||
const size_t old_pos = buffer->size();
|
std::vector<u8>* buffer = static_cast<std::vector<u8>*>(png_get_io_ptr(png_ptr));
|
||||||
buffer->resize(old_pos + size);
|
const size_t old_pos = buffer->size();
|
||||||
std::memcpy(buffer->data() + old_pos, data_ptr, size); }, [](png_structp png_ptr) {});
|
buffer->resize(old_pos + size);
|
||||||
|
std::memcpy(buffer->data() + old_pos, data_ptr, size);
|
||||||
|
},
|
||||||
|
[](png_structp png_ptr) {});
|
||||||
|
|
||||||
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
PNGSaveCommon(image, png_ptr, info_ptr, quality);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
struct JPEGErrorHandler
|
||||||
|
{
|
||||||
|
jpeg_error_mgr err;
|
||||||
|
jmp_buf jbuf;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
static bool HandleJPEGError(JPEGErrorHandler* eh)
|
||||||
|
{
|
||||||
|
jpeg_std_error(&eh->err);
|
||||||
|
|
||||||
|
eh->err.error_exit = [](j_common_ptr cinfo) {
|
||||||
|
JPEGErrorHandler* eh = (JPEGErrorHandler*)cinfo->err;
|
||||||
|
char msg[JMSG_LENGTH_MAX];
|
||||||
|
eh->err.format_message(cinfo, msg);
|
||||||
|
Console.ErrorFmt("libjpeg fatal error: {}", msg);
|
||||||
|
longjmp(eh->jbuf, 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (setjmp(eh->jbuf) == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static bool WrapJPEGDecompress(RGBA8Image* image, T setup_func)
|
||||||
|
{
|
||||||
|
std::vector<u8> scanline;
|
||||||
|
|
||||||
|
JPEGErrorHandler err;
|
||||||
|
if (!HandleJPEGError(&err))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
jpeg_decompress_struct info;
|
||||||
|
info.err = &err.err;
|
||||||
|
jpeg_create_decompress(&info);
|
||||||
|
setup_func(info);
|
||||||
|
|
||||||
|
const int herr = jpeg_read_header(&info, TRUE);
|
||||||
|
if (herr != JPEG_HEADER_OK)
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("jpeg_read_header() returned {}", herr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.image_width == 0 || info.image_height == 0 || info.num_components < 3)
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("Invalid image dimensions: {}x{}x{}", info.image_width, info.image_height, info.num_components);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
info.out_color_space = JCS_RGB;
|
||||||
|
info.out_color_components = 3;
|
||||||
|
|
||||||
|
if (!jpeg_start_decompress(&info))
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("jpeg_start_decompress() returned failure");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
image->SetSize(info.image_width, info.image_height);
|
||||||
|
scanline.resize(info.image_width * 3);
|
||||||
|
|
||||||
|
u8* scanline_buffer[1] = {scanline.data()};
|
||||||
|
bool result = true;
|
||||||
|
for (u32 y = 0; y < info.image_height; y++)
|
||||||
|
{
|
||||||
|
if (jpeg_read_scanlines(&info, scanline_buffer, 1) != 1)
|
||||||
|
{
|
||||||
|
Console.ErrorFmt("jpeg_read_scanlines() failed at row {}", y);
|
||||||
|
result = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RGB -> RGBA
|
||||||
|
const u8* src_ptr = scanline.data();
|
||||||
|
u32* dst_ptr = image->GetRowPixels(y);
|
||||||
|
for (u32 x = 0; x < info.image_width; x++)
|
||||||
|
{
|
||||||
|
*(dst_ptr++) = (static_cast<u32>(src_ptr[0]) | (static_cast<u32>(src_ptr[1]) << 8) | (static_cast<u32>(src_ptr[2]) << 16) | 0xFF000000u);
|
||||||
|
src_ptr += 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
jpeg_finish_decompress(&info);
|
||||||
|
jpeg_destroy_decompress(&info);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
bool JPEGBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
{
|
{
|
||||||
int width, height, file_comps;
|
return WrapJPEGDecompress(image, [buffer, buffer_size](jpeg_decompress_struct& info) {
|
||||||
u8* data = jpgd::decompress_jpeg_image_from_memory(static_cast<const u8*>(buffer), static_cast<int>(buffer_size),
|
jpeg_mem_src(&info, static_cast<const unsigned char*>(buffer), buffer_size);
|
||||||
&width, &height, &file_comps, 4, 0);
|
});
|
||||||
if (!data)
|
|
||||||
{
|
|
||||||
Console.Error("jpgd::decompress_jpeg_image_from_memory() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(data));
|
|
||||||
std::free(data);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
bool JPEGFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||||
{
|
{
|
||||||
class FileStream : public jpgd::jpeg_decoder_stream
|
return WrapJPEGDecompress(image, [fp](jpeg_decompress_struct& info) { jpeg_stdio_src(&info, fp); });
|
||||||
{
|
|
||||||
std::FILE* m_fp;
|
|
||||||
bool m_error_flag = false;
|
|
||||||
bool m_eof_flag = false;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit FileStream(std::FILE* fp_)
|
|
||||||
: m_fp(fp_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
int read(jpgd::uint8* pBuf, int max_bytes_to_read, bool* pEOF_flag) override
|
|
||||||
{
|
|
||||||
if (m_eof_flag)
|
|
||||||
{
|
|
||||||
*pEOF_flag = true;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_error_flag)
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
int bytes_read = static_cast<int>(std::fread(pBuf, 1, max_bytes_to_read, m_fp));
|
|
||||||
if (bytes_read < max_bytes_to_read)
|
|
||||||
{
|
|
||||||
if (std::ferror(m_fp))
|
|
||||||
{
|
|
||||||
m_error_flag = true;
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_eof_flag = true;
|
|
||||||
*pEOF_flag = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return bytes_read;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
FileStream stream(fp);
|
|
||||||
int width, height, file_comps;
|
|
||||||
u8* data = jpgd::decompress_jpeg_image_from_stream(&stream, &width, &height, &file_comps, 4, 0);
|
|
||||||
if (!data)
|
|
||||||
{
|
|
||||||
Console.Error("jpgd::decompress_jpeg_image_from_stream() failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
image->SetPixels(static_cast<u32>(width), static_cast<u32>(height), reinterpret_cast<const u32*>(data));
|
|
||||||
std::free(data);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool JPEGCommonSaver(const RGBA8Image& image, jpge::output_stream& stream, int quality)
|
template <typename T>
|
||||||
|
static bool WrapJPEGCompress(const RGBA8Image& image, u8 quality, T setup_func)
|
||||||
{
|
{
|
||||||
jpge::params params;
|
std::vector<u8> scanline;
|
||||||
params.m_quality = quality;
|
|
||||||
|
|
||||||
jpge::jpeg_encoder dst_image;
|
JPEGErrorHandler err;
|
||||||
if (!dst_image.init(&stream, image.GetWidth(), image.GetHeight(), 3, params))
|
if (!HandleJPEGError(&err))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
// for RGBA->RGB
|
jpeg_compress_struct info;
|
||||||
std::vector<u8> row;
|
info.err = &err.err;
|
||||||
row.resize(image.GetWidth() * 3);
|
jpeg_create_compress(&info);
|
||||||
|
setup_func(info);
|
||||||
|
|
||||||
for (uint pass_index = 0; pass_index < dst_image.get_total_passes(); pass_index++)
|
info.image_width = image.GetWidth();
|
||||||
|
info.image_height = image.GetHeight();
|
||||||
|
info.in_color_space = JCS_RGB;
|
||||||
|
info.input_components = 3;
|
||||||
|
|
||||||
|
jpeg_set_defaults(&info);
|
||||||
|
jpeg_set_quality(&info, quality, TRUE);
|
||||||
|
jpeg_start_compress(&info, TRUE);
|
||||||
|
|
||||||
|
scanline.resize(image.GetWidth() * 3);
|
||||||
|
u8* scanline_buffer[1] = {scanline.data()};
|
||||||
|
bool result = true;
|
||||||
|
for (u32 y = 0; y < info.image_height; y++)
|
||||||
{
|
{
|
||||||
for (u32 i = 0; i < image.GetHeight(); i++)
|
// RGBA -> RGB
|
||||||
|
u8* dst_ptr = scanline.data();
|
||||||
|
const u32* src_ptr = image.GetRowPixels(y);
|
||||||
|
for (u32 x = 0; x < info.image_width; x++)
|
||||||
{
|
{
|
||||||
const u8* row_in = reinterpret_cast<const u8*>(image.GetRowPixels(i));
|
const u32 rgba = *(src_ptr++);
|
||||||
u8* row_out = row.data();
|
*(dst_ptr++) = static_cast<u8>(rgba);
|
||||||
for (u32 j = 0; j < image.GetWidth(); j++)
|
*(dst_ptr++) = static_cast<u8>(rgba >> 8);
|
||||||
{
|
*(dst_ptr++) = static_cast<u8>(rgba >> 16);
|
||||||
*(row_out++) = *(row_in++);
|
}
|
||||||
*(row_out++) = *(row_in++);
|
|
||||||
*(row_out++) = *(row_in++);
|
if (jpeg_write_scanlines(&info, scanline_buffer, 1) != 1)
|
||||||
row_in++;
|
{
|
||||||
}
|
Console.ErrorFmt("jpeg_write_scanlines() failed at row {}", y);
|
||||||
|
result = false;
|
||||||
if (!dst_image.process_scanline(row.data()))
|
break;
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
if (!dst_image.process_scanline(NULL))
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dst_image.deinit();
|
jpeg_finish_compress(&info);
|
||||||
|
jpeg_destroy_compress(&info);
|
||||||
return true;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
bool JPEGBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||||
{
|
{
|
||||||
class BufferStream : public jpge::output_stream
|
|
||||||
{
|
|
||||||
std::vector<u8>* buffer;
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit BufferStream(std::vector<u8>* buffer_)
|
|
||||||
: buffer(buffer_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool put_buf(const void* Pbuf, int len) override
|
|
||||||
{
|
|
||||||
const size_t old_size = buffer->size();
|
|
||||||
buffer->resize(buffer->size() + static_cast<size_t>(len));
|
|
||||||
std::memcpy(buffer->data() + old_size, Pbuf, static_cast<size_t>(len));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// give enough space to avoid reallocs
|
// give enough space to avoid reallocs
|
||||||
buffer->reserve(image.GetWidth() * image.GetHeight() * 2);
|
buffer->resize(image.GetWidth() * image.GetHeight() * 2);
|
||||||
|
|
||||||
BufferStream stream(buffer);
|
struct MemCallback
|
||||||
return JPEGCommonSaver(image, stream, quality);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool JPEGFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
|
||||||
{
|
|
||||||
class FileStream : public jpge::output_stream
|
|
||||||
{
|
{
|
||||||
std::FILE* m_fp;
|
jpeg_destination_mgr mgr;
|
||||||
bool m_error_flag = false;
|
std::vector<u8>* buffer;
|
||||||
|
size_t buffer_used;
|
||||||
public:
|
|
||||||
explicit FileStream(std::FILE* fp_)
|
|
||||||
: m_fp(fp_)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool put_buf(const void* Pbuf, int len) override
|
|
||||||
{
|
|
||||||
if (m_error_flag)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (std::fwrite(Pbuf, len, 1, m_fp) != 1)
|
|
||||||
{
|
|
||||||
m_error_flag = true;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
FileStream stream(fp);
|
MemCallback cb;
|
||||||
return JPEGCommonSaver(image, stream, quality);
|
cb.buffer = buffer;
|
||||||
|
cb.buffer_used = 0;
|
||||||
|
cb.mgr.next_output_byte = buffer->data();
|
||||||
|
cb.mgr.free_in_buffer = buffer->size();
|
||||||
|
cb.mgr.init_destination = [](j_compress_ptr cinfo) {};
|
||||||
|
cb.mgr.empty_output_buffer = [](j_compress_ptr cinfo) -> boolean {
|
||||||
|
MemCallback* cb = (MemCallback*)cinfo->dest;
|
||||||
|
|
||||||
|
// double size
|
||||||
|
cb->buffer_used = cb->buffer->size();
|
||||||
|
cb->buffer->resize(cb->buffer->size() * 2);
|
||||||
|
cb->mgr.next_output_byte = cb->buffer->data() + cb->buffer_used;
|
||||||
|
cb->mgr.free_in_buffer = cb->buffer->size() - cb->buffer_used;
|
||||||
|
return TRUE;
|
||||||
|
};
|
||||||
|
cb.mgr.term_destination = [](j_compress_ptr cinfo) {
|
||||||
|
MemCallback* cb = (MemCallback*)cinfo->dest;
|
||||||
|
|
||||||
|
// get final size
|
||||||
|
cb->buffer->resize(cb->buffer->size() - cb->mgr.free_in_buffer);
|
||||||
|
};
|
||||||
|
|
||||||
|
return WrapJPEGCompress(image, quality, [&cb](jpeg_compress_struct& info) { info.dest = &cb.mgr; });
|
||||||
|
}
|
||||||
|
|
||||||
|
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); });
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
|
@ -550,9 +580,8 @@ bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
|
|
||||||
std::vector<u32> pixels;
|
std::vector<u32> pixels;
|
||||||
pixels.resize(static_cast<u32>(width) * static_cast<u32>(height));
|
pixels.resize(static_cast<u32>(width) * static_cast<u32>(height));
|
||||||
if (!WebPDecodeRGBAInto(static_cast<const u8*>(buffer), buffer_size,
|
if (!WebPDecodeRGBAInto(static_cast<const u8*>(buffer), buffer_size, reinterpret_cast<u8*>(pixels.data()),
|
||||||
reinterpret_cast<u8*>(pixels.data()), sizeof(u32) * pixels.size(),
|
sizeof(u32) * pixels.size(), sizeof(u32) * static_cast<u32>(width)))
|
||||||
sizeof(u32) * static_cast<u32>(width)))
|
|
||||||
{
|
{
|
||||||
Console.Error("WebPDecodeRGBAInto() failed");
|
Console.Error("WebPDecodeRGBAInto() failed");
|
||||||
return false;
|
return false;
|
||||||
|
@ -562,12 +591,12 @@ bool WebPBufferLoader(RGBA8Image* image, const void* buffer, size_t buffer_size)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quality)
|
bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, u8 quality)
|
||||||
{
|
{
|
||||||
#if 0
|
|
||||||
u8* encoded_data;
|
u8* encoded_data;
|
||||||
const size_t encoded_size = WebPEncodeRGBA(reinterpret_cast<const u8*>(image.GetPixels()),
|
const size_t encoded_size =
|
||||||
image.GetWidth(), image.GetHeight(), image.GetByteStride(), static_cast<float>(quality), &encoded_data);
|
WebPEncodeRGBA(reinterpret_cast<const u8*>(image.GetPixels()), image.GetWidth(), image.GetHeight(),
|
||||||
|
image.GetPitch(), static_cast<float>(quality), &encoded_data);
|
||||||
if (encoded_size == 0)
|
if (encoded_size == 0)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -575,10 +604,6 @@ bool WebPBufferSaver(const RGBA8Image& image, std::vector<u8>* buffer, int quali
|
||||||
std::memcpy(buffer->data(), encoded_data, encoded_size);
|
std::memcpy(buffer->data(), encoded_data, encoded_size);
|
||||||
WebPFree(encoded_data);
|
WebPFree(encoded_data);
|
||||||
return true;
|
return true;
|
||||||
#else
|
|
||||||
Console.Error("Not compiled with WebP encoder.");
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||||
|
@ -590,7 +615,7 @@ bool WebPFileLoader(RGBA8Image* image, const char* filename, std::FILE* fp)
|
||||||
return WebPBufferLoader(image, data->data(), data->size());
|
return WebPBufferLoader(image, data->data(), data->size());
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, int quality)
|
bool WebPFileSaver(const RGBA8Image& image, const char* filename, std::FILE* fp, u8 quality)
|
||||||
{
|
{
|
||||||
std::vector<u8> buffer;
|
std::vector<u8> buffer;
|
||||||
if (!WebPBufferSaver(image, &buffer, quality))
|
if (!WebPBufferSaver(image, &buffer, quality))
|
||||||
|
|
231
common/Image.h
231
common/Image.h
|
@ -1,4 +1,4 @@
|
||||||
// SPDX-FileCopyrightText: 2002-2023 PCSX2 Dev Team
|
// SPDX-FileCopyrightText: 2002-2024 PCSX2 Dev Team
|
||||||
// SPDX-License-Identifier: LGPL-3.0+
|
// SPDX-License-Identifier: LGPL-3.0+
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
@ -9,124 +9,125 @@
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Common
|
template <typename PixelType>
|
||||||
|
class Image
|
||||||
{
|
{
|
||||||
template <typename PixelType>
|
public:
|
||||||
class Image
|
Image() = default;
|
||||||
|
Image(u32 width, u32 height) { SetSize(width, height); }
|
||||||
|
Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); }
|
||||||
|
Image(u32 width, u32 height, std::vector<PixelType> pixels) { SetPixels(width, height, std::move(pixels)); }
|
||||||
|
Image(const Image& copy)
|
||||||
{
|
{
|
||||||
public:
|
m_width = copy.m_width;
|
||||||
Image() = default;
|
m_height = copy.m_height;
|
||||||
Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); }
|
m_pixels = copy.m_pixels;
|
||||||
Image(const Image& copy)
|
}
|
||||||
{
|
Image(Image&& move)
|
||||||
m_width = copy.m_width;
|
|
||||||
m_height = copy.m_height;
|
|
||||||
m_pixels = copy.m_pixels;
|
|
||||||
}
|
|
||||||
Image(Image&& move)
|
|
||||||
{
|
|
||||||
m_width = move.m_width;
|
|
||||||
m_height = move.m_height;
|
|
||||||
m_pixels = std::move(move.m_pixels);
|
|
||||||
move.m_width = 0;
|
|
||||||
move.m_height = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
Image& operator=(const Image& copy)
|
|
||||||
{
|
|
||||||
m_width = copy.m_width;
|
|
||||||
m_height = copy.m_height;
|
|
||||||
m_pixels = copy.m_pixels;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
Image& operator=(Image&& move)
|
|
||||||
{
|
|
||||||
m_width = move.m_width;
|
|
||||||
m_height = move.m_height;
|
|
||||||
m_pixels = std::move(move.m_pixels);
|
|
||||||
move.m_width = 0;
|
|
||||||
move.m_height = 0;
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
__fi bool IsValid() const { return (m_width > 0 && m_height > 0); }
|
|
||||||
__fi u32 GetWidth() const { return m_width; }
|
|
||||||
__fi u32 GetHeight() const { return m_height; }
|
|
||||||
__fi u32 GetByteStride() const { return (sizeof(PixelType) * m_width); }
|
|
||||||
__fi const PixelType* GetPixels() const { return m_pixels.data(); }
|
|
||||||
__fi PixelType* GetPixels() { return m_pixels.data(); }
|
|
||||||
__fi const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; }
|
|
||||||
__fi PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; }
|
|
||||||
__fi void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
|
|
||||||
__fi PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
|
|
||||||
|
|
||||||
void Clear(PixelType fill_value = static_cast<PixelType>(0))
|
|
||||||
{
|
|
||||||
std::fill(m_pixels.begin(), m_pixels.end(), fill_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Invalidate()
|
|
||||||
{
|
|
||||||
m_width = 0;
|
|
||||||
m_height = 0;
|
|
||||||
m_pixels.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast<PixelType>(0))
|
|
||||||
{
|
|
||||||
m_width = new_width;
|
|
||||||
m_height = new_height;
|
|
||||||
m_pixels.resize(new_width * new_height);
|
|
||||||
Clear(fill_value);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetPixels(u32 width, u32 height, const PixelType* pixels)
|
|
||||||
{
|
|
||||||
m_width = width;
|
|
||||||
m_height = height;
|
|
||||||
m_pixels.resize(width * height);
|
|
||||||
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
|
|
||||||
}
|
|
||||||
|
|
||||||
void SetPixels(u32 width, u32 height, std::vector<PixelType> pixels)
|
|
||||||
{
|
|
||||||
m_width = width;
|
|
||||||
m_height = height;
|
|
||||||
m_pixels = std::move(pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<PixelType> TakePixels()
|
|
||||||
{
|
|
||||||
m_width = 0;
|
|
||||||
m_height = 0;
|
|
||||||
return std::move(m_pixels);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
|
||||||
u32 m_width = 0;
|
|
||||||
u32 m_height = 0;
|
|
||||||
std::vector<PixelType> m_pixels;
|
|
||||||
};
|
|
||||||
|
|
||||||
class RGBA8Image : public Image<u32>
|
|
||||||
{
|
{
|
||||||
public:
|
m_width = move.m_width;
|
||||||
static constexpr int DEFAULT_SAVE_QUALITY = 85;
|
m_height = move.m_height;
|
||||||
|
m_pixels = std::move(move.m_pixels);
|
||||||
|
move.m_width = 0;
|
||||||
|
move.m_height = 0;
|
||||||
|
}
|
||||||
|
|
||||||
RGBA8Image();
|
Image& operator=(const Image& copy)
|
||||||
RGBA8Image(u32 width, u32 height, const u32* pixels);
|
{
|
||||||
RGBA8Image(const RGBA8Image& copy);
|
m_width = copy.m_width;
|
||||||
RGBA8Image(RGBA8Image&& move);
|
m_height = copy.m_height;
|
||||||
|
m_pixels = copy.m_pixels;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
Image& operator=(Image&& move)
|
||||||
|
{
|
||||||
|
m_width = move.m_width;
|
||||||
|
m_height = move.m_height;
|
||||||
|
m_pixels = std::move(move.m_pixels);
|
||||||
|
move.m_width = 0;
|
||||||
|
move.m_height = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
RGBA8Image& operator=(const RGBA8Image& copy);
|
__fi bool IsValid() const { return (m_width > 0 && m_height > 0); }
|
||||||
RGBA8Image& operator=(RGBA8Image&& move);
|
__fi u32 GetWidth() const { return m_width; }
|
||||||
|
__fi u32 GetHeight() const { return m_height; }
|
||||||
|
__fi u32 GetPitch() const { return (sizeof(PixelType) * m_width); }
|
||||||
|
__fi const PixelType* GetPixels() const { return m_pixels.data(); }
|
||||||
|
__fi PixelType* GetPixels() { return m_pixels.data(); }
|
||||||
|
__fi const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; }
|
||||||
|
__fi PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; }
|
||||||
|
__fi void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
|
||||||
|
__fi PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
|
||||||
|
|
||||||
bool LoadFromFile(const char* filename);
|
void Clear(PixelType fill_value = static_cast<PixelType>(0))
|
||||||
bool LoadFromFile(const char* filename, std::FILE* fp);
|
{
|
||||||
bool LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size);
|
std::fill(m_pixels.begin(), m_pixels.end(), fill_value);
|
||||||
|
}
|
||||||
|
|
||||||
bool SaveToFile(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const;
|
void Invalidate()
|
||||||
bool SaveToFile(const char* filename, std::FILE* fp, int quality = DEFAULT_SAVE_QUALITY) const;
|
{
|
||||||
std::optional<std::vector<u8>> SaveToBuffer(const char* filename, int quality = DEFAULT_SAVE_QUALITY) const;
|
m_width = 0;
|
||||||
};
|
m_height = 0;
|
||||||
} // namespace Common
|
m_pixels.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast<PixelType>(0))
|
||||||
|
{
|
||||||
|
m_width = new_width;
|
||||||
|
m_height = new_height;
|
||||||
|
m_pixels.resize(new_width * new_height);
|
||||||
|
Clear(fill_value);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPixels(u32 width, u32 height, const PixelType* pixels)
|
||||||
|
{
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
m_pixels.resize(width * height);
|
||||||
|
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetPixels(u32 width, u32 height, std::vector<PixelType> pixels)
|
||||||
|
{
|
||||||
|
m_width = width;
|
||||||
|
m_height = height;
|
||||||
|
m_pixels = std::move(pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<PixelType> TakePixels()
|
||||||
|
{
|
||||||
|
m_width = 0;
|
||||||
|
m_height = 0;
|
||||||
|
return std::move(m_pixels);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
u32 m_width = 0;
|
||||||
|
u32 m_height = 0;
|
||||||
|
std::vector<PixelType> m_pixels;
|
||||||
|
};
|
||||||
|
|
||||||
|
class RGBA8Image : public Image<u32>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static constexpr u8 DEFAULT_SAVE_QUALITY = 85;
|
||||||
|
|
||||||
|
RGBA8Image();
|
||||||
|
RGBA8Image(u32 width, u32 height);
|
||||||
|
RGBA8Image(u32 width, u32 height, const u32* pixels);
|
||||||
|
RGBA8Image(u32 width, u32 height, std::vector<u32> pixels);
|
||||||
|
RGBA8Image(const RGBA8Image& copy);
|
||||||
|
RGBA8Image(RGBA8Image&& move);
|
||||||
|
|
||||||
|
RGBA8Image& operator=(const RGBA8Image& copy);
|
||||||
|
RGBA8Image& operator=(RGBA8Image&& move);
|
||||||
|
|
||||||
|
bool LoadFromFile(const char* filename);
|
||||||
|
bool LoadFromFile(const char* filename, std::FILE* fp);
|
||||||
|
bool LoadFromBuffer(const char* filename, const void* buffer, size_t buffer_size);
|
||||||
|
|
||||||
|
bool SaveToFile(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||||
|
bool SaveToFile(const char* filename, std::FILE* fp, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||||
|
std::optional<std::vector<u8>> SaveToBuffer(const char* filename, u8 quality = DEFAULT_SAVE_QUALITY) const;
|
||||||
|
};
|
||||||
|
|
|
@ -428,7 +428,7 @@ static const char* GetScreenshotSuffix()
|
||||||
|
|
||||||
static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 height, std::vector<u32> pixels)
|
static void CompressAndWriteScreenshot(std::string filename, u32 width, u32 height, std::vector<u32> pixels)
|
||||||
{
|
{
|
||||||
Common::RGBA8Image image;
|
RGBA8Image image;
|
||||||
image.SetPixels(width, height, std::move(pixels));
|
image.SetPixels(width, height, std::move(pixels));
|
||||||
|
|
||||||
std::string key(fmt::format("GSScreenshot_{}", filename));
|
std::string key(fmt::format("GSScreenshot_{}", filename));
|
||||||
|
|
|
@ -38,8 +38,8 @@ namespace ImGuiFullscreen
|
||||||
|
|
||||||
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f;
|
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f;
|
||||||
|
|
||||||
static std::optional<Common::RGBA8Image> LoadTextureImage(const char* path);
|
static std::optional<RGBA8Image> LoadTextureImage(const char* path);
|
||||||
static std::shared_ptr<GSTexture> UploadTexture(const char* path, const Common::RGBA8Image& image);
|
static std::shared_ptr<GSTexture> UploadTexture(const char* path, const RGBA8Image& image);
|
||||||
static void TextureLoaderThread();
|
static void TextureLoaderThread();
|
||||||
|
|
||||||
static void DrawFileSelector();
|
static void DrawFileSelector();
|
||||||
|
@ -92,7 +92,7 @@ namespace ImGuiFullscreen
|
||||||
static std::mutex s_texture_load_mutex;
|
static std::mutex s_texture_load_mutex;
|
||||||
static std::condition_variable s_texture_load_cv;
|
static std::condition_variable s_texture_load_cv;
|
||||||
static std::deque<std::string> s_texture_load_queue;
|
static std::deque<std::string> s_texture_load_queue;
|
||||||
static std::deque<std::pair<std::string, Common::RGBA8Image>> s_texture_upload_queue;
|
static std::deque<std::pair<std::string, RGBA8Image>> s_texture_upload_queue;
|
||||||
static Threading::Thread s_texture_load_thread;
|
static Threading::Thread s_texture_load_thread;
|
||||||
|
|
||||||
static bool s_choice_dialog_open = false;
|
static bool s_choice_dialog_open = false;
|
||||||
|
@ -263,9 +263,9 @@ const std::shared_ptr<GSTexture>& ImGuiFullscreen::GetPlaceholderTexture()
|
||||||
return s_placeholder_texture;
|
return s_placeholder_texture;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<Common::RGBA8Image> ImGuiFullscreen::LoadTextureImage(const char* path)
|
std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(const char* path)
|
||||||
{
|
{
|
||||||
std::optional<Common::RGBA8Image> image;
|
std::optional<RGBA8Image> image;
|
||||||
|
|
||||||
std::optional<std::vector<u8>> data;
|
std::optional<std::vector<u8>> data;
|
||||||
if (Path::IsAbsolute(path))
|
if (Path::IsAbsolute(path))
|
||||||
|
@ -274,7 +274,7 @@ std::optional<Common::RGBA8Image> ImGuiFullscreen::LoadTextureImage(const char*
|
||||||
data = FileSystem::ReadBinaryFile(Path::Combine(EmuFolders::Resources, path).c_str());
|
data = FileSystem::ReadBinaryFile(Path::Combine(EmuFolders::Resources, path).c_str());
|
||||||
if (data.has_value())
|
if (data.has_value())
|
||||||
{
|
{
|
||||||
image = Common::RGBA8Image();
|
image = RGBA8Image();
|
||||||
if (!image->LoadFromBuffer(path, data->data(), data->size()))
|
if (!image->LoadFromBuffer(path, data->data(), data->size()))
|
||||||
{
|
{
|
||||||
Console.Error("Failed to read texture resource '%s'", path);
|
Console.Error("Failed to read texture resource '%s'", path);
|
||||||
|
@ -289,7 +289,7 @@ std::optional<Common::RGBA8Image> ImGuiFullscreen::LoadTextureImage(const char*
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<GSTexture> ImGuiFullscreen::UploadTexture(const char* path, const Common::RGBA8Image& image)
|
std::shared_ptr<GSTexture> ImGuiFullscreen::UploadTexture(const char* path, const RGBA8Image& image)
|
||||||
{
|
{
|
||||||
GSTexture* texture = g_gs_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, GSTexture::Format::Color);
|
GSTexture* texture = g_gs_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, GSTexture::Format::Color);
|
||||||
if (!texture)
|
if (!texture)
|
||||||
|
@ -298,7 +298,7 @@ std::shared_ptr<GSTexture> ImGuiFullscreen::UploadTexture(const char* path, cons
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetByteStride()))
|
if (!texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetPitch()))
|
||||||
{
|
{
|
||||||
Console.Error("Failed to upload %ux%u texture for resource", image.GetWidth(), image.GetHeight());
|
Console.Error("Failed to upload %ux%u texture for resource", image.GetWidth(), image.GetHeight());
|
||||||
g_gs_device->Recycle(texture);
|
g_gs_device->Recycle(texture);
|
||||||
|
@ -311,7 +311,7 @@ std::shared_ptr<GSTexture> ImGuiFullscreen::UploadTexture(const char* path, cons
|
||||||
|
|
||||||
std::shared_ptr<GSTexture> ImGuiFullscreen::LoadTexture(const char* path)
|
std::shared_ptr<GSTexture> ImGuiFullscreen::LoadTexture(const char* path)
|
||||||
{
|
{
|
||||||
std::optional<Common::RGBA8Image> image(LoadTextureImage(path));
|
std::optional<RGBA8Image> image(LoadTextureImage(path));
|
||||||
if (image.has_value())
|
if (image.has_value())
|
||||||
{
|
{
|
||||||
std::shared_ptr<GSTexture> ret(UploadTexture(path, image.value()));
|
std::shared_ptr<GSTexture> ret(UploadTexture(path, image.value()));
|
||||||
|
@ -361,7 +361,7 @@ void ImGuiFullscreen::UploadAsyncTextures()
|
||||||
std::unique_lock lock(s_texture_load_mutex);
|
std::unique_lock lock(s_texture_load_mutex);
|
||||||
while (!s_texture_upload_queue.empty())
|
while (!s_texture_upload_queue.empty())
|
||||||
{
|
{
|
||||||
std::pair<std::string, Common::RGBA8Image> it(std::move(s_texture_upload_queue.front()));
|
std::pair<std::string, RGBA8Image> it(std::move(s_texture_upload_queue.front()));
|
||||||
s_texture_upload_queue.pop_front();
|
s_texture_upload_queue.pop_front();
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
|
@ -393,7 +393,7 @@ void ImGuiFullscreen::TextureLoaderThread()
|
||||||
s_texture_load_queue.pop_front();
|
s_texture_load_queue.pop_front();
|
||||||
|
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
std::optional<Common::RGBA8Image> image(LoadTextureImage(path.c_str()));
|
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str()));
|
||||||
lock.lock();
|
lock.lock();
|
||||||
|
|
||||||
// don't bother queuing back if it doesn't exist
|
// don't bother queuing back if it doesn't exist
|
||||||
|
|
|
@ -978,7 +978,7 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::RGBA8Image image;
|
RGBA8Image image;
|
||||||
if (!image.LoadFromFile(sc.image_path.c_str()))
|
if (!image.LoadFromFile(sc.image_path.c_str()))
|
||||||
{
|
{
|
||||||
Console.Error("Failed to load software cursor %u image '%s'", index, sc.image_path.c_str());
|
Console.Error("Failed to load software cursor %u image '%s'", index, sc.image_path.c_str());
|
||||||
|
@ -991,7 +991,7 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
|
||||||
"Failed to upload %ux%u software cursor %u image '%s'", image.GetWidth(), image.GetHeight(), index, sc.image_path.c_str());
|
"Failed to upload %ux%u software cursor %u image '%s'", image.GetWidth(), image.GetHeight(), index, sc.image_path.c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sc.texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetByteStride(), 0);
|
sc.texture->Update(GSVector4i(0, 0, image.GetWidth(), image.GetHeight()), image.GetPixels(), image.GetPitch(), 0);
|
||||||
|
|
||||||
sc.extent_x = std::ceil(static_cast<float>(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f;
|
sc.extent_x = std::ceil(static_cast<float>(image.GetWidth()) * sc.scale * s_global_scale) / 2.0f;
|
||||||
sc.extent_y = std::ceil(static_cast<float>(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f;
|
sc.extent_y = std::ceil(static_cast<float>(image.GetHeight()) * sc.scale * s_global_scale) / 2.0f;
|
||||||
|
|
Loading…
Reference in New Issue