Skip to main content

astroimage/
converter.rs

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    /// Output RGBA (4 bytes/pixel) instead of RGB, suitable for HTML Canvas `ImageData`.
53    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    /// Read raw pixel data from a FITS/XISF file without any processing.
64    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    /// Process pre-read image data (skips file I/O).
69    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    /// Process a FITS/XISF image and return raw pixel data without writing to disk.
83    ///
84    /// Returns a `ProcessedImage` containing interleaved RGB u8 bytes,
85    /// suitable for display in a GUI, web backend, or further processing.
86    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    /// Save a `ProcessedImage` to disk as JPEG or PNG.
108    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    /// Process a FITS/XISF image and save the result as JPEG or PNG.
118    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}