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 /// ```no_run
120 /// use rawlib::RawProcessor;
121 ///
122 /// let processor = RawProcessor::new()?;
123 /// # Ok::<(), rawlib::RawError>(())
124 /// ```
125 pub fn new() -> Result<Self> {
126 // 调用 LibRaw C API 初始化实例
127 let data = unsafe { ffi::libraw_init(ffi::LIBRAW_OPTIONS_NONE) };
128
129 if data.is_null() {
130 return Err(RawError {
131 code: -1,
132 message: "Failed to initialize LibRaw".to_string(),
133 });
134 }
135
136 Ok(RawProcessor { data })
137 }
138
139 /// 从文件系统打开 RAW 文件
140 ///
141 /// 打开指定的 RAW 文件并读取其基本信息。这个方法会:
142 /// 1. 检查文件是否存在
143 /// 2. 根据平台选择合适的 API(Windows 使用宽字符,Unix 使用 UTF-8)
144 /// 3. 调用 LibRaw 打开文件
145 ///
146 /// # 参数
147 /// * `path` - RAW 文件的路径
148 ///
149 /// # 返回值
150 /// `Ok(())` - 文件成功打开
151 /// `Err(RawError)` - 打开失败时的错误信息
152 ///
153 /// # 平台支持
154 /// - Windows: 使用宽字符 API 支持完整的 Unicode 文件名
155 /// - Unix/Linux/macOS: 使用 UTF-8 字符串
156 pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
157 let path_ref = path.as_ref();
158
159 // 验证文件是否存在
160 if !path_ref.exists() {
161 return Err(RawError {
162 code: -1,
163 message: format!("File does not exist: {}", path_ref.display()),
164 });
165 }
166
167 // Windows 平台:使用宽字符 API 以获得更好的 Unicode 支持
168 #[cfg(windows)]
169 {
170 use std::os::windows::ffi::OsStrExt;
171 // 将路径转换为 UTF-16 宽字符字符串,并以 null 结尾
172 let wide: Vec<u16> = path_ref
173 .as_os_str()
174 .encode_wide()
175 .chain(std::iter::once(0))
176 .collect();
177
178 // 调用 LibRaw 的宽字符 API
179 let ret = unsafe { ffi::libraw_open_wfile(self.data, wide.as_ptr()) };
180
181 if ret != ffi::LIBRAW_SUCCESS {
182 let mut err = self.make_error(ret);
183 err.message = format!("{} (file: {})", err.message, path_ref.display());
184 return Err(err);
185 }
186 }
187
188 // Unix 平台:使用常规的 UTF-8 路径
189 #[cfg(not(windows))]
190 {
191 // 确保 Path 可以转换为有效的 UTF-8 字符串
192 let path_str = path_ref.to_str().ok_or_else(|| RawError {
193 code: -1,
194 message: format!("Invalid path encoding: {}", path_ref.display()),
195 })?;
196
197 let c_path = CString::new(path_str).map_err(|e| RawError {
198 code: -1,
199 message: format!("Path contains null byte: {} (error: {})", path_str, e),
200 })?;
201
202 let ret = unsafe { ffi::libraw_open_file(self.data, c_path.as_ptr()) };
203
204 if ret != ffi::LIBRAW_SUCCESS {
205 let mut err = self.make_error(ret);
206 err.message = format!("{} (file: {})", err.message, path_str);
207 return Err(err);
208 }
209 }
210
211 Ok(())
212 }
213
214 /// Unpack the RAW data
215 pub fn unpack(&mut self) -> Result<()> {
216 let ret = unsafe { ffi::libraw_unpack(self.data) };
217
218 if ret != ffi::LIBRAW_SUCCESS {
219 return Err(self.make_error(ret));
220 }
221
222 Ok(())
223 }
224
225 /// Process the RAW data (demosaic, white balance, etc.)
226 pub fn dcraw_process(&mut self) -> Result<()> {
227 let ret = unsafe { ffi::libraw_dcraw_process(self.data) };
228
229 if ret != ffi::LIBRAW_SUCCESS {
230 return Err(self.make_error(ret));
231 }
232
233 Ok(())
234 }
235
236 /// Recycle internal buffers
237 pub fn recycle(&mut self) {
238 unsafe { ffi::libraw_recycle(self.data) };
239 }
240
241 /// Unpack thumbnail data
242 pub fn unpack_thumb(&mut self) -> Result<()> {
243 let ret = unsafe { ffi::libraw_unpack_thumb(self.data) };
244
245 if ret != ffi::LIBRAW_SUCCESS {
246 return Err(self.make_error(ret));
247 }
248
249 Ok(())
250 }
251
252 /// Extract thumbnail as raw bytes
253 ///
254 /// This method opens the RAW file, extracts the embedded thumbnail,
255 /// and returns it as a byte vector. The thumbnail is typically in JPEG format.
256 ///
257 /// # Arguments
258 ///
259 /// * `path` - Path to the RAW file
260 ///
261 /// # Returns
262 ///
263 /// Returns `ThumbnailData` containing the thumbnail image data and metadata
264 ///
265 /// # Example
266 ///
267 /// ```no_run
268 /// use rawlib::RawProcessor;
269 ///
270 /// let thumb_data = RawProcessor::extract_thumbnail("image.cr2").unwrap();
271 /// std::fs::write("thumb.jpg", &thumb_data.data).unwrap();
272 /// ```
273 ///
274 /// 这是提取缩略图的最简单方法,它处理了整个流程:
275 /// 1. 创建新的处理器实例
276 /// 2. 打开 RAW 文件
277 /// 3. 解包缩略图数据
278 /// 4. 获取处理后的缩略图数据
279 pub fn extract_thumbnail<P: AsRef<Path>>(path: P) -> Result<ThumbnailData> {
280 let mut processor = RawProcessor::new()?;
281 processor.open_file(path)?;
282 processor.unpack_thumb()?;
283 processor.get_thumbnail()
284 }
285
286 /// 从已打开和解包的文件中获取缩略图数据
287 ///
288 /// 这个方法假设:
289 /// - 文件已经被 `open_file()` 打开
290 /// - 缩略图数据已经被 `unpack_thumb()` 解包
291 ///
292 /// # 返回值
293 /// `Ok(ThumbnailData)` - 包含格式、尺寸和原始数据的缩略图信息
294 /// `Err(RawError)` - 获取缩略图失败时的错误信息
295 pub fn get_thumbnail(&self) -> Result<ThumbnailData> {
296 let mut errc: i32 = 0;
297
298 // 调用 LibRaw 创建内存中的缩略图图像
299 let img_ptr = unsafe {
300 ffi::libraw_dcraw_make_mem_thumb(self.data, &mut errc as *mut i32)
301 };
302
303 if img_ptr.is_null() {
304 return Err(self.make_error(errc));
305 }
306
307 // SAFETY: img_ptr is valid and we'll copy the data before freeing
308 let thumbnail = unsafe {
309 let img = &*img_ptr;
310 let format = ImageFormat::from_code(img.image_type);
311
312 // Calculate the actual data size
313 let data_size = img.data_size as usize;
314
315 // Copy the image data
316 // SAFETY: data field is a flexible array member,
317 // actual size is data_size bytes
318 let data_ptr = img.data.as_ptr();
319 let mut data = vec![0u8; data_size];
320 std::ptr::copy_nonoverlapping(data_ptr, data.as_mut_ptr(), data_size);
321
322 ThumbnailData {
323 format,
324 width: img.width,
325 height: img.height,
326 colors: img.colors,
327 bits: img.bits,
328 data,
329 }
330 };
331
332 // Free the LibRaw allocated memory
333 unsafe {
334 ffi::libraw_dcraw_clear_mem(img_ptr);
335 }
336
337 Ok(thumbnail)
338 }
339
340 /// 获取 LibRaw 版本字符串
341 ///
342 /// 返回当前链接的 LibRaw 库的版本信息字符串,例如 "0.21.4-Release"。
343 /// 这个方法对于调试和兼容性检查很有用。
344 ///
345 /// # 返回值
346 /// 包含版本信息的字符串
347 pub fn version() -> String {
348 unsafe {
349 // 调用 LibRaw 获取版本字符串
350 let ver = ffi::libraw_version();
351 CStr::from_ptr(ver)
352 .to_string_lossy()
353 .into_owned()
354 }
355 }
356
357 /// 获取 LibRaw 版本号(整数格式)
358 ///
359 /// 返回 LibRaw 库的数值版本号,例如 5380(对应 0.21.4)。
360 /// 版本号编码格式为:major * 10000 + minor * 100 + patch
361 ///
362 /// # 返回值
363 /// 整数格式的版本号
364 pub fn version_number() -> i32 {
365 unsafe { ffi::libraw_versionNumber() }
366 }
367
368 /// 根据 LibRaw 错误代码创建错误信息
369 ///
370 /// 调用 LibRaw 的错误字符串函数获取用户友好的错误描述
371 fn make_error(&self, code: i32) -> RawError {
372 let msg = unsafe {
373 // 获取 LibRaw 提供的错误描述字符串
374 let err_str = ffi::libraw_strerror(code);
375 CStr::from_ptr(err_str)
376 .to_string_lossy()
377 .into_owned()
378 };
379
380 RawError {
381 code,
382 message: msg,
383 }
384 }
385}
386
387/// RAII 资源清理实现
388///
389/// 当 RawProcessor 实例离开作用域时,自动释放 LibRaw 分配的资源。
390/// 这确保了即使在发生错误时也不会出现内存泄漏。
391impl Drop for RawProcessor {
392 fn drop(&mut self) {
393 if !self.data.is_null() {
394 // 调用 LibRaw 的清理函数释放所有资源
395 unsafe { ffi::libraw_close(self.data) };
396 }
397 }
398}
399
400/// 线程安全实现
401///
402/// LibRaw 本身不是线程安全的,但是 RawProcessor 实例可以安全地
403/// 在不同线程之间传递(只要不在多个线程中同时使用同一个实例)。
404/// Send trait 允许我们将 RawProcessor 实例发送到其他线程。
405unsafe impl Send for RawProcessor {}
406
407/// 默认构造函数实现
408///
409/// 提供了默认构造函数,使 RawProcessor 可以更容易地在需要默认值的
410/// 场景中使用。如果创建失败,会 panic,因为这是默认实现的一部分。
411impl Default for RawProcessor {
412 fn default() -> Self {
413 Self::new().expect("Failed to create RawProcessor")
414 }
415}