From e740234a062a11aca832c72c88e26c89063f484c Mon Sep 17 00:00:00 2001 From: "Christoph K." Date: Sat, 14 Mar 2026 09:56:39 +0100 Subject: [PATCH] fix: respect config film_type to force negative inversion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit NegativeDetector now accepts an optional forced FilmType. When film_type != "auto" in config.ini, auto-detection is skipped and the configured type is applied directly. build_pipeline() in CliRunner maps c41→ColorNegative and bw→BWNegative accordingly. Default config changed from film_type=auto to film_type=c41 to match the project's primary use case (C-41 color negatives). Co-Authored-By: Claude Sonnet 4.6 --- config.ini | 2 +- src/cli/CliRunner.cpp | 12 +++++++++++- src/converter/negative/NegativeDetector.cpp | 17 +++++++++++++++++ src/converter/negative/NegativeDetector.h | 20 +++++++++++++++++++- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/config.ini b/config.ini index b3cee43..8fd008f 100644 --- a/config.ini +++ b/config.ini @@ -21,7 +21,7 @@ file_extensions = arw,cr2,cr3,nef,dng,orf,rw2,raf,pef,jpg,jpeg,png,tif,tiff # auto – NegativeDetector analyses the histogram and orange mask # c41 – force C-41 colour negative processing # bw – force B&W negative processing -film_type = auto +film_type = c41 # Output format: # png16 – 16-bit PNG (lossless, archival quality) diff --git a/src/cli/CliRunner.cpp b/src/cli/CliRunner.cpp index d9c1859..3e0afb0 100644 --- a/src/cli/CliRunner.cpp +++ b/src/cli/CliRunner.cpp @@ -1,5 +1,6 @@ #include "CliRunner.h" +#include "../converter/pipeline/ImageData.h" #include "../converter/rawloader/RawLoader.h" #include "../converter/preprocess/Preprocessor.h" #include "../converter/negative/NegativeDetector.h" @@ -269,8 +270,17 @@ std::vector CliRunner::collect_files( Pipeline CliRunner::build_pipeline(const AppConfig& app_cfg) { Pipeline pipeline; + // Resolve forced film type from config. + // "auto" → Unknown (auto-detection), "c41" → ColorNegative, "bw" → BWNegative. + FilmType forced_film = FilmType::Unknown; + if (app_cfg.conversion.film_type == "c41") { + forced_film = FilmType::ColorNegative; + } else if (app_cfg.conversion.film_type == "bw") { + forced_film = FilmType::BWNegative; + } + pipeline.add_stage(std::make_unique()); - pipeline.add_stage(std::make_unique()); + pipeline.add_stage(std::make_unique(forced_film)); if (app_cfg.conversion.invert) { pipeline.add_stage(std::make_unique()); diff --git a/src/converter/negative/NegativeDetector.cpp b/src/converter/negative/NegativeDetector.cpp index d44d8f8..a3dec9b 100644 --- a/src/converter/negative/NegativeDetector.cpp +++ b/src/converter/negative/NegativeDetector.cpp @@ -13,6 +13,23 @@ StageResult NegativeDetector::process(ImageData data) const { ErrorCode::DetectionFailed, "NegativeDetector received empty image")); } + // If the caller forced a specific film type, skip analysis entirely. + if (forced_type_ != FilmType::Unknown) { + data.film_type = forced_type_; + std::cout << std::format("[Detect] Film type forced by config: {}", + [&] { + switch (data.film_type) { + case FilmType::ColorNegative: return "Color Negative (C-41)"; + case FilmType::BWNegative: return "B&W Negative"; + case FilmType::ColorPositive: return "Color Positive (Slide)"; + case FilmType::BWPositive: return "B&W Positive"; + default: return "Unknown"; + } + }()) << std::endl; + return data; + } + + // Auto-detection: histogram + orange mask analysis. const bool is_negative = is_negative_histogram(data.rgb); const bool is_bw = is_monochrome(data.rgb); diff --git a/src/converter/negative/NegativeDetector.h b/src/converter/negative/NegativeDetector.h index 81af63a..a60ddef 100644 --- a/src/converter/negative/NegativeDetector.h +++ b/src/converter/negative/NegativeDetector.h @@ -13,7 +13,11 @@ namespace photoconv { * The detected FilmType is stored in ImageData::film_type for use * by the Invert and Color Correction stages. * - * Detection strategy: + * When constructed with a forced FilmType (anything other than Unknown), + * the stage skips automatic analysis and sets that type directly. + * This allows the config's film_type setting to override auto-detection. + * + * Detection strategy (auto mode): * 1. Compute per-channel histograms * 2. Analyze distribution skewness (negatives have inverted distributions) * 3. Check for C-41 orange mask (dominant red/orange in unexposed regions) @@ -27,13 +31,27 @@ public: /// Minimum saturation to distinguish color from B&W. static constexpr float kColorSaturationThreshold = 15.0f; + /** + * @brief Construct in auto-detection mode. + */ NegativeDetector() = default; + + /** + * @brief Construct with a forced film type (skips auto-detection). + * + * @param forced_type Film type to force. Use FilmType::Unknown for auto mode. + */ + explicit NegativeDetector(FilmType forced_type) : forced_type_(forced_type) {} + ~NegativeDetector() override = default; [[nodiscard]] StageResult process(ImageData data) const override; [[nodiscard]] std::string name() const override { return "Detect"; } private: + /// When set to anything other than Unknown, auto-detection is skipped. + FilmType forced_type_ = FilmType::Unknown; + /** * @brief Analyze histogram to detect inverted (negative) distribution. *