// Copyright 2016 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "Common/Image.h" #include #include #include #include #include "Common/Assert.h" #include "Common/CommonTypes.h" #include "Common/IOFile.h" #include "Common/Logging/Log.h" #include "Common/Timer.h" namespace Common { static void spng_free(spng_ctx* ctx) { if (ctx) spng_ctx_free(ctx); } static auto make_spng_ctx(int flags) { return std::unique_ptr(spng_ctx_new(flags), spng_free); } bool LoadPNG(const std::vector& input, std::vector* data_out, u32* width_out, u32* height_out) { auto ctx = make_spng_ctx(0); if (!ctx) return false; if (spng_set_png_buffer(ctx.get(), input.data(), input.size())) return false; spng_ihdr ihdr{}; if (spng_get_ihdr(ctx.get(), &ihdr)) return false; const int format = SPNG_FMT_RGBA8; size_t decoded_len = 0; if (spng_decoded_image_size(ctx.get(), format, &decoded_len)) return false; data_out->resize(decoded_len); if (spng_decode_image(ctx.get(), data_out->data(), decoded_len, format, SPNG_DECODE_TRNS)) return false; *width_out = ihdr.width; *height_out = ihdr.height; return true; } bool SavePNG(const std::string& path, const u8* input, ImageByteFormat format, u32 width, u32 height, u32 stride, int level) { Common::Timer timer; timer.Start(); spng_color_type color_type; switch (format) { case ImageByteFormat::RGB: color_type = SPNG_COLOR_TYPE_TRUECOLOR; break; case ImageByteFormat::RGBA: color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA; break; default: ASSERT_MSG(FRAMEDUMP, false, "Invalid format {}", static_cast(format)); return false; } auto ctx = make_spng_ctx(SPNG_CTX_ENCODER); if (!ctx) return false; auto outfile = File::IOFile(path, "wb"); if (spng_set_png_file(ctx.get(), outfile.GetHandle())) return false; if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level)) return false; spng_ihdr ihdr{}; ihdr.width = width; ihdr.height = height; ihdr.color_type = color_type; ihdr.bit_depth = 8; if (spng_set_ihdr(ctx.get(), &ihdr)) return false; if (spng_encode_image(ctx.get(), nullptr, 0, SPNG_FMT_PNG, SPNG_ENCODE_PROGRESSIVE | SPNG_ENCODE_FINALIZE)) { return false; } for (u32 row = 0; row < height; row++) { const int err = spng_encode_row(ctx.get(), &input[row * stride], stride); if (err == SPNG_EOI) break; if (err) { ERROR_LOG_FMT(FRAMEDUMP, "Failed to save {} by {} image to {} at level {}: error {}", width, height, path, level, err); return false; } } size_t image_len = 0; spng_decoded_image_size(ctx.get(), SPNG_FMT_PNG, &image_len); INFO_LOG_FMT(FRAMEDUMP, "{} byte {} by {} image saved to {} at level {} in {} ms", image_len, width, height, path, level, timer.ElapsedMs()); return true; } static std::vector RGBAToRGB(const u8* input, u32 width, u32 height, u32 row_stride) { std::vector buffer; buffer.reserve(width * height * 3); for (u32 y = 0; y < height; ++y) { const u8* pos = input + y * row_stride; for (u32 x = 0; x < width; ++x) { buffer.push_back(pos[x * 4]); buffer.push_back(pos[x * 4 + 1]); buffer.push_back(pos[x * 4 + 2]); } } return buffer; } bool ConvertRGBAToRGBAndSavePNG(const std::string& path, const u8* input, u32 width, u32 height, u32 stride, int level) { const std::vector data = RGBAToRGB(input, width, height, stride); return SavePNG(path, data.data(), ImageByteFormat::RGB, width, height, width * 3, level); } } // namespace Common