diff --git a/Source/Core/Common/Image.cpp b/Source/Core/Common/Image.cpp index 91d1bc68e0..678a64db8c 100644 --- a/Source/Core/Common/Image.cpp +++ b/Source/Core/Common/Image.cpp @@ -4,6 +4,7 @@ #include "Common/Image.h" +#include #include #include @@ -37,4 +38,31 @@ bool LoadPNG(const std::vector& input, std::vector* data_out, u32* width return true; } + +bool SavePNG(const std::string& path, const u8* input, ImageByteFormat format, u32 width, + u32 height, int stride) +{ + png_image png = {}; + png.version = PNG_IMAGE_VERSION; + png.width = width; + png.height = height; + + switch (format) + { + case ImageByteFormat::RGB: + png.format = PNG_FORMAT_RGB; + break; + case ImageByteFormat::RGBA: + png.format = PNG_FORMAT_RGBA; + break; + default: + return false; + } + + png_image_write_to_file(&png, path.c_str(), 0, input, stride, nullptr); + if (png.warning_or_error & PNG_IMAGE_ERROR) + return false; + + return true; +} } // namespace Common diff --git a/Source/Core/Common/Image.h b/Source/Core/Common/Image.h index d1354b8e0b..9ef399138e 100644 --- a/Source/Core/Common/Image.h +++ b/Source/Core/Common/Image.h @@ -4,6 +4,7 @@ #pragma once +#include #include #include "Common/CommonTypes.h" @@ -12,4 +13,13 @@ namespace Common { bool LoadPNG(const std::vector& input, std::vector* data_out, u32* width_out, u32* height_out); -} + +enum class ImageByteFormat +{ + RGB, + RGBA, +}; + +bool SavePNG(const std::string& path, const u8* input, ImageByteFormat format, u32 width, + u32 height, int stride = 0); +} // namespace Common diff --git a/Source/Core/InputCommon/ImageOperations.cpp b/Source/Core/InputCommon/ImageOperations.cpp index 348fcc0a98..b659a9358b 100644 --- a/Source/Core/InputCommon/ImageOperations.cpp +++ b/Source/Core/InputCommon/ImageOperations.cpp @@ -8,8 +8,8 @@ #include #include #include - -#include +#include +#include #include "Common/File.h" #include "Common/FileUtil.h" @@ -73,113 +73,28 @@ std::optional LoadImage(const std::string& path) return image; } -// For Visual Studio, ignore the error caused by the 'setjmp' call -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4611) -#endif - bool WriteImage(const std::string& path, const ImagePixelData& image) { - bool success = false; - char title[] = "Dynamic Input Texture"; - char title_key[] = "Title"; - png_structp png_ptr = nullptr; - png_infop info_ptr = nullptr; std::vector buffer; + buffer.reserve(image.width * image.height * 4); - // Open file for writing (binary mode) - File::IOFile fp(path, "wb"); - if (!fp.IsOpen()) - { - goto finalise; - } - - // Initialize write structure - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) - { - goto finalise; - } - - // Initialize info structure - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) - { - goto finalise; - } - - // Classical libpng error handling uses longjmp to do C-style unwind. - // Modern libpng does support a user callback, but it's required to operate - // in the same way (just gives a chance to do stuff before the longjmp). - // Instead of futzing with it, we use gotos specifically so the compiler - // will still generate proper destructor calls for us (hopefully). - // We also do not use any local variables outside the region longjmp may - // have been called from if they were modified inside that region (they - // would need to be volatile). - if (setjmp(png_jmpbuf(png_ptr))) - { - goto finalise; - } - - // Begin region which may call longjmp - - png_init_io(png_ptr, fp.GetHandle()); - - // Write header (8 bit color depth) - png_set_IHDR(png_ptr, info_ptr, image.width, image.height, 8, PNG_COLOR_TYPE_RGB_ALPHA, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_text title_text; - title_text.compression = PNG_TEXT_COMPRESSION_NONE; - title_text.key = title_key; - title_text.text = title; - png_set_text(png_ptr, info_ptr, &title_text, 1); - - png_write_info(png_ptr, info_ptr); - - buffer.resize(image.width * 4); - - // Write image data for (u32 y = 0; y < image.height; ++y) { - for (u32 x = 0; x < image.width; x++) + for (u32 x = 0; x < image.width; ++x) { const auto index = x + y * image.width; const auto pixel = image.pixels[index]; - - const auto buffer_index = 4 * x; - buffer[buffer_index] = pixel.r; - buffer[buffer_index + 1] = pixel.g; - buffer[buffer_index + 2] = pixel.b; - buffer[buffer_index + 3] = pixel.a; + buffer.push_back(pixel.r); + buffer.push_back(pixel.g); + buffer.push_back(pixel.b); + buffer.push_back(pixel.a); } - - // The old API uses u8* instead of const u8*. It doesn't write - // to this pointer, but to fit the API, we have to drop the const qualifier. - png_write_row(png_ptr, const_cast(buffer.data())); } - // End write - png_write_end(png_ptr, nullptr); - - // End region which may call longjmp - - success = true; - -finalise: - if (info_ptr != nullptr) - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - if (png_ptr != nullptr) - png_destroy_write_struct(&png_ptr, nullptr); - - return success; + return Common::SavePNG(path, buffer.data(), Common::ImageByteFormat::RGBA, image.width, + image.height); } -#ifdef _MSC_VER -#pragma warning(pop) -#endif - ImagePixelData Resize(ResizeMode mode, const ImagePixelData& src, u32 new_width, u32 new_height) { ImagePixelData result(new_width, new_height); diff --git a/Source/Core/VideoCommon/ImageWrite.cpp b/Source/Core/VideoCommon/ImageWrite.cpp index aa3bdbe7e9..d2dcbc9dc5 100644 --- a/Source/Core/VideoCommon/ImageWrite.cpp +++ b/Source/Core/VideoCommon/ImageWrite.cpp @@ -9,9 +9,7 @@ #include "Common/CommonTypes.h" #include "Common/File.h" #include "Common/FileUtil.h" -#include "Common/MsgHandler.h" -#include "VideoCommon/ImageWrite.h" -#include "png.h" +#include "Common/Image.h" bool SaveData(const std::string& filename, const std::string& data) { @@ -22,11 +20,6 @@ bool SaveData(const std::string& filename, const std::string& data) return true; } -#ifdef _MSC_VER -#pragma warning(push) -#pragma warning(disable : 4611) -#endif - /* TextureToPng @@ -35,111 +28,30 @@ data : This is an array of RGBA with 8 bits per channel. 4 bytes for each p row_stride: Determines the amount of bytes per row of pixels. */ bool TextureToPng(const u8* data, int row_stride, const std::string& filename, int width, - int height, bool saveAlpha) + int height, bool save_alpha) { if (!data) return false; - bool success = false; - char title[] = "Dolphin Screenshot"; - char title_key[] = "Title"; - png_structp png_ptr = nullptr; - png_infop info_ptr = nullptr; + if (save_alpha) + { + return Common::SavePNG(filename, data, Common::ImageByteFormat::RGBA, width, height, + row_stride); + } + std::vector buffer; + buffer.reserve(width * height * 3); - // Open file for writing (binary mode) - File::IOFile fp(filename, "wb"); - if (!fp.IsOpen()) + for (int y = 0; y < height; ++y) { - PanicAlertFmtT("Screenshot failed: Could not open file \"{0}\" (error {1})", filename, errno); - goto finalise; - } - - // Initialize write structure - png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, nullptr, nullptr); - if (png_ptr == nullptr) - { - PanicAlertFmt("Screenshot failed: Could not allocate write struct"); - goto finalise; - } - - // Initialize info structure - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == nullptr) - { - PanicAlertFmt("Screenshot failed: Could not allocate info struct"); - goto finalise; - } - - // Classical libpng error handling uses longjmp to do C-style unwind. - // Modern libpng does support a user callback, but it's required to operate - // in the same way (just gives a chance to do stuff before the longjmp). - // Instead of futzing with it, we use gotos specifically so the compiler - // will still generate proper destructor calls for us (hopefully). - // We also do not use any local variables outside the region longjmp may - // have been called from if they were modified inside that region (they - // would need to be volatile). - if (setjmp(png_jmpbuf(png_ptr))) - { - PanicAlertFmt("Screenshot failed: Error during PNG creation"); - goto finalise; - } - - // Begin region which may call longjmp - - png_init_io(png_ptr, fp.GetHandle()); - - // Write header (8 bit color depth) - png_set_IHDR(png_ptr, info_ptr, width, height, 8, PNG_COLOR_TYPE_RGB_ALPHA, PNG_INTERLACE_NONE, - PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - png_text title_text; - title_text.compression = PNG_TEXT_COMPRESSION_NONE; - title_text.key = title_key; - title_text.text = title; - png_set_text(png_ptr, info_ptr, &title_text, 1); - - png_write_info(png_ptr, info_ptr); - - if (!saveAlpha) - buffer.resize(width * 4); - - // Write image data - for (auto y = 0; y < height; ++y) - { - const u8* row_ptr = data + y * row_stride; - if (!saveAlpha) + const u8* pos = data + y * row_stride; + for (int x = 0; x < width; ++x) { - for (int x = 0; x < width; x++) - { - for (int i = 0; i < 3; i++) - buffer[4 * x + i] = row_ptr[4 * x + i]; - buffer[4 * x + 3] = 0xff; - } - row_ptr = buffer.data(); + buffer.push_back(pos[x * 4]); + buffer.push_back(pos[x * 4 + 1]); + buffer.push_back(pos[x * 4 + 2]); } - - // The old API uses u8* instead of const u8*. It doesn't write - // to this pointer, but to fit the API, we have to drop the const qualifier. - png_write_row(png_ptr, const_cast(row_ptr)); } - // End write - png_write_end(png_ptr, nullptr); - - // End region which may call longjmp - - success = true; - -finalise: - if (info_ptr != nullptr) - png_free_data(png_ptr, info_ptr, PNG_FREE_ALL, -1); - if (png_ptr != nullptr) - png_destroy_write_struct(&png_ptr, (png_infopp) nullptr); - - return success; + return Common::SavePNG(filename, buffer.data(), Common::ImageByteFormat::RGB, width, height); } - -#ifdef _MSC_VER -#pragma warning(pop) -#endif diff --git a/Source/Core/VideoCommon/ImageWrite.h b/Source/Core/VideoCommon/ImageWrite.h index 5e486f7e7d..46ce760e16 100644 --- a/Source/Core/VideoCommon/ImageWrite.h +++ b/Source/Core/VideoCommon/ImageWrite.h @@ -9,4 +9,4 @@ bool SaveData(const std::string& filename, const std::string& data); bool TextureToPng(const u8* data, int row_stride, const std::string& filename, int width, - int height, bool saveAlpha = true); + int height, bool save_alpha = true);