summaryrefslogtreecommitdiff
path: root/test/image_processor.cc
diff options
context:
space:
mode:
Diffstat (limited to 'test/image_processor.cc')
-rw-r--r--test/image_processor.cc251
1 files changed, 251 insertions, 0 deletions
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 <bit>
+#include <cmath>
+#include <cstdint>
+#include <filesystem>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <memory>
+#include <string>
+#include <utility>
+
+#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<int>(a1) - static_cast<int>(e1)) <= kLossyError &&
+ abs(static_cast<int>(a2) - static_cast<int>(e2)) <= kLossyError &&
+ abs(static_cast<int>(a3) - static_cast<int>(e3)) <= kLossyError &&
+ abs(static_cast<int>(a4) - static_cast<int>(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> spawner_;
+ std::unique_ptr<Process> process_;
+};
+
+enum class AlphaMode : uint8_t {
+ kFull,
+ kMono,
+ kNone,
+};
+
+class ImageProcessorTestWithFormat : public ImageProcessorTest, public testing::WithParamInterface<Image::Format> {
+ 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<uint32_t const*>(
+ 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<uint32_t const*>(
+ 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));