1use crate::ffi;
4use std::ffi::CStr;
5#[cfg(not(windows))]
6use std::ffi::CString;
7use std::fmt;
8use std::path::Path;
9
10#[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#[derive(Debug, Clone)]
29pub struct ThumbnailData {
30 pub format: ImageFormat,
32 pub width: u16,
34 pub height: u16,
36 pub colors: u16,
38 pub bits: u16,
40 pub data: Vec<u8>,
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
46pub enum ImageFormat {
47 Jpeg,
49 Bitmap,
51 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 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
74pub struct RawProcessor {
76 data: *mut ffi::libraw_data_t,
77}
78
79impl RawProcessor {
80 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 pub fn open_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
96 let path_ref = path.as_ref();
97
98 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 #[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 #[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 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 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 pub fn recycle(&mut self) {
174 unsafe { ffi::libraw_recycle(self.data) };
175 }
176
177 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 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 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 let thumbnail = unsafe {
230 let img = &*img_ptr;
231 let format = ImageFormat::from_code(img.image_type);
232
233 let data_size = img.data_size as usize;
235
236 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 unsafe {
255 ffi::libraw_dcraw_clear_mem(img_ptr);
256 }
257
258 Ok(thumbnail)
259 }
260
261 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 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}