fits_converter/
lib_c.rs

1use std::ffi::CString;
2use std::os::raw::{c_char, c_int};
3use std::path::Path;
4use anyhow::{Context, Result};
5
6// C structures and functions (from fits_processor.h)
7#[repr(C)]
8struct ProcessConfig {
9    downscale_factor: c_int,
10    jpeg_quality: c_int,
11    apply_debayer: c_int,
12    preview_mode: c_int,
13    auto_stretch: c_int,
14    manual_shadows: f32,
15    manual_highlights: f32,
16    manual_midtones: f32,
17}
18
19#[repr(C)]
20struct ProcessedImage {
21    data: *mut u8,
22    width: usize,
23    height: usize,
24    is_color: c_int,
25}
26
27extern "C" {
28    fn process_image_file(
29        path: *const c_char,
30        config: *const ProcessConfig,
31        out_image: *mut ProcessedImage,
32    ) -> c_int;
33
34    fn save_jpeg(
35        image: *const ProcessedImage,
36        output_path: *const c_char,
37        quality: c_int,
38    ) -> c_int;
39
40    fn free_processed_image(image: *mut ProcessedImage);
41
42    fn get_last_error() -> *const c_char;
43}
44
45pub struct FitsConverter {
46    downscale: usize,
47    quality: u8,
48    apply_debayer: bool,
49    preview_mode: bool,
50}
51
52impl FitsConverter {
53    pub fn new() -> Self {
54        FitsConverter {
55            downscale: 1,
56            quality: 95,
57            apply_debayer: true,
58            preview_mode: false,
59        }
60    }
61
62    pub fn with_downscale(mut self, factor: usize) -> Self {
63        self.downscale = factor;
64        self
65    }
66
67    pub fn with_quality(mut self, quality: u8) -> Self {
68        self.quality = quality.clamp(1, 100);
69        self
70    }
71
72    pub fn without_debayer(mut self) -> Self {
73        self.apply_debayer = false;
74        self
75    }
76
77    pub fn with_preview_mode(mut self) -> Self {
78        self.preview_mode = true;
79        self
80    }
81
82    pub fn convert<P: AsRef<Path>, Q: AsRef<Path>>(
83        &self,
84        input_path: P,
85        output_path: Q,
86    ) -> Result<()> {
87        let input_cstr = CString::new(input_path.as_ref().to_str().unwrap())
88            .context("Invalid input path")?;
89        let output_cstr = CString::new(output_path.as_ref().to_str().unwrap())
90            .context("Invalid output path")?;
91
92        let config = ProcessConfig {
93            downscale_factor: self.downscale as c_int,
94            jpeg_quality: self.quality as c_int,
95            apply_debayer: if self.apply_debayer { 1 } else { 0 },
96            preview_mode: if self.preview_mode { 1 } else { 0 },
97            auto_stretch: 1,
98            manual_shadows: 0.0,
99            manual_highlights: 1.0,
100            manual_midtones: 0.5,
101        };
102
103        let mut image = ProcessedImage {
104            data: std::ptr::null_mut(),
105            width: 0,
106            height: 0,
107            is_color: 0,
108        };
109
110        unsafe {
111            let result = process_image_file(
112                input_cstr.as_ptr(),
113                &config as *const ProcessConfig,
114                &mut image as *mut ProcessedImage,
115            );
116
117            if result != 0 {
118                let err_ptr = get_last_error();
119                let err_msg = if !err_ptr.is_null() {
120                    std::ffi::CStr::from_ptr(err_ptr)
121                        .to_string_lossy()
122                        .into_owned()
123                } else {
124                    "Unknown error".to_string()
125                };
126                anyhow::bail!("Image processing failed: {}", err_msg);
127            }
128
129            let result = save_jpeg(
130                &image as *const ProcessedImage,
131                output_cstr.as_ptr(),
132                self.quality as c_int,
133            );
134
135            free_processed_image(&mut image as *mut ProcessedImage);
136
137            if result != 0 {
138                anyhow::bail!("JPEG save failed");
139            }
140        }
141
142        Ok(())
143    }
144
145}
146
147impl Default for FitsConverter {
148    fn default() -> Self {
149        Self::new()
150    }
151}