rs_vips/
connection.rs

1// (c) Copyright 2019-2025 OLX
2// (c) Copyright 2025 mrdkprj
3use crate::{
4    bindings,
5    error::Error,
6    region::VipsBlob,
7    utils::{self, vips_source_result, vips_target_result},
8    Result,
9};
10use std::{
11    borrow::Cow,
12    ffi::{c_void, CStr, CString},
13    ptr::null_mut,
14};
15
16#[derive(Debug, Clone)]
17pub struct VipsConnection {
18    pub(crate) ctx: *mut bindings::VipsConnection,
19}
20
21#[derive(Debug, Clone)]
22pub struct VipsSource {
23    pub(crate) ctx: *mut bindings::VipsSource,
24}
25
26#[derive(Debug, Clone)]
27pub struct VipsTarget {
28    pub(crate) ctx: *mut bindings::VipsTarget,
29}
30
31impl VipsConnection {
32    pub fn connection_filename(&self) -> Option<String> {
33        unsafe {
34            let result = bindings::vips_connection_filename(self.ctx);
35            if result.is_null() {
36                None
37            } else {
38                let cstr = CStr::from_ptr(result);
39                match cstr.to_string_lossy() {
40                    Cow::Borrowed(slice) => Some(slice.to_string()),
41                    Cow::Owned(string) => Some(string),
42                }
43            }
44        }
45    }
46
47    pub fn connection_nick(&self) -> Option<String> {
48        unsafe {
49            let result = bindings::vips_connection_nick(self.ctx);
50            if result.is_null() {
51                None
52            } else {
53                let cstr = CStr::from_ptr(result);
54                match cstr.to_string_lossy() {
55                    Cow::Borrowed(slice) => Some(slice.to_string()),
56                    Cow::Owned(string) => Some(string),
57                }
58            }
59        }
60    }
61}
62
63impl VipsSource {
64    /// Create an source attached to a file descriptor. descriptor is closed with close() when source is finalized.
65    pub fn new_from_descriptor(descriptor: i32) -> Result<Self> {
66        unsafe {
67            let res = bindings::vips_source_new_from_descriptor(descriptor);
68            vips_source_result(
69                res,
70                Error::InitializationError(
71                    "Could not initialise VipsSource from descriptor".to_string(),
72                ),
73            )
74        }
75    }
76
77    /// Create a source attached to a file.
78    pub fn new_from_file(filename: &str) -> Result<Self> {
79        unsafe {
80            let f = utils::new_c_string(filename)?;
81            let res = bindings::vips_source_new_from_file(f.as_ptr());
82            vips_source_result(
83                res,
84                Error::InitializationError("Could not initialise VipsSource from file".to_string()),
85            )
86        }
87    }
88
89    // Create a source attached to an area of memory.
90    // not sure if it this is safe
91    // should test before making it public
92    fn new_from_blob(blob: VipsBlob) -> Result<Self> {
93        unsafe {
94            let res = bindings::vips_source_new_from_blob(blob.ctx);
95            vips_source_result(
96                res,
97                Error::InitializationError("Could not initialise VipsSource from blob".to_string()),
98            )
99        }
100    }
101
102    /// Create a source attached to an area of memory. You must not free data while the source is active.
103    pub fn new_from_memory(buffer: &[u8]) -> Result<Self> {
104        unsafe {
105            let res = bindings::vips_source_new_from_memory(
106                buffer.as_ptr() as *const c_void,
107                buffer.len() as u64,
108            );
109            vips_source_result(
110                res,
111                Error::InitializationError(
112                    "Could not initialise VipsSource from memory".to_string(),
113                ),
114            )
115        }
116    }
117
118    /// Create a source from an option string.
119    pub fn new_from_options(option_str: &str) -> Result<Self> {
120        unsafe {
121            let options = utils::new_c_string(option_str)?;
122            let res = bindings::vips_source_new_from_options(options.as_ptr());
123            vips_source_result(
124                res,
125                Error::InitializationError(
126                    "Could not initialise VipsSource from options".to_string(),
127                ),
128            )
129        }
130    }
131
132    /// Minimise the source. As many resources as can be safely removed are removed.
133    pub fn minimise(&mut self) {
134        unsafe {
135            bindings::vips_source_minimise(self.ctx);
136        }
137    }
138
139    /// Restore the source after minimisation. This is called at the start of every source method, so loaders should not usually need this.
140    pub fn unminimise(&mut self) -> Result<()> {
141        unsafe {
142            let result = bindings::vips_source_unminimise(self.ctx);
143            utils::result(
144                result,
145                (),
146                Error::OperationError("Error on vips unminimise".to_string()),
147            )
148        }
149    }
150
151    /// Signal the end of header read and the start of the pixel decode phase. After this, you can no longer seek on this source.
152    pub fn decode(&mut self) -> Result<()> {
153        unsafe {
154            let result = bindings::vips_source_decode(self.ctx);
155            utils::result(
156                result,
157                (),
158                Error::OperationError("Error on vips decode".to_string()),
159            )
160        }
161    }
162
163    /// Read up to length bytes from source and store the bytes in buffer.
164    pub fn read(&mut self, length: u64) -> Result<Vec<u8>> {
165        unsafe {
166            let bytes: *mut c_void = null_mut();
167            let result = bindings::vips_source_read(
168                self.ctx,
169                bytes,
170                length,
171            );
172            if result != -1 {
173                let buffer = Vec::from_raw_parts(
174                    bytes as *mut u8,
175                    result as usize,
176                    result as usize,
177                );
178                Ok(buffer)
179            } else {
180                Err(Error::OperationError("Error on vips read".to_string()))
181            }
182        }
183    }
184
185    /// Whether the source can be efficiently mapped into memory.
186    pub fn is_mappable(&self) -> bool {
187        unsafe { bindings::vips_source_is_mappable(self.ctx) == 1 }
188    }
189
190    /// Move the file read position.
191    pub fn seek(&mut self, offset: i64, whence: i32) -> Result<i64> {
192        unsafe {
193            let result = bindings::vips_source_seek(
194                self.ctx,
195                offset,
196                whence,
197            );
198            if result == -1 {
199                Err(Error::OperationError("Error on vips seek".to_string()))
200            } else {
201                Ok(result)
202            }
203        }
204    }
205
206    /// Rewind the source to the start.
207    pub fn rewind(&mut self) -> Result<()> {
208        unsafe {
209            let result = bindings::vips_source_rewind(self.ctx);
210            if result == -1 {
211                Err(Error::OperationError("Error on vips rewind".to_string()))
212            } else {
213                Ok(())
214            }
215        }
216    }
217
218    /// Return the length in bytes of the source.
219    pub fn length(&self) -> Result<i64> {
220        unsafe {
221            let result = bindings::vips_source_length(self.ctx);
222            if result == -1 {
223                Err(Error::OperationError("Error on vips length".to_string()))
224            } else {
225                Ok(result)
226            }
227        }
228    }
229}
230
231impl<'a> VipsSource {
232    /// Map the source entirely into memory and return a pointer to the start.
233    pub fn map(&'a self) -> Result<&'a [u8]> {
234        unsafe {
235            let length: *mut u64 = null_mut();
236            let result = bindings::vips_source_map(
237                self.ctx,
238                length,
239            );
240            if length.is_null() {
241                Err(Error::OperationError("Error on vips map".to_string()))
242            } else {
243                let size = (*length)
244                    .try_into()
245                    .map_err(|_| Error::OperationError("Can't get size of array".to_string()))?;
246                Ok(
247                    std::slice::from_raw_parts(
248                        result as *mut u8,
249                        size,
250                    ),
251                )
252            }
253        }
254    }
255}
256
257impl VipsTarget {
258    /// Create a target attached to a file descriptor. descriptor is kept open until the target is finalized.
259    pub fn new_to_descriptor(descriptor: i32) -> Result<Self> {
260        unsafe {
261            let res = bindings::vips_target_new_to_descriptor(descriptor);
262            vips_target_result(
263                res,
264                Error::InitializationError(
265                    "Could not initialise VipsTarget from descriptor".to_string(),
266                ),
267            )
268        }
269    }
270
271    /// Create a target attached to a file.
272    pub fn new_to_file(filename: &str) -> Result<Self> {
273        unsafe {
274            let f = utils::new_c_string(filename)?;
275            let res = bindings::vips_target_new_to_file(f.as_ptr());
276            vips_target_result(
277                res,
278                Error::InitializationError("Could not initialise VipsTarget from file".to_string()),
279            )
280        }
281    }
282
283    /// Create a target which will write to a memory area. Read from blob to get memory.
284    pub fn new_to_memory() -> Result<Self> {
285        unsafe {
286            let res = bindings::vips_target_new_to_memory();
287            vips_target_result(
288                res,
289                Error::InitializationError(
290                    "Could not initialise VipsTarget from memory".to_string(),
291                ),
292            )
293        }
294    }
295
296    /// Call this at the end of write to make the target do any cleaning up.
297    pub fn end(self) {
298        unsafe {
299            bindings::vips_target_end(self.ctx);
300        }
301    }
302
303    /// Write a single character ch to target.
304    pub fn putc(&mut self, ch: char) -> Result<()> {
305        unsafe {
306            let res = bindings::vips_target_putc(
307                self.ctx,
308                ch as i32,
309            );
310            if res == -1 {
311                Err(Error::OperationError("Could not write to buffer".to_string()))
312            } else {
313                Ok(())
314            }
315        }
316    }
317
318    /// Write buffer to the output.
319    pub fn write(&mut self, buffer: &[u8]) -> Result<()> {
320        unsafe {
321            let res = bindings::vips_target_write(
322                self.ctx,
323                buffer.as_ptr() as *const c_void,
324                buffer.len() as u64,
325            );
326            if res == -1 {
327                Err(Error::OperationError("Could not write to buffer".to_string()))
328            } else {
329                Ok(())
330            }
331        }
332    }
333
334    /// Write str to target.
335    pub fn write_amp(&mut self, text: &str) -> Result<()> {
336        unsafe {
337            let cstr = CString::new(text)
338                .map_err(|_| Error::OperationError("Cannot initialize C string".to_string()))?;
339            let res = bindings::vips_target_write_amp(
340                self.ctx,
341                cstr.as_ptr(),
342            );
343            if res == -1 {
344                Err(Error::OperationError("Could not write to buffer".to_string()))
345            } else {
346                Ok(())
347            }
348        }
349    }
350
351    /// Write a null-terminated string to target.
352    pub fn writes(&mut self, text: &str) -> Result<()> {
353        unsafe {
354            let cstr = CString::new(text)
355                .map_err(|_| Error::OperationError("Cannot initialize C string".to_string()))?;
356            let res = bindings::vips_target_writes(
357                self.ctx,
358                cstr.as_ptr(),
359            );
360            if res == -1 {
361                Err(Error::OperationError("Could not write to buffer".to_string()))
362            } else {
363                Ok(())
364            }
365        }
366    }
367
368    pub fn get_blob(&self) -> VipsBlob {
369        unsafe {
370            VipsBlob {
371                ctx: (*self.ctx).blob,
372            }
373        }
374    }
375}
376
377impl Drop for VipsConnection {
378    fn drop(&mut self) {
379        unsafe {
380            if !self
381                .ctx
382                .is_null()
383            {
384                bindings::g_object_unref(self.ctx as *mut c_void);
385            }
386        }
387    }
388}
389
390impl Drop for VipsSource {
391    fn drop(&mut self) {
392        unsafe {
393            if !self
394                .ctx
395                .is_null()
396            {
397                bindings::g_object_unref(self.ctx as *mut c_void);
398            }
399        }
400    }
401}
402
403impl Drop for VipsTarget {
404    fn drop(&mut self) {
405        unsafe {
406            if !self
407                .ctx
408                .is_null()
409            {
410                bindings::g_object_unref(self.ctx as *mut c_void);
411            }
412        }
413    }
414}
415
416impl From<*mut bindings::VipsSource> for VipsSource {
417    fn from(value: *mut bindings::VipsSource) -> Self {
418        Self {
419            ctx: value,
420        }
421    }
422}
423
424impl From<*mut bindings::VipsTarget> for VipsTarget {
425    fn from(value: *mut bindings::VipsTarget) -> Self {
426        Self {
427            ctx: value,
428        }
429    }
430}