#pragma once #include #include #include #include #include #include #include #include #ifndef NOMINMAX #define NOMINMAX #endif #include namespace RenderingIntegrationTestUtils { struct PpmImage { uint32_t width = 0; uint32_t height = 0; std::vector rgb; std::array GetPixel(uint32_t x, uint32_t y) const { const size_t index = (static_cast(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(std::stoul(widthToken)); image.height = static_cast(std::stoul(heightToken)); EXPECT_EQ(std::stoul(maxValueToken), 255u); file.get(); image.rgb.resize(static_cast(image.width) * image.height * 3u); file.read(reinterpret_cast(image.rgb.data()), static_cast(image.rgb.size())); EXPECT_EQ(file.gcount(), static_cast(image.rgb.size())); return image; } inline int PixelLuminance(const std::array& pixel) { return static_cast(pixel[0]) + static_cast(pixel[1]) + static_cast(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& expected, uint8_t tolerance, const char* label) { AssertPixelInBounds(image, x, y, label); const std::array actual = image.GetPixel(x, y); for (size_t channel = 0; channel < actual.size(); ++channel) { const int delta = std::abs(static_cast(actual[channel]) - static_cast(expected[channel])); EXPECT_LE(delta, static_cast(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 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 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(3)) << label; const std::array 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(actual[channel])); } EXPECT_GE(static_cast(actual[dominantChannel]) - maxOtherChannel, minMargin) << label << " @ (" << x << ", " << y << ")"; } } // namespace RenderingIntegrationTestUtils