177 lines
5.1 KiB
C
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
|