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}