1use std::path::Path;
2use std::sync::Arc;
3
4use anyhow::{Context, Result};
5
6use crate::formats;
7use crate::output;
8use crate::pipeline;
9use crate::types::{ImageMetadata, PixelData, ProcessConfig, ProcessedImage};
10
11pub struct ImageConverter {
12 downscale: usize,
13 quality: u8,
14 apply_debayer: bool,
15 preview_mode: bool,
16 rgba_output: bool,
17 thread_pool: Option<Arc<rayon::ThreadPool>>,
18}
19
20impl ImageConverter {
21 pub fn new() -> Self {
22 ImageConverter {
23 downscale: 1,
24 quality: 95,
25 apply_debayer: true,
26 preview_mode: false,
27 rgba_output: false,
28 thread_pool: None,
29 }
30 }
31
32 pub fn with_downscale(mut self, factor: usize) -> Self {
33 self.downscale = factor;
34 self
35 }
36
37 pub fn with_quality(mut self, quality: u8) -> Self {
38 self.quality = quality.clamp(1, 100);
39 self
40 }
41
42 pub fn without_debayer(mut self) -> Self {
43 self.apply_debayer = false;
44 self
45 }
46
47 pub fn with_preview_mode(mut self) -> Self {
48 self.preview_mode = true;
49 self
50 }
51
52 pub fn with_rgba_output(mut self) -> Self {
54 self.rgba_output = true;
55 self
56 }
57
58 pub fn with_thread_pool(mut self, pool: Arc<rayon::ThreadPool>) -> Self {
59 self.thread_pool = Some(pool);
60 self
61 }
62
63 pub fn read_raw<P: AsRef<Path>>(path: P) -> Result<(ImageMetadata, PixelData)> {
65 formats::read_image(path.as_ref()).context("Failed to read image")
66 }
67
68 pub fn process_data(
70 &self,
71 meta: ImageMetadata,
72 pixels: PixelData,
73 ) -> Result<ProcessedImage> {
74 let config = self.build_config();
75 match &self.thread_pool {
76 Some(pool) => pool.install(|| pipeline::process_image_data(meta, pixels, &config)),
77 None => pipeline::process_image_data(meta, pixels, &config),
78 }
79 .context("Image processing failed")
80 }
81
82 pub fn process<P: AsRef<Path>>(&self, input_path: P) -> Result<ProcessedImage> {
87 let config = self.build_config();
88 let path = input_path.as_ref();
89 match &self.thread_pool {
90 Some(pool) => pool.install(|| pipeline::process_image(path, &config)),
91 None => pipeline::process_image(path, &config),
92 }
93 .context("Image processing failed")
94 }
95
96 fn build_config(&self) -> ProcessConfig {
97 ProcessConfig {
98 downscale_factor: self.downscale,
99 jpeg_quality: self.quality,
100 apply_debayer: self.apply_debayer,
101 preview_mode: self.preview_mode,
102 auto_stretch: true,
103 rgba_output: self.rgba_output,
104 }
105 }
106
107 pub fn save_processed<P: AsRef<Path>>(
109 image: &ProcessedImage,
110 output_path: P,
111 quality: u8,
112 ) -> Result<()> {
113 output::save_image(image, output_path.as_ref(), quality)
114 .context("Image save failed")
115 }
116
117 pub fn convert<P: AsRef<Path>, Q: AsRef<Path>>(
119 &self,
120 input_path: P,
121 output_path: Q,
122 ) -> Result<()> {
123 let image = self.process(&input_path)?;
124
125 output::save_image(&image, output_path.as_ref(), self.quality)
126 .context("Image save failed")?;
127
128 Ok(())
129 }
130}
131
132impl Default for ImageConverter {
133 fn default() -> Self {
134 Self::new()
135 }
136}