Files
citra/src/common/image_util.cpp
2023-02-22 21:22:42 +02:00

154 lines
4.0 KiB
C++

// Copyright 2023 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <spng.h>
#define DDSKTX_IMPLEMENT
#include "common/file_util.h"
#include "common/image_util.h"
#include "common/logging/log.h"
namespace Common {
namespace {
void spng_free(spng_ctx* ctx) {
if (ctx) {
spng_ctx_free(ctx);
}
}
auto make_spng_ctx(int flags) {
return std::unique_ptr<spng_ctx, decltype(&spng_free)>(spng_ctx_new(flags), spng_free);
}
} // Anonymous namespace
bool ParsePNG(std::span<const u8> png_data, size_t& decoded_size, u32& width, u32& height) {
auto ctx = make_spng_ctx(0);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
return false;
}
spng_ihdr ihdr{};
if (spng_get_ihdr(ctx.get(), &ihdr)) {
return false;
}
width = ihdr.width;
height = ihdr.height;
const int format = SPNG_FMT_RGBA8;
if (spng_decoded_image_size(ctx.get(), format, &decoded_size)) {
return false;
}
return true;
}
bool DecodePNG(std::span<const u8> png_data, std::span<u8> out_data) {
auto ctx = make_spng_ctx(0);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_png_buffer(ctx.get(), png_data.data(), png_data.size())) {
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;
}
ASSERT(out_data.size() == decoded_len);
if (spng_decode_image(ctx.get(), out_data.data(), decoded_len, format, 0)) {
return false;
}
return true;
}
bool ParseDDSKTX(std::span<const u8> in_data, std::vector<u8>& out_data, u32& width, u32& height,
ddsktx_format& format) {
ddsktx_texture_info tc{};
if (!ddsktx_parse(&tc, in_data.data(), in_data.size(), nullptr)) {
return false;
}
width = tc.width;
height = tc.height;
format = tc.format;
ddsktx_sub_data sub_data{};
ddsktx_get_sub(&tc, &sub_data, in_data.data(), in_data.size(), 0, 0, 0);
out_data.resize(sub_data.size_bytes);
std::memcpy(out_data.data(), sub_data.buff, sub_data.size_bytes);
return true;
}
bool EncodePNG(const std::string& out_path, std::span<u8> in_data, u32 width, u32 height,
s32 level) {
auto ctx = make_spng_ctx(SPNG_CTX_ENCODER);
if (!ctx) [[unlikely]] {
return false;
}
if (spng_set_option(ctx.get(), SPNG_IMG_COMPRESSION_LEVEL, level)) {
return false;
}
if (spng_set_option(ctx.get(), SPNG_ENCODE_TO_BUFFER, 1)) {
return false;
}
spng_ihdr ihdr{};
ihdr.width = width;
ihdr.height = height;
ihdr.color_type = SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
ihdr.bit_depth = 8;
if (spng_set_ihdr(ctx.get(), &ihdr)) {
return false;
}
if (spng_encode_image(ctx.get(), in_data.data(), in_data.size(), SPNG_FMT_PNG,
SPNG_ENCODE_FINALIZE)) {
return false;
}
int ret{};
size_t png_size{};
u8* png_buf = reinterpret_cast<u8*>(spng_get_png_buffer(ctx.get(), &png_size, &ret));
if (!png_buf) {
return false;
}
auto file = FileUtil::IOFile(out_path, "wb");
file.WriteBytes(png_buf, png_size);
size_t image_len = 0;
spng_decoded_image_size(ctx.get(), SPNG_FMT_PNG, &image_len);
LOG_ERROR(Common, "{} byte {} by {} image saved to {} at level {}", image_len, width, height,
out_path, level);
return true;
}
void FlipTexture(std::span<u8> in_data, u32 width, u32 height, u32 stride) {
for (u32 line = 0; line < height / 2; line++) {
const u32 offset_1 = line * stride;
const u32 offset_2 = (height - line - 1) * stride;
// Swap lines
std::swap_ranges(in_data.begin() + offset_1, in_data.begin() + offset_1 + stride,
in_data.begin() + offset_2);
}
}
} // namespace Common