vips/image/
mod.rs

1use crate::{take_vips_error, Error, Result, VipsInterpolate};
2use std::ffi::CString;
3use std::marker::PhantomData;
4use std::os::raw::{c_char, c_int, c_void};
5use std::ptr::{null, null_mut};
6use vips_sys::{VipsBandFormat, VipsCombineMode, VipsDirection, VipsKernel, VipsSize};
7
8/// Representation of a libvips image.
9/// This struct wraps a raw pointer to a `VipsImage` from the libvips C library.
10/// It provides methods for creating, manipulating, and destroying images.
11/// # Lifetimes
12/// The `'a` lifetime parameter ensures that the `VipsImage` does not outlive any data it references.
13/// This is particularly important for images created from memory buffers, where the buffer must remain valid
14/// for the lifetime of the `VipsImage`.
15/// # Memory Management
16/// The `VipsImage` struct implements the `Drop` trait to automatically unreference the underlying
17/// libvips image when the `VipsImage` instance goes out of scope. This helps prevent memory leaks
18/// when working with images in Rust.
19///
20/// # Thread Safety
21/// The `VipsImage` struct is not inherently thread-safe. Users must ensure that instances are not
22/// accessed concurrently from multiple threads unless proper synchronization is implemented.
23/// # Error Handling
24/// Many methods on `VipsImage` return a `Result` type to handle errors that may occur during
25/// image operations. Users should handle these errors appropriately in their code.
26///
27/// # Safety Note
28/// The `VipsImage` struct contains a raw pointer to a libvips image.
29/// The user must ensure that the pointer is valid and that the image is properly managed.
30/// The `Drop` implementation will unreference the image when the `VipsImage` instance is dropped.
31///
32/// # Example
33/// ```no_run
34/// use vips::*;
35///
36/// fn main() -> Result<()> {
37///     let _instance = VipsInstance::new("app_test", true)?;
38///     let img = VipsImage::from_file("input.jpg")?;
39///     let thumb = img.thumbnail(100, 100, VipsSize::VIPS_SIZE_BOTH)?;
40///     thumb.write_to_file("thumb.jpg")?;
41///     Ok(())
42/// }
43/// ```
44pub struct VipsImage<'a> {
45    pub c: *mut vips_sys::VipsImage,
46    marker: PhantomData<&'a ()>,
47}
48
49impl<'a> Drop for VipsImage<'a> {
50    fn drop(&mut self) {
51        unsafe {
52            vips_sys::g_object_unref(self.c as *mut c_void);
53        }
54    }
55}
56
57/// Callback function to free memory after a VipsImage created from memory is closed.
58///
59/// # Safety
60/// This function is called by libvips when the image is closed.
61/// The `user_data` pointer must be a valid pointer to a `Box<Box<[u8]>>`.
62///
63/// # Arguments
64/// * `_ptr` - Pointer to the VipsImage (unused)
65/// * `user_data` - Pointer to the user data (Boxed buffer)
66///
67pub unsafe extern "C" fn image_postclose(_ptr: *mut vips_sys::VipsImage, user_data: *mut c_void) {
68    let b: Box<Box<[u8]>> = Box::from_raw(user_data as *mut Box<[u8]>);
69    drop(b);
70}
71
72impl<'a> VipsImage<'a> {
73    //
74    // ─── CONSTRUCTORS ───────────────────────────────────────────────────────────────
75    //
76
77    /// Create a new empty VipsImage.
78    ///
79    /// # Errors
80    /// Returns an error if the image creation fails.
81    ///
82    /// # Example
83    /// ```no_run
84    /// use vips::*;
85    ///
86    /// fn main() -> Result<()> {
87    ///     let _instance = VipsInstance::new("app_test", true)?;
88    ///     let img = VipsImage::new()?;
89    ///     Ok(())
90    /// }
91    /// ```
92    pub fn new() -> Result<VipsImage<'a>> {
93        let c = unsafe { vips_sys::vips_image_new() };
94        result(c)
95    }
96
97    /// Create a new empty VipsImage in memory.
98    ///
99    /// # Errors
100    /// Returns an error if the image creation fails.
101    ///
102    /// # Example
103    /// ```no_run
104    /// use vips::*;
105    ///
106    /// fn main() -> Result<()> {
107    ///     let _instance = VipsInstance::new("app_test", true)?;
108    ///     let img = VipsImage::new_memory()?;
109    ///     Ok(())
110    /// }
111    /// ```
112    pub fn new_memory() -> Result<VipsImage<'a>> {
113        let c = unsafe { vips_sys::vips_image_new_memory() };
114        result(c)
115    }
116
117    /// Create a VipsImage from a file.
118    ///
119    /// # Arguments
120    /// * `path` - The file path to load the image from.
121    ///
122    /// # Errors
123    /// Returns an error if the image loading fails.
124    ///
125    /// # Returns
126    /// A `Result` containing the loaded `VipsImage` or an error.
127    ///
128    /// # Example
129    /// ```no_run
130    /// use vips::*;
131    ///
132    /// fn main() -> Result<()> {
133    ///     let _instance = VipsInstance::new("app_test", true)?;
134    ///     let img = VipsImage::from_file("input.jpg")?;
135    ///     Ok(())
136    /// }
137    /// ```
138    pub fn from_file<S: Into<Vec<u8>>>(path: S) -> Result<VipsImage<'a>> {
139        let path = path.into();
140        let path = std::str::from_utf8(&path)
141            .map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
142        let path =
143            CString::new(path).map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
144        unsafe {
145            let ptr = vips_sys::vips_image_new_from_file(path.as_ptr(), null() as *const c_char);
146            result(ptr)
147        }
148    }
149
150    /// Create a VipsImage from a memory buffer.
151    ///
152    /// # Arguments
153    /// * `buf` - The buffer containing the image data.
154    /// * `width` - The width of the image.
155    /// * `height` - The height of the image.
156    /// * `bands` - The number of bands (channels) in the image.
157    /// * `format` - The band format of the image.
158    ///
159    /// # Errors
160    /// Returns an error if the image creation fails.
161    ///
162    /// # Example
163    /// ```no_run
164    /// use vips::*;
165    ///
166    /// fn main() -> Result<()> {
167    ///     let _instance = VipsInstance::new("app_test", true)?;
168    ///     let img_data: Vec<u8> = vec![/* image data */];
169    ///     let img = VipsImage::from_memory(img_data, 800, 600, 3, VipsBandFormat::VIPS_FORMAT_UCHAR)?;
170    ///     Ok(())
171    /// }
172    /// ```
173    ///
174    pub fn from_memory(
175        buf: Vec<u8>,
176        width: u32,
177        height: u32,
178        bands: u8,
179        format: VipsBandFormat,
180    ) -> Result<VipsImage<'a>> {
181        let b: Box<[_]> = buf.into_boxed_slice();
182        let c = unsafe {
183            vips_sys::vips_image_new_from_memory(
184                b.as_ptr() as *const c_void,
185                b.len(),
186                width as i32,
187                height as i32,
188                bands as i32,
189                format,
190            )
191        };
192
193        let bb: Box<Box<_>> = Box::new(b);
194        let raw: *mut c_void = Box::into_raw(bb) as *mut c_void;
195
196        unsafe {
197            let callback: unsafe extern "C" fn() =
198                std::mem::transmute(image_postclose as *const ());
199            vips_sys::g_signal_connect_data(
200                c as *mut c_void,
201                c"postclose".as_ptr(),
202                Some(callback),
203                raw,
204                None,
205                vips_sys::GConnectFlags::G_CONNECT_AFTER,
206            );
207        };
208
209        result(c)
210    }
211
212    /// Create a VipsImage from a memory buffer reference.
213    ///
214    /// # Arguments
215    /// * `buf` - The buffer slice containing the image data.
216    /// * `width` - The width of the image.
217    /// * `height` - The height of the image.
218    /// * `bands` - The number of bands (channels) in the image.
219    /// * `format` - The band format of the image.
220    ///
221    /// # Returns
222    /// A `Result` containing the created `VipsImage` or an error.
223    ///
224    /// # Errors
225    /// Returns an error if the image creation fails.
226    ///
227    /// # Example
228    /// ```no_run
229    /// use vips::*;
230    ///
231    /// fn main() -> Result<()> {
232    ///     let _instance = VipsInstance::new("app_test", true)?;
233    ///     let img_data: &[u8] = &[/* image data */];
234    ///     let img = VipsImage::from_memory_reference(img_data, 800, 600, 3, VipsBandFormat::VIPS_FORMAT_UCHAR)?;
235    ///     Ok(())
236    /// }
237    /// ```
238    ///
239    pub fn from_memory_reference(
240        buf: &'a [u8],
241        width: u32,
242        height: u32,
243        bands: u8,
244        format: VipsBandFormat,
245    ) -> Result<VipsImage<'a>> {
246        let c = unsafe {
247            vips_sys::vips_image_new_from_memory(
248                buf.as_ptr() as *const c_void,
249                buf.len(),
250                width as i32,
251                height as i32,
252                bands as i32,
253                format,
254            )
255        };
256
257        result(c)
258    }
259
260    /// Create a VipsImage from a byte buffer.
261    ///
262    /// # Arguments
263    /// * `buf` - The buffer slice containing the image data.
264    ///
265    /// # Returns
266    /// A `Result` containing the created `VipsImage` or an error.
267    ///
268    /// # Errors
269    /// Returns an error if the image creation fails.
270    ///
271    /// # Example
272    /// ```no_run
273    /// use vips::*;
274    ///
275    /// fn main() -> Result<()> {
276    ///     let _instance = VipsInstance::new("app_test", true)?;
277    ///     let img_data: &[u8] = &[/* image data */];
278    ///     let img = VipsImage::from_buffer(img_data)?;
279    ///     Ok(())
280    /// }
281    /// ```
282    pub fn from_buffer(buf: &'a [u8]) -> Result<VipsImage<'a>> {
283        let c = unsafe {
284            vips_sys::vips_image_new_from_buffer(
285                buf.as_ptr() as *const c_void,
286                buf.len(),
287                null(),
288                null() as *const c_char,
289            )
290        };
291
292        result(c)
293    }
294
295    //
296    // ─── DRAW ───────────────────────────────────────────────────────────────────────
297    //
298
299    /// Draw a rectangle on the image.
300    ///
301    /// # Arguments
302    /// * `ink` - The color to use for drawing, as a slice of f64 values.
303    /// * `left` - The left coordinate of the rectangle.
304    /// * `top` - The top coordinate of the rectangle.
305    /// * `width` - The width of the rectangle.
306    /// * `height` - The height of the rectangle.
307    ///
308    /// # Returns
309    /// A `Result` indicating success or failure.
310    ///
311    /// # Errors
312    /// Returns an error if the drawing operation fails.
313    ///
314    /// # Example
315    /// ```no_run
316    /// use vips::*;
317    ///
318    /// fn main() -> Result<()> {
319    ///     let _instance = VipsInstance::new("app_test", true)?;
320    ///     let mut img = VipsImage::from_file("input.jpg")?;
321    ///     img.draw_rect(&[255.0, 0.0, 0.0], 10, 10, 100, 50)?;
322    ///     img.write_to_file("output.jpg")?;
323    ///     Ok(())
324    /// }
325    /// ```
326    pub fn draw_rect(
327        &mut self,
328        ink: &[f64],
329        left: u32,
330        top: u32,
331        width: u32,
332        height: u32,
333    ) -> Result<()> {
334        let ret = unsafe {
335            vips_sys::vips_draw_rect(
336                self.c as *mut vips_sys::VipsImage,
337                ink.as_ptr() as *mut f64,
338                ink.len() as i32,
339                left as i32,
340                top as i32,
341                width as i32,
342                height as i32,
343                null() as *const c_char,
344            )
345        };
346        result_draw(ret)
347    }
348    pub fn draw_rect1(
349        &mut self,
350        ink: f64,
351        left: u32,
352        top: u32,
353        width: u32,
354        height: u32,
355    ) -> Result<()> {
356        let ret = unsafe {
357            vips_sys::vips_draw_rect1(
358                self.c as *mut vips_sys::VipsImage,
359                ink,
360                left as i32,
361                top as i32,
362                width as i32,
363                height as i32,
364                null() as *const c_char,
365            )
366        };
367        result_draw(ret)
368    }
369    pub fn draw_point(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
370        let ret = unsafe {
371            vips_sys::vips_draw_point(
372                self.c as *mut vips_sys::VipsImage,
373                ink.as_ptr() as *mut f64,
374                ink.len() as i32,
375                x,
376                y,
377                null() as *const c_char,
378            )
379        };
380        result_draw(ret)
381    }
382    pub fn draw_point1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
383        let ret = unsafe {
384            vips_sys::vips_draw_point1(
385                self.c as *mut vips_sys::VipsImage,
386                ink,
387                x,
388                y,
389                null() as *const c_char,
390            )
391        };
392        result_draw(ret)
393    }
394    pub fn draw_image(
395        &mut self,
396        img: &VipsImage,
397        x: i32,
398        y: i32,
399        mode: VipsCombineMode,
400    ) -> Result<()> {
401        let ret = unsafe {
402            vips_sys::vips_draw_image(
403                self.c as *mut vips_sys::VipsImage,
404                img.c as *mut vips_sys::VipsImage,
405                x,
406                y,
407                c"mode".as_ptr(),
408                mode,
409                null() as *const c_char,
410            )
411        };
412        result_draw(ret)
413    }
414    pub fn draw_mask(&mut self, ink: &[f64], mask: &VipsImage, x: i32, y: i32) -> Result<()> {
415        let ret = unsafe {
416            vips_sys::vips_draw_mask(
417                self.c as *mut vips_sys::VipsImage,
418                ink.as_ptr() as *mut f64,
419                ink.len() as i32,
420                mask.c as *mut vips_sys::VipsImage,
421                x,
422                y,
423                null() as *const c_char,
424            )
425        };
426        result_draw(ret)
427    }
428    pub fn draw_mask1(&mut self, ink: f64, mask: &VipsImage, x: i32, y: i32) -> Result<()> {
429        let ret = unsafe {
430            vips_sys::vips_draw_mask1(
431                self.c as *mut vips_sys::VipsImage,
432                ink,
433                mask.c as *mut vips_sys::VipsImage,
434                x,
435                y,
436                null() as *const c_char,
437            )
438        };
439        result_draw(ret)
440    }
441    pub fn draw_line(&mut self, ink: &[f64], x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
442        let ret = unsafe {
443            vips_sys::vips_draw_line(
444                self.c as *mut vips_sys::VipsImage,
445                ink.as_ptr() as *mut f64,
446                ink.len() as i32,
447                x1,
448                y1,
449                x2,
450                y2,
451                null() as *const c_char,
452            )
453        };
454        result_draw(ret)
455    }
456    pub fn draw_line1(&mut self, ink: f64, x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
457        let ret = unsafe {
458            vips_sys::vips_draw_line1(
459                self.c as *mut vips_sys::VipsImage,
460                ink,
461                x1,
462                y1,
463                x2,
464                y2,
465                null() as *const c_char,
466            )
467        };
468        result_draw(ret)
469    }
470    pub fn draw_circle(&mut self, ink: &[f64], cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
471        let ret = unsafe {
472            vips_sys::vips_draw_circle(
473                self.c as *mut vips_sys::VipsImage,
474                ink.as_ptr() as *mut f64,
475                ink.len() as i32,
476                cx,
477                cy,
478                r,
479                c"fill".as_ptr(),
480                fill as i32,
481                null() as *const c_char,
482            )
483        };
484        result_draw(ret)
485    }
486    pub fn draw_circle1(&mut self, ink: f64, cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
487        let ret = unsafe {
488            vips_sys::vips_draw_circle1(
489                self.c as *mut vips_sys::VipsImage,
490                ink,
491                cx,
492                cy,
493                r,
494                c"fill".as_ptr(),
495                fill as i32,
496                null() as *const c_char,
497            )
498        };
499        result_draw(ret)
500    }
501    pub fn draw_flood(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
502        let ret = unsafe {
503            vips_sys::vips_draw_flood(
504                self.c as *mut vips_sys::VipsImage,
505                ink.as_ptr() as *mut f64,
506                ink.len() as i32,
507                x,
508                y,
509                null() as *const c_char,
510            )
511        };
512        result_draw(ret)
513    }
514    pub fn draw_flood1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
515        let ret = unsafe {
516            vips_sys::vips_draw_flood1(
517                self.c as *mut vips_sys::VipsImage,
518                ink,
519                x,
520                y,
521                null() as *const c_char,
522            )
523        };
524        result_draw(ret)
525    }
526    pub fn draw_smudge(&mut self, left: u32, top: u32, width: u32, height: u32) -> Result<()> {
527        let ret = unsafe {
528            vips_sys::vips_draw_smudge(
529                self.c as *mut vips_sys::VipsImage,
530                left as i32,
531                top as i32,
532                width as i32,
533                height as i32,
534                null() as *const c_char,
535            )
536        };
537        result_draw(ret)
538    }
539
540    //
541    // ─── MOSAIC ─────────────────────────────────────────────────────────────────────
542    //
543
544    pub fn merge(
545        &self,
546        another: &VipsImage,
547        direction: VipsDirection,
548        dx: i32,
549        dy: i32,
550        mblend: Option<i32>,
551    ) -> Result<VipsImage<'a>> {
552        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
553        let ret = unsafe {
554            vips_sys::vips_merge(
555                self.c as *mut vips_sys::VipsImage,
556                another.c as *mut vips_sys::VipsImage,
557                &mut out_ptr,
558                direction,
559                dx,
560                dy,
561                c"mblend".as_ptr(),
562                mblend.unwrap_or(-1),
563                null() as *const c_char,
564            )
565        };
566        result_with_ret(out_ptr, ret)
567    }
568
569    #[allow(clippy::too_many_arguments)]
570    pub fn mosaic(
571        &self,
572        sec: &VipsImage,
573        direction: VipsDirection,
574        xref: i32,
575        yref: i32,
576        xsec: i32,
577        ysec: i32,
578        bandno: Option<i32>,
579        hwindow: Option<i32>,
580        harea: Option<i32>,
581        mblend: Option<i32>,
582    ) -> Result<VipsImage<'_>> {
583        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
584        let ret = unsafe {
585            vips_sys::vips_mosaic(
586                self.c as *mut vips_sys::VipsImage,
587                sec.c as *mut vips_sys::VipsImage,
588                &mut out_ptr,
589                direction,
590                xref,
591                yref,
592                xsec,
593                ysec,
594                c"bandno".as_ptr(),
595                bandno.unwrap_or(0),
596                c"hwindow".as_ptr(),
597                hwindow.unwrap_or(1),
598                c"harea".as_ptr(),
599                harea.unwrap_or(1),
600                c"mblend".as_ptr(),
601                mblend.unwrap_or(-1),
602                null() as *const c_char,
603            )
604        };
605        result_with_ret(out_ptr, ret)
606    }
607
608    #[allow(clippy::too_many_arguments)]
609    pub fn mosaic1(
610        &self,
611        sec: &VipsImage,
612        direction: VipsDirection,
613        xr1: i32,
614        yr1: i32,
615        xs1: i32,
616        ys1: i32,
617        xr2: i32,
618        yr2: i32,
619        xs2: i32,
620        ys2: i32,
621        search: Option<bool>,
622        hwindow: Option<i32>,
623        harea: Option<i32>,
624        interpolate: Option<VipsInterpolate>,
625        mblend: Option<i32>,
626        bandno: Option<i32>,
627    ) -> Result<VipsImage<'_>> {
628        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
629        let ret = unsafe {
630            match interpolate {
631                Some(interpolate) => vips_sys::vips_mosaic1(
632                    self.c,
633                    sec.c,
634                    &mut out_ptr,
635                    direction,
636                    xr1,
637                    yr1,
638                    xs1,
639                    ys1,
640                    xr2,
641                    yr2,
642                    xs2,
643                    ys2,
644                    c"search".as_ptr(),
645                    search.unwrap_or(false) as i32,
646                    c"hwindow".as_ptr(),
647                    hwindow.unwrap_or(1),
648                    c"harea".as_ptr(),
649                    harea.unwrap_or(1),
650                    c"interpolate".as_ptr(),
651                    interpolate.c,
652                    c"mblend".as_ptr(),
653                    mblend.unwrap_or(-1),
654                    c"bandno".as_ptr(),
655                    bandno.unwrap_or(0),
656                    null() as *const c_char,
657                ),
658                None => vips_sys::vips_mosaic1(
659                    self.c as *mut vips_sys::VipsImage,
660                    sec.c as *mut vips_sys::VipsImage,
661                    &mut out_ptr,
662                    direction,
663                    xr1,
664                    yr1,
665                    xs1,
666                    ys1,
667                    xr2,
668                    yr2,
669                    xs2,
670                    ys2,
671                    c"search".as_ptr(),
672                    search.unwrap_or(false) as i32,
673                    c"hwindow".as_ptr(),
674                    hwindow.unwrap_or(1),
675                    c"harea".as_ptr(),
676                    harea.unwrap_or(1),
677                    c"mblend".as_ptr(),
678                    mblend.unwrap_or(-1),
679                    c"bandno".as_ptr(),
680                    bandno.unwrap_or(0),
681                    null() as *const c_char,
682                ),
683            }
684        };
685        result_with_ret(out_ptr, ret)
686    }
687
688    #[allow(clippy::too_many_arguments)]
689    pub fn match_(
690        &self,
691        sec: &VipsImage,
692        xr1: i32,
693        yr1: i32,
694        xs1: i32,
695        ys1: i32,
696        xr2: i32,
697        yr2: i32,
698        xs2: i32,
699        ys2: i32,
700        search: Option<bool>,
701        hwindow: Option<i32>,
702        harea: Option<i32>,
703        interpolate: Option<VipsInterpolate>,
704    ) -> Result<VipsImage<'_>> {
705        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
706        let ret = unsafe {
707            match interpolate {
708                Some(interpolate) => vips_sys::vips_match(
709                    self.c as *mut vips_sys::VipsImage,
710                    sec.c as *mut vips_sys::VipsImage,
711                    &mut out_ptr,
712                    xr1,
713                    yr1,
714                    xs1,
715                    ys1,
716                    xr2,
717                    yr2,
718                    xs2,
719                    ys2,
720                    c"search".as_ptr(),
721                    search.unwrap_or(false) as i32,
722                    c"hwindow".as_ptr(),
723                    hwindow.unwrap_or(1),
724                    c"harea".as_ptr(),
725                    harea.unwrap_or(1),
726                    c"interpolate".as_ptr(),
727                    interpolate.c as *mut vips_sys::VipsInterpolate,
728                    null() as *const c_char,
729                ),
730                None => vips_sys::vips_match(
731                    self.c as *mut vips_sys::VipsImage,
732                    sec.c as *mut vips_sys::VipsImage,
733                    &mut out_ptr,
734                    xr1,
735                    yr1,
736                    xs1,
737                    ys1,
738                    xr2,
739                    yr2,
740                    xs2,
741                    ys2,
742                    c"search".as_ptr(),
743                    search.unwrap_or(false) as i32,
744                    c"hwindow".as_ptr(),
745                    hwindow.unwrap_or(1),
746                    c"harea".as_ptr(),
747                    harea.unwrap_or(1),
748                    null() as *const c_char,
749                ),
750            }
751        };
752        result_with_ret(out_ptr, ret)
753    }
754
755    pub fn globalbalance(
756        &self,
757        gamma: Option<f64>,
758        int_output: Option<bool>,
759    ) -> Result<VipsImage<'_>> {
760        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
761        let ret = unsafe {
762            vips_sys::vips_globalbalance(
763                self.c as *mut vips_sys::VipsImage,
764                &mut out_ptr,
765                c"gamma".as_ptr(),
766                gamma.unwrap_or(1.6),
767                c"int_output".as_ptr(),
768                int_output.unwrap_or(false) as i32,
769                null() as *const c_char,
770            )
771        };
772        result_with_ret(out_ptr, ret)
773    }
774
775    pub fn remosaic(&self, old_str: &str, new_str: &str) -> Result<VipsImage<'_>> {
776        let old_str = CString::new(old_str)
777            .map_err(|e| Error::InitFailed(format!("invalid old_str: {}", e)))?;
778        let new_str = CString::new(new_str)
779            .map_err(|e| Error::InitFailed(format!("invalid new_str: {}", e)))?;
780        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
781        let ret = unsafe {
782            vips_sys::vips_remosaic(
783                self.c as *mut vips_sys::VipsImage,
784                &mut out_ptr,
785                old_str.as_ptr(),
786                new_str.as_ptr(),
787                null() as *const c_char,
788            )
789        };
790        result_with_ret(out_ptr, ret)
791    }
792
793    //
794    // ─── PROPERTIES ─────────────────────────────────────────────────────────────────
795    //
796
797    #[allow(dead_code)]
798    fn width(&self) -> u32 {
799        unsafe { (*self.c).Xsize as u32 }
800    }
801
802    #[allow(dead_code)]
803    fn height(&self) -> u32 {
804        unsafe { (*self.c).Ysize as u32 }
805    }
806
807    //
808    // ─── RESIZE ─────────────────────────────────────────────────────────────────────
809    //
810
811    /// Create a thumbnail of the image.
812    ///
813    /// # Arguments
814    /// * `width` - The desired width of the thumbnail.
815    /// * `height` - The desired height of the thumbnail.
816    /// * `size` - The size mode for the thumbnail (e.g., `VIPS_SIZE_BOTH`, `VIPS_SIZE_UP`, etc.).
817    ///
818    /// # Returns
819    /// A `Result` containing the thumbnail `VipsImage` or an error.
820    ///
821    /// # Errors
822    /// Returns an error if the thumbnail creation fails.
823    ///
824    /// # Example
825    /// ```no_run
826    /// use vips::*;
827    ///
828    /// fn main() -> Result<()> {
829    ///     let _instance = VipsInstance::new("app_test", true)?;
830    ///     let img = VipsImage::from_file("input.jpg")?;
831    ///     let thumb = img.thumbnail(100, 100, VipsSize::VIPS_SIZE_BOTH)?;
832    ///     thumb.write_to_file("thumb.jpg")?;
833    ///     Ok(())
834    /// }
835    /// ```
836    pub fn thumbnail(&self, width: u32, height: u32, size: VipsSize) -> Result<VipsImage<'_>> {
837        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
838        unsafe {
839            vips_sys::vips_thumbnail_image(
840                self.c as *mut vips_sys::VipsImage,
841                &mut out_ptr,
842                width as i32,
843                c"height".as_ptr(),
844                height as i32,
845                c"size".as_ptr(),
846                size,
847                null() as *const c_char,
848            );
849        };
850        result(out_ptr)
851    }
852
853    /// Resize the image.
854    ///
855    /// # Arguments
856    /// * `scale` - The scaling factor for the horizontal dimension.
857    /// * `vscale` - Optional scaling factor for the vertical dimension. If not provided, it defaults to the value of `scale`.
858    /// * `kernel` - Optional kernel to use for resizing. If not provided, it defaults to `VIPS_KERNEL_LANCZOS3`.
859    ///
860    /// # Returns
861    /// A `Result` containing the resized `VipsImage` or an error.
862    ///
863    /// # Errors
864    /// Returns an error if the resizing operation fails.
865    ///
866    /// # Example
867    /// ```no_run
868    /// use vips::*;
869    ///
870    /// fn main() -> Result<()> {
871    ///     let _instance = VipsInstance::new("app_test", true)?;
872    ///     let img = VipsImage::from_file("input.jpg")?;
873    ///     let resized_img = img.resize(0.5, None, None)?;
874    ///     resized_img.write_to_file("resized.jpg")?;
875    ///     Ok(())
876    /// }
877    /// ```
878    pub fn resize(
879        &self,
880        scale: f64,
881        vscale: Option<f64>,
882        kernel: Option<VipsKernel>,
883    ) -> Result<VipsImage<'_>> {
884        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
885        let ret = unsafe {
886            vips_sys::vips_resize(
887                self.c as *mut vips_sys::VipsImage,
888                &mut out_ptr,
889                scale,
890                c"vscale".as_ptr(),
891                vscale.unwrap_or(scale),
892                c"kernel".as_ptr(),
893                kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
894                null() as *const c_char,
895            )
896        };
897        result_with_ret(out_ptr, ret)
898    }
899    #[allow(dead_code)]
900    fn resize_to_size(
901        &self,
902        width: u32,
903        height: Option<u32>,
904        kernel: Option<VipsKernel>,
905    ) -> Result<VipsImage<'_>> {
906        self.resize(
907            width as f64 / self.width() as f64,
908            height.map(|h| h as f64 / self.height() as f64),
909            kernel,
910        )
911    }
912
913    // low-level
914    // default: 2 * 1D lanczos3 (not recommended for shrink factor > 3)
915    // or other kernels
916    #[allow(dead_code)]
917    fn reduce(
918        &self,
919        hshrink: f64,
920        vshrink: f64,
921        kernel: Option<VipsKernel>,
922        centre: Option<bool>,
923    ) -> VipsImage<'_> {
924        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
925        let ret = unsafe {
926            vips_sys::vips_reduce(
927                self.c as *mut vips_sys::VipsImage,
928                &mut out_ptr,
929                hshrink,
930                vshrink,
931                c"kernel".as_ptr(),
932                kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
933                c"centre".as_ptr(),
934                centre.unwrap_or(false) as i32,
935                null() as *const c_char,
936            )
937        };
938        if ret == 0 {
939            VipsImage {
940                c: out_ptr,
941                marker: PhantomData,
942            }
943        } else {
944            panic!(
945                "{}",
946                take_vips_error().unwrap_or_else(|| { "Unknown error from libvips".to_string() })
947            )
948        }
949    }
950
951    #[allow(dead_code)]
952    fn shrink(&self) -> VipsImage<'_> {
953        // simple average of nxn -> 1/n size
954        // use a 2x2 box filter to average neighbouring pixels
955        self.reduce(2.0, 2.0, Some(VipsKernel::VIPS_KERNEL_LINEAR), Some(false))
956    }
957
958    //
959    // ─── IO ─────────────────────────────────────────────────────────────────────────
960    //
961
962    #[allow(dead_code)]
963    fn jpegsave<S: Into<Vec<u8>>>(&mut self, path: S) -> Result<()> {
964        let path = path.into();
965        let path = std::str::from_utf8(&path)
966            .map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
967        let path =
968            CString::new(path).map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
969        let ret = unsafe {
970            vips_sys::vips_jpegsave(
971                self.c as *mut vips_sys::VipsImage,
972                path.as_ptr(),
973                null() as *const c_char,
974            )
975        };
976        result_draw(ret)
977    }
978
979    pub fn write_to_file<S: Into<Vec<u8>>>(&self, path: S) -> Result<()> {
980        let path = path.into();
981        let path = std::str::from_utf8(&path)
982            .map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
983        let path =
984            CString::new(path).map_err(|e| Error::InitFailed(format!("invalid path: {}", e)))?;
985        let ret = unsafe {
986            vips_sys::vips_image_write_to_file(
987                self.c as *mut vips_sys::VipsImage,
988                path.as_ptr(),
989                null() as *const c_char,
990            )
991        };
992        result_draw(ret)
993    }
994
995    //
996    // ─── CONVERT ────────────────────────────────────────────────────────────────────
997    //
998
999    #[allow(dead_code)]
1000    fn to_vec(&self) -> Vec<u8> {
1001        unsafe {
1002            let mut result_size: usize = 0;
1003            let ptr = vips_sys::vips_image_write_to_memory(self.c, &mut result_size as *mut usize)
1004                as *mut u8;
1005            if ptr.is_null() {
1006                return Vec::new();
1007            }
1008            let slice = std::slice::from_raw_parts(ptr as *const u8, result_size);
1009            let vec = slice.to_vec();
1010            vips_sys::g_free(ptr as *mut c_void); // Release with GLib
1011            vec
1012        }
1013    }
1014}
1015
1016fn result<'a>(ptr: *mut vips_sys::VipsImage) -> Result<VipsImage<'a>> {
1017    if ptr.is_null() {
1018        Err(Error::Vips(take_vips_error().unwrap_or_else(|| {
1019            "Unknown error from libvips".to_string()
1020        })))
1021    } else {
1022        Ok(VipsImage {
1023            c: ptr,
1024            marker: PhantomData,
1025        })
1026    }
1027}
1028
1029fn result_with_ret<'a>(ptr: *mut vips_sys::VipsImage, ret: c_int) -> Result<VipsImage<'a>> {
1030    match ret {
1031        0 => Ok(VipsImage {
1032            c: ptr,
1033            marker: PhantomData,
1034        }),
1035        -1 => {
1036            Err(Error::Vips(take_vips_error().unwrap_or_else(|| {
1037                "Unknown error from libvips".to_string()
1038            })))
1039        }
1040        _ => Err(Error::Vips("Unknown error from libvips".to_string())),
1041    }
1042}
1043
1044fn result_draw(ret: c_int) -> Result<()> {
1045    match ret {
1046        0 => Ok(()),
1047        -1 => {
1048            Err(Error::Vips(take_vips_error().unwrap_or_else(|| {
1049                "Unknown error from libvips".to_string()
1050            })))
1051        }
1052        _ => Err(Error::Vips("Unknown error from libvips".to_string())),
1053    }
1054}