Files
XCEngine/tests/Rendering/integration/RenderingIntegrationImageAssert.h

177 lines
5.1 KiB
C++

#pragma once
#include <gtest/gtest.h>
#include <algorithm>
#include <array>
#include <cstdint>
#include <filesystem>
#include <fstream>
#include <string>
#include <vector>
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <windows.h>
namespace RenderingIntegrationTestUtils {
struct PpmImage {
uint32_t width = 0;
uint32_t height = 0;
std::vector<uint8_t> rgb;
std::array<uint8_t, 3> GetPixel(uint32_t x, uint32_t y) const {
const size_t index = (static_cast<size_t>(y) * width + x) * 3u;
return { rgb[index + 0], rgb[index + 1], rgb[index + 2] };
}
};
inline std::filesystem::path GetExecutableDirectory() {
char exePath[MAX_PATH] = {};
const DWORD length = GetModuleFileNameA(nullptr, exePath, MAX_PATH);
if (length == 0 || length >= MAX_PATH) {
return std::filesystem::current_path();
}
return std::filesystem::path(exePath).parent_path();
}
inline std::filesystem::path ResolveRuntimePath(const char* path) {
std::filesystem::path resolved(path);
if (resolved.is_absolute()) {
return resolved;
}
return GetExecutableDirectory() / resolved;
}
inline std::string ReadNextPpmToken(std::istream& stream) {
std::string token;
while (stream >> token) {
if (!token.empty() && token[0] == '#') {
std::string ignored;
std::getline(stream, ignored);
continue;
}
return token;
}
return {};
}
inline PpmImage LoadPpmImage(const char* path) {
const std::filesystem::path resolvedPath = ResolveRuntimePath(path);
std::ifstream file(resolvedPath, std::ios::binary);
EXPECT_TRUE(file.is_open()) << resolvedPath.string();
PpmImage image;
if (!file.is_open()) {
return image;
}
const std::string magic = ReadNextPpmToken(file);
EXPECT_EQ(magic, "P6");
if (magic != "P6") {
return image;
}
const std::string widthToken = ReadNextPpmToken(file);
const std::string heightToken = ReadNextPpmToken(file);
const std::string maxValueToken = ReadNextPpmToken(file);
EXPECT_FALSE(widthToken.empty());
EXPECT_FALSE(heightToken.empty());
EXPECT_FALSE(maxValueToken.empty());
if (widthToken.empty() || heightToken.empty() || maxValueToken.empty()) {
return image;
}
image.width = static_cast<uint32_t>(std::stoul(widthToken));
image.height = static_cast<uint32_t>(std::stoul(heightToken));
EXPECT_EQ(std::stoul(maxValueToken), 255u);
file.get();
image.rgb.resize(static_cast<size_t>(image.width) * image.height * 3u);
file.read(reinterpret_cast<char*>(image.rgb.data()), static_cast<std::streamsize>(image.rgb.size()));
EXPECT_EQ(file.gcount(), static_cast<std::streamsize>(image.rgb.size()));
return image;
}
inline int PixelLuminance(const std::array<uint8_t, 3>& pixel) {
return static_cast<int>(pixel[0]) + static_cast<int>(pixel[1]) + static_cast<int>(pixel[2]);
}
inline void AssertPixelInBounds(const PpmImage& image, uint32_t x, uint32_t y, const char* label) {
ASSERT_LT(x, image.width) << label;
ASSERT_LT(y, image.height) << label;
}
inline void ExpectPixelNear(
const PpmImage& image,
uint32_t x,
uint32_t y,
const std::array<uint8_t, 3>& expected,
uint8_t tolerance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
for (size_t channel = 0; channel < actual.size(); ++channel) {
const int delta = std::abs(static_cast<int>(actual[channel]) - static_cast<int>(expected[channel]));
EXPECT_LE(delta, static_cast<int>(tolerance))
<< label << " @ (" << x << ", " << y << ") channel=" << channel;
}
}
inline void ExpectPixelLuminanceAtLeast(
const PpmImage& image,
uint32_t x,
uint32_t y,
int minLuminance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
EXPECT_GE(PixelLuminance(actual), minLuminance) << label << " @ (" << x << ", " << y << ")";
}
inline void ExpectPixelLuminanceAtMost(
const PpmImage& image,
uint32_t x,
uint32_t y,
int maxLuminance,
const char* label) {
AssertPixelInBounds(image, x, y, label);
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
EXPECT_LE(PixelLuminance(actual), maxLuminance) << label << " @ (" << x << ", " << y << ")";
}
inline void ExpectPixelChannelDominates(
const PpmImage& image,
uint32_t x,
uint32_t y,
size_t dominantChannel,
int minMargin,
const char* label) {
AssertPixelInBounds(image, x, y, label);
ASSERT_LT(dominantChannel, static_cast<size_t>(3)) << label;
const std::array<uint8_t, 3> actual = image.GetPixel(x, y);
int maxOtherChannel = 0;
for (size_t channel = 0; channel < actual.size(); ++channel) {
if (channel == dominantChannel) {
continue;
}
maxOtherChannel = std::max(maxOtherChannel, static_cast<int>(actual[channel]));
}
EXPECT_GE(static_cast<int>(actual[dominantChannel]) - maxOtherChannel, minMargin)
<< label << " @ (" << x << ", " << y << ")";
}
} // namespace RenderingIntegrationTestUtils