#include #include "converter/pipeline/Pipeline.h" #include "converter/pipeline/ImageData.h" #include "converter/pipeline/Error.h" #include "converter/preprocess/Preprocessor.h" #include "converter/negative/NegativeDetector.h" #include "converter/invert/Inverter.h" #include "converter/color/ColorCorrector.h" #include "converter/crop/CropProcessor.h" #include using namespace photoconv; /** * @brief Create a synthetic 16-bit test image. * * @param width Image width. * @param height Image height. * @param value Fill value for all channels (0-65535). * @return CV_16UC3 Mat filled with the given value. */ static ImageData make_test_image(int width, int height, uint16_t value) { ImageData data; data.rgb = cv::Mat(height, width, CV_16UC3, cv::Scalar(value, value, value)); data.source_path = "test_synthetic.png"; data.metadata.camera_make = "Test"; data.metadata.camera_model = "Synthetic"; return data; } // ────────────────────────────────────────────── // Pipeline orchestration tests // ────────────────────────────────────────────── TEST(PipelineTest, EmptyPipelinePassesThrough) { Pipeline pipeline; auto data = make_test_image(100, 100, 32768); auto result = pipeline.execute(std::move(data)); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->rgb.cols, 100); EXPECT_EQ(result->rgb.rows, 100); } TEST(PipelineTest, StageCountIsCorrect) { Pipeline pipeline; EXPECT_EQ(pipeline.stage_count(), 0); pipeline.add_stage(std::make_unique()); EXPECT_EQ(pipeline.stage_count(), 1); pipeline.add_stage(std::make_unique()); EXPECT_EQ(pipeline.stage_count(), 2); } TEST(PipelineTest, FullPipelineRunsWithoutError) { Pipeline pipeline; pipeline.add_stage(std::make_unique()); pipeline.add_stage(std::make_unique()); pipeline.add_stage(std::make_unique()); pipeline.add_stage(std::make_unique()); pipeline.add_stage(std::make_unique()); auto data = make_test_image(200, 200, 40000); auto result = pipeline.execute(std::move(data)); ASSERT_TRUE(result.has_value()); } TEST(PipelineTest, ProgressCallbackIsCalled) { Pipeline pipeline; pipeline.add_stage(std::make_unique()); pipeline.add_stage(std::make_unique()); int callback_count = 0; auto data = make_test_image(100, 100, 32768); auto result = pipeline.execute(std::move(data), [&callback_count](const std::string&, float) { ++callback_count; }); ASSERT_TRUE(result.has_value()); // 2 stage callbacks + 1 "done" callback = 3 EXPECT_EQ(callback_count, 3); } // ────────────────────────────────────────────── // Preprocessor tests // ────────────────────────────────────────────── TEST(PreprocessorTest, AcceptsValidImage) { Preprocessor stage; auto data = make_test_image(100, 100, 32768); auto result = stage.process(std::move(data)); ASSERT_TRUE(result.has_value()); EXPECT_EQ(result->rgb.type(), CV_16UC3); } TEST(PreprocessorTest, RejectsEmptyImage) { Preprocessor stage; ImageData data; auto result = stage.process(std::move(data)); ASSERT_FALSE(result.has_value()); EXPECT_EQ(result.error().code, ErrorCode::InvalidBitDepth); } // ────────────────────────────────────────────── // NegativeDetector tests // ────────────────────────────────────────────── TEST(NegativeDetectorTest, DetectsBrightImageAsNegative) { NegativeDetector stage; // High values = likely negative (inverted) auto data = make_test_image(100, 100, 50000); auto result = stage.process(std::move(data)); ASSERT_TRUE(result.has_value()); EXPECT_NE(result->film_type, FilmType::Unknown); } TEST(NegativeDetectorTest, DetectsDarkImageAsPositive) { NegativeDetector stage; // Low values = likely positive auto data = make_test_image(100, 100, 10000); auto result = stage.process(std::move(data)); ASSERT_TRUE(result.has_value()); // Should be classified as positive (below midpoint) EXPECT_TRUE(result->film_type == FilmType::ColorPositive || result->film_type == FilmType::BWPositive); } // ────────────────────────────────────────────── // Inverter tests // ────────────────────────────────────────────── TEST(InverterTest, InvertsNegative) { Inverter stage; auto data = make_test_image(10, 10, 60000); data.film_type = FilmType::ColorNegative; auto result = stage.process(std::move(data)); ASSERT_TRUE(result.has_value()); // After inversion, values should be near 65535 - 60000 = 5535 cv::Scalar mean = cv::mean(result->rgb); EXPECT_LT(mean[0], 10000); } TEST(InverterTest, SkipsPositive) { Inverter stage; auto data = make_test_image(10, 10, 30000); data.film_type = FilmType::ColorPositive; auto result = stage.process(std::move(data)); ASSERT_TRUE(result.has_value()); // Should be unchanged cv::Scalar mean = cv::mean(result->rgb); EXPECT_NEAR(mean[0], 30000.0, 1.0); } // ────────────────────────────────────────────── // Error type tests // ────────────────────────────────────────────── TEST(ErrorTest, FormatIncludesAllInfo) { auto err = make_error(ErrorCode::FileNotFound, "test.arw not found"); auto formatted = err.format(); EXPECT_NE(formatted.find("test.arw not found"), std::string::npos); EXPECT_NE(formatted.find("test_pipeline.cpp"), std::string::npos); }