Skip to main content

rs_vips/
source.rs

1// (c) Copyright 2019-2025 OLX
2// (c) Copyright 2025 mrdkprj
3use crate::{
4    bindings::{self, gint64},
5    connection::VipsConnection,
6    error::Error,
7    utils::{self, new_c_string, path_to_cstring, result_cond, safe_result_cond},
8    Result,
9};
10use std::{
11    ffi::{c_int, c_void},
12    io::{Read, Seek, SeekFrom},
13    path::Path,
14};
15
16/// A unified interface for reading, seeking, and mapping data
17#[derive(Debug, Clone)]
18pub struct VipsSource {
19    pub(crate) ctx: *mut bindings::VipsSource,
20}
21
22pub trait Reader: Read + Seek + Send + 'static {}
23impl<T: Read + Seek + Send + 'static> Reader for T {}
24
25struct SourceContext {
26    reader: Box<dyn Reader>,
27}
28
29impl VipsSource {
30    /// Create a target from a reader.
31    pub fn new_from_reader<R: Reader>(input: R) -> Result<VipsSource> {
32        unsafe {
33            let source = bindings::vips_source_custom_new();
34
35            if source.is_null() {
36                return Err(
37                    Error::InitializationError("Cannot create VipsSourceCustom".to_string()),
38                );
39            }
40
41            let context = Box::new(
42                SourceContext {
43                    reader: Box::new(input),
44                },
45            );
46
47            let user_data = Box::into_raw(context) as *mut c_void;
48
49            bindings::g_signal_connect_data(
50                source as *mut c_void,
51                c"read".as_ptr(),
52                Some(
53                    std::mem::transmute::<
54                        unsafe extern "C" fn(
55                            source: *mut bindings::VipsSourceCustom,
56                            buffer: *mut c_void,
57                            length: gint64,
58                            user_data: *mut c_void,
59                        ) -> gint64,
60                        unsafe extern "C" fn(),
61                    >(on_read),
62                ),
63                user_data,
64                Some(source_destroy),
65                0,
66            );
67
68            bindings::g_signal_connect_data(
69                source as *mut c_void,
70                c"seek".as_ptr(),
71                Some(
72                    std::mem::transmute::<
73                        unsafe extern "C" fn(
74                            source: *mut bindings::VipsSourceCustom,
75                            offset: gint64,
76                            whence: c_int,
77                            user_data: *mut c_void,
78                        ) -> gint64,
79                        unsafe extern "C" fn(),
80                    >(on_seek),
81                ),
82                user_data,
83                None,
84                0,
85            );
86
87            Ok(
88                VipsSource {
89                    ctx: source as *mut bindings::VipsSource,
90                },
91            )
92        }
93    }
94
95    /// Create an source attached to a file descriptor. descriptor is closed with close() when source is finalized.
96    pub fn new_from_descriptor(descriptor: i32) -> Result<VipsSource> {
97        unsafe {
98            let res = bindings::vips_source_new_from_descriptor(descriptor);
99            result_cond(
100                !res.is_null(),
101                VipsSource {
102                    ctx: res,
103                },
104                Error::InitializationError(
105                    "Could not initialise VipsSource from descriptor".to_string(),
106                ),
107            )
108        }
109    }
110
111    /// Create a source attached to a file.
112    pub fn new_from_file<P: AsRef<Path>>(filename: P) -> Result<VipsSource> {
113        unsafe {
114            let filename_c_str = path_to_cstring(filename)?;
115            let res = bindings::vips_source_new_from_file(filename_c_str.as_ptr());
116            result_cond(
117                !res.is_null(),
118                VipsSource {
119                    ctx: res,
120                },
121                Error::InitializationError("Could not initialise VipsSource from file".to_string()),
122            )
123        }
124    }
125
126    /// Create a source attached to an area of memory. You must not free data while the source is active.
127    pub fn new_from_memory(buffer: &[u8]) -> Result<VipsSource> {
128        unsafe {
129            let res = bindings::vips_source_new_from_memory(
130                buffer.as_ptr() as *const c_void,
131                buffer.len(),
132            );
133            result_cond(
134                !res.is_null(),
135                VipsSource {
136                    ctx: res,
137                },
138                Error::InitializationError(
139                    "Could not initialise VipsSource from memory".to_string(),
140                ),
141            )
142        }
143    }
144
145    /// Create a source from an option string.
146    pub fn new_from_options(option_str: &str) -> Result<VipsSource> {
147        unsafe {
148            let options = new_c_string(option_str)?;
149            let res = bindings::vips_source_new_from_options(options.as_ptr());
150            result_cond(
151                !res.is_null(),
152                VipsSource {
153                    ctx: res,
154                },
155                Error::InitializationError(
156                    "Could not initialise VipsSource from options".to_string(),
157                ),
158            )
159        }
160    }
161
162    /// Minimise the source. As many resources as can be safely removed are removed.
163    pub fn minimise(&mut self) {
164        unsafe {
165            bindings::vips_source_minimise(self.ctx);
166        }
167    }
168
169    /// Restore the source after minimisation. This is called at the start of every source method, so loaders should not usually need this.
170    pub fn unminimise(&mut self) -> Result<()> {
171        unsafe {
172            let result = bindings::vips_source_unminimise(self.ctx);
173            utils::result(
174                result,
175                (),
176                Error::OperationError("Error on vips unminimise".to_string()),
177            )
178        }
179    }
180
181    /// Signal the end of header read and the start of the pixel decode phase. After this, you can no longer seek on this source.
182    pub fn decode(&mut self) -> Result<()> {
183        unsafe {
184            let result = bindings::vips_source_decode(self.ctx);
185            utils::result(
186                result,
187                (),
188                Error::OperationError("Error on vips decode".to_string()),
189            )
190        }
191    }
192
193    /// Read up to length bytes from source and store the bytes in buffer.
194    pub fn read(&mut self, length: u64) -> Result<Vec<u8>> {
195        unsafe {
196            let mut bytes = vec![0u8; length as usize];
197            let result = bindings::vips_source_read(
198                self.ctx,
199                bytes.as_mut_ptr() as *mut c_void,
200                length as usize,
201            );
202            result_cond(
203                result != -1,
204                bytes,
205                Error::OperationError("Error on vips_source_read".to_string()),
206            )
207        }
208    }
209
210    /// Whether the source can be efficiently mapped into memory.
211    pub fn is_mappable(&self) -> bool {
212        unsafe { bindings::vips_source_is_mappable(self.ctx) == 1 }
213    }
214
215    /// Map the source entirely into memory and return a pointer to the start.
216    pub fn map(&self) -> Result<Vec<u8>> {
217        unsafe {
218            let mut length = 0;
219            let result = bindings::vips_source_map(
220                self.ctx,
221                &mut length,
222            );
223            safe_result_cond(
224                !result.is_null(),
225                || {
226                    std::slice::from_raw_parts(
227                        result as *const u8,
228                        length,
229                    )
230                    .to_vec()
231                },
232                Error::OperationError("Error on vips map".to_string()),
233            )
234        }
235    }
236
237    /// Move the file read position.
238    pub fn seek(&mut self, offset: i64, whence: i32) -> Result<i64> {
239        unsafe {
240            let result = bindings::vips_source_seek(
241                self.ctx,
242                offset,
243                whence,
244            );
245            result_cond(
246                result != -1,
247                result,
248                Error::OperationError("Error on vips seek".to_string()),
249            )
250        }
251    }
252
253    /// Rewind the source to the start.
254    pub fn rewind(&mut self) -> Result<()> {
255        unsafe {
256            let result = bindings::vips_source_rewind(self.ctx);
257            result_cond(
258                result != -1,
259                (),
260                Error::OperationError("Error on vips rewind".to_string()),
261            )
262        }
263    }
264
265    /// Return the length in bytes of the source.
266    pub fn length(&self) -> Result<i64> {
267        unsafe {
268            let result = bindings::vips_source_length(self.ctx);
269            result_cond(
270                result != -1,
271                result,
272                Error::OperationError("Error on vips length".to_string()),
273            )
274        }
275    }
276
277    // VipsConnection for VipsSource
278    pub fn filename(&self) -> Option<String> {
279        unsafe { VipsConnection::filename(&mut (*self.ctx).parent_object) }
280    }
281
282    // VipsConnection for VipsSource
283    pub fn nick(&self) -> Option<String> {
284        unsafe { VipsConnection::nick(&mut (*self.ctx).parent_object) }
285    }
286}
287
288unsafe extern "C" fn on_read(
289    _: *mut bindings::VipsSourceCustom,
290    buffer: *mut c_void,
291    length: gint64,
292    user_data: *mut c_void,
293) -> gint64 {
294    let ctx = &mut *(user_data as *mut SourceContext);
295
296    let slice = std::slice::from_raw_parts_mut(
297        buffer as *mut u8,
298        length as usize,
299    );
300
301    match ctx
302        .reader
303        .read(slice)
304    {
305        Ok(n) => n as gint64,
306        Err(_) => -1,
307    }
308}
309
310unsafe extern "C" fn on_seek(
311    _: *mut bindings::VipsSourceCustom,
312    offset: gint64,
313    whence: c_int,
314    user_data: *mut c_void,
315) -> gint64 {
316    let ctx = &mut *(user_data as *mut SourceContext);
317
318    let pos = match whence {
319        0 => SeekFrom::Start(offset as u64),
320        1 => SeekFrom::Current(offset),
321        2 => SeekFrom::End(offset),
322        _ => return -1,
323    };
324
325    match ctx
326        .reader
327        .seek(pos)
328    {
329        Ok(n) => n as gint64,
330        Err(_) => -1,
331    }
332}
333
334unsafe extern "C" fn source_destroy(data: *mut c_void, _: *mut bindings::_GClosure) {
335    let _ = Box::from_raw(data as *mut SourceContext);
336}
337
338impl Drop for VipsSource {
339    fn drop(&mut self) {
340        unsafe {
341            if !self
342                .ctx
343                .is_null()
344            {
345                bindings::g_object_unref(self.ctx as *mut c_void);
346            }
347        }
348    }
349}