rawlib/
raw_processor.rs

1//! High-level safe wrapper around LibRaw
2
3use crate::ffi;
4use std::ffi::CStr;
5#[cfg(not(windows))]
6use std::ffi::CString;
7use std::fmt;
8use std::path::Path;
9
10/// Error type for LibRaw operations
11#[derive(Debug, Clone)]
12pub struct RawError {
13    code: i32,
14    message: String,
15}
16
17impl fmt::Display for RawError {
18    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
19        write!(f, "LibRaw error {}: {}", self.code, self.message)
20    }
21}
22
23impl std::error::Error for RawError {}
24
25pub type Result<T> = std::result::Result<T, RawError>;
26
27/// Thumbnail data with format information
28#[derive(Debug, Clone)]
29pub struct ThumbnailData {
30    /// Image format (JPEG or Bitmap)
31    pub format: ImageFormat,
32    /// Width of the thumbnail
33    pub width: u16,
34    /// Height of the thumbnail
35    pub height: u16,
36    /// Number of color channels
37    pub colors: u16,
38    /// Bits per sample
39    pub bits: u16,
40    /// Raw image data bytes
41    pub data: Vec<u8>,
42}
43
44/// Image format enumeration
45#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum ImageFormat {
47    /// JPEG compressed image
48    Jpeg,
49    /// Uncompressed bitmap
50    Bitmap,
51    /// Unknown format
52    Unknown(i32),
53}
54
55impl ImageFormat {
56    fn from_code(code: i32) -> Self {
57        match code {
58            ffi::LIBRAW_IMAGE_JPEG => ImageFormat::Jpeg,
59            ffi::LIBRAW_IMAGE_BITMAP => ImageFormat::Bitmap,
60            _ => ImageFormat::Unknown(code),
61        }
62    }
63    
64    /// Get MIME type for the format
65    pub fn mime_type(&self) -> &'static str {
66        match self {
67            ImageFormat::Jpeg => "image/jpeg",
68            ImageFormat::Bitmap => "image/bmp",
69            ImageFormat::Unknown(_) => "application/octet-stream",
70        }
71    }
72}
73
74/// Main processor for working with RAW image files
75pub struct RawProcessor {
76    data: *mut ffi::libraw_data_t,
77}
78
79impl RawProcessor {
80    /// Create a new RawProcessor instance
81    pub fn new() -> Result<Self> {
82        let data = unsafe { ffi::libraw_init(ffi::LIBRAW_OPTIONS_NONE) };
83        
84        if data.is_null() {
85            return Err(RawError {
86                code: -1,
87                message: "Failed to initialize LibRaw".to_string(),
88            });
89        }
90        
91        Ok(RawProcessor { data })
92    }
93    
94    /// Open a RAW file from the filesystem
95    pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
96        let path_ref = path.as_ref();
97        
98        // Check if file exists
99        if !path_ref.exists() {
100            return Err(RawError {
101                code: -1,
102                message: format!("File does not exist: {}", path_ref.display()),
103            });
104        }
105        
106        // On Windows, use wide character API for better Unicode support
107        #[cfg(windows)]
108        {
109            use std::os::windows::ffi::OsStrExt;
110            let wide: Vec<u16> = path_ref
111                .as_os_str()
112                .encode_wide()
113                .chain(std::iter::once(0))
114                .collect();
115            
116            let ret = unsafe { ffi::libraw_open_wfile(self.data, wide.as_ptr()) };
117            
118            if ret != ffi::LIBRAW_SUCCESS {
119                let mut err = self.make_error(ret);
120                err.message = format!("{} (file: {})", err.message, path_ref.display());
121                return Err(err);
122            }
123        }
124        
125        // On Unix, use regular UTF-8 path
126        #[cfg(not(windows))]
127        {
128            let path_str = path_ref.to_str().ok_or_else(|| RawError {
129                code: -1,
130                message: format!("Invalid path encoding: {}", path_ref.display()),
131            })?;
132            
133            let c_path = CString::new(path_str).map_err(|e| RawError {
134                code: -1,
135                message: format!("Path contains null byte: {} (error: {})", path_str, e),
136            })?;
137            
138            let ret = unsafe { ffi::libraw_open_file(self.data, c_path.as_ptr()) };
139            
140            if ret != ffi::LIBRAW_SUCCESS {
141                let mut err = self.make_error(ret);
142                err.message = format!("{} (file: {})", err.message, path_str);
143                return Err(err);
144            }
145        }
146        
147        Ok(())
148    }
149    
150    /// Unpack the RAW data
151    pub fn unpack(&mut self) -> Result<()> {
152        let ret = unsafe { ffi::libraw_unpack(self.data) };
153        
154        if ret != ffi::LIBRAW_SUCCESS {
155            return Err(self.make_error(ret));
156        }
157        
158        Ok(())
159    }
160    
161    /// Process the RAW data (demosaic, white balance, etc.)
162    pub fn dcraw_process(&mut self) -> Result<()> {
163        let ret = unsafe { ffi::libraw_dcraw_process(self.data) };
164        
165        if ret != ffi::LIBRAW_SUCCESS {
166            return Err(self.make_error(ret));
167        }
168        
169        Ok(())
170    }
171    
172    /// Recycle internal buffers
173    pub fn recycle(&mut self) {
174        unsafe { ffi::libraw_recycle(self.data) };
175    }
176    
177    /// Unpack thumbnail data
178    pub fn unpack_thumb(&mut self) -> Result<()> {
179        let ret = unsafe { ffi::libraw_unpack_thumb(self.data) };
180        
181        if ret != ffi::LIBRAW_SUCCESS {
182            return Err(self.make_error(ret));
183        }
184        
185        Ok(())
186    }
187    
188    /// Extract thumbnail as raw bytes
189    /// 
190    /// This method opens the RAW file, extracts the embedded thumbnail,
191    /// and returns it as a byte vector. The thumbnail is typically in JPEG format.
192    /// 
193    /// # Arguments
194    /// 
195    /// * `path` - Path to the RAW file
196    /// 
197    /// # Returns
198    /// 
199    /// Returns `ThumbnailData` containing the thumbnail image data and metadata
200    /// 
201    /// # Example
202    /// 
203    /// ```no_run
204    /// use rawlib::RawProcessor;
205    /// 
206    /// let thumb_data = RawProcessor::extract_thumbnail("image.cr2").unwrap();
207    /// std::fs::write("thumb.jpg", &thumb_data.data).unwrap();
208    /// ```
209    pub fn extract_thumbnail<P: AsRef<Path>>(path: P) -> Result<ThumbnailData> {
210        let mut processor = RawProcessor::new()?;
211        processor.open_file(path)?;
212        processor.unpack_thumb()?;
213        processor.get_thumbnail()
214    }
215    
216    /// Get the thumbnail data from an already opened and unpacked file
217    pub fn get_thumbnail(&self) -> Result<ThumbnailData> {
218        let mut errc: i32 = 0;
219        
220        let img_ptr = unsafe {
221            ffi::libraw_dcraw_make_mem_thumb(self.data, &mut errc as *mut i32)
222        };
223        
224        if img_ptr.is_null() {
225            return Err(self.make_error(errc));
226        }
227        
228        // SAFETY: img_ptr is valid and we'll copy the data before freeing
229        let thumbnail = unsafe {
230            let img = &*img_ptr;
231            let format = ImageFormat::from_code(img.image_type);
232            
233            // Calculate the actual data size
234            let data_size = img.data_size as usize;
235            
236            // Copy the image data
237            // SAFETY: data field is a flexible array member, 
238            // actual size is data_size bytes
239            let data_ptr = img.data.as_ptr();
240            let mut data = vec![0u8; data_size];
241            std::ptr::copy_nonoverlapping(data_ptr, data.as_mut_ptr(), data_size);
242            
243            ThumbnailData {
244                format,
245                width: img.width,
246                height: img.height,
247                colors: img.colors,
248                bits: img.bits,
249                data,
250            }
251        };
252        
253        // Free the LibRaw allocated memory
254        unsafe {
255            ffi::libraw_dcraw_clear_mem(img_ptr);
256        }
257        
258        Ok(thumbnail)
259    }
260    
261    /// Get LibRaw version string
262    pub fn version() -> String {
263        unsafe {
264            let ver = ffi::libraw_version();
265            CStr::from_ptr(ver)
266                .to_string_lossy()
267                .into_owned()
268        }
269    }
270    
271    /// Get LibRaw version number
272    pub fn version_number() -> i32 {
273        unsafe { ffi::libraw_versionNumber() }
274    }
275    
276    fn make_error(&self, code: i32) -> RawError {
277        let msg = unsafe {
278            let err_str = ffi::libraw_strerror(code);
279            CStr::from_ptr(err_str)
280                .to_string_lossy()
281                .into_owned()
282        };
283        
284        RawError {
285            code,
286            message: msg,
287        }
288    }
289}
290
291impl Drop for RawProcessor {
292    fn drop(&mut self) {
293        if !self.data.is_null() {
294            unsafe { ffi::libraw_close(self.data) };
295        }
296    }
297}
298
299unsafe impl Send for RawProcessor {}
300
301impl Default for RawProcessor {
302    fn default() -> Self {
303        Self::new().expect("Failed to create RawProcessor")
304    }
305}