1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
|
// Copyright 2016 Dolphin Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "Common/Image.h"
#include <memory>
#include <string>
#include <spng.h>
#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, decltype(&spng_free)>(spng_ctx_new(flags), spng_free);
}
bool LoadPNG(std::span<const u8> input, Common::UniqueBuffer<u8>* 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->reset(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<int>(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 Common::UniqueBuffer<u8> RGBAToRGB(const u8* input, u32 width, u32 height, u32 row_stride)
{
Common::UniqueBuffer<u8> buffer;
buffer.reset(width * height * 3);
std::size_t buffer_index = 0;
for (u32 y = 0; y < height; ++y)
{
const u8* pos = input + y * row_stride;
for (u32 x = 0; x < width; ++x)
{
buffer[buffer_index++] = pos[x * 4];
buffer[buffer_index++] = pos[x * 4 + 1];
buffer[buffer_index++] = pos[x * 4 + 2];
}
}
return buffer;
}
bool ConvertRGBAToRGBAndSavePNG(const std::string& path, const u8* input, u32 width, u32 height,
u32 stride, int level)
{
const auto data = RGBAToRGB(input, width, height, stride);
return SavePNG(path, data.data(), ImageByteFormat::RGB, width, height, width * 3, level);
}
} // namespace Common
|