chore: initial project scaffold from architecture design

- Add CLAUDE.md with project overview, tech stack, build commands,
  architecture description, coding standards, and sample images section
- Add full directory structure: src/, docs/, tests/, import/
- Add CMakeLists.txt with C++20, OpenCV/LibRaw/Qt6 dependencies,
  converter_core static lib, optional GUI, and GTest tests
- Add architecture documentation: ARCHITECTURE.md, PIPELINE.md, MODULES.md
- Add source skeletons for all pipeline stages:
  RawLoader, Preprocessor, NegativeDetector, Inverter, ColorCorrector,
  CropProcessor, OutputWriter, Pipeline, MainWindow, CliRunner, main.cpp
- Add initial test stubs for pipeline and rawloader
- Add sample ARW files in import/ for integration testing

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Christoph K.
2026-03-14 09:28:32 +01:00
commit 65b411b23d
34 changed files with 3191 additions and 0 deletions

265
docs/MODULES.md Normal file
View File

@@ -0,0 +1,265 @@
# Module Catalog
## Module Overview
| Module | Location | Responsibility | Dependencies |
|--------|----------|---------------|--------------|
| Pipeline | `src/converter/pipeline/` | Stage orchestration, data types | OpenCV (core) |
| RawLoader | `src/converter/rawloader/` | File loading (RAW + standard) | OpenCV, LibRaw |
| Preprocessor | `src/converter/preprocess/` | Format normalization, deskew | OpenCV |
| NegativeDetector | `src/converter/negative/` | Film type classification | OpenCV |
| Inverter | `src/converter/invert/` | Negative-to-positive inversion | OpenCV |
| ColorCorrector | `src/converter/color/` | White balance, C-41 correction | OpenCV |
| CropProcessor | `src/converter/crop/` | Auto-crop, levels, sharpening | OpenCV |
| OutputWriter | `src/converter/output/` | File output | OpenCV |
| MainWindow | `src/gui/` | Qt GUI | Qt 6.8, converter_core |
| CliRunner | `src/cli/` | CLI batch processing | converter_core |
## Dependency Graph
```
MainWindow ──> Pipeline ──> PipelineStage (interface)
| | ^
| | |
v | +--------+--------+--------+--------+--------+--------+
Qt 6.8 | | | | | | | |
| Preproc Detect Invert Color Crop Output (future)
| | | | | | |
+-----+--------+--------+--------+--------+--------+
|
v
RawLoader
|
+----+----+
| |
LibRaw OpenCV
CliRunner ──> Pipeline ──> (same stages as above)
|
+──> RawLoader
```
## Module Details
---
### Pipeline Infrastructure (`src/converter/pipeline/`)
#### Error.h
| Type | Description |
|------|-------------|
| `enum class ErrorCode` | Error classification (30+ codes across all stages) |
| `struct Error` | Error with code, message, and source location |
| `make_error()` | Factory function with automatic source_location capture |
#### ImageData.h
| Type | Description |
|------|-------------|
| `enum class FilmType` | Film classification: Unknown, ColorNegative, BWNegative, ColorPositive, BWPositive |
| `struct RawMetadata` | Camera make/model, ISO, shutter, aperture, WB multipliers, dimensions |
| `struct ImageData` | Core carrier: cv::Mat rgb (CV_16UC3), source_path, metadata, film_type, crop_region |
#### PipelineStage.h
| Type | Description |
|------|-------------|
| `using StageResult` | `std::expected<ImageData, Error>` |
| `class PipelineStage` | Abstract interface: `process(ImageData) -> StageResult`, `name() -> string` |
| `using ProgressCallback` | `function<void(string, float)>` for GUI progress reporting |
#### Pipeline.h / Pipeline.cpp
| Class | `Pipeline` |
|-------|-----------|
| **Pattern** | Chain of Responsibility + Strategy |
| **Owns** | `vector<unique_ptr<PipelineStage>>` |
| **Methods** | |
| `add_stage(unique_ptr<PipelineStage>)` | Append a stage |
| `execute(ImageData, ProgressCallback) -> StageResult` | Run all stages sequentially |
| `stage_count() -> size_t` | Number of registered stages |
| **Thread safety** | Not thread-safe (single-threaded execution) |
| **Copyable** | No (deleted copy ctor/assignment) |
| **Movable** | Yes |
---
### RawLoader (`src/converter/rawloader/`)
| Class | `RawLoader` |
|-------|------------|
| **Pattern** | Factory Method (routes to load_raw or load_standard) |
| **Methods** | |
| `load(path) -> expected<ImageData, Error>` | Main entry point |
| `load_raw(path)` | LibRaw loader (private) |
| `load_standard(path)` | OpenCV loader (private) |
| `is_raw_format(path) -> bool` | Extension check (static) |
| `is_standard_format(path) -> bool` | Extension check (static) |
| `log_metadata(RawMetadata)` | Print metadata to stdout (static) |
| **Constants** | |
| `kMaxRawFileSize` | 4 GB (4,294,967,296 bytes) |
| `kRawExtensions[]` | cr2, cr3, nef, arw, dng, orf, rw2, raf, pef |
| `kStandardExtensions[]` | jpg, jpeg, png, tif, tiff |
| **Internal** | |
| `LibRawGuard` | RAII class ensuring `LibRaw::recycle()` |
**LibRaw configuration:**
```cpp
params.use_camera_wb = 1; // Use camera white balance
params.output_bps = 16; // 16-bit output
params.no_auto_bright = 1; // No auto-brightness
params.half_size = 0; // Full resolution
params.output_color = 1; // sRGB color space
```
---
### Preprocessor (`src/converter/preprocess/`)
| Class | `Preprocessor` : `PipelineStage` |
|-------|----------------------------------|
| **Stage name** | "Preprocess" |
| **Methods** | |
| `process(ImageData) -> StageResult` | Validate + deskew |
| `validate_and_convert(ImageData)` | Ensure CV_16UC3 (static, private) |
| `deskew(ImageData)` | Geometric correction (static, private, TODO) |
---
### NegativeDetector (`src/converter/negative/`)
| Class | `NegativeDetector` : `PipelineStage` |
|-------|--------------------------------------|
| **Stage name** | "Detect" |
| **Methods** | |
| `process(ImageData) -> StageResult` | Classify film type |
| `is_negative_histogram(Mat) -> bool` | Mean intensity analysis (static, private) |
| `has_orange_mask(Mat) -> bool` | R/B ratio check (static, private) |
| `is_monochrome(Mat) -> bool` | HSV saturation check (static, private) |
| **Constants** | |
| `kOrangeMaskThreshold` | 1.4f |
| `kColorSaturationThreshold` | 15.0f |
---
### Inverter (`src/converter/invert/`)
| Class | `Inverter` : `PipelineStage` |
|-------|------------------------------|
| **Stage name** | "Invert" |
| **Methods** | |
| `process(ImageData) -> StageResult` | Route by film type |
| `invert_color_negative(ImageData)` | Orange mask removal + bitwise_not (static, private) |
| `invert_bw_negative(ImageData)` | Simple bitwise_not (static, private) |
---
### ColorCorrector (`src/converter/color/`)
| Class | `ColorCorrector` : `PipelineStage` |
|-------|-------------------------------------|
| **Stage name** | "ColorCorrection" |
| **Methods** | |
| `process(ImageData) -> StageResult` | Route by film type |
| `correct_c41(ImageData)` | C-41 orange cast removal (static, private, TODO) |
| `auto_white_balance(ImageData)` | Gray-world AWB (static, private) |
| `apply_exif_wb(ImageData)` | EXIF-based WB (static, private) |
---
### CropProcessor (`src/converter/crop/`)
| Class | `CropProcessor` : `PipelineStage` |
|-------|-------------------------------------|
| **Stage name** | "PostProcess" |
| **Methods** | |
| `process(ImageData) -> StageResult` | Auto-crop + levels + sharpen |
| `auto_crop(ImageData)` | Frame detection via contours (static, private, TODO) |
| `adjust_levels(ImageData)` | Percentile-based levels (static, private, TODO) |
| `sharpen(ImageData)` | Unsharp mask (static, private, TODO) |
| **Constants** | |
| `kMinFrameAreaRatio` | 0.3 |
| `kSharpenSigma` | 1.5 |
| `kSharpenStrength` | 0.5 |
| `kBlackPointPercentile` | 0.5 |
| `kWhitePointPercentile` | 99.5 |
---
### OutputWriter (`src/converter/output/`)
| Class | `OutputWriter` : `PipelineStage` |
|-------|-----------------------------------|
| **Stage name** | "Output" |
| **Constructor** | `OutputWriter(OutputConfig config)` |
| **Methods** | |
| `process(ImageData) -> StageResult` | Write file to disk |
| `build_output_path(string) -> path` | Construct output filename (private) |
| `format_extension(OutputFormat) -> string` | Map format to extension (static, private) |
| **Config** | |
| `OutputConfig::output_dir` | Target directory |
| `OutputConfig::format` | PNG_16bit, PNG_8bit, TIFF_16bit, JPEG |
| `OutputConfig::jpeg_quality` | 0-100 (default 95) |
---
### MainWindow (`src/gui/`)
| Class | `MainWindow` : `QMainWindow` |
|-------|-------------------------------|
| **Qt slots** | |
| `on_open_files()` | File dialog with RAW filter |
| `on_convert()` | Execute pipeline on all selected files |
| `on_select_output_dir()` | Directory picker |
| **Private** | |
| `setup_ui()` | Construct widgets and layout |
| `setup_pipeline()` | Build default pipeline |
| `update_preview(Mat)` | Convert 16-bit BGR to QPixmap for display |
| **State** | |
| `input_files_` | Selected file paths |
| `output_dir_` | Chosen output directory |
| `pipeline_` | Pipeline instance |
---
### CliRunner (`src/cli/`)
| Class | `CliRunner` |
|-------|------------|
| **Nested types** | `Config` (input_files, output_dir, output_format, jpeg_quality, verbose) |
| **Static methods** | |
| `parse_args(argc, argv) -> expected<Config, Error>` | Parse CLI arguments |
| **Methods** | |
| `run(Config) -> expected<int, Error>` | Execute batch processing, return success count |
**CLI usage:**
```
photo-converter --cli -i file1.arw file2.cr2 -o output/ --format png16
```
---
## Build Targets
| Target | Type | Sources | Dependencies |
|--------|------|---------|-------------|
| `converter_core` | Static library | All `src/converter/` + `src/cli/` | OpenCV, LibRaw |
| `photo-converter` | Executable | `src/main.cpp` + `src/gui/` | converter_core, Qt6::Widgets |
| `test_pipeline` | Test executable | `tests/test_pipeline.cpp` | converter_core, GTest |
| `test_rawloader` | Test executable | `tests/test_rawloader.cpp` | converter_core, GTest |
## Implementation Status
| Module | Status | Notes |
|--------|--------|-------|
| Pipeline | Complete | Orchestration, error propagation, progress |
| RawLoader | Complete | Full LibRaw + OpenCV loading |
| Preprocessor | Skeleton | Validation done, deskew TODO |
| NegativeDetector | Functional | Basic histogram + orange mask detection |
| Inverter | Skeleton | Basic bitwise_not, C-41 mask removal TODO |
| ColorCorrector | Partial | Gray-world AWB implemented, C-41 TODO |
| CropProcessor | Skeleton | All sub-stages TODO (pass-through) |
| OutputWriter | Complete | All formats supported |
| MainWindow | Complete | File dialogs, preview, progress |
| CliRunner | Complete | Argument parsing, batch loop |