#include "util/image.h"
#include "common/error.h"
#include "gtest/gtest.h"
#include <type_traits>
namespace {
class ImageTest : public ::testing::Test
{
protected:
void SetUp() override
{
m_test_image = Image(4, 4, ImageFormat::RGBA8);
for (u32 y = 0; y < m_test_image.GetHeight(); y++)
{
for (u32 x = 0; x < m_test_image.GetWidth(); x++)
{
u32* pixel = reinterpret_cast<u32*>(m_test_image.GetRowPixels(y) + x * sizeof(u32));
*pixel = (x * 64) | (0u << 8) | (0u << 16) | (0xFFu << 24);
}
}
}
Image m_test_image;
};
}
TEST_F(ImageTest, DefaultConstructor)
{
Image img;
EXPECT_FALSE(img.IsValid());
EXPECT_EQ(img.GetWidth(), 0u);
EXPECT_EQ(img.GetHeight(), 0u);
EXPECT_EQ(img.GetFormat(), ImageFormat::None);
}
TEST_F(ImageTest, SizeFormatConstructor)
{
const u32 width = 16;
const u32 height = 8;
Image img(width, height, ImageFormat::RGBA8);
EXPECT_TRUE(img.IsValid());
EXPECT_EQ(img.GetWidth(), width);
EXPECT_EQ(img.GetHeight(), height);
EXPECT_EQ(img.GetFormat(), ImageFormat::RGBA8);
EXPECT_NE(img.GetPixels(), nullptr);
}
TEST_F(ImageTest, CopyConstructor)
{
const u32 width = 16;
const u32 height = 8;
Image src(width, height, ImageFormat::RGBA8);
std::memset(src.GetPixels(), 0xAA, src.GetStorageSize());
Image copy(src);
EXPECT_TRUE(copy.IsValid());
EXPECT_EQ(copy.GetWidth(), width);
EXPECT_EQ(copy.GetHeight(), height);
EXPECT_EQ(copy.GetFormat(), ImageFormat::RGBA8);
EXPECT_EQ(std::memcmp(copy.GetPixels(), src.GetPixels(), src.GetStorageSize()), 0);
EXPECT_NE(copy.GetPixels(), src.GetPixels());
}
TEST_F(ImageTest, CopyAssignmentOperator)
{
const u32 width = 8;
const u32 height = 6;
Image src(width, height, ImageFormat::RGBA8);
std::memset(src.GetPixels(), 0xCCu, src.GetStorageSize());
Image dest(4, 4, ImageFormat::BGRA8);
std::memset(dest.GetPixels(), 0x55u, dest.GetStorageSize());
dest = src;
EXPECT_EQ(dest.GetWidth(), width);
EXPECT_EQ(dest.GetHeight(), height);
EXPECT_EQ(dest.GetFormat(), ImageFormat::RGBA8);
EXPECT_EQ(std::memcmp(dest.GetPixels(), src.GetPixels(), src.GetStorageSize()), 0);
EXPECT_NE(dest.GetPixels(), src.GetPixels());
}
TEST_F(ImageTest, MoveAssignmentOperator)
{
const u32 width = 8;
const u32 height = 6;
Image src(width, height, ImageFormat::RGBA8);
std::memset(src.GetPixels(), 0xCC, src.GetStorageSize());
const u8* original_pixels = src.GetPixels();
Image dest(4, 4, ImageFormat::BGRA8);
dest = std::move(src);
EXPECT_EQ(dest.GetWidth(), width);
EXPECT_EQ(dest.GetHeight(), height);
EXPECT_EQ(dest.GetFormat(), ImageFormat::RGBA8);
EXPECT_EQ(dest.GetPixels(), original_pixels);
EXPECT_FALSE(src.IsValid());
EXPECT_EQ(src.GetWidth(), 0u);
EXPECT_EQ(src.GetHeight(), 0u);
EXPECT_EQ(src.GetFormat(), ImageFormat::None);
EXPECT_EQ(src.GetPixels(), nullptr);
}
TEST_F(ImageTest, FormatUtilities)
{
EXPECT_STREQ(Image::GetFormatName(ImageFormat::RGBA8), "RGBA8");
EXPECT_STREQ(Image::GetFormatName(ImageFormat::BC1), "BC1");
EXPECT_EQ(Image::GetPixelSize(ImageFormat::RGBA8), 4u);
EXPECT_EQ(Image::GetPixelSize(ImageFormat::RGB565), 2u);
EXPECT_FALSE(Image::IsCompressedFormat(ImageFormat::RGBA8));
EXPECT_TRUE(Image::IsCompressedFormat(ImageFormat::BC1));
}
TEST_F(ImageTest, PixelManipulation)
{
const u32* first_pixel = reinterpret_cast<const u32*>(m_test_image.GetPixels());
EXPECT_EQ(*first_pixel, 0xFF000000u);
m_test_image.Clear();
EXPECT_EQ(*first_pixel, 0u);
std::memset(m_test_image.GetPixels(), 0x0u, m_test_image.GetStorageSize());
m_test_image.SetAllPixelsOpaque();
for (u32 y = 0; y < m_test_image.GetHeight(); y++)
{
for (u32 x = 0; x < m_test_image.GetWidth(); x++)
{
const u32* pixel = reinterpret_cast<const u32*>(m_test_image.GetRowPixels(y) + x * sizeof(u32));
EXPECT_EQ((*pixel & 0xFF000000), 0xFF000000u);
}
}
}
TEST_F(ImageTest, Resize)
{
const u32 new_width = 8;
const u32 new_height = 10;
m_test_image.Resize(new_width, new_height, false);
EXPECT_EQ(m_test_image.GetWidth(), new_width);
EXPECT_EQ(m_test_image.GetHeight(), new_height);
m_test_image.Resize(new_width, new_height, ImageFormat::BGRA8, false);
EXPECT_EQ(m_test_image.GetFormat(), ImageFormat::BGRA8);
std::memset(m_test_image.GetPixels(), 0xBBu, m_test_image.GetStorageSize());
const u32 final_width = 6;
const u32 final_height = 7;
m_test_image.Resize(final_width, final_height, true);
EXPECT_EQ(m_test_image.GetPixels()[0], 0xBBu);
EXPECT_EQ(m_test_image.GetWidth(), final_width);
EXPECT_EQ(m_test_image.GetHeight(), final_height);
}
TEST_F(ImageTest, RGB565ToRGBA8)
{
constexpr u32 width = 4;
constexpr u32 height = 4;
Image rgb565_image(width, height, ImageFormat::RGB565);
for (u32 y = 0; y < height; y++)
{
u16* row = reinterpret_cast<u16*>(rgb565_image.GetRowPixels(y));
for (u32 x = 0; x < width; x++)
{
row[x] = 0xF800;
}
}
Error err;
std::optional<Image> rgba8_image = rgb565_image.ConvertToRGBA8(&err);
ASSERT_TRUE(rgba8_image.has_value());
EXPECT_EQ(rgba8_image->GetFormat(), ImageFormat::RGBA8);
const u32* first_pixel = reinterpret_cast<const u32*>(rgba8_image->GetPixels());
EXPECT_GE((*first_pixel & 0xFFu), 0xF8u);
EXPECT_EQ((*first_pixel & 0xFF00u), 0u);
EXPECT_EQ((*first_pixel & 0xFF0000u), 0u);
EXPECT_EQ((*first_pixel & 0xFF000000u), 0xFF000000u);
}
TEST_F(ImageTest, RGB5A1ToRGBA8)
{
constexpr u32 width = 4;
constexpr u32 height = 4;
Image rgb5a1_image(width, height, ImageFormat::RGB5A1);
for (u32 y = 0; y < height; y++)
{
u16* row = reinterpret_cast<u16*>(rgb5a1_image.GetRowPixels(y));
for (u32 x = 0; x < width; x++)
{
row[x] = 0x87C0;
}
}
Error err;
std::optional<Image> rgba8_image = rgb5a1_image.ConvertToRGBA8(&err);
ASSERT_TRUE(rgba8_image.has_value());
EXPECT_EQ(rgba8_image->GetFormat(), ImageFormat::RGBA8);
const u32* first_pixel = reinterpret_cast<const u32*>(rgba8_image->GetPixels());
EXPECT_GE((*first_pixel & 0xFFu), 8u);
EXPECT_LT((*first_pixel & 0xFFu), 16u);
EXPECT_GE(((*first_pixel >> 8) & 0xFFu), 0xF0u);
EXPECT_LT(((*first_pixel >> 8) & 0xFFu), 0xF8u);
EXPECT_EQ((*first_pixel & 0xFF0000u), 0u);
EXPECT_EQ((*first_pixel & 0xFF000000u), 0xFF000000u);
}
TEST_F(ImageTest, BlockSizes)
{
const u32 width = 16;
const u32 height = 16;
Image bc1_image(width, height, ImageFormat::BC1);
EXPECT_EQ(bc1_image.GetBlocksWide(), width / 4);
EXPECT_EQ(bc1_image.GetBlocksHigh(), height / 4);
EXPECT_EQ(bc1_image.GetPitch(), (width / 4) * 8);
const u32 odd_width = 10;
const u32 odd_height = 6;
Image bc1_odd_image(odd_width, odd_height, ImageFormat::BC1);
EXPECT_EQ(bc1_odd_image.GetBlocksWide(), (odd_width + 3) / 4);
EXPECT_EQ(bc1_odd_image.GetBlocksHigh(), (odd_height + 3) / 4);
Image bc3_image(width, height, ImageFormat::BC3);
EXPECT_EQ(bc3_image.GetBlocksWide(), width / 4);
EXPECT_EQ(bc3_image.GetBlocksHigh(), height / 4);
EXPECT_EQ(bc3_image.GetPitch(), (width / 4) * 16);
const u32 bc1_storage = bc1_image.GetStorageSize();
EXPECT_EQ(bc1_storage, (width / 4) * (height / 4) * 8);
const u32 bc3_storage = bc3_image.GetStorageSize();
EXPECT_EQ(bc3_storage, (width / 4) * (height / 4) * 16);
}
TEST_F(ImageTest, PixelSpans)
{
std::span<const u8> const_span = m_test_image.GetPixelsSpan();
EXPECT_EQ(const_span.data(), m_test_image.GetPixels());
EXPECT_EQ(const_span.size(), m_test_image.GetStorageSize());
std::span<u8> mutable_span = m_test_image.GetPixelsSpan();
EXPECT_EQ(mutable_span.data(), m_test_image.GetPixels());
EXPECT_EQ(mutable_span.size(), m_test_image.GetStorageSize());
if (!mutable_span.empty())
{
mutable_span[0] = 0xAA;
EXPECT_EQ(m_test_image.GetPixels()[0], 0xAAu);
}
}
TEST_F(ImageTest, TakePixels)
{
const u32 width = 8;
const u32 height = 6;
Image src(width, height, ImageFormat::RGBA8);
std::memset(src.GetPixels(), 0xCC, src.GetStorageSize());
const u8* original_pixels = src.GetPixels();
Image::PixelStorage pixels = src.TakePixels();
EXPECT_FALSE(src.IsValid());
EXPECT_EQ(src.GetWidth(), 0u);
EXPECT_EQ(src.GetHeight(), 0u);
EXPECT_EQ(src.GetFormat(), ImageFormat::None);
EXPECT_EQ(src.GetPixels(), nullptr);
EXPECT_EQ(pixels.get(), original_pixels);
}
TEST_F(ImageTest, OperationsOnInvalidImage)
{
Image invalid_image;
invalid_image.Clear();
invalid_image.FlipY();
EXPECT_EQ(invalid_image.GetStorageSize(), 0u);
EXPECT_EQ(invalid_image.GetPixels(), nullptr);
EXPECT_TRUE(invalid_image.GetPixelsSpan().empty());
}
TEST_F(ImageTest, ConvertMultipleFormatsToRGBA8)
{
const u32 width = 4;
const u32 height = 4;
Error err;
{
Image rgba8_image(width, height, ImageFormat::RGBA8);
std::memset(rgba8_image.GetPixels(), 0xAA, rgba8_image.GetStorageSize());
std::optional<Image> converted = rgba8_image.ConvertToRGBA8(&err);
ASSERT_TRUE(converted.has_value());
EXPECT_EQ(converted->GetFormat(), ImageFormat::RGBA8);
EXPECT_EQ(std::memcmp(converted->GetPixels(), rgba8_image.GetPixels(), rgba8_image.GetStorageSize()), 0);
}
{
Image bgra8_image(width, height, ImageFormat::BGRA8);
std::fill_n(reinterpret_cast<u32*>(bgra8_image.GetPixels()), width * height, 0xFF0000FF);
std::optional<Image> converted = bgra8_image.ConvertToRGBA8(&err);
ASSERT_TRUE(converted.has_value());
const u32* first_pixel = reinterpret_cast<const u32*>(converted->GetPixels());
EXPECT_EQ(*first_pixel, 0xFFFF0000u);
}
{
Image a1bgr5_image(width, height, ImageFormat::A1BGR5);
std::fill_n(reinterpret_cast<u16*>(a1bgr5_image.GetPixels()), width * height, static_cast<u16>(0x837b));
std::optional<Image> converted = a1bgr5_image.ConvertToRGBA8(&err);
ASSERT_TRUE(converted.has_value());
const u32* first_pixel = reinterpret_cast<const u32*>(converted->GetPixels());
EXPECT_GE((*first_pixel & 0xFFu), 0x80u);
EXPECT_GE(((*first_pixel >> 8) & 0xFFu), 0x34u);
EXPECT_GE(((*first_pixel >> 16) & 0xFFu), 0xE8u);
EXPECT_EQ((*first_pixel & 0xFF000000u), 0xFF000000u);
}
}
TEST_F(ImageTest, PitchAndStorage)
{
const u32 width = 16;
const u32 height = 8;
const u32 rgba_pitch = Image::CalculatePitch(width, height, ImageFormat::RGBA8);
EXPECT_EQ(rgba_pitch, width * 4);
const u32 rgba_storage = Image::CalculateStorageSize(width, height, ImageFormat::RGBA8);
EXPECT_EQ(rgba_storage, rgba_pitch * height);
const u32 bc1_pitch = Image::CalculatePitch(width, height, ImageFormat::BC1);
EXPECT_EQ(bc1_pitch, (width / 4) * 8);
const u32 bc1_storage = Image::CalculateStorageSize(width, height, ImageFormat::BC1);
EXPECT_EQ(bc1_storage, bc1_pitch * (height / 4));
}
TEST_F(ImageTest, FlipY)
{
Image test_image(2, 2, ImageFormat::RGBA8);
u32* top_left = reinterpret_cast<u32*>(test_image.GetRowPixels(0));
u32* top_right = top_left + 1;
*top_left = *top_right = 0xFF0000FFu;
u32* bottom_left = reinterpret_cast<u32*>(test_image.GetRowPixels(1));
u32* bottom_right = bottom_left + 1;
*bottom_left = *bottom_right = 0xFFFF0000u;
test_image.FlipY();
top_left = reinterpret_cast<u32*>(test_image.GetRowPixels(0));
top_right = top_left + 1;
bottom_left = reinterpret_cast<u32*>(test_image.GetRowPixels(1));
bottom_right = bottom_left + 1;
EXPECT_EQ(*top_left, 0xFFFF0000u);
EXPECT_EQ(*top_right, 0xFFFF0000u);
EXPECT_EQ(*bottom_left, 0xFF0000FFu);
EXPECT_EQ(*bottom_right, 0xFF0000FFu);
}
TEST_F(ImageTest, ZeroDimensions)
{
Image zero_width(0, 10, ImageFormat::RGBA8);
EXPECT_FALSE(zero_width.IsValid());
Image zero_height(10, 0, ImageFormat::RGBA8);
EXPECT_FALSE(zero_height.IsValid());
Image normal(8, 8, ImageFormat::RGBA8);
normal.Resize(0, 8, false);
EXPECT_FALSE(normal.IsValid());
}
TEST_F(ImageTest, Invalidate)
{
Image img(16, 16, ImageFormat::RGBA8);
EXPECT_TRUE(img.IsValid());
EXPECT_NE(img.GetPixels(), nullptr);
img.Invalidate();
EXPECT_FALSE(img.IsValid());
EXPECT_EQ(img.GetWidth(), 0u);
EXPECT_EQ(img.GetHeight(), 0u);
EXPECT_EQ(img.GetFormat(), ImageFormat::None);
EXPECT_EQ(img.GetPixels(), nullptr);
}