mirror of
				https://github.com/dolphin-emu/dolphin.git
				synced 2025-10-25 09:29:43 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			147 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			147 lines
		
	
	
	
		
			3.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| // Copyright 2016 Dolphin Emulator Project
 | |
| // SPDX-License-Identifier: GPL-2.0-or-later
 | |
| 
 | |
| #include "Common/Image.h"
 | |
| 
 | |
| #include <memory>
 | |
| #include <string>
 | |
| #include <vector>
 | |
| 
 | |
| #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(const std::vector<u8>& input, std::vector<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->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<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 std::vector<u8> RGBAToRGB(const u8* input, u32 width, u32 height, u32 row_stride)
 | |
| {
 | |
|   std::vector<u8> 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<u8> data = RGBAToRGB(input, width, height, stride);
 | |
|   return SavePNG(path, data.data(), ImageByteFormat::RGB, width, height, width * 3, level);
 | |
| }
 | |
| }  // namespace Common
 |