rawlib/
raw_processor.rs

1//! 高级安全的 LibRaw 包装器
2//!
3//! 这个模块提供了一个类型安全的 Rust API 来操作 LibRaw 库。
4//! 它封装了底层的 C FFI 调用,提供了 Rust 风格的错误处理和内存管理。
5//!
6//! 主要功能:
7//! - 安全的 LibRaw 实例管理(RAII 模式)
8//! - 缩略图提取和处理
9//! - 错误转换和处理
10//! - 多平台文件名支持
11
12use crate::ffi;
13use std::ffi::CStr;
14#[cfg(not(windows))]
15use std::ffi::CString;
16use std::fmt;
17use std::path::Path;
18
19/// LibRaw 操作的错误类型
20///
21/// 封装了 LibRaw C 库的错误代码,并提供用户友好的错误信息
22#[derive(Debug, Clone)]
23pub struct RawError {
24    /// LibRaw 错误代码
25    code: i32,
26    /// 错误描述信息
27    message: String,
28}
29
30impl fmt::Display for RawError {
31    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32        write!(f, "LibRaw error {}: {}", self.code, self.message)
33    }
34}
35
36impl std::error::Error for RawError {}
37
38/// 模块级别的结果类型别名
39pub type Result<T> = std::result::Result<T, RawError>;
40
41/// 包含格式信息的缩略图数据结构
42///
43/// 这个结构体包含了从 RAW 文件中提取的缩略图的完整信息,
44/// 包括图像格式、尺寸和原始数据。
45#[derive(Debug, Clone)]
46pub struct ThumbnailData {
47    /// 图像格式(JPEG 或位图)
48    pub format: ImageFormat,
49    /// 缩略图宽度(像素)
50    pub width: u16,
51    /// 缩略图高度(像素)
52    pub height: u16,
53    /// 颜色通道数
54    pub colors: u16,
55    /// 每样本位数
56    pub bits: u16,
57    /// 原始图像数据字节数组
58    pub data: Vec<u8>,
59}
60
61/// 图像格式枚举
62///
63/// 定义了 LibRaw 支持的输出图像格式
64#[derive(Debug, Clone, Copy, PartialEq, Eq)]
65pub enum ImageFormat {
66    /// JPEG 压缩图像
67    Jpeg,
68    /// 未压缩的位图(RGB 数据)
69    Bitmap,
70    /// Unknown format
71    Unknown(i32),
72}
73
74impl ImageFormat {
75    fn from_code(code: i32) -> Self {
76        match code {
77            ffi::LIBRAW_IMAGE_JPEG => ImageFormat::Jpeg,
78            ffi::LIBRAW_IMAGE_BITMAP => ImageFormat::Bitmap,
79            _ => ImageFormat::Unknown(code),
80        }
81    }
82    
83    /// Get MIME type for the format
84    pub fn mime_type(&self) -> &'static str {
85        match self {
86            ImageFormat::Jpeg => "image/jpeg",
87            ImageFormat::Bitmap => "image/bmp",
88            ImageFormat::Unknown(_) => "application/octet-stream",
89        }
90    }
91}
92
93/// RAW 图像文件的主要处理器
94///
95/// 这是 RawLib 库的核心结构体,提供了与 LibRaw 库交互的高级接口。
96/// 它使用 RAII 模式管理 LibRaw 实例的生命周期,确保资源被正确释放。
97///
98/// # 安全性
99/// - 使用 RAII 模式自动管理 LibRaw 实例
100/// - 实现了 Send trait,支持跨线程使用
101/// - 所有 FFI 调用都被包装在安全的接口中
102pub struct RawProcessor {
103    /// 指向 LibRaw 数据结构的不透明指针
104    /// 注意:这个指针由 LibRaw 管理,不应该被直接操作
105    data: *mut ffi::libraw_data_t,
106}
107
108impl RawProcessor {
109    /// 创建新的 RawProcessor 实例
110    ///
111    /// 初始化一个 LibRaw 实例并准备处理 RAW 文件。
112    /// 如果初始化失败,将返回错误。
113    ///
114    /// # 返回值
115    /// `Ok(RawProcessor)` - 成功创建的处理器实例
116    /// `Err(RawError)` - 初始化失败时的错误信息
117    ///
118    /// # 示例
119    /// ```rust
120    /// let processor = RawProcessor::new()?;
121    /// ```
122    pub fn new() -> Result<Self> {
123        // 调用 LibRaw C API 初始化实例
124        let data = unsafe { ffi::libraw_init(ffi::LIBRAW_OPTIONS_NONE) };
125
126        if data.is_null() {
127            return Err(RawError {
128                code: -1,
129                message: "Failed to initialize LibRaw".to_string(),
130            });
131        }
132
133        Ok(RawProcessor { data })
134    }
135    
136    /// 从文件系统打开 RAW 文件
137    ///
138    /// 打开指定的 RAW 文件并读取其基本信息。这个方法会:
139    /// 1. 检查文件是否存在
140    /// 2. 根据平台选择合适的 API(Windows 使用宽字符,Unix 使用 UTF-8)
141    /// 3. 调用 LibRaw 打开文件
142    ///
143    /// # 参数
144    /// * `path` - RAW 文件的路径
145    ///
146    /// # 返回值
147    /// `Ok(())` - 文件成功打开
148    /// `Err(RawError)` - 打开失败时的错误信息
149    ///
150    /// # 平台支持
151    /// - Windows: 使用宽字符 API 支持完整的 Unicode 文件名
152    /// - Unix/Linux/macOS: 使用 UTF-8 字符串
153    pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
154        let path_ref = path.as_ref();
155
156        // 验证文件是否存在
157        if !path_ref.exists() {
158            return Err(RawError {
159                code: -1,
160                message: format!("File does not exist: {}", path_ref.display()),
161            });
162        }
163
164        // Windows 平台:使用宽字符 API 以获得更好的 Unicode 支持
165        #[cfg(windows)]
166        {
167            use std::os::windows::ffi::OsStrExt;
168            // 将路径转换为 UTF-16 宽字符字符串,并以 null 结尾
169            let wide: Vec<u16> = path_ref
170                .as_os_str()
171                .encode_wide()
172                .chain(std::iter::once(0))
173                .collect();
174
175            // 调用 LibRaw 的宽字符 API
176            let ret = unsafe { ffi::libraw_open_wfile(self.data, wide.as_ptr()) };
177
178            if ret != ffi::LIBRAW_SUCCESS {
179                let mut err = self.make_error(ret);
180                err.message = format!("{} (file: {})", err.message, path_ref.display());
181                return Err(err);
182            }
183        }
184
185        // Unix 平台:使用常规的 UTF-8 路径
186        #[cfg(not(windows))]
187        {
188            // 确保 Path 可以转换为有效的 UTF-8 字符串
189            let path_str = path_ref.to_str().ok_or_else(|| RawError {
190                code: -1,
191                message: format!("Invalid path encoding: {}", path_ref.display()),
192            })?;
193            
194            let c_path = CString::new(path_str).map_err(|e| RawError {
195                code: -1,
196                message: format!("Path contains null byte: {} (error: {})", path_str, e),
197            })?;
198            
199            let ret = unsafe { ffi::libraw_open_file(self.data, c_path.as_ptr()) };
200            
201            if ret != ffi::LIBRAW_SUCCESS {
202                let mut err = self.make_error(ret);
203                err.message = format!("{} (file: {})", err.message, path_str);
204                return Err(err);
205            }
206        }
207        
208        Ok(())
209    }
210    
211    /// Unpack the RAW data
212    pub fn unpack(&mut self) -> Result<()> {
213        let ret = unsafe { ffi::libraw_unpack(self.data) };
214        
215        if ret != ffi::LIBRAW_SUCCESS {
216            return Err(self.make_error(ret));
217        }
218        
219        Ok(())
220    }
221    
222    /// Process the RAW data (demosaic, white balance, etc.)
223    pub fn dcraw_process(&mut self) -> Result<()> {
224        let ret = unsafe { ffi::libraw_dcraw_process(self.data) };
225        
226        if ret != ffi::LIBRAW_SUCCESS {
227            return Err(self.make_error(ret));
228        }
229        
230        Ok(())
231    }
232    
233    /// Recycle internal buffers
234    pub fn recycle(&mut self) {
235        unsafe { ffi::libraw_recycle(self.data) };
236    }
237    
238    /// Unpack thumbnail data
239    pub fn unpack_thumb(&mut self) -> Result<()> {
240        let ret = unsafe { ffi::libraw_unpack_thumb(self.data) };
241        
242        if ret != ffi::LIBRAW_SUCCESS {
243            return Err(self.make_error(ret));
244        }
245        
246        Ok(())
247    }
248    
249    /// Extract thumbnail as raw bytes
250    /// 
251    /// This method opens the RAW file, extracts the embedded thumbnail,
252    /// and returns it as a byte vector. The thumbnail is typically in JPEG format.
253    /// 
254    /// # Arguments
255    /// 
256    /// * `path` - Path to the RAW file
257    /// 
258    /// # Returns
259    /// 
260    /// Returns `ThumbnailData` containing the thumbnail image data and metadata
261    /// 
262    /// # Example
263    /// 
264    /// ```no_run
265    /// use rawlib::RawProcessor;
266    /// 
267    /// let thumb_data = RawProcessor::extract_thumbnail("image.cr2").unwrap();
268    /// std::fs::write("thumb.jpg", &thumb_data.data).unwrap();
269    /// ```
270    ///
271    /// 这是提取缩略图的最简单方法,它处理了整个流程:
272    /// 1. 创建新的处理器实例
273    /// 2. 打开 RAW 文件
274    /// 3. 解包缩略图数据
275    /// 4. 获取处理后的缩略图数据
276    pub fn extract_thumbnail<P: AsRef<Path>>(path: P) -> Result<ThumbnailData> {
277        let mut processor = RawProcessor::new()?;
278        processor.open_file(path)?;
279        processor.unpack_thumb()?;
280        processor.get_thumbnail()
281    }
282
283    /// 从已打开和解包的文件中获取缩略图数据
284    ///
285    /// 这个方法假设:
286    /// - 文件已经被 `open_file()` 打开
287    /// - 缩略图数据已经被 `unpack_thumb()` 解包
288    ///
289    /// # 返回值
290    /// `Ok(ThumbnailData)` - 包含格式、尺寸和原始数据的缩略图信息
291    /// `Err(RawError)` - 获取缩略图失败时的错误信息
292    pub fn get_thumbnail(&self) -> Result<ThumbnailData> {
293        let mut errc: i32 = 0;
294
295        // 调用 LibRaw 创建内存中的缩略图图像
296        let img_ptr = unsafe {
297            ffi::libraw_dcraw_make_mem_thumb(self.data, &mut errc as *mut i32)
298        };
299        
300        if img_ptr.is_null() {
301            return Err(self.make_error(errc));
302        }
303        
304        // SAFETY: img_ptr is valid and we'll copy the data before freeing
305        let thumbnail = unsafe {
306            let img = &*img_ptr;
307            let format = ImageFormat::from_code(img.image_type);
308            
309            // Calculate the actual data size
310            let data_size = img.data_size as usize;
311            
312            // Copy the image data
313            // SAFETY: data field is a flexible array member, 
314            // actual size is data_size bytes
315            let data_ptr = img.data.as_ptr();
316            let mut data = vec![0u8; data_size];
317            std::ptr::copy_nonoverlapping(data_ptr, data.as_mut_ptr(), data_size);
318            
319            ThumbnailData {
320                format,
321                width: img.width,
322                height: img.height,
323                colors: img.colors,
324                bits: img.bits,
325                data,
326            }
327        };
328        
329        // Free the LibRaw allocated memory
330        unsafe {
331            ffi::libraw_dcraw_clear_mem(img_ptr);
332        }
333        
334        Ok(thumbnail)
335    }
336    
337    /// 获取 LibRaw 版本字符串
338    ///
339    /// 返回当前链接的 LibRaw 库的版本信息字符串,例如 "0.21.4-Release"。
340    /// 这个方法对于调试和兼容性检查很有用。
341    ///
342    /// # 返回值
343    /// 包含版本信息的字符串
344    pub fn version() -> String {
345        unsafe {
346            // 调用 LibRaw 获取版本字符串
347            let ver = ffi::libraw_version();
348            CStr::from_ptr(ver)
349                .to_string_lossy()
350                .into_owned()
351        }
352    }
353
354    /// 获取 LibRaw 版本号(整数格式)
355    ///
356    /// 返回 LibRaw 库的数值版本号,例如 5380(对应 0.21.4)。
357    /// 版本号编码格式为:major * 10000 + minor * 100 + patch
358    ///
359    /// # 返回值
360    /// 整数格式的版本号
361    pub fn version_number() -> i32 {
362        unsafe { ffi::libraw_versionNumber() }
363    }
364    
365    /// 根据 LibRaw 错误代码创建错误信息
366    ///
367    /// 调用 LibRaw 的错误字符串函数获取用户友好的错误描述
368    fn make_error(&self, code: i32) -> RawError {
369        let msg = unsafe {
370            // 获取 LibRaw 提供的错误描述字符串
371            let err_str = ffi::libraw_strerror(code);
372            CStr::from_ptr(err_str)
373                .to_string_lossy()
374                .into_owned()
375        };
376
377        RawError {
378            code,
379            message: msg,
380        }
381    }
382}
383
384/// RAII 资源清理实现
385///
386/// 当 RawProcessor 实例离开作用域时,自动释放 LibRaw 分配的资源。
387/// 这确保了即使在发生错误时也不会出现内存泄漏。
388impl Drop for RawProcessor {
389    fn drop(&mut self) {
390        if !self.data.is_null() {
391            // 调用 LibRaw 的清理函数释放所有资源
392            unsafe { ffi::libraw_close(self.data) };
393        }
394    }
395}
396
397/// 线程安全实现
398///
399/// LibRaw 本身不是线程安全的,但是 RawProcessor 实例可以安全地
400/// 在不同线程之间传递(只要不在多个线程中同时使用同一个实例)。
401/// Send trait 允许我们将 RawProcessor 实例发送到其他线程。
402unsafe impl Send for RawProcessor {}
403
404/// 默认构造函数实现
405///
406/// 提供了默认构造函数,使 RawProcessor 可以更容易地在需要默认值的
407/// 场景中使用。如果创建失败,会 panic,因为这是默认实现的一部分。
408impl Default for RawProcessor {
409    fn default() -> Self {
410        Self::new().expect("Failed to create RawProcessor")
411    }
412}