Image: Export RGBA8 conversion functions

This commit is contained in:
Stenzek 2025-04-13 13:09:53 +10:00
parent 903a1570f1
commit 03d6d12b8a
No known key found for this signature in database
2 changed files with 108 additions and 99 deletions

View File

@ -80,8 +80,8 @@ static const FormatHandler* GetFormatHandler(std::string_view extension)
return nullptr; return nullptr;
} }
static void SwapBGRAToRGBA(void* pixels_out, u32 pixels_out_pitch, const void* pixels_in, u32 pixels_in_pitch, static void SwapBGRAToRGBA(void* RESTRICT pixels_out, u32 pixels_out_pitch, const void* RESTRICT pixels_in,
u32 width, u32 height); u32 pixels_in_pitch, u32 width, u32 height);
Image::Image() = default; Image::Image() = default;
@ -500,16 +500,17 @@ void SwapBGRAToRGBA(void* pixels_out, u32 pixels_out_pitch, const void* pixels_i
} }
template<ImageFormat format> template<ImageFormat format>
static void DecompressBC(Image& image_out, const Image& image_in) static void DecompressBC(void* RESTRICT pixels_out, u32 pixels_out_pitch, const void* RESTRICT pixels_in,
u32 pixels_in_pitch, u32 width, u32 height)
{ {
constexpr u32 BC_BLOCK_SIZE = 4; constexpr u32 BC_BLOCK_SIZE = 4;
constexpr u32 BC_BLOCK_BYTES = 16; constexpr u32 BC_BLOCK_BYTES = 16;
const u32 blocks_wide = image_in.GetBlocksWide(); const u32 blocks_wide = Common::AlignUpPow2(width, 4) / 4;
const u32 blocks_high = image_in.GetBlocksHigh(); const u32 blocks_high = Common::AlignUpPow2(height, 4) / 4;
for (u32 y = 0; y < blocks_high; y++) for (u32 y = 0; y < blocks_high; y++)
{ {
const u8* block_in = image_in.GetRowPixels(y); const u8* block_in = static_cast<const u8*>(pixels_in) + (y * pixels_in_pitch);
for (u32 x = 0; x < blocks_wide; x++, block_in += BC_BLOCK_BYTES) for (u32 x = 0; x < blocks_wide; x++, block_in += BC_BLOCK_BYTES)
{ {
// decompress block // decompress block
@ -517,20 +518,20 @@ static void DecompressBC(Image& image_out, const Image& image_in)
{ {
case ImageFormat::BC1: case ImageFormat::BC1:
{ {
DecompressBlockBC1(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, image_out.GetPitch(), block_in, DecompressBlockBC1(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, pixels_out_pitch, block_in,
image_out.GetPixels()); static_cast<unsigned char*>(pixels_out));
} }
break; break;
case ImageFormat::BC2: case ImageFormat::BC2:
{ {
DecompressBlockBC2(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, image_out.GetPitch(), block_in, DecompressBlockBC2(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, pixels_out_pitch, block_in,
image_out.GetPixels()); static_cast<unsigned char*>(pixels_out));
} }
break; break;
case ImageFormat::BC3: case ImageFormat::BC3:
{ {
DecompressBlockBC3(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, image_out.GetPitch(), block_in, DecompressBlockBC3(x * BC_BLOCK_SIZE, y * BC_BLOCK_SIZE, pixels_out_pitch, block_in,
image_out.GetPixels()); static_cast<unsigned char*>(pixels_out));
} }
break; break;
@ -540,13 +541,14 @@ static void DecompressBC(Image& image_out, const Image& image_in)
bc7decomp::unpack_bc7(block_in, reinterpret_cast<bc7decomp::color_rgba*>(block_pixels_out)); bc7decomp::unpack_bc7(block_in, reinterpret_cast<bc7decomp::color_rgba*>(block_pixels_out));
// and write it to the new image // and write it to the new image
const u32* copy_in_ptr = block_pixels_out; const u32* RESTRICT copy_in_ptr = block_pixels_out;
u8* copy_out_ptr = image_out.GetRowPixels(y * BC_BLOCK_SIZE) + (x * BC_BLOCK_SIZE * sizeof(u32)); u8* RESTRICT copy_out_ptr =
static_cast<u8*>(pixels_out) + (y * BC_BLOCK_SIZE * pixels_out_pitch) + (x * BC_BLOCK_SIZE * sizeof(u32));
for (u32 sy = 0; sy < 4; sy++) for (u32 sy = 0; sy < 4; sy++)
{ {
std::memcpy(copy_out_ptr, copy_in_ptr, sizeof(u32) * BC_BLOCK_SIZE); std::memcpy(copy_out_ptr, copy_in_ptr, sizeof(u32) * BC_BLOCK_SIZE);
copy_in_ptr += BC_BLOCK_SIZE; copy_in_ptr += BC_BLOCK_SIZE;
copy_out_ptr += image_out.GetPitch(); copy_out_ptr += pixels_out_pitch;
} }
} }
break; break;
@ -565,39 +567,46 @@ std::optional<Image> Image::ConvertToRGBA8(Error* error) const
return ret; return ret;
} }
switch (m_format) ret = Image(m_width, m_height, ImageFormat::RGBA8);
if (!ConvertToRGBA8(ret->GetPixels(), ret->GetPitch(), m_pixels.get(), m_pitch, m_width, m_height, m_format, error))
ret.reset();
return ret;
}
bool Image::ConvertToRGBA8(void* RESTRICT pixels_out, u32 pixels_out_pitch, const void* RESTRICT pixels_in,
u32 pixels_in_pitch, u32 width, u32 height, ImageFormat format, Error* error)
{
switch (format)
{ {
case ImageFormat::BGRA8: case ImageFormat::BGRA8:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); SwapBGRAToRGBA(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, width, height);
SwapBGRAToRGBA(ret->GetPixels(), ret->GetPitch(), m_pixels.get(), m_pitch, m_width, m_height); return true;
} }
break;
case ImageFormat::RGBA8: case ImageFormat::RGBA8:
{ {
ret = Image(m_width, m_height, m_format, m_pixels.get(), m_pitch); StringUtil::StrideMemCpy(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, sizeof(u32) * width, height);
return true;
} }
break;
case ImageFormat::RGB565: case ImageFormat::RGB565:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8);
constexpr u32 pixels_per_vec = 8; constexpr u32 pixels_per_vec = 8;
[[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(m_width, pixels_per_vec); [[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec);
for (u32 y = 0; y < m_height; y++) for (u32 y = 0; y < height; y++)
{ {
const u8* pixels_in = GetRowPixels(y); const u8* RESTRICT row_pixels_in = static_cast<const u8*>(pixels_in) + (y * pixels_in_pitch);
u8* pixels_out = ret->GetRowPixels(y); u8* RESTRICT row_pixels_out = static_cast<u8*>(pixels_out) + (y * pixels_out_pitch);
u32 x = 0; u32 x = 0;
#ifdef CPU_ARCH_SIMD #ifdef CPU_ARCH_SIMD
for (; x < aligned_width; x += pixels_per_vec) for (; x < aligned_width; x += pixels_per_vec)
{ {
GSVector4i rgb565 = GSVector4i::load<false>(pixels_in); GSVector4i rgb565 = GSVector4i::load<false>(row_pixels_in);
pixels_in += sizeof(u16) * pixels_per_vec; row_pixels_in += sizeof(u16) * pixels_per_vec;
GSVector4i r = rgb565.srl16<11>(); GSVector4i r = rgb565.srl16<11>();
r = r.sll16<3>() | r.sll16<13>().srl16<13>(); r = r.sll16<3>() | r.sll16<13>().srl16<13>();
@ -613,51 +622,50 @@ std::optional<Image> Image::ConvertToRGBA8(Error* error) const
const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() | const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() |
b.uph64().u16to32().sll32<16>() | GSVector4i::cxpr(0xFF000000); b.uph64().u16to32().sll32<16>() | GSVector4i::cxpr(0xFF000000);
GSVector4i::store<false>(pixels_out, low); GSVector4i::store<false>(row_pixels_out, low);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
GSVector4i::store<false>(pixels_out, high); GSVector4i::store<false>(row_pixels_out, high);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
} }
#endif #endif
DONT_VECTORIZE_THIS_LOOP DONT_VECTORIZE_THIS_LOOP
for (; x < m_width; x++) for (; x < width; x++)
{ {
// RGB565 -> RGBA8 // RGB565 -> RGBA8
u16 pixel_in; u16 pixel_in;
std::memcpy(&pixel_in, pixels_in, sizeof(u16)); std::memcpy(&pixel_in, row_pixels_in, sizeof(u16));
pixels_in += sizeof(u16); row_pixels_in += sizeof(u16);
const u8 r5 = Truncate8(pixel_in >> 11); const u8 r5 = Truncate8(pixel_in >> 11);
const u8 g6 = Truncate8((pixel_in >> 5) & 0x3F); const u8 g6 = Truncate8((pixel_in >> 5) & 0x3F);
const u8 b5 = Truncate8(pixel_in & 0x1F); const u8 b5 = Truncate8(pixel_in & 0x1F);
const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) | const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) |
(ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u); (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u);
std::memcpy(pixels_out, &rgba8, sizeof(u32)); std::memcpy(row_pixels_out, &rgba8, sizeof(u32));
pixels_out += sizeof(u32); row_pixels_out += sizeof(u32);
} }
} }
return true;
} }
break;
case ImageFormat::RGB5A1: case ImageFormat::RGB5A1:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8);
constexpr u32 pixels_per_vec = 8; constexpr u32 pixels_per_vec = 8;
[[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(m_width, pixels_per_vec); [[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec);
for (u32 y = 0; y < m_height; y++) for (u32 y = 0; y < height; y++)
{ {
const u8* pixels_in = GetRowPixels(y); const u8* RESTRICT row_pixels_in = static_cast<const u8*>(pixels_in) + (y * pixels_in_pitch);
u8* pixels_out = ret->GetRowPixels(y); u8* RESTRICT row_pixels_out = static_cast<u8*>(pixels_out) + (y * pixels_out_pitch);
u32 x = 0; u32 x = 0;
#ifdef CPU_ARCH_SIMD #ifdef CPU_ARCH_SIMD
for (; x < aligned_width; x += pixels_per_vec) for (; x < aligned_width; x += pixels_per_vec)
{ {
GSVector4i rgb5a1 = GSVector4i::load<false>(pixels_in); GSVector4i rgb5a1 = GSVector4i::load<false>(row_pixels_in);
pixels_in += sizeof(u16) * pixels_per_vec; row_pixels_in += sizeof(u16) * pixels_per_vec;
GSVector4i r = rgb5a1.sll16<1>().srl16<11>(); GSVector4i r = rgb5a1.sll16<1>().srl16<11>();
r = r.sll16<3>() | r.sll16<13>().srl16<13>(); r = r.sll16<3>() | r.sll16<13>().srl16<13>();
@ -675,52 +683,51 @@ std::optional<Image> Image::ConvertToRGBA8(Error* error) const
const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() | const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() |
b.uph64().u16to32().sll32<16>() | a.uph64().u16to32().sll32<24>(); b.uph64().u16to32().sll32<16>() | a.uph64().u16to32().sll32<24>();
GSVector4i::store<false>(pixels_out, low); GSVector4i::store<false>(row_pixels_out, low);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
GSVector4i::store<false>(pixels_out, high); GSVector4i::store<false>(row_pixels_out, high);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
} }
#endif #endif
DONT_VECTORIZE_THIS_LOOP DONT_VECTORIZE_THIS_LOOP
for (; x < m_width; x++) for (; x < width; x++)
{ {
// RGB5A1 -> RGBA8 // RGB5A1 -> RGBA8
u16 pixel_in; u16 pixel_in;
std::memcpy(&pixel_in, pixels_in, sizeof(u16)); std::memcpy(&pixel_in, row_pixels_in, sizeof(u16));
pixels_in += sizeof(u16); row_pixels_in += sizeof(u16);
const u8 a1 = Truncate8(pixel_in >> 15); const u8 a1 = Truncate8(pixel_in >> 15);
const u8 r5 = Truncate8((pixel_in >> 10) & 0x1F); const u8 r5 = Truncate8((pixel_in >> 10) & 0x1F);
const u8 g6 = Truncate8((pixel_in >> 5) & 0x1F); const u8 g6 = Truncate8((pixel_in >> 5) & 0x1F);
const u8 b5 = Truncate8(pixel_in & 0x1F); const u8 b5 = Truncate8(pixel_in & 0x1F);
const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) | const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) |
(ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u); (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u);
std::memcpy(pixels_out, &rgba8, sizeof(u32)); std::memcpy(row_pixels_out, &rgba8, sizeof(u32));
pixels_out += sizeof(u32); row_pixels_out += sizeof(u32);
} }
} }
return true;
} }
break;
case ImageFormat::A1BGR5: case ImageFormat::A1BGR5:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8);
constexpr u32 pixels_per_vec = 8; constexpr u32 pixels_per_vec = 8;
[[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(m_width, pixels_per_vec); [[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec);
for (u32 y = 0; y < m_height; y++) for (u32 y = 0; y < height; y++)
{ {
const u8* pixels_in = GetRowPixels(y); const u8* RESTRICT row_pixels_in = static_cast<const u8*>(pixels_in) + (y * pixels_in_pitch);
u8* pixels_out = ret->GetRowPixels(y); u8* RESTRICT row_pixels_out = static_cast<u8*>(pixels_out) + (y * pixels_out_pitch);
u32 x = 0; u32 x = 0;
#ifdef CPU_ARCH_SIMD #ifdef CPU_ARCH_SIMD
for (; x < aligned_width; x += pixels_per_vec) for (; x < aligned_width; x += pixels_per_vec)
{ {
GSVector4i a1bgr5 = GSVector4i::load<false>(pixels_in); GSVector4i a1bgr5 = GSVector4i::load<false>(row_pixels_in);
pixels_in += sizeof(u16) * pixels_per_vec; row_pixels_in += sizeof(u16) * pixels_per_vec;
GSVector4i r = a1bgr5.srl16<11>(); GSVector4i r = a1bgr5.srl16<11>();
r = r.sll16<3>() | r.sll16<13>().srl16<13>(); r = r.sll16<3>() | r.sll16<13>().srl16<13>();
@ -738,91 +745,90 @@ std::optional<Image> Image::ConvertToRGBA8(Error* error) const
const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() | const GSVector4i high = r.uph64().u16to32() | g.uph64().u16to32().sll32<8>() |
b.uph64().u16to32().sll32<16>() | a.uph64().u16to32().sll32<24>(); b.uph64().u16to32().sll32<16>() | a.uph64().u16to32().sll32<24>();
GSVector4i::store<false>(pixels_out, low); GSVector4i::store<false>(row_pixels_out, low);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
GSVector4i::store<false>(pixels_out, high); GSVector4i::store<false>(row_pixels_out, high);
pixels_out += sizeof(GSVector4i); row_pixels_out += sizeof(GSVector4i);
} }
#endif #endif
DONT_VECTORIZE_THIS_LOOP DONT_VECTORIZE_THIS_LOOP
for (; x < m_width; x++) for (; x < width; x++)
{ {
// RGB5A1 -> RGBA8 // RGB5A1 -> RGBA8
u16 pixel_in; u16 pixel_in;
std::memcpy(&pixel_in, pixels_in, sizeof(u16)); std::memcpy(&pixel_in, row_pixels_in, sizeof(u16));
pixels_in += sizeof(u16); row_pixels_in += sizeof(u16);
const u8 a1 = Truncate8(pixel_in & 0x01); const u8 a1 = Truncate8(pixel_in & 0x01);
const u8 r5 = Truncate8((pixel_in >> 11) & 0x1F); const u8 r5 = Truncate8((pixel_in >> 11) & 0x1F);
const u8 g6 = Truncate8((pixel_in >> 6) & 0x1F); const u8 g6 = Truncate8((pixel_in >> 6) & 0x1F);
const u8 b5 = Truncate8((pixel_in >> 1) & 0x1F); const u8 b5 = Truncate8((pixel_in >> 1) & 0x1F);
const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) | const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) |
(ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u); (ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u);
std::memcpy(pixels_out, &rgba8, sizeof(u32)); std::memcpy(row_pixels_out, &rgba8, sizeof(u32));
pixels_out += sizeof(u32); row_pixels_out += sizeof(u32);
} }
} }
return true;
} }
break;
case ImageFormat::BGR8: case ImageFormat::BGR8:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); for (u32 y = 0; y < height; y++)
for (u32 y = 0; y < m_height; y++)
{ {
const u8* pixels_in = GetRowPixels(y); const u8* RESTRICT row_pixels_in = static_cast<const u8*>(pixels_in) + (y * pixels_in_pitch);
u8* pixels_out = ret->GetRowPixels(y); u8* RESTRICT row_pixels_out = static_cast<u8*>(pixels_out) + (y * pixels_out_pitch);
for (u32 x = 0; x < m_width; x++) for (u32 x = 0; x < width; x++)
{ {
// Set alpha channel to full intensity. // Set alpha channel to full intensity.
const u32 rgba = (ZeroExtend32(pixels_in[0]) | (ZeroExtend32(pixels_in[2]) << 8) | const u32 rgba = (ZeroExtend32(row_pixels_in[0]) | (ZeroExtend32(row_pixels_in[2]) << 8) |
(ZeroExtend32(pixels_in[2]) << 16) | 0xFF000000u); (ZeroExtend32(row_pixels_in[2]) << 16) | 0xFF000000u);
std::memcpy(pixels_out, &rgba, sizeof(rgba)); std::memcpy(row_pixels_out, &rgba, sizeof(rgba));
pixels_in += 3; row_pixels_in += 3;
pixels_out += sizeof(rgba); row_pixels_out += sizeof(rgba);
} }
} }
return true;
} }
break; break;
case ImageFormat::BC1: case ImageFormat::BC1:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); DecompressBC<ImageFormat::BC1>(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, width, height);
DecompressBC<ImageFormat::BC1>(ret.value(), *this); return true;
} }
break; break;
case ImageFormat::BC2: case ImageFormat::BC2:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); DecompressBC<ImageFormat::BC2>(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, width, height);
DecompressBC<ImageFormat::BC2>(ret.value(), *this); return true;
} }
break; break;
case ImageFormat::BC3: case ImageFormat::BC3:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); DecompressBC<ImageFormat::BC3>(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, width, height);
DecompressBC<ImageFormat::BC3>(ret.value(), *this); return true;
} }
break; break;
case ImageFormat::BC7: case ImageFormat::BC7:
{ {
ret = Image(m_width, m_height, ImageFormat::RGBA8); DecompressBC<ImageFormat::BC7>(pixels_out, pixels_out_pitch, pixels_in, pixels_in_pitch, width, height);
DecompressBC<ImageFormat::BC7>(ret.value(), *this); return true;
} }
break;
default: default:
{ {
Error::SetStringFmt(error, "Unhandled format {}", GetFormatName(m_format)); Error::SetStringFmt(error, "Unhandled format {}", GetFormatName(format));
return false;
} }
break;
} }
return ret;
} }
void Image::FlipY() void Image::FlipY()

View File

@ -98,7 +98,10 @@ public:
std::optional<DynamicHeapArray<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY, std::optional<DynamicHeapArray<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY,
Error* error = nullptr) const; Error* error = nullptr) const;
std::optional<Image> ConvertToRGBA8(Error* error) const; std::optional<Image> ConvertToRGBA8(Error* error = nullptr) const;
static bool ConvertToRGBA8(void* RESTRICT pixels_out, u32 pixels_out_pitch, const void* RESTRICT pixels_in,
u32 pixels_in_pitch, u32 width, u32 height, ImageFormat format, Error* error = nullptr);
void FlipY(); void FlipY();