From 6ed8f5151719fbc14ec0ac6d28a346d1f74cf2ca Mon Sep 17 00:00:00 2001 From: Joel Klinghed Date: Fri, 2 Jan 2026 22:42:31 +0100 Subject: Initial commit --- test/image_processor.cc | 251 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 251 insertions(+) create mode 100644 test/image_processor.cc (limited to 'test/image_processor.cc') diff --git a/test/image_processor.cc b/test/image_processor.cc new file mode 100644 index 0000000..c005f01 --- /dev/null +++ b/test/image_processor.cc @@ -0,0 +1,251 @@ +#include "image_processor.hh" + +#include "config.h" +#include "image.hh" +#include "image_loader.hh" +#include "spawner.hh" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef TESTDIR +# define TESTDIR "." +#endif + +namespace { + +// JPEG compression at quality 90 +constexpr int kLossyError = 32; + +MATCHER_P(ColourLossyEq, expected, "") { + uint8_t a1 = arg >> 24; + uint8_t e1 = expected >> 24; + uint8_t a2 = arg >> 16; + uint8_t e2 = expected >> 16; + uint8_t a3 = arg >> 8; + uint8_t e3 = expected >> 8; + uint8_t a4 = arg; + uint8_t e4 = expected; + return abs(static_cast(a1) - static_cast(e1)) <= kLossyError && + abs(static_cast(a2) - static_cast(e2)) <= kLossyError && + abs(static_cast(a3) - static_cast(e3)) <= kLossyError && + abs(static_cast(a4) - static_cast(e4)) <= kLossyError; +} + +std::string error2str(ImageLoadError err) { + switch (err) { + case ImageLoadError::kNoSuchFile: + return "no such file"; + case ImageLoadError::kError: + return "error"; + case ImageLoadError::kUnsupportedFormat: + return "unsupported format"; + case ImageLoadError::kProcessError: + return "process error"; + } + std::unreachable(); +} + +class ImageProcessorTest : public testing::Test { + protected: + void SetUp() override { + { + auto spawner = Spawner::create(); + ASSERT_TRUE(spawner.has_value()); + spawner_ = std::move(spawner.value()); + } + + { + auto process = spawner_->run(Spawner::Exec::kImageProcessor); + ASSERT_TRUE(process.has_value()); + process_ = std::move(process.value()); + } + } + + [[nodiscard]] + Process& process() const { + return *process_; + } + + [[nodiscard]] + static std::filesystem::path testdir() { + return TESTDIR; + } + + void peek(std::string const& name) { + auto ret = image_processor::peek(process(), testdir() / name); + ASSERT_TRUE(ret.has_value()) << error2str(ret.error()); + EXPECT_EQ(10, ret->width); + EXPECT_EQ(10, ret->height); + } + + private: + std::unique_ptr spawner_; + std::unique_ptr process_; +}; + +enum class AlphaMode : uint8_t { + kFull, + kMono, + kNone, +}; + +class ImageProcessorTestWithFormat : public ImageProcessorTest, public testing::WithParamInterface { + protected: + void load(std::string const& name, AlphaMode alpha = AlphaMode::kFull, bool lossy = false) { + auto ret = image_processor::load(process(), testdir() / name, GetParam()); + ASSERT_TRUE(ret.has_value()) << error2str(ret.error()); + EXPECT_EQ(10, ret.value()->width()); + EXPECT_EQ(10, ret.value()->height()); + EXPECT_EQ(GetParam(), ret.value()->format()); + + EXPECT_GE(4 * ret.value()->width(), ret.value()->scanline()); + if (lossy) { + for (uint32_t y = 0; y < ret.value()->height(); ++y) { + for (uint32_t x = 0; x < ret.value()->width(); ++x) { + EXPECT_THAT(reinterpret_cast( + ret.value()->data() + (y * ret.value()->scanline()))[x], + ColourLossyEq(expected_color(x, y, alpha))) + << x << "x" << y; + } + } + } else { + for (uint32_t y = 0; y < ret.value()->height(); ++y) { + for (uint32_t x = 0; x < ret.value()->width(); ++x) { + EXPECT_EQ(expected_color(x, y, alpha), + reinterpret_cast( + ret.value()->data() + (y * ret.value()->scanline()))[x]) + << x << "x" << y; + } + } + } + } + + private: + static uint32_t expected_argb(uint32_t x, uint32_t y) { + uint32_t argb; + if (x < 5) { + if (y < 5) { + argb = 0xffff0000; + } else { + argb = 0xff0000ff; + } + } else { + if (y < 5) { + argb = 0xff00ff00; + } else { + argb = 0x80ff0000; + } + } + return argb; + } + + static uint32_t argb_to_format(uint32_t argb) { + if constexpr (std::endian::native == std::endian::big) { + switch (GetParam()) { + case Image::Format::ARGB_8888: + return argb; + case Image::Format::BGRA_8888: + return std::byteswap(argb); + case Image::Format::RGBA_8888: + return std::rotl(argb, 8); + case Image::Format::ABGR_8888: + return std::byteswap(std::rotl(argb, 8)); + } + } else { + switch (GetParam()) { + case Image::Format::ARGB_8888: + return std::byteswap(argb); + case Image::Format::BGRA_8888: + return argb; + case Image::Format::RGBA_8888: + return std::byteswap(std::rotl(argb, 8)); + case Image::Format::ABGR_8888: + return std::rotl(argb, 8); + } + } + std::unreachable(); + } + + static uint32_t expected_color(uint32_t x, uint32_t y, AlphaMode alpha) { + uint32_t argb = expected_argb(x, y); + switch (alpha) { + case AlphaMode::kFull: + break; + case AlphaMode::kMono: + if (argb >> 24 != 0xff) + argb = 0; + break; + case AlphaMode::kNone: + argb |= 0xff000000; + break; + } + return argb_to_format(argb); + } + + static uint32_t expected_color_no_alpha(uint32_t x, uint32_t y) { + return argb_to_format(0xff000000 | expected_argb(x, y)); + } +}; + +} // namespace + +TEST_F(ImageProcessorTest, peek_no_such_file) { + auto ret = image_processor::peek(process(), testdir() / "does_not_exist.png"); + ASSERT_FALSE(ret.has_value()); + EXPECT_EQ(ImageLoadError::kNoSuchFile, ret.error()); +} + +#if HAVE_PNG +TEST_F(ImageProcessorTest, peek_png) { + peek("test.png"); +} + +TEST_P(ImageProcessorTestWithFormat, load_png) { + load("test.png"); +} +#endif + +#if HAVE_JPEG +TEST_F(ImageProcessorTest, peek_jpeg) { + peek("test.jpeg"); +} + +TEST_P(ImageProcessorTestWithFormat, load_jpeg) { + load("test.jpeg", AlphaMode::kNone, /* lossy */ true); +} +#endif + +#if HAVE_XPM +TEST_F(ImageProcessorTest, peek_xpm) { + peek("test.xpm"); +} + +TEST_P(ImageProcessorTestWithFormat, load_xpm) { + load("test.xpm", AlphaMode::kMono); +} +#endif + +#if HAVE_RSVG +TEST_F(ImageProcessorTest, peek_svg) { + peek("test.svg"); +} + +TEST_P(ImageProcessorTestWithFormat, load_svg) { + load("test.svg"); +} +#endif + +INSTANTIATE_TEST_SUITE_P(ImageProcessor, + ImageProcessorTestWithFormat, + testing::Values(Image::Format::RGBA_8888, + Image::Format::ARGB_8888, + Image::Format::BGRA_8888, + Image::Format::ABGR_8888)); -- cgit v1.2.3-70-g09d2