#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));