Skip to main content

vips/image/
mod.rs

1use crate::ffi;
2use crate::{Error, Result, VipsInterpolate, take_vips_error};
3use std::ffi::CString;
4use std::marker::PhantomData;
5use std::os::raw::{c_char, c_int, c_void};
6use std::path::Path;
7use std::ptr::{null, null_mut};
8use vips_sys::{
9    VipsBandFormat, VipsCombineMode, VipsCompassDirection, VipsDirection, VipsExtend,
10    VipsInteresting, VipsKernel, VipsSize,
11};
12
13#[cfg(feature = "v8_18")]
14mod v8_18;
15
16/// Number of colour bands used by an interpretation (libvips 8.18+).
17#[cfg(feature = "v8_18")]
18pub fn interpretation_bands(interpretation: vips_sys::VipsInterpretation) -> i32 {
19    // SAFETY: the C function accepts a value enum and returns a scalar.
20    unsafe { vips_sys::vips_interpretation_bands(interpretation) }
21}
22
23/// Convert a filesystem path to a C string without intermediate UTF-8 `Vec` allocations.
24fn path_to_cstring(path: &Path) -> Result<CString> {
25    #[cfg(unix)]
26    {
27        use std::os::unix::ffi::OsStrExt;
28        CString::new(path.as_os_str().as_bytes())
29            .map_err(|e| Error::InitFailed(format!("invalid path: {e}")))
30    }
31    #[cfg(not(unix))]
32    {
33        CString::new(path.to_string_lossy().as_bytes())
34            .map_err(|e| Error::InitFailed(format!("invalid path: {e}")))
35    }
36}
37
38fn validate_memory(
39    buf: &[u8],
40    width: u32,
41    height: u32,
42    bands: u8,
43    format: VipsBandFormat,
44) -> Result<()> {
45    use VipsBandFormat::*;
46    let (sample_bytes, alignment) = match format {
47        VIPS_FORMAT_UCHAR | VIPS_FORMAT_CHAR => (1, 1),
48        VIPS_FORMAT_USHORT | VIPS_FORMAT_SHORT => (2, 2),
49        VIPS_FORMAT_UINT | VIPS_FORMAT_INT | VIPS_FORMAT_FLOAT => (4, 4),
50        VIPS_FORMAT_COMPLEX => (8, 4),
51        VIPS_FORMAT_DOUBLE => (8, 8),
52        VIPS_FORMAT_DPCOMPLEX => (16, 8),
53        _ => return Err(Error::Other("invalid memory image band format".into())),
54    };
55    let required = (width as usize)
56        .checked_mul(height as usize)
57        .and_then(|n| n.checked_mul(bands as usize))
58        .and_then(|n| n.checked_mul(sample_bytes));
59    if width == 0
60        || height == 0
61        || bands == 0
62        || width > i32::MAX as u32
63        || height > i32::MAX as u32
64        || required.is_none_or(|n| n > buf.len())
65        || buf.as_ptr().align_offset(alignment) != 0
66    {
67        return Err(Error::Other(
68            "memory image requires positive i32 dimensions, enough bytes, and aligned samples"
69                .into(),
70        ));
71    }
72    Ok(())
73}
74
75/// Safe RAII wrapper around a libvips `VipsImage*`.
76///
77/// The raw pointer is private. Use the safe methods, or [`VipsImage::as_ptr`]
78/// when you must pass the handle into a lower-level FFI call.
79///
80/// # Lifetimes
81/// `'a` ties images created from borrowed buffers to that buffer's lifetime.
82/// Owned constructors (`from_file`, `from_memory`) use an unbound lifetime.
83///
84/// # Thread safety
85/// Not `Send`/`Sync`. Do not share an image across threads without external
86/// synchronization. libvips pipelines are otherwise process-global.
87///
88/// # Example
89/// ```no_run
90/// use vips::*;
91///
92/// fn main() -> Result<()> {
93///     let _instance = VipsInstance::new("app_test", true)?;
94///     let thumb = VipsImage::thumbnail_file("input.jpg", 100, 100, VipsSize::VIPS_SIZE_BOTH)?;
95///     thumb.write_to_file("thumb.jpg")?;
96///     Ok(())
97/// }
98/// ```
99pub struct VipsImage<'a> {
100    /// Unique ownership of a `VipsImage*`; always non-null.
101    c: std::ptr::NonNull<vips_sys::VipsImage>,
102    marker: PhantomData<&'a ()>,
103    /// Only explicitly owned memory can be modified without detaching first.
104    writable: bool,
105}
106
107impl<'a> Drop for VipsImage<'a> {
108    fn drop(&mut self) {
109        // SAFETY: exclusive refcount ownership; NonNull is never null.
110        unsafe { ffi::unref(self.c.as_ptr().cast()) };
111    }
112}
113
114/// Free the boxed pixel buffer after libvips closes a memory image.
115///
116/// # Safety
117/// Called by GObject with `user_data` from `g_signal_connect_data`; must be a
118/// `Box<Vec<u8>>` allocated by `from_memory`.
119unsafe extern "C" fn image_postclose(_ptr: *mut vips_sys::VipsImage, user_data: *mut c_void) {
120    if user_data.is_null() {
121        return;
122    }
123    // SAFETY: reconstructed exactly as boxed in `from_memory`.
124    let b: Box<Vec<u8>> = unsafe { Box::from_raw(user_data as *mut Vec<u8>) };
125    drop(b);
126}
127
128impl<'a> VipsImage<'a> {
129    /// Borrow the underlying libvips pointer for FFI.
130    ///
131    /// The pointer is valid while `self` is alive. Do not unref it.
132    #[inline]
133    pub fn as_ptr(&self) -> *mut vips_sys::VipsImage {
134        self.c.as_ptr()
135    }
136
137    /// Take ownership of a non-null `VipsImage*`.
138    ///
139    /// # Safety
140    /// `ptr` must be a uniquely owned, non-null `VipsImage*` whose refcount is
141    /// transferred to the returned value (`Drop` will unref it).
142    pub(crate) unsafe fn from_raw(ptr: *mut vips_sys::VipsImage) -> VipsImage<'a> {
143        let c = std::ptr::NonNull::new(ptr).expect("VipsImage::from_raw(null)");
144        VipsImage {
145            c,
146            marker: PhantomData,
147            writable: false,
148        }
149    }
150
151    /// Detach borrowed or shared pixels before an in-place draw. Unlike
152    /// vips_image_copy_memory, writing to a fresh memory image always copies
153    /// even when the input is already backed by a caller's immutable slice.
154    fn ensure_writable(&mut self) -> Result<()> {
155        if !self.writable {
156            let mut copy = Self::new_memory()?;
157            // SAFETY: both images are live; the destination owns its storage.
158            let ret = unsafe { vips_sys::vips_image_write(self.as_ptr(), copy.as_ptr()) };
159            ffi::ret_to_result(ret)?;
160            copy.writable = true;
161            *self = copy;
162        }
163        Ok(())
164    }
165
166    //
167    // ─── CONSTRUCTORS ───────────────────────────────────────────────────────────────
168    //
169
170    /// Create a new empty VipsImage.
171    ///
172    /// # Errors
173    /// Returns an error if the image creation fails.
174    ///
175    /// # Example
176    /// ```no_run
177    /// use vips::*;
178    ///
179    /// fn main() -> Result<()> {
180    ///     let _instance = VipsInstance::new("app_test", true)?;
181    ///     let img = VipsImage::new()?;
182    ///     Ok(())
183    /// }
184    /// ```
185    pub fn new() -> Result<VipsImage<'a>> {
186        // SAFETY: libvips allocates a fresh empty image or returns null.
187        let c = unsafe { vips_sys::vips_image_new() };
188        result(c)
189    }
190
191    /// Create a new empty VipsImage in memory.
192    ///
193    /// # Errors
194    /// Returns an error if the image creation fails.
195    ///
196    /// # Example
197    /// ```no_run
198    /// use vips::*;
199    ///
200    /// fn main() -> Result<()> {
201    ///     let _instance = VipsInstance::new("app_test", true)?;
202    ///     let img = VipsImage::new_memory()?;
203    ///     Ok(())
204    /// }
205    /// ```
206    pub fn new_memory() -> Result<VipsImage<'a>> {
207        // SAFETY: libvips allocates a fresh memory-backed image or returns null.
208        let c = unsafe { vips_sys::vips_image_new_memory() };
209        result(c)
210    }
211
212    /// Create a VipsImage from a file.
213    ///
214    /// # Arguments
215    /// * `path` - The file path to load the image from.
216    ///
217    /// # Errors
218    /// Returns an error if the image loading fails.
219    ///
220    /// # Returns
221    /// A `Result` containing the loaded `VipsImage` or an error.
222    ///
223    /// # Example
224    /// ```no_run
225    /// use vips::*;
226    ///
227    /// fn main() -> Result<()> {
228    ///     let _instance = VipsInstance::new("app_test", true)?;
229    ///     let img = VipsImage::from_file("input.jpg")?;
230    ///     Ok(())
231    /// }
232    /// ```
233    pub fn from_file(path: impl AsRef<Path>) -> Result<VipsImage<'a>> {
234        let path = path_to_cstring(path.as_ref())?;
235        unsafe {
236            let ptr = vips_sys::vips_image_new_from_file(path.as_ptr(), null() as *const c_char);
237            result(ptr)
238        }
239    }
240
241    /// Load a file with an explicit access hint. Use `VIPS_ACCESS_SEQUENTIAL`
242    /// for one-pass pipelines; random access is needed for arbitrary regions.
243    pub fn from_file_with_access(
244        path: impl AsRef<Path>,
245        access: vips_sys::VipsAccess,
246    ) -> Result<VipsImage<'a>> {
247        let path = path_to_cstring(path.as_ref())?;
248        // SAFETY: path is NUL-terminated and access is passed as a C enum.
249        let ptr = unsafe {
250            vips_sys::vips_image_new_from_file(
251                path.as_ptr(),
252                c"access".as_ptr(),
253                access,
254                null() as *const c_char,
255            )
256        };
257        result(ptr)
258    }
259
260    /// Create a VipsImage from a memory buffer.
261    ///
262    /// # Arguments
263    /// * `buf` - The buffer containing the image data.
264    /// * `width` - The width of the image.
265    /// * `height` - The height of the image.
266    /// * `bands` - The number of bands (channels) in the image.
267    /// * `format` - The band format of the image.
268    ///
269    /// # Errors
270    /// Returns an error if the image creation fails.
271    ///
272    /// # Example
273    /// ```no_run
274    /// use vips::*;
275    ///
276    /// fn main() -> Result<()> {
277    ///     let _instance = VipsInstance::new("app_test", true)?;
278    ///     let img_data: Vec<u8> = vec![/* image data */];
279    ///     let img = VipsImage::from_memory(img_data, 800, 600, 3, VipsBandFormat::VIPS_FORMAT_UCHAR)?;
280    ///     Ok(())
281    /// }
282    /// ```
283    ///
284    pub fn from_memory(
285        buf: Vec<u8>,
286        width: u32,
287        height: u32,
288        bands: u8,
289        format: VipsBandFormat,
290    ) -> Result<VipsImage<'a>> {
291        validate_memory(&buf, width, height, bands, format)?;
292        let b = buf;
293        let c = unsafe {
294            vips_sys::vips_image_new_from_memory(
295                b.as_ptr() as *const c_void,
296                b.len(),
297                width as i32,
298                height as i32,
299                bands as i32,
300                format,
301            )
302        };
303
304        if c.is_null() {
305            return Err(Error::Vips(
306                take_vips_error().unwrap_or_else(|| "Unknown error from libvips".to_string()),
307            ));
308        }
309
310        let bb: Box<Vec<u8>> = Box::new(b);
311        let raw: *mut c_void = Box::into_raw(bb) as *mut c_void;
312
313        // SAFETY: `c` is a valid image; callback frees `raw` exactly once on close.
314        unsafe {
315            let callback: unsafe extern "C" fn() =
316                std::mem::transmute(image_postclose as *const ());
317            vips_sys::g_signal_connect_data(
318                c as *mut c_void,
319                c"postclose".as_ptr(),
320                Some(callback),
321                raw,
322                None,
323                vips_sys::GConnectFlags::G_CONNECT_AFTER,
324            );
325        };
326
327        // SAFETY: unique ownership transferred to the wrapper.
328        let mut image = unsafe { VipsImage::from_raw(c) };
329        image.writable = true;
330        Ok(image)
331    }
332
333    /// Create a VipsImage from a memory buffer reference.
334    ///
335    /// # Arguments
336    /// * `buf` - The buffer slice containing the image data.
337    /// * `width` - The width of the image.
338    /// * `height` - The height of the image.
339    /// * `bands` - The number of bands (channels) in the image.
340    /// * `format` - The band format of the image.
341    ///
342    /// # Returns
343    /// A `Result` containing the created `VipsImage` or an error.
344    ///
345    /// # Errors
346    /// Returns an error if the image creation fails.
347    ///
348    /// # Example
349    /// ```no_run
350    /// use vips::*;
351    ///
352    /// fn main() -> Result<()> {
353    ///     let _instance = VipsInstance::new("app_test", true)?;
354    ///     let img_data: &[u8] = &[/* image data */];
355    ///     let img = VipsImage::from_memory_reference(img_data, 800, 600, 3, VipsBandFormat::VIPS_FORMAT_UCHAR)?;
356    ///     Ok(())
357    /// }
358    /// ```
359    ///
360    pub fn from_memory_reference(
361        buf: &'a [u8],
362        width: u32,
363        height: u32,
364        bands: u8,
365        format: VipsBandFormat,
366    ) -> Result<VipsImage<'a>> {
367        validate_memory(buf, width, height, bands, format)?;
368        let c = unsafe {
369            vips_sys::vips_image_new_from_memory(
370                buf.as_ptr() as *const c_void,
371                buf.len(),
372                width as i32,
373                height as i32,
374                bands as i32,
375                format,
376            )
377        };
378
379        result(c)
380    }
381
382    /// Create a VipsImage from a byte buffer.
383    ///
384    /// # Arguments
385    /// * `buf` - The buffer slice containing the image data.
386    ///
387    /// # Returns
388    /// A `Result` containing the created `VipsImage` or an error.
389    ///
390    /// # Errors
391    /// Returns an error if the image creation fails.
392    ///
393    /// # Example
394    /// ```no_run
395    /// use vips::*;
396    ///
397    /// fn main() -> Result<()> {
398    ///     let _instance = VipsInstance::new("app_test", true)?;
399    ///     let img_data: &[u8] = &[/* image data */];
400    ///     let img = VipsImage::from_buffer(img_data)?;
401    ///     Ok(())
402    /// }
403    /// ```
404    pub fn from_buffer(buf: &'a [u8]) -> Result<VipsImage<'a>> {
405        let c = unsafe {
406            vips_sys::vips_image_new_from_buffer(
407                buf.as_ptr() as *const c_void,
408                buf.len(),
409                null(),
410                null() as *const c_char,
411            )
412        };
413
414        result(c)
415    }
416
417    /// Decode encoded bytes with an explicit access hint. The input bytes
418    /// must outlive the returned image and every derived lazy pipeline.
419    pub fn from_buffer_with_access(
420        buf: &'a [u8],
421        access: vips_sys::VipsAccess,
422    ) -> Result<VipsImage<'a>> {
423        // SAFETY: input remains borrowed by the returned image; varargs end in NULL.
424        let ptr = unsafe {
425            vips_sys::vips_image_new_from_buffer(
426                buf.as_ptr().cast(),
427                buf.len(),
428                c"".as_ptr(),
429                c"access".as_ptr(),
430                access,
431                null() as *const c_char,
432            )
433        };
434        result(ptr)
435    }
436
437    //
438    // ─── DRAW ───────────────────────────────────────────────────────────────────────
439    //
440
441    /// Draw a rectangle on the image.
442    ///
443    /// # Arguments
444    /// * `ink` - The color to use for drawing, as a slice of f64 values.
445    /// * `left` - The left coordinate of the rectangle.
446    /// * `top` - The top coordinate of the rectangle.
447    /// * `width` - The width of the rectangle.
448    /// * `height` - The height of the rectangle.
449    ///
450    /// # Returns
451    /// A `Result` indicating success or failure.
452    ///
453    /// # Errors
454    /// Returns an error if the drawing operation fails.
455    ///
456    /// # Example
457    /// ```no_run
458    /// use vips::*;
459    ///
460    /// fn main() -> Result<()> {
461    ///     let _instance = VipsInstance::new("app_test", true)?;
462    ///     let mut img = VipsImage::from_file("input.jpg")?;
463    ///     img.draw_rect(&[255.0, 0.0, 0.0], 10, 10, 100, 50)?;
464    ///     img.write_to_file("output.jpg")?;
465    ///     Ok(())
466    /// }
467    /// ```
468    pub fn draw_rect(
469        &mut self,
470        ink: &[f64],
471        left: u32,
472        top: u32,
473        width: u32,
474        height: u32,
475    ) -> Result<()> {
476        self.ensure_writable()?;
477        let ret = unsafe {
478            vips_sys::vips_draw_rect(
479                self.c.as_ptr(),
480                ink.as_ptr() as *mut f64,
481                ink.len() as i32,
482                left as i32,
483                top as i32,
484                width as i32,
485                height as i32,
486                null() as *const c_char,
487            )
488        };
489        result_draw(ret)
490    }
491    pub fn draw_rect1(
492        &mut self,
493        ink: f64,
494        left: u32,
495        top: u32,
496        width: u32,
497        height: u32,
498    ) -> Result<()> {
499        self.ensure_writable()?;
500        let ret = unsafe {
501            vips_sys::vips_draw_rect1(
502                self.c.as_ptr(),
503                ink,
504                left as i32,
505                top as i32,
506                width as i32,
507                height as i32,
508                null() as *const c_char,
509            )
510        };
511        result_draw(ret)
512    }
513    pub fn draw_point(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
514        self.ensure_writable()?;
515        let ret = unsafe {
516            vips_sys::vips_draw_point(
517                self.c.as_ptr(),
518                ink.as_ptr() as *mut f64,
519                ink.len() as i32,
520                x,
521                y,
522                null() as *const c_char,
523            )
524        };
525        result_draw(ret)
526    }
527    pub fn draw_point1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
528        self.ensure_writable()?;
529        let ret = unsafe {
530            vips_sys::vips_draw_point1(self.c.as_ptr(), ink, x, y, null() as *const c_char)
531        };
532        result_draw(ret)
533    }
534    pub fn draw_image(
535        &mut self,
536        img: &VipsImage,
537        x: i32,
538        y: i32,
539        mode: VipsCombineMode,
540    ) -> Result<()> {
541        self.ensure_writable()?;
542        let ret = unsafe {
543            vips_sys::vips_draw_image(
544                self.c.as_ptr(),
545                img.as_ptr(),
546                x,
547                y,
548                c"mode".as_ptr(),
549                mode,
550                null() as *const c_char,
551            )
552        };
553        result_draw(ret)
554    }
555    pub fn draw_mask(&mut self, ink: &[f64], mask: &VipsImage, x: i32, y: i32) -> Result<()> {
556        self.ensure_writable()?;
557        let ret = unsafe {
558            vips_sys::vips_draw_mask(
559                self.c.as_ptr(),
560                ink.as_ptr() as *mut f64,
561                ink.len() as i32,
562                mask.as_ptr(),
563                x,
564                y,
565                null() as *const c_char,
566            )
567        };
568        result_draw(ret)
569    }
570    pub fn draw_mask1(&mut self, ink: f64, mask: &VipsImage, x: i32, y: i32) -> Result<()> {
571        self.ensure_writable()?;
572        let ret = unsafe {
573            vips_sys::vips_draw_mask1(
574                self.c.as_ptr(),
575                ink,
576                mask.as_ptr(),
577                x,
578                y,
579                null() as *const c_char,
580            )
581        };
582        result_draw(ret)
583    }
584    pub fn draw_line(&mut self, ink: &[f64], x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
585        self.ensure_writable()?;
586        let ret = unsafe {
587            vips_sys::vips_draw_line(
588                self.c.as_ptr(),
589                ink.as_ptr() as *mut f64,
590                ink.len() as i32,
591                x1,
592                y1,
593                x2,
594                y2,
595                null() as *const c_char,
596            )
597        };
598        result_draw(ret)
599    }
600    pub fn draw_line1(&mut self, ink: f64, x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
601        self.ensure_writable()?;
602        let ret = unsafe {
603            vips_sys::vips_draw_line1(
604                self.c.as_ptr(),
605                ink,
606                x1,
607                y1,
608                x2,
609                y2,
610                null() as *const c_char,
611            )
612        };
613        result_draw(ret)
614    }
615    pub fn draw_circle(&mut self, ink: &[f64], cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
616        self.ensure_writable()?;
617        let ret = unsafe {
618            vips_sys::vips_draw_circle(
619                self.c.as_ptr(),
620                ink.as_ptr() as *mut f64,
621                ink.len() as i32,
622                cx,
623                cy,
624                r,
625                c"fill".as_ptr(),
626                fill as i32,
627                null() as *const c_char,
628            )
629        };
630        result_draw(ret)
631    }
632    pub fn draw_circle1(&mut self, ink: f64, cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
633        self.ensure_writable()?;
634        let ret = unsafe {
635            vips_sys::vips_draw_circle1(
636                self.c.as_ptr(),
637                ink,
638                cx,
639                cy,
640                r,
641                c"fill".as_ptr(),
642                fill as i32,
643                null() as *const c_char,
644            )
645        };
646        result_draw(ret)
647    }
648    pub fn draw_flood(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
649        self.ensure_writable()?;
650        let ret = unsafe {
651            vips_sys::vips_draw_flood(
652                self.c.as_ptr(),
653                ink.as_ptr() as *mut f64,
654                ink.len() as i32,
655                x,
656                y,
657                null() as *const c_char,
658            )
659        };
660        result_draw(ret)
661    }
662    pub fn draw_flood1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
663        self.ensure_writable()?;
664        let ret = unsafe {
665            vips_sys::vips_draw_flood1(self.c.as_ptr(), ink, x, y, null() as *const c_char)
666        };
667        result_draw(ret)
668    }
669    pub fn draw_smudge(&mut self, left: u32, top: u32, width: u32, height: u32) -> Result<()> {
670        self.ensure_writable()?;
671        let ret = unsafe {
672            vips_sys::vips_draw_smudge(
673                self.c.as_ptr(),
674                left as i32,
675                top as i32,
676                width as i32,
677                height as i32,
678                null() as *const c_char,
679            )
680        };
681        result_draw(ret)
682    }
683
684    //
685    // ─── MOSAIC ─────────────────────────────────────────────────────────────────────
686    //
687
688    pub fn merge(
689        &self,
690        another: &VipsImage<'a>,
691        direction: VipsDirection,
692        dx: i32,
693        dy: i32,
694        mblend: Option<i32>,
695    ) -> Result<VipsImage<'a>> {
696        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
697        let ret = unsafe {
698            match mblend {
699                Some(mblend) => vips_sys::vips_merge(
700                    self.c.as_ptr(),
701                    another.as_ptr(),
702                    &mut out_ptr,
703                    direction,
704                    dx,
705                    dy,
706                    c"mblend".as_ptr(),
707                    mblend,
708                    null() as *const c_char,
709                ),
710                None => vips_sys::vips_merge(
711                    self.c.as_ptr(),
712                    another.as_ptr(),
713                    &mut out_ptr,
714                    direction,
715                    dx,
716                    dy,
717                    null() as *const c_char,
718                ),
719            }
720        };
721        result_with_ret(out_ptr, ret)
722    }
723
724    #[allow(clippy::too_many_arguments)]
725    pub fn mosaic(
726        &self,
727        sec: &VipsImage<'a>,
728        direction: VipsDirection,
729        xref: i32,
730        yref: i32,
731        xsec: i32,
732        ysec: i32,
733        bandno: Option<i32>,
734        hwindow: Option<i32>,
735        harea: Option<i32>,
736        mblend: Option<i32>,
737    ) -> Result<VipsImage<'a>> {
738        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
739        let ret = unsafe {
740            vips_sys::vips_mosaic(
741                self.c.as_ptr(),
742                sec.as_ptr(),
743                &mut out_ptr,
744                direction,
745                xref,
746                yref,
747                xsec,
748                ysec,
749                c"bandno".as_ptr(),
750                bandno.unwrap_or(-1),
751                c"hwindow".as_ptr(),
752                hwindow.unwrap_or(5),
753                c"harea".as_ptr(),
754                harea.unwrap_or(2),
755                c"mblend".as_ptr(),
756                mblend.unwrap_or(-1),
757                null() as *const c_char,
758            )
759        };
760        result_with_ret(out_ptr, ret)
761    }
762
763    #[allow(clippy::too_many_arguments)]
764    pub fn mosaic1(
765        &self,
766        sec: &VipsImage<'a>,
767        direction: VipsDirection,
768        xr1: i32,
769        yr1: i32,
770        xs1: i32,
771        ys1: i32,
772        xr2: i32,
773        yr2: i32,
774        xs2: i32,
775        ys2: i32,
776        search: Option<bool>,
777        hwindow: Option<i32>,
778        harea: Option<i32>,
779        interpolate: Option<VipsInterpolate>,
780        mblend: Option<i32>,
781        bandno: Option<i32>,
782    ) -> Result<VipsImage<'a>> {
783        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
784        let ret = unsafe {
785            match interpolate {
786                Some(interpolate) => vips_sys::vips_mosaic1(
787                    self.c.as_ptr(),
788                    sec.as_ptr(),
789                    &mut out_ptr,
790                    direction,
791                    xr1,
792                    yr1,
793                    xs1,
794                    ys1,
795                    xr2,
796                    yr2,
797                    xs2,
798                    ys2,
799                    c"search".as_ptr(),
800                    search.unwrap_or(false) as i32,
801                    c"hwindow".as_ptr(),
802                    hwindow.unwrap_or(5),
803                    c"harea".as_ptr(),
804                    harea.unwrap_or(2),
805                    c"interpolate".as_ptr(),
806                    interpolate.as_ptr(),
807                    c"mblend".as_ptr(),
808                    mblend.unwrap_or(-1),
809                    c"bandno".as_ptr(),
810                    bandno.unwrap_or(-1),
811                    null() as *const c_char,
812                ),
813                None => vips_sys::vips_mosaic1(
814                    self.c.as_ptr(),
815                    sec.as_ptr(),
816                    &mut out_ptr,
817                    direction,
818                    xr1,
819                    yr1,
820                    xs1,
821                    ys1,
822                    xr2,
823                    yr2,
824                    xs2,
825                    ys2,
826                    c"search".as_ptr(),
827                    search.unwrap_or(false) as i32,
828                    c"hwindow".as_ptr(),
829                    hwindow.unwrap_or(5),
830                    c"harea".as_ptr(),
831                    harea.unwrap_or(2),
832                    c"mblend".as_ptr(),
833                    mblend.unwrap_or(-1),
834                    c"bandno".as_ptr(),
835                    bandno.unwrap_or(-1),
836                    null() as *const c_char,
837                ),
838            }
839        };
840        result_with_ret(out_ptr, ret)
841    }
842
843    #[allow(clippy::too_many_arguments)]
844    pub fn match_(
845        &self,
846        sec: &VipsImage<'a>,
847        xr1: i32,
848        yr1: i32,
849        xs1: i32,
850        ys1: i32,
851        xr2: i32,
852        yr2: i32,
853        xs2: i32,
854        ys2: i32,
855        search: Option<bool>,
856        hwindow: Option<i32>,
857        harea: Option<i32>,
858        interpolate: Option<VipsInterpolate>,
859    ) -> Result<VipsImage<'a>> {
860        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
861        let ret = unsafe {
862            match interpolate {
863                Some(interpolate) => vips_sys::vips_match(
864                    self.c.as_ptr(),
865                    sec.as_ptr(),
866                    &mut out_ptr,
867                    xr1,
868                    yr1,
869                    xs1,
870                    ys1,
871                    xr2,
872                    yr2,
873                    xs2,
874                    ys2,
875                    c"search".as_ptr(),
876                    search.unwrap_or(false) as i32,
877                    c"hwindow".as_ptr(),
878                    hwindow.unwrap_or(5),
879                    c"harea".as_ptr(),
880                    harea.unwrap_or(2),
881                    c"interpolate".as_ptr(),
882                    interpolate.as_ptr(),
883                    null() as *const c_char,
884                ),
885                None => vips_sys::vips_match(
886                    self.c.as_ptr(),
887                    sec.as_ptr(),
888                    &mut out_ptr,
889                    xr1,
890                    yr1,
891                    xs1,
892                    ys1,
893                    xr2,
894                    yr2,
895                    xs2,
896                    ys2,
897                    c"search".as_ptr(),
898                    search.unwrap_or(false) as i32,
899                    c"hwindow".as_ptr(),
900                    hwindow.unwrap_or(5),
901                    c"harea".as_ptr(),
902                    harea.unwrap_or(2),
903                    null() as *const c_char,
904                ),
905            }
906        };
907        result_with_ret(out_ptr, ret)
908    }
909
910    pub fn globalbalance(
911        &self,
912        gamma: Option<f64>,
913        int_output: Option<bool>,
914    ) -> Result<VipsImage<'a>> {
915        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
916        let ret = unsafe {
917            vips_sys::vips_globalbalance(
918                self.c.as_ptr(),
919                &mut out_ptr,
920                c"gamma".as_ptr(),
921                gamma.unwrap_or(1.6),
922                c"int_output".as_ptr(),
923                int_output.unwrap_or(false) as i32,
924                null() as *const c_char,
925            )
926        };
927        result_with_ret(out_ptr, ret)
928    }
929
930    pub fn remosaic(&self, old_str: &str, new_str: &str) -> Result<VipsImage<'a>> {
931        let old_str = CString::new(old_str)
932            .map_err(|e| Error::InitFailed(format!("invalid old_str: {e}")))?;
933        let new_str = CString::new(new_str)
934            .map_err(|e| Error::InitFailed(format!("invalid new_str: {e}")))?;
935        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
936        let ret = unsafe {
937            vips_sys::vips_remosaic(
938                self.c.as_ptr(),
939                &mut out_ptr,
940                old_str.as_ptr(),
941                new_str.as_ptr(),
942                null() as *const c_char,
943            )
944        };
945        result_with_ret(out_ptr, ret)
946    }
947
948    //
949    // ─── PROPERTIES ─────────────────────────────────────────────────────────────────
950    //
951
952    /// Image width in pixels.
953    #[inline]
954    pub fn width(&self) -> u32 {
955        // SAFETY: `c` is a live image owned by `self`.
956        unsafe { (*self.c.as_ptr()).Xsize as u32 }
957    }
958
959    /// Image height in pixels.
960    #[inline]
961    pub fn height(&self) -> u32 {
962        // SAFETY: `c` is a live image owned by `self`.
963        unsafe { (*self.c.as_ptr()).Ysize as u32 }
964    }
965
966    /// Image dimensions as `(width, height)`.
967    #[inline]
968    pub fn size(&self) -> (u32, u32) {
969        (self.width(), self.height())
970    }
971
972    /// Number of bands (channels).
973    #[inline]
974    pub fn bands(&self) -> u32 {
975        // SAFETY: `c` is a live image owned by `self`.
976        unsafe { (*self.c.as_ptr()).Bands as u32 }
977    }
978
979    //
980    // ─── RESIZE ─────────────────────────────────────────────────────────────────────
981    //
982
983    /// Create a thumbnail from already-decoded pixels (`vips_thumbnail_image`).
984    ///
985    /// Use this only when the source is already a live image (e.g. from
986    /// [`Self::from_memory`]). For files prefer [`Self::thumbnail_file`], and
987    /// for encoded buffers prefer [`Self::thumbnail_buffer`]: those combine
988    /// load + resize so libvips can shrink-on-load (faster, lower peak memory,
989    /// better quality). See the [libvips developer checklist](https://www.libvips.org/API/current/developer-checklist.html#performance).
990    ///
991    /// # Arguments
992    /// * `width` - The desired width of the thumbnail.
993    /// * `height` - The desired height of the thumbnail.
994    /// * `size` - The size mode for the thumbnail (e.g., `VIPS_SIZE_BOTH`, `VIPS_SIZE_UP`, etc.).
995    ///
996    /// # Errors
997    /// Returns an error if the thumbnail creation fails.
998    ///
999    /// # Example
1000    /// ```no_run
1001    /// use vips::*;
1002    ///
1003    /// fn main() -> Result<()> {
1004    ///     let _instance = VipsInstance::new("app_test", true)?;
1005    ///     let pixels = vec![0u8; 256 * 256 * 3];
1006    ///     let img = VipsImage::from_memory(pixels, 256, 256, 3, VipsBandFormat::VIPS_FORMAT_UCHAR)?;
1007    ///     let thumb = img.thumbnail(100, 100, VipsSize::VIPS_SIZE_BOTH)?;
1008    ///     thumb.write_to_file("thumb.jpg")?;
1009    ///     Ok(())
1010    /// }
1011    /// ```
1012    pub fn thumbnail(&self, width: u32, height: u32, size: VipsSize) -> Result<VipsImage<'a>> {
1013        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1014        let ret = unsafe {
1015            vips_sys::vips_thumbnail_image(
1016                self.c.as_ptr(),
1017                &mut out_ptr,
1018                width as i32,
1019                c"height".as_ptr(),
1020                height as i32,
1021                c"size".as_ptr(),
1022                size,
1023                null() as *const c_char,
1024            )
1025        };
1026        result_with_ret(out_ptr, ret)
1027    }
1028
1029    /// Create a thumbnail directly from a file path (`vips_thumbnail`).
1030    ///
1031    /// Preferred for file sources: load and resize are combined so libvips can
1032    /// use format shrink-on-load (JPEG/PNG/WebP/etc.). Typically several times
1033    /// faster and far lower peak memory than `from_file` + [`Self::thumbnail`].
1034    ///
1035    /// # Arguments
1036    /// * `path` - Path to the source image.
1037    /// * `width` - Target thumbnail width.
1038    /// * `height` - Target thumbnail height.
1039    /// * `size` - Fit mode (`VIPS_SIZE_BOTH`, `VIPS_SIZE_DOWN`, `VIPS_SIZE_UP`, `VIPS_SIZE_FORCE`).
1040    ///
1041    /// # Errors
1042    /// Returns an error if the file cannot be loaded or thumbnailing fails.
1043    ///
1044    /// # Example
1045    /// ```no_run
1046    /// use vips::*;
1047    ///
1048    /// fn main() -> Result<()> {
1049    ///     let _instance = VipsInstance::new("app_test", true)?;
1050    ///     let thumb = VipsImage::thumbnail_file("input.jpg", 256, 256, VipsSize::VIPS_SIZE_BOTH)?;
1051    ///     thumb.write_to_file("thumb.jpg")?;
1052    ///     Ok(())
1053    /// }
1054    /// ```
1055    pub fn thumbnail_file(
1056        path: impl AsRef<Path>,
1057        width: u32,
1058        height: u32,
1059        size: VipsSize,
1060    ) -> Result<VipsImage<'static>> {
1061        let path = path_to_cstring(path.as_ref())?;
1062        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1063        // SAFETY: path is a valid C string; out is written by libvips on success.
1064        let ret = unsafe {
1065            vips_sys::vips_thumbnail(
1066                path.as_ptr(),
1067                &mut out_ptr,
1068                width as c_int,
1069                c"height".as_ptr(),
1070                height as c_int,
1071                c"size".as_ptr(),
1072                size,
1073                null() as *const c_char,
1074            )
1075        };
1076        result_with_ret(out_ptr, ret)
1077    }
1078
1079    /// Create a thumbnail from an encoded image buffer (`vips_thumbnail_buffer`).
1080    ///
1081    /// Preferred for in-memory file contents (JPEG/PNG/WebP/… bytes): combines
1082    /// decode + resize so libvips can shrink-on-load. Prefer this over
1083    /// `from_buffer` + [`Self::thumbnail`].
1084    ///
1085    /// The returned image may retain a lazy load pipeline that reads from
1086    /// `buf`; keep the buffer alive until the thumbnail is fully consumed.
1087    ///
1088    /// # Arguments
1089    /// * `buf` - Encoded image bytes.
1090    /// * `width` - Target thumbnail width.
1091    /// * `height` - Target thumbnail height.
1092    /// * `size` - Fit mode.
1093    ///
1094    /// # Errors
1095    /// Returns an error if the buffer is not a valid image or thumbnailing fails.
1096    ///
1097    /// # Example
1098    /// ```no_run
1099    /// use vips::*;
1100    ///
1101    /// fn main() -> Result<()> {
1102    ///     let _instance = VipsInstance::new("app_test", true)?;
1103    ///     let bytes = std::fs::read("input.jpg")?;
1104    ///     let thumb = VipsImage::thumbnail_buffer(&bytes, 256, 256, VipsSize::VIPS_SIZE_BOTH)?;
1105    ///     thumb.write_to_file("thumb.jpg")?;
1106    ///     Ok(())
1107    /// }
1108    /// ```
1109    pub fn thumbnail_buffer(
1110        buf: &'a [u8],
1111        width: u32,
1112        height: u32,
1113        size: VipsSize,
1114    ) -> Result<VipsImage<'a>> {
1115        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1116        // SAFETY: buf comes from a valid Rust slice; out is written by libvips.
1117        let ret = unsafe {
1118            vips_sys::vips_thumbnail_buffer(
1119                buf.as_ptr() as *mut c_void,
1120                buf.len(),
1121                &mut out_ptr,
1122                width as c_int,
1123                c"height".as_ptr(),
1124                height as c_int,
1125                c"size".as_ptr(),
1126                size,
1127                null() as *const c_char,
1128            )
1129        };
1130        result_with_ret(out_ptr, ret)
1131    }
1132
1133    /// Resize the image.
1134    ///
1135    /// # Arguments
1136    /// * `scale` - The scaling factor for the horizontal dimension.
1137    /// * `vscale` - Optional scaling factor for the vertical dimension. If not provided, it defaults to the value of `scale`.
1138    /// * `kernel` - Optional kernel to use for resizing. If not provided, it defaults to `VIPS_KERNEL_LANCZOS3`.
1139    ///
1140    /// # Returns
1141    /// A `Result` containing the resized `VipsImage` or an error.
1142    ///
1143    /// # Errors
1144    /// Returns an error if the resizing operation fails.
1145    ///
1146    /// # Example
1147    /// ```no_run
1148    /// use vips::*;
1149    ///
1150    /// fn main() -> Result<()> {
1151    ///     let _instance = VipsInstance::new("app_test", true)?;
1152    ///     let img = VipsImage::from_file("input.jpg")?;
1153    ///     let resized_img = img.resize(0.5, None, None)?;
1154    ///     resized_img.write_to_file("resized.jpg")?;
1155    ///     Ok(())
1156    /// }
1157    /// ```
1158    pub fn resize(
1159        &self,
1160        scale: f64,
1161        vscale: Option<f64>,
1162        kernel: Option<VipsKernel>,
1163    ) -> Result<VipsImage<'a>> {
1164        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1165        let ret = unsafe {
1166            vips_sys::vips_resize(
1167                self.c.as_ptr(),
1168                &mut out_ptr,
1169                scale,
1170                c"vscale".as_ptr(),
1171                vscale.unwrap_or(scale),
1172                c"kernel".as_ptr(),
1173                kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
1174                null() as *const c_char,
1175            )
1176        };
1177        result_with_ret(out_ptr, ret)
1178    }
1179
1180    /// Large-downscale helper: box pre-shrink then `resize`.
1181    ///
1182    /// libvips recommends shrinking by an integer factor with a box filter
1183    /// before the final kernel resample. For small images the extra pass
1184    /// costs more than it saves, so this falls back to plain `resize`.
1185    pub fn resize_reduce(
1186        &self,
1187        scale: f64,
1188        vscale: Option<f64>,
1189        kernel: Option<VipsKernel>,
1190    ) -> Result<VipsImage<'a>> {
1191        let vscale = vscale.unwrap_or(scale);
1192        if scale >= 1.0 && vscale >= 1.0 {
1193            return self.resize(scale, Some(vscale), kernel);
1194        }
1195
1196        let hscale = scale.max(f64::EPSILON);
1197        let vscale = vscale.max(f64::EPSILON);
1198        let pixels = self.width() as u64 * self.height() as u64;
1199        // Pre-shrink only pays off on large sources.
1200        if pixels < 4_000_000 {
1201            return self.resize(hscale, Some(vscale), kernel);
1202        }
1203
1204        let hshrink = if hscale < 0.5 {
1205            (1.0 / hscale / 2.0).floor().max(1.0)
1206        } else {
1207            1.0
1208        };
1209        let vshrink = if vscale < 0.5 {
1210            (1.0 / vscale / 2.0).floor().max(1.0)
1211        } else {
1212            1.0
1213        };
1214
1215        let pre = if hshrink > 1.0 || vshrink > 1.0 {
1216            Some(self.shrink_box(hshrink, vshrink)?)
1217        } else {
1218            None
1219        };
1220        let base = pre.as_ref().unwrap_or(self);
1221        base.resize(hscale * hshrink, Some(vscale * vshrink), kernel)
1222    }
1223
1224    /// Integer box shrink (`vips_shrink`) — cheapest pre-pass for large downscale.
1225    pub fn shrink_box(&self, xshrink: f64, yshrink: f64) -> Result<VipsImage<'a>> {
1226        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1227        let ret = unsafe {
1228            vips_sys::vips_shrink(
1229                self.c.as_ptr(),
1230                &mut out_ptr,
1231                xshrink,
1232                yshrink,
1233                null() as *const c_char,
1234            )
1235        };
1236        result_with_ret(out_ptr, ret)
1237    }
1238
1239    /// Low-level reduce (shrink by a floating factor using a kernel).
1240    pub fn reduce(
1241        &self,
1242        hshrink: f64,
1243        vshrink: f64,
1244        kernel: Option<VipsKernel>,
1245        centre: Option<bool>,
1246    ) -> Result<VipsImage<'a>> {
1247        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1248        let ret = unsafe {
1249            vips_sys::vips_reduce(
1250                self.c.as_ptr(),
1251                &mut out_ptr,
1252                hshrink,
1253                vshrink,
1254                c"kernel".as_ptr(),
1255                kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
1256                c"centre".as_ptr(),
1257                centre.unwrap_or(false) as i32,
1258                null() as *const c_char,
1259            )
1260        };
1261        result_with_ret(out_ptr, ret)
1262    }
1263
1264    //
1265    // ─── GEOMETRY ──────────────────────────────────────────────────────────────────
1266    //
1267
1268    /// Crop a rectangle (`left`, `top`, `width`, `height`).
1269    pub fn crop(&self, left: i32, top: i32, width: i32, height: i32) -> Result<VipsImage<'a>> {
1270        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1271        // SAFETY: live image; libvips writes a new owned out image.
1272        let ret = unsafe {
1273            vips_sys::vips_crop(
1274                self.c.as_ptr(),
1275                &mut out_ptr,
1276                left,
1277                top,
1278                width,
1279                height,
1280                null() as *const c_char,
1281            )
1282        };
1283        result_with_ret(out_ptr, ret)
1284    }
1285
1286    /// Alias of [`Self::crop`] (`vips_extract_area`).
1287    #[inline]
1288    pub fn extract_area(
1289        &self,
1290        left: i32,
1291        top: i32,
1292        width: i32,
1293        height: i32,
1294    ) -> Result<VipsImage<'a>> {
1295        self.crop(left, top, width, height)
1296    }
1297
1298    /// Embed the image in a larger canvas at `(x, y)`.
1299    pub fn embed(
1300        &self,
1301        x: i32,
1302        y: i32,
1303        width: i32,
1304        height: i32,
1305        extend: Option<VipsExtend>,
1306    ) -> Result<VipsImage<'a>> {
1307        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1308        // SAFETY: live image; optional extend key defaults to black when omitted.
1309        let ret = unsafe {
1310            vips_sys::vips_embed(
1311                self.c.as_ptr(),
1312                &mut out_ptr,
1313                x,
1314                y,
1315                width,
1316                height,
1317                c"extend".as_ptr(),
1318                extend.unwrap_or(VipsExtend::VIPS_EXTEND_BLACK),
1319                null() as *const c_char,
1320            )
1321        };
1322        result_with_ret(out_ptr, ret)
1323    }
1324
1325    /// Place the image inside a larger canvas using a compass direction.
1326    pub fn gravity(
1327        &self,
1328        direction: VipsCompassDirection,
1329        width: i32,
1330        height: i32,
1331        extend: Option<VipsExtend>,
1332    ) -> Result<VipsImage<'a>> {
1333        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1334        // SAFETY: live image; optional extend key defaults to black.
1335        let ret = unsafe {
1336            vips_sys::vips_gravity(
1337                self.c.as_ptr(),
1338                &mut out_ptr,
1339                direction,
1340                width,
1341                height,
1342                c"extend".as_ptr(),
1343                extend.unwrap_or(VipsExtend::VIPS_EXTEND_BLACK),
1344                null() as *const c_char,
1345            )
1346        };
1347        result_with_ret(out_ptr, ret)
1348    }
1349
1350    /// Insert `sub` into a copy of `self` at `(x, y)`.
1351    pub fn insert(&self, sub: &VipsImage<'a>, x: i32, y: i32) -> Result<VipsImage<'a>> {
1352        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1353        // SAFETY: both images live for the call.
1354        let ret = unsafe {
1355            vips_sys::vips_insert(
1356                self.c.as_ptr(),
1357                sub.as_ptr(),
1358                &mut out_ptr,
1359                x,
1360                y,
1361                null() as *const c_char,
1362            )
1363        };
1364        result_with_ret(out_ptr, ret)
1365    }
1366
1367    /// Flip about a horizontal or vertical axis.
1368    pub fn flip(&self, direction: VipsDirection) -> Result<VipsImage<'a>> {
1369        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1370        // SAFETY: live image.
1371        let ret = unsafe {
1372            vips_sys::vips_flip(
1373                self.c.as_ptr(),
1374                &mut out_ptr,
1375                direction,
1376                null() as *const c_char,
1377            )
1378        };
1379        result_with_ret(out_ptr, ret)
1380    }
1381
1382    /// Flip horizontally.
1383    #[inline]
1384    pub fn fliphor(&self) -> Result<VipsImage<'a>> {
1385        self.flip(VipsDirection::VIPS_DIRECTION_HORIZONTAL)
1386    }
1387
1388    /// Flip vertically.
1389    #[inline]
1390    pub fn flipver(&self) -> Result<VipsImage<'a>> {
1391        self.flip(VipsDirection::VIPS_DIRECTION_VERTICAL)
1392    }
1393
1394    /// Rotate by a multiple of 90°.
1395    pub fn rot(&self, angle: vips_sys::VipsAngle) -> Result<VipsImage<'a>> {
1396        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1397        // SAFETY: live image.
1398        let ret = unsafe {
1399            vips_sys::vips_rot(
1400                self.c.as_ptr(),
1401                &mut out_ptr,
1402                angle,
1403                null() as *const c_char,
1404            )
1405        };
1406        result_with_ret(out_ptr, ret)
1407    }
1408
1409    /// Rotate 90° clockwise.
1410    #[inline]
1411    pub fn rot90(&self) -> Result<VipsImage<'a>> {
1412        self.rot(vips_sys::VipsAngle::VIPS_ANGLE_D90)
1413    }
1414
1415    /// Rotate 180°.
1416    #[inline]
1417    pub fn rot180(&self) -> Result<VipsImage<'a>> {
1418        self.rot(vips_sys::VipsAngle::VIPS_ANGLE_D180)
1419    }
1420
1421    /// Rotate 270° clockwise.
1422    #[inline]
1423    pub fn rot270(&self) -> Result<VipsImage<'a>> {
1424        self.rot(vips_sys::VipsAngle::VIPS_ANGLE_D270)
1425    }
1426
1427    /// Rotate by an arbitrary angle in degrees (affine).
1428    pub fn rotate(&self, angle: f64) -> Result<VipsImage<'a>> {
1429        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1430        // SAFETY: live image.
1431        let ret = unsafe {
1432            vips_sys::vips_rotate(
1433                self.c.as_ptr(),
1434                &mut out_ptr,
1435                angle,
1436                null() as *const c_char,
1437            )
1438        };
1439        result_with_ret(out_ptr, ret)
1440    }
1441
1442    /// Rotate according to EXIF orientation tag.
1443    pub fn autorot(&self) -> Result<VipsImage<'a>> {
1444        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1445        // SAFETY: live image.
1446        let ret = unsafe {
1447            vips_sys::vips_autorot(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
1448        };
1449        result_with_ret(out_ptr, ret)
1450    }
1451
1452    /// Integer zoom (nearest-neighbour scale).
1453    pub fn zoom(&self, xfac: i32, yfac: i32) -> Result<VipsImage<'a>> {
1454        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1455        // SAFETY: live image.
1456        let ret = unsafe {
1457            vips_sys::vips_zoom(
1458                self.c.as_ptr(),
1459                &mut out_ptr,
1460                xfac,
1461                yfac,
1462                null() as *const c_char,
1463            )
1464        };
1465        result_with_ret(out_ptr, ret)
1466    }
1467
1468    /// Extract a single band.
1469    pub fn extract_band(&self, band: i32) -> Result<VipsImage<'a>> {
1470        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1471        // SAFETY: live image.
1472        let ret = unsafe {
1473            vips_sys::vips_extract_band(
1474                self.c.as_ptr(),
1475                &mut out_ptr,
1476                band,
1477                null() as *const c_char,
1478            )
1479        };
1480        result_with_ret(out_ptr, ret)
1481    }
1482
1483    /// Join with another image band-wise.
1484    pub fn bandjoin2(&self, other: &VipsImage<'a>) -> Result<VipsImage<'a>> {
1485        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1486        // SAFETY: both images live for the call.
1487        let ret = unsafe {
1488            vips_sys::vips_bandjoin2(
1489                self.c.as_ptr(),
1490                other.as_ptr(),
1491                &mut out_ptr,
1492                null() as *const c_char,
1493            )
1494        };
1495        result_with_ret(out_ptr, ret)
1496    }
1497
1498    /// Append constant bands (e.g. alpha = 255).
1499    pub fn bandjoin_const(&self, constants: &[f64]) -> Result<VipsImage<'a>> {
1500        if constants.is_empty() {
1501            return self.copy();
1502        }
1503        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1504        // SAFETY: `constants` is a valid contiguous f64 buffer for `n` bands.
1505        let ret = unsafe {
1506            vips_sys::vips_bandjoin_const(
1507                self.c.as_ptr(),
1508                &mut out_ptr,
1509                constants.as_ptr() as *mut f64,
1510                constants.len() as i32,
1511                null() as *const c_char,
1512            )
1513        };
1514        result_with_ret(out_ptr, ret)
1515    }
1516
1517    /// Materialize a memory image (end a sequential pipeline). libvips may
1518    /// share existing memory storage; safe drawing detaches it when needed.
1519    pub fn copy_memory(&self) -> Result<VipsImage<'a>> {
1520        // SAFETY: live image; libvips returns a new owned memory image.
1521        let ptr = unsafe { vips_sys::vips_image_copy_memory(self.c.as_ptr()) };
1522        result(ptr)
1523    }
1524
1525    /// Copy with optional metadata overrides (format, interpretation, …).
1526    pub fn copy(&self) -> Result<VipsImage<'a>> {
1527        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1528        // SAFETY: live image.
1529        let ret =
1530            unsafe { vips_sys::vips_copy(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1531        result_with_ret(out_ptr, ret)
1532    }
1533
1534    //
1535    // ─── ARITHMETIC / COLOR / FILTER ──────────────────────────────────────────────
1536    //
1537
1538    /// Pixel-wise add of two images.
1539    pub fn add(&self, other: &VipsImage<'a>) -> Result<VipsImage<'a>> {
1540        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1541        // SAFETY: both images live for the call.
1542        let ret = unsafe {
1543            vips_sys::vips_add(
1544                self.c.as_ptr(),
1545                other.as_ptr(),
1546                &mut out_ptr,
1547                null() as *const c_char,
1548            )
1549        };
1550        result_with_ret(out_ptr, ret)
1551    }
1552
1553    /// Pixel-wise subtract.
1554    pub fn subtract(&self, other: &VipsImage<'a>) -> Result<VipsImage<'a>> {
1555        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1556        // SAFETY: both images live for the call.
1557        let ret = unsafe {
1558            vips_sys::vips_subtract(
1559                self.c.as_ptr(),
1560                other.as_ptr(),
1561                &mut out_ptr,
1562                null() as *const c_char,
1563            )
1564        };
1565        result_with_ret(out_ptr, ret)
1566    }
1567
1568    /// Pixel-wise multiply.
1569    pub fn multiply(&self, other: &VipsImage<'a>) -> Result<VipsImage<'a>> {
1570        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1571        // SAFETY: both images live for the call.
1572        let ret = unsafe {
1573            vips_sys::vips_multiply(
1574                self.c.as_ptr(),
1575                other.as_ptr(),
1576                &mut out_ptr,
1577                null() as *const c_char,
1578            )
1579        };
1580        result_with_ret(out_ptr, ret)
1581    }
1582
1583    /// Pixel-wise divide.
1584    pub fn divide(&self, other: &VipsImage<'a>) -> Result<VipsImage<'a>> {
1585        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1586        // SAFETY: both images live for the call.
1587        let ret = unsafe {
1588            vips_sys::vips_divide(
1589                self.c.as_ptr(),
1590                other.as_ptr(),
1591                &mut out_ptr,
1592                null() as *const c_char,
1593            )
1594        };
1595        result_with_ret(out_ptr, ret)
1596    }
1597
1598    /// Affine intensity transform: `out = a * in + b`.
1599    pub fn linear1(&self, a: f64, b: f64) -> Result<VipsImage<'a>> {
1600        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1601        // SAFETY: live image.
1602        let ret = unsafe {
1603            vips_sys::vips_linear1(self.c.as_ptr(), &mut out_ptr, a, b, null() as *const c_char)
1604        };
1605        result_with_ret(out_ptr, ret)
1606    }
1607
1608    /// Per-band affine intensity transform (`a[i] * in + b[i]`).
1609    pub fn linear(&self, a: &[f64], b: &[f64]) -> Result<VipsImage<'a>> {
1610        if a.is_empty() || a.len() != b.len() || a.len() > i32::MAX as usize {
1611            return Err(Error::Other(
1612                "linear requires equal nonempty a/b arrays with an i32 length".into(),
1613            ));
1614        }
1615        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1616        // SAFETY: equal-length contiguous buffers for n bands.
1617        let ret = unsafe {
1618            vips_sys::vips_linear(
1619                self.c.as_ptr(),
1620                &mut out_ptr,
1621                a.as_ptr(),
1622                b.as_ptr(),
1623                a.len() as i32,
1624                null() as *const c_char,
1625            )
1626        };
1627        result_with_ret(out_ptr, ret)
1628    }
1629
1630    /// Photometric negative (`max - in` for unsigned formats).
1631    pub fn invert(&self) -> Result<VipsImage<'a>> {
1632        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1633        // SAFETY: live image.
1634        let ret = unsafe {
1635            vips_sys::vips_invert(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
1636        };
1637        result_with_ret(out_ptr, ret)
1638    }
1639
1640    /// Cast to another band format.
1641    pub fn cast(&self, format: VipsBandFormat) -> Result<VipsImage<'a>> {
1642        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1643        // SAFETY: live image.
1644        let ret = unsafe {
1645            vips_sys::vips_cast(
1646                self.c.as_ptr(),
1647                &mut out_ptr,
1648                format,
1649                null() as *const c_char,
1650            )
1651        };
1652        result_with_ret(out_ptr, ret)
1653    }
1654
1655    /// Convert colour space (e.g. sRGB ↔ scRGB ↔ Lab).
1656    pub fn colourspace(
1657        &self,
1658        interpretation: vips_sys::VipsInterpretation,
1659    ) -> Result<VipsImage<'a>> {
1660        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1661        // SAFETY: live image.
1662        let ret = unsafe {
1663            vips_sys::vips_colourspace(
1664                self.c.as_ptr(),
1665                &mut out_ptr,
1666                interpretation,
1667                null() as *const c_char,
1668            )
1669        };
1670        result_with_ret(out_ptr, ret)
1671    }
1672
1673    /// Gaussian blur.
1674    pub fn gaussblur(&self, sigma: f64) -> Result<VipsImage<'a>> {
1675        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1676        // SAFETY: live image.
1677        let ret = unsafe {
1678            vips_sys::vips_gaussblur(
1679                self.c.as_ptr(),
1680                &mut out_ptr,
1681                sigma,
1682                null() as *const c_char,
1683            )
1684        };
1685        result_with_ret(out_ptr, ret)
1686    }
1687
1688    /// Unsharp-mask sharpen.
1689    pub fn sharpen(&self, sigma: f64) -> Result<VipsImage<'a>> {
1690        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1691        // SAFETY: live image.
1692        let ret = unsafe {
1693            vips_sys::vips_sharpen(
1694                self.c.as_ptr(),
1695                &mut out_ptr,
1696                c"sigma".as_ptr(),
1697                sigma,
1698                null() as *const c_char,
1699            )
1700        };
1701        result_with_ret(out_ptr, ret)
1702    }
1703
1704    //
1705    // ─── STATISTICS ───────────────────────────────────────────────────────────────
1706    //
1707
1708    /// Average of all pixels.
1709    pub fn avg(&self) -> Result<f64> {
1710        let mut out: f64 = 0.0;
1711        // SAFETY: live image; out is written by libvips.
1712        let ret = unsafe { vips_sys::vips_avg(self.c.as_ptr(), &mut out, null() as *const c_char) };
1713        ffi::ret_to_result(ret)?;
1714        Ok(out)
1715    }
1716
1717    /// Minimum pixel value.
1718    pub fn min_value(&self) -> Result<f64> {
1719        let mut out: f64 = 0.0;
1720        // SAFETY: live image; out is written by libvips.
1721        let ret = unsafe { vips_sys::vips_min(self.c.as_ptr(), &mut out, null() as *const c_char) };
1722        ffi::ret_to_result(ret)?;
1723        Ok(out)
1724    }
1725
1726    /// Maximum pixel value.
1727    pub fn max_value(&self) -> Result<f64> {
1728        let mut out: f64 = 0.0;
1729        // SAFETY: live image; out is written by libvips.
1730        let ret = unsafe { vips_sys::vips_max(self.c.as_ptr(), &mut out, null() as *const c_char) };
1731        ffi::ret_to_result(ret)?;
1732        Ok(out)
1733    }
1734
1735    /// Pixel values at `(x, y)` as a `Vec<f64>` (one entry per band).
1736    pub fn getpoint(&self, x: i32, y: i32) -> Result<Vec<f64>> {
1737        let mut vector: *mut f64 = null_mut();
1738        let mut n: c_int = 0;
1739        // SAFETY: live image; libvips allocates `vector` of length `n`.
1740        let ret = unsafe {
1741            vips_sys::vips_getpoint(
1742                self.c.as_ptr(),
1743                &mut vector,
1744                &mut n,
1745                x,
1746                y,
1747                null() as *const c_char,
1748            )
1749        };
1750        ffi::ret_to_result(ret)?;
1751        if vector.is_null() || n <= 0 {
1752            return Ok(Vec::new());
1753        }
1754        // SAFETY: non-null buffer of `n` doubles owned by us; free with g_free.
1755        let values = unsafe {
1756            let slice = std::slice::from_raw_parts(vector as *const f64, n as usize);
1757            let owned = slice.to_vec();
1758            vips_sys::g_free(vector.cast());
1759            owned
1760        };
1761        Ok(values)
1762    }
1763
1764    /// Standard deviation of all pixels.
1765    pub fn deviate(&self) -> Result<f64> {
1766        let mut out: f64 = 0.0;
1767        // SAFETY: live image; out is written by libvips.
1768        let ret =
1769            unsafe { vips_sys::vips_deviate(self.c.as_ptr(), &mut out, null() as *const c_char) };
1770        ffi::ret_to_result(ret)?;
1771        Ok(out)
1772    }
1773
1774    /// Threshold at percentile `percent` of the image histogram (0–100).
1775    pub fn percent(&self, percent: f64) -> Result<i32> {
1776        let mut threshold: c_int = 0;
1777        // SAFETY: live image; threshold is written by libvips.
1778        let ret = unsafe {
1779            vips_sys::vips_percent(
1780                self.c.as_ptr(),
1781                percent,
1782                &mut threshold,
1783                null() as *const c_char,
1784            )
1785        };
1786        ffi::ret_to_result(ret)?;
1787        Ok(threshold)
1788    }
1789
1790    /// Bounds of non-background pixels as `(left, top, width, height)`.
1791    pub fn find_trim(
1792        &self,
1793        threshold: Option<f64>,
1794        background: Option<&[f64]>,
1795    ) -> Result<(i32, i32, i32, i32)> {
1796        let mut left = 0;
1797        let mut top = 0;
1798        let mut width = 0;
1799        let mut height = 0;
1800        // SAFETY: live image; out integers written by libvips.
1801        // Optional keys omitted when None so libvips defaults apply.
1802        let ret = unsafe {
1803            match (threshold, background) {
1804                (Some(t), Some(bg)) => vips_sys::vips_find_trim(
1805                    self.c.as_ptr(),
1806                    &mut left,
1807                    &mut top,
1808                    &mut width,
1809                    &mut height,
1810                    c"threshold".as_ptr(),
1811                    t,
1812                    c"background".as_ptr(),
1813                    bg.as_ptr() as *mut f64,
1814                    bg.len() as i32,
1815                    null() as *const c_char,
1816                ),
1817                (Some(t), None) => vips_sys::vips_find_trim(
1818                    self.c.as_ptr(),
1819                    &mut left,
1820                    &mut top,
1821                    &mut width,
1822                    &mut height,
1823                    c"threshold".as_ptr(),
1824                    t,
1825                    null() as *const c_char,
1826                ),
1827                (None, Some(bg)) => vips_sys::vips_find_trim(
1828                    self.c.as_ptr(),
1829                    &mut left,
1830                    &mut top,
1831                    &mut width,
1832                    &mut height,
1833                    c"background".as_ptr(),
1834                    bg.as_ptr() as *mut f64,
1835                    bg.len() as i32,
1836                    null() as *const c_char,
1837                ),
1838                (None, None) => vips_sys::vips_find_trim(
1839                    self.c.as_ptr(),
1840                    &mut left,
1841                    &mut top,
1842                    &mut width,
1843                    &mut height,
1844                    null() as *const c_char,
1845                ),
1846            }
1847        };
1848        ffi::ret_to_result(ret)?;
1849        Ok((left, top, width, height))
1850    }
1851
1852    //
1853    // ─── MATH / RELATIONAL / BOOLEAN (lua-vips convenience style) ─────────────────
1854    //
1855
1856    /// Apply a math op (`sin`, `cos`, `log`, …).
1857    pub fn math(&self, op: vips_sys::VipsOperationMath) -> Result<VipsImage<'a>> {
1858        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1859        // SAFETY: live image.
1860        let ret = unsafe {
1861            vips_sys::vips_math(self.c.as_ptr(), &mut out_ptr, op, null() as *const c_char)
1862        };
1863        result_with_ret(out_ptr, ret)
1864    }
1865
1866    /// Sine (degrees).
1867    #[inline]
1868    pub fn sin(&self) -> Result<VipsImage<'a>> {
1869        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1870        // SAFETY: live image.
1871        let ret =
1872            unsafe { vips_sys::vips_sin(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1873        result_with_ret(out_ptr, ret)
1874    }
1875
1876    /// Cosine (degrees).
1877    #[inline]
1878    pub fn cos(&self) -> Result<VipsImage<'a>> {
1879        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1880        // SAFETY: live image.
1881        let ret =
1882            unsafe { vips_sys::vips_cos(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1883        result_with_ret(out_ptr, ret)
1884    }
1885
1886    /// Tangent (degrees).
1887    #[inline]
1888    pub fn tan(&self) -> Result<VipsImage<'a>> {
1889        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1890        // SAFETY: live image.
1891        let ret =
1892            unsafe { vips_sys::vips_tan(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1893        result_with_ret(out_ptr, ret)
1894    }
1895
1896    /// Natural logarithm.
1897    #[inline]
1898    pub fn ln(&self) -> Result<VipsImage<'a>> {
1899        self.math(vips_sys::VipsOperationMath::VIPS_OPERATION_MATH_LOG)
1900    }
1901
1902    /// Base-10 logarithm.
1903    #[inline]
1904    pub fn log10(&self) -> Result<VipsImage<'a>> {
1905        self.math(vips_sys::VipsOperationMath::VIPS_OPERATION_MATH_LOG10)
1906    }
1907
1908    /// e^pixel.
1909    #[inline]
1910    pub fn exp(&self) -> Result<VipsImage<'a>> {
1911        self.math(vips_sys::VipsOperationMath::VIPS_OPERATION_MATH_EXP)
1912    }
1913
1914    /// 10^pixel.
1915    #[inline]
1916    pub fn exp10(&self) -> Result<VipsImage<'a>> {
1917        self.math(vips_sys::VipsOperationMath::VIPS_OPERATION_MATH_EXP10)
1918    }
1919
1920    /// Floor.
1921    pub fn floor(&self) -> Result<VipsImage<'a>> {
1922        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1923        // SAFETY: live image.
1924        let ret =
1925            unsafe { vips_sys::vips_floor(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1926        result_with_ret(out_ptr, ret)
1927    }
1928
1929    /// Ceiling.
1930    pub fn ceil(&self) -> Result<VipsImage<'a>> {
1931        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1932        // SAFETY: live image.
1933        let ret =
1934            unsafe { vips_sys::vips_ceil(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1935        result_with_ret(out_ptr, ret)
1936    }
1937
1938    /// Round to nearest integer.
1939    pub fn rint(&self) -> Result<VipsImage<'a>> {
1940        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1941        // SAFETY: live image.
1942        let ret =
1943            unsafe { vips_sys::vips_rint(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1944        result_with_ret(out_ptr, ret)
1945    }
1946
1947    /// Absolute value.
1948    pub fn abs(&self) -> Result<VipsImage<'a>> {
1949        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1950        // SAFETY: live image.
1951        let ret =
1952            unsafe { vips_sys::vips_abs(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1953        result_with_ret(out_ptr, ret)
1954    }
1955
1956    /// Sign of each pixel (-1, 0, +1).
1957    pub fn sign(&self) -> Result<VipsImage<'a>> {
1958        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1959        // SAFETY: live image.
1960        let ret =
1961            unsafe { vips_sys::vips_sign(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1962        result_with_ret(out_ptr, ret)
1963    }
1964
1965    /// Clamp to the band format range.
1966    pub fn clamp(&self) -> Result<VipsImage<'a>> {
1967        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1968        // SAFETY: live image.
1969        let ret =
1970            unsafe { vips_sys::vips_clamp(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
1971        result_with_ret(out_ptr, ret)
1972    }
1973
1974    /// Raise each pixel to `exp`.
1975    pub fn pow_const(&self, exp: f64) -> Result<VipsImage<'a>> {
1976        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1977        // SAFETY: live image.
1978        let ret = unsafe {
1979            vips_sys::vips_pow_const1(self.c.as_ptr(), &mut out_ptr, exp, null() as *const c_char)
1980        };
1981        result_with_ret(out_ptr, ret)
1982    }
1983
1984    /// Relational compare with a constant → 255/0 mask.
1985    pub fn relational_const(
1986        &self,
1987        op: vips_sys::VipsOperationRelational,
1988        c: f64,
1989    ) -> Result<VipsImage<'a>> {
1990        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
1991        // SAFETY: live image.
1992        let ret = unsafe {
1993            vips_sys::vips_relational_const1(
1994                self.c.as_ptr(),
1995                &mut out_ptr,
1996                op,
1997                c,
1998                null() as *const c_char,
1999            )
2000        };
2001        result_with_ret(out_ptr, ret)
2002    }
2003
2004    /// `self < c` mask.
2005    #[inline]
2006    pub fn less(&self, c: f64) -> Result<VipsImage<'a>> {
2007        self.relational_const(
2008            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_LESS,
2009            c,
2010        )
2011    }
2012
2013    /// `self <= c` mask.
2014    #[inline]
2015    pub fn lesseq(&self, c: f64) -> Result<VipsImage<'a>> {
2016        self.relational_const(
2017            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_LESSEQ,
2018            c,
2019        )
2020    }
2021
2022    /// `self > c` mask.
2023    #[inline]
2024    pub fn more(&self, c: f64) -> Result<VipsImage<'a>> {
2025        self.relational_const(
2026            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_MORE,
2027            c,
2028        )
2029    }
2030
2031    /// `self >= c` mask.
2032    #[inline]
2033    pub fn moreeq(&self, c: f64) -> Result<VipsImage<'a>> {
2034        self.relational_const(
2035            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_MOREEQ,
2036            c,
2037        )
2038    }
2039
2040    /// `self == c` mask.
2041    #[inline]
2042    pub fn equal_const(&self, c: f64) -> Result<VipsImage<'a>> {
2043        self.relational_const(
2044            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_EQUAL,
2045            c,
2046        )
2047    }
2048
2049    /// `self != c` mask.
2050    #[inline]
2051    pub fn notequal_const(&self, c: f64) -> Result<VipsImage<'a>> {
2052        self.relational_const(
2053            vips_sys::VipsOperationRelational::VIPS_OPERATION_RELATIONAL_NOTEQ,
2054            c,
2055        )
2056    }
2057
2058    /// Boolean op with a constant.
2059    pub fn boolean_const(
2060        &self,
2061        op: vips_sys::VipsOperationBoolean,
2062        c: f64,
2063    ) -> Result<VipsImage<'a>> {
2064        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2065        // SAFETY: live image.
2066        let ret = unsafe {
2067            vips_sys::vips_boolean_const1(
2068                self.c.as_ptr(),
2069                &mut out_ptr,
2070                op,
2071                c,
2072                null() as *const c_char,
2073            )
2074        };
2075        result_with_ret(out_ptr, ret)
2076    }
2077
2078    /// Bitwise AND with constant.
2079    #[inline]
2080    pub fn and_const(&self, c: f64) -> Result<VipsImage<'a>> {
2081        self.boolean_const(
2082            vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_AND,
2083            c,
2084        )
2085    }
2086
2087    /// Bitwise OR with constant.
2088    #[inline]
2089    pub fn or_const(&self, c: f64) -> Result<VipsImage<'a>> {
2090        self.boolean_const(vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_OR, c)
2091    }
2092
2093    /// Bitwise XOR with constant.
2094    #[inline]
2095    pub fn eor_const(&self, c: f64) -> Result<VipsImage<'a>> {
2096        self.boolean_const(
2097            vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_EOR,
2098            c,
2099        )
2100    }
2101
2102    /// Bitwise left shift by constant.
2103    pub fn lshift_const(&self, c: f64) -> Result<VipsImage<'a>> {
2104        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2105        // SAFETY: live image.
2106        let ret = unsafe {
2107            vips_sys::vips_lshift_const1(self.c.as_ptr(), &mut out_ptr, c, null() as *const c_char)
2108        };
2109        result_with_ret(out_ptr, ret)
2110    }
2111
2112    /// Bitwise right shift by constant.
2113    pub fn rshift_const(&self, c: f64) -> Result<VipsImage<'a>> {
2114        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2115        // SAFETY: live image.
2116        let ret = unsafe {
2117            vips_sys::vips_rshift_const1(self.c.as_ptr(), &mut out_ptr, c, null() as *const c_char)
2118        };
2119        result_with_ret(out_ptr, ret)
2120    }
2121
2122    /// Collapse all bands with a boolean op (`and`/`or`/`eor`).
2123    pub fn bandbool(&self, op: vips_sys::VipsOperationBoolean) -> Result<VipsImage<'a>> {
2124        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2125        // SAFETY: live image.
2126        let ret = unsafe {
2127            vips_sys::vips_bandbool(self.c.as_ptr(), &mut out_ptr, op, null() as *const c_char)
2128        };
2129        result_with_ret(out_ptr, ret)
2130    }
2131
2132    /// Collapse bands with AND.
2133    #[inline]
2134    pub fn bandand(&self) -> Result<VipsImage<'a>> {
2135        self.bandbool(vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_AND)
2136    }
2137
2138    /// Collapse bands with OR.
2139    #[inline]
2140    pub fn bandor(&self) -> Result<VipsImage<'a>> {
2141        self.bandbool(vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_OR)
2142    }
2143
2144    /// Collapse bands with XOR.
2145    #[inline]
2146    pub fn bando(&self) -> Result<VipsImage<'a>> {
2147        self.bandbool(vips_sys::VipsOperationBoolean::VIPS_OPERATION_BOOLEAN_EOR)
2148    }
2149
2150    /// Mean of each pixel across bands → 1-band image.
2151    pub fn bandmean(&self) -> Result<VipsImage<'a>> {
2152        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2153        // SAFETY: live image.
2154        let ret = unsafe {
2155            vips_sys::vips_bandmean(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2156        };
2157        result_with_ret(out_ptr, ret)
2158    }
2159
2160    /// Split into one single-band image per band.
2161    pub fn bandsplit(&self) -> Result<Vec<VipsImage<'a>>> {
2162        let n = self.bands() as i32;
2163        let mut out = Vec::with_capacity(n as usize);
2164        for band in 0..n {
2165            out.push(self.extract_band(band)?);
2166        }
2167        Ok(out)
2168    }
2169
2170    //
2171    // ─── FILTER / MORPH / EDGE ────────────────────────────────────────────────────
2172    //
2173
2174    /// Convolve with a mask image.
2175    pub fn conv(&self, mask: &VipsImage<'a>) -> Result<VipsImage<'a>> {
2176        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2177        // SAFETY: both images live for the call.
2178        let ret = unsafe {
2179            vips_sys::vips_conv(
2180                self.c.as_ptr(),
2181                &mut out_ptr,
2182                mask.as_ptr(),
2183                null() as *const c_char,
2184            )
2185        };
2186        result_with_ret(out_ptr, ret)
2187    }
2188
2189    /// Morphological op with a structuring element.
2190    pub fn morph(
2191        &self,
2192        mask: &VipsImage<'a>,
2193        op: vips_sys::VipsOperationMorphology,
2194    ) -> Result<VipsImage<'a>> {
2195        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2196        // SAFETY: both images live for the call.
2197        let ret = unsafe {
2198            vips_sys::vips_morph(
2199                self.c.as_ptr(),
2200                &mut out_ptr,
2201                mask.as_ptr(),
2202                op,
2203                null() as *const c_char,
2204            )
2205        };
2206        result_with_ret(out_ptr, ret)
2207    }
2208
2209    /// Erode with structuring element.
2210    #[inline]
2211    pub fn erode(&self, mask: &VipsImage<'a>) -> Result<VipsImage<'a>> {
2212        self.morph(
2213            mask,
2214            vips_sys::VipsOperationMorphology::VIPS_OPERATION_MORPHOLOGY_ERODE,
2215        )
2216    }
2217
2218    /// Dilate with structuring element.
2219    #[inline]
2220    pub fn dilate(&self, mask: &VipsImage<'a>) -> Result<VipsImage<'a>> {
2221        self.morph(
2222            mask,
2223            vips_sys::VipsOperationMorphology::VIPS_OPERATION_MORPHOLOGY_DILATE,
2224        )
2225    }
2226
2227    /// Rank filter (median when `index = w*h/2`).
2228    pub fn rank(&self, width: i32, height: i32, index: i32) -> Result<VipsImage<'a>> {
2229        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2230        // SAFETY: live image.
2231        let ret = unsafe {
2232            vips_sys::vips_rank(
2233                self.c.as_ptr(),
2234                &mut out_ptr,
2235                width,
2236                height,
2237                index,
2238                null() as *const c_char,
2239            )
2240        };
2241        result_with_ret(out_ptr, ret)
2242    }
2243
2244    /// `size × size` median filter.
2245    #[inline]
2246    pub fn median(&self, size: i32) -> Result<VipsImage<'a>> {
2247        let area = size.checked_mul(size).filter(|_| size > 0).ok_or_else(|| {
2248            Error::Other("median size must be positive and its square must fit i32".into())
2249        })?;
2250        let index = area / 2;
2251        self.rank(size, size, index)
2252    }
2253
2254    /// Sobel edge magnitude.
2255    pub fn sobel(&self) -> Result<VipsImage<'a>> {
2256        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2257        // SAFETY: live image.
2258        let ret =
2259            unsafe { vips_sys::vips_sobel(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
2260        result_with_ret(out_ptr, ret)
2261    }
2262
2263    /// Canny edge detector.
2264    pub fn canny(&self, sigma: Option<f64>) -> Result<VipsImage<'a>> {
2265        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2266        // SAFETY: live image.
2267        let ret = unsafe {
2268            match sigma {
2269                Some(s) => vips_sys::vips_canny(
2270                    self.c.as_ptr(),
2271                    &mut out_ptr,
2272                    c"sigma".as_ptr(),
2273                    s,
2274                    null() as *const c_char,
2275                ),
2276                None => {
2277                    vips_sys::vips_canny(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2278                }
2279            }
2280        };
2281        result_with_ret(out_ptr, ret)
2282    }
2283
2284    //
2285    // ─── LOGIC / CROP / COLOR extras ──────────────────────────────────────────────
2286    //
2287
2288    /// Pick pixels from `then` or `else` using this image as a 0/non-zero mask.
2289    pub fn ifthenelse(&self, then: &VipsImage<'a>, els: &VipsImage<'a>) -> Result<VipsImage<'a>> {
2290        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2291        // SAFETY: all three images live for the call.
2292        let ret = unsafe {
2293            vips_sys::vips_ifthenelse(
2294                self.c.as_ptr(),
2295                then.as_ptr(),
2296                els.as_ptr(),
2297                &mut out_ptr,
2298                null() as *const c_char,
2299            )
2300        };
2301        result_with_ret(out_ptr, ret)
2302    }
2303
2304    /// Attention-based crop to `width`×`height`.
2305    pub fn smartcrop(
2306        &self,
2307        width: i32,
2308        height: i32,
2309        interesting: Option<VipsInteresting>,
2310    ) -> Result<VipsImage<'a>> {
2311        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2312        // SAFETY: live image; optional interesting key omitted when None.
2313        let ret = unsafe {
2314            match interesting {
2315                Some(i) => vips_sys::vips_smartcrop(
2316                    self.c.as_ptr(),
2317                    &mut out_ptr,
2318                    width,
2319                    height,
2320                    c"interesting".as_ptr(),
2321                    i,
2322                    null() as *const c_char,
2323                ),
2324                None => vips_sys::vips_smartcrop(
2325                    self.c.as_ptr(),
2326                    &mut out_ptr,
2327                    width,
2328                    height,
2329                    null() as *const c_char,
2330                ),
2331            }
2332        };
2333        result_with_ret(out_ptr, ret)
2334    }
2335
2336    /// False-colour visualisation.
2337    pub fn falsecolour(&self) -> Result<VipsImage<'a>> {
2338        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2339        // SAFETY: live image.
2340        let ret = unsafe {
2341            vips_sys::vips_falsecolour(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2342        };
2343        result_with_ret(out_ptr, ret)
2344    }
2345
2346    /// Premultiply alpha.
2347    pub fn premultiply(&self, max_alpha: Option<f64>) -> Result<VipsImage<'a>> {
2348        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2349        // SAFETY: live image.
2350        let ret = unsafe {
2351            match max_alpha {
2352                Some(m) => vips_sys::vips_premultiply(
2353                    self.c.as_ptr(),
2354                    &mut out_ptr,
2355                    c"max-alpha".as_ptr(),
2356                    m,
2357                    null() as *const c_char,
2358                ),
2359                None => vips_sys::vips_premultiply(
2360                    self.c.as_ptr(),
2361                    &mut out_ptr,
2362                    null() as *const c_char,
2363                ),
2364            }
2365        };
2366        result_with_ret(out_ptr, ret)
2367    }
2368
2369    /// Undo premultiply.
2370    pub fn unpremultiply(&self, max_alpha: Option<f64>) -> Result<VipsImage<'a>> {
2371        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2372        // SAFETY: live image.
2373        let ret = unsafe {
2374            match max_alpha {
2375                Some(m) => vips_sys::vips_unpremultiply(
2376                    self.c.as_ptr(),
2377                    &mut out_ptr,
2378                    c"max-alpha".as_ptr(),
2379                    m,
2380                    null() as *const c_char,
2381                ),
2382                None => vips_sys::vips_unpremultiply(
2383                    self.c.as_ptr(),
2384                    &mut out_ptr,
2385                    null() as *const c_char,
2386                ),
2387            }
2388        };
2389        result_with_ret(out_ptr, ret)
2390    }
2391
2392    /// Sequential access hint (streaming pipelines).
2393    pub fn sequential(&self) -> Result<VipsImage<'a>> {
2394        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2395        // SAFETY: live image.
2396        let ret = unsafe {
2397            vips_sys::vips_sequential(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2398        };
2399        result_with_ret(out_ptr, ret)
2400    }
2401
2402    /// Cut into tiles and wrap to a new sheet.
2403    pub fn wrap(&self) -> Result<VipsImage<'a>> {
2404        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2405        // SAFETY: live image.
2406        let ret =
2407            unsafe { vips_sys::vips_wrap(self.c.as_ptr(), &mut out_ptr, null() as *const c_char) };
2408        result_with_ret(out_ptr, ret)
2409    }
2410
2411    /// Replicate the image `across`×`down` times.
2412    pub fn replicate(&self, across: i32, down: i32) -> Result<VipsImage<'a>> {
2413        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2414        // SAFETY: live image.
2415        let ret = unsafe {
2416            vips_sys::vips_replicate(
2417                self.c.as_ptr(),
2418                &mut out_ptr,
2419                across,
2420                down,
2421                null() as *const c_char,
2422            )
2423        };
2424        result_with_ret(out_ptr, ret)
2425    }
2426
2427    /// Reassemble a large image into a tiled grid.
2428    pub fn grid(&self, tile_height: i32, across: i32, down: i32) -> Result<VipsImage<'a>> {
2429        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2430        // SAFETY: live image.
2431        let ret = unsafe {
2432            vips_sys::vips_grid(
2433                self.c.as_ptr(),
2434                &mut out_ptr,
2435                tile_height,
2436                across,
2437                down,
2438                null() as *const c_char,
2439            )
2440        };
2441        result_with_ret(out_ptr, ret)
2442    }
2443
2444    /// Copy through a temporary file (break a pipeline).
2445    pub fn copy_file(&self) -> Result<VipsImage<'a>> {
2446        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2447        // SAFETY: live image.
2448        let ret = unsafe {
2449            vips_sys::vips_copy_file(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2450        };
2451        result_with_ret(out_ptr, ret)
2452    }
2453
2454    //
2455    // ─── COLOR / HIST / EDGE extras ───────────────────────────────────────────────
2456    //
2457
2458    /// Non-linear gamma encode/decode (`vips_gamma`).
2459    pub fn gamma(&self, exponent: Option<f64>) -> Result<VipsImage<'a>> {
2460        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2461        // SAFETY: live image.
2462        let ret = unsafe {
2463            match exponent {
2464                Some(e) => vips_sys::vips_gamma(
2465                    self.c.as_ptr(),
2466                    &mut out_ptr,
2467                    c"exponent".as_ptr(),
2468                    e,
2469                    null() as *const c_char,
2470                ),
2471                None => {
2472                    vips_sys::vips_gamma(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2473                }
2474            }
2475        };
2476        result_with_ret(out_ptr, ret)
2477    }
2478
2479    /// One-band intensity histogram.
2480    pub fn hist_find(&self) -> Result<VipsImage<'a>> {
2481        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2482        // SAFETY: live image.
2483        let ret = unsafe {
2484            vips_sys::vips_hist_find(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2485        };
2486        result_with_ret(out_ptr, ret)
2487    }
2488
2489    /// Local histogram equalisation.
2490    pub fn hist_local(&self, width: i32, height: i32) -> Result<VipsImage<'a>> {
2491        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2492        // SAFETY: live image.
2493        let ret = unsafe {
2494            vips_sys::vips_hist_local(
2495                self.c.as_ptr(),
2496                &mut out_ptr,
2497                width,
2498                height,
2499                null() as *const c_char,
2500            )
2501        };
2502        result_with_ret(out_ptr, ret)
2503    }
2504
2505    /// Global histogram equalisation.
2506    pub fn hist_equal(&self) -> Result<VipsImage<'a>> {
2507        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2508        // SAFETY: live image.
2509        let ret = unsafe {
2510            vips_sys::vips_hist_equal(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2511        };
2512        result_with_ret(out_ptr, ret)
2513    }
2514
2515    /// Histogram entropy.
2516    pub fn hist_entropy(&self) -> Result<f64> {
2517        let mut out: f64 = 0.0;
2518        // SAFETY: live image.
2519        let ret = unsafe {
2520            vips_sys::vips_hist_entropy(self.c.as_ptr(), &mut out, null() as *const c_char)
2521        };
2522        ffi::ret_to_result(ret)?;
2523        Ok(out)
2524    }
2525
2526    /// Map each pixel through a lookup table image.
2527    pub fn maplut(&self, lut: &VipsImage<'a>) -> Result<VipsImage<'a>> {
2528        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2529        // SAFETY: both images live for the call.
2530        let ret = unsafe {
2531            vips_sys::vips_maplut(
2532                self.c.as_ptr(),
2533                &mut out_ptr,
2534                lut.as_ptr(),
2535                null() as *const c_char,
2536            )
2537        };
2538        result_with_ret(out_ptr, ret)
2539    }
2540
2541    /// Fold bands into width (`band × n`).
2542    pub fn bandfold(&self) -> Result<VipsImage<'a>> {
2543        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2544        // SAFETY: live image.
2545        let ret = unsafe {
2546            vips_sys::vips_bandfold(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2547        };
2548        result_with_ret(out_ptr, ret)
2549    }
2550
2551    /// Unfold width into bands.
2552    pub fn bandunfold(&self) -> Result<VipsImage<'a>> {
2553        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2554        // SAFETY: live image.
2555        let ret = unsafe {
2556            vips_sys::vips_bandunfold(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2557        };
2558        result_with_ret(out_ptr, ret)
2559    }
2560
2561    /// Label connected regions; returns a label mask image.
2562    pub fn labelregions(&self) -> Result<VipsImage<'a>> {
2563        let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2564        // SAFETY: live image; mask out-param is the labelled image.
2565        let ret = unsafe {
2566            vips_sys::vips_labelregions(self.c.as_ptr(), &mut out_ptr, null() as *const c_char)
2567        };
2568        result_with_ret(out_ptr, ret)
2569    }
2570
2571    //
2572    // ─── IO ─────────────────────────────────────────────────────────────────────────
2573    //
2574
2575    /// Write the image to `path`. Format is inferred from the extension.
2576    pub fn write_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
2577        let path = path_to_cstring(path.as_ref())?;
2578        let ret = unsafe {
2579            vips_sys::vips_image_write_to_file(
2580                self.c.as_ptr(),
2581                path.as_ptr(),
2582                null() as *const c_char,
2583            )
2584        };
2585        result_draw(ret)
2586    }
2587
2588    /// Export raw band-packed pixels into a freshly allocated `Vec<u8>`.
2589    ///
2590    /// Prefer `write_to_file` when the destination is a filesystem path — this
2591    /// method always materializes raw pixels and copies them into Rust memory.
2592    /// Use [`Self::write_to_memory_owned`] to avoid that additional copy.
2593    pub fn write_to_memory(&self) -> Result<Vec<u8>> {
2594        Ok(self.write_to_memory_owned()?.as_slice().to_vec())
2595    }
2596
2597    /// Materialize raw pixels in a libvips-owned buffer without an extra Rust
2598    /// allocation or full-buffer copy. Drop frees the buffer with `g_free`.
2599    pub fn write_to_memory_owned(&self) -> Result<crate::VipsOwnedBuffer> {
2600        let mut size = 0;
2601        // SAFETY: libvips returns a newly allocated, initialized pixel buffer.
2602        let ptr = unsafe { vips_sys::vips_image_write_to_memory(self.as_ptr(), &mut size) };
2603        if ptr.is_null() {
2604            return Err(Error::Vips(
2605                take_vips_error().unwrap_or_else(|| "could not export image pixels".into()),
2606            ));
2607        }
2608        // SAFETY: successful call transfers ownership of this g_free allocation.
2609        unsafe { crate::VipsOwnedBuffer::from_raw(ptr, size) }
2610    }
2611
2612    /// Encode to a Rust-owned buffer (e.g. `".jpg"`, `".png"`).
2613    pub fn write_to_buffer(&self, suffix: &str) -> Result<Vec<u8>> {
2614        Ok(self.write_to_buffer_owned(suffix)?.as_slice().to_vec())
2615    }
2616
2617    /// Encode without copying the encoded bytes into the Rust allocator.
2618    pub fn write_to_buffer_owned(&self, suffix: &str) -> Result<crate::VipsOwnedBuffer> {
2619        let suffix = CString::new(suffix)
2620            .map_err(|_| Error::Other("invalid suffix: contains NUL".into()))?;
2621        let mut buf: *mut c_void = null_mut();
2622        let mut size = 0;
2623        // SAFETY: live image and valid output pointers; optional args end in NULL.
2624        let ret = unsafe {
2625            vips_sys::vips_image_write_to_buffer(
2626                self.as_ptr(),
2627                suffix.as_ptr(),
2628                &mut buf,
2629                &mut size,
2630                null() as *const c_char,
2631            )
2632        };
2633        // Own any returned allocation before propagating an error or empty result.
2634        let buffer = unsafe { crate::VipsOwnedBuffer::from_raw(buf, size) }?;
2635        ffi::ret_to_result(ret)?;
2636        Ok(buffer)
2637    }
2638
2639    /// JPEG-specific writer (quality path for `.jpg` destinations).
2640    pub fn write_jpeg(&self, path: impl AsRef<Path>, q: Option<i32>) -> Result<()> {
2641        let path = path_to_cstring(path.as_ref())?;
2642        let ret = unsafe {
2643            match q {
2644                Some(q) => vips_sys::vips_jpegsave(
2645                    self.c.as_ptr(),
2646                    path.as_ptr(),
2647                    c"Q".as_ptr(),
2648                    q,
2649                    null() as *const c_char,
2650                ),
2651                None => {
2652                    vips_sys::vips_jpegsave(self.c.as_ptr(), path.as_ptr(), null() as *const c_char)
2653                }
2654            }
2655        };
2656        result_draw(ret)
2657    }
2658}
2659
2660fn result<'a>(ptr: *mut vips_sys::VipsImage) -> Result<VipsImage<'a>> {
2661    let owned = ffi::image_from_ptr(ptr)?;
2662    // SAFETY: ownership transferred from libvips into this wrapper.
2663    Ok(unsafe { VipsImage::from_raw(owned.into_ptr()) })
2664}
2665
2666fn result_with_ret<'a>(ptr: *mut vips_sys::VipsImage, ret: c_int) -> Result<VipsImage<'a>> {
2667    let owned = ffi::image_from_ret(ptr, ret)?;
2668    // SAFETY: ownership transferred from libvips into this wrapper.
2669    Ok(unsafe { VipsImage::from_raw(owned.into_ptr()) })
2670}
2671
2672fn result_draw(ret: c_int) -> Result<()> {
2673    ffi::ret_to_result(ret)
2674}
2675
2676/// Loader nickname libvips would use for `path` (e.g. `"VipsForeignLoadJpegFile"`).
2677///
2678/// Returns `None` when no loader is registered for the file.
2679pub fn find_load(path: impl AsRef<Path>) -> Option<String> {
2680    let path = path_to_cstring(path.as_ref()).ok()?;
2681    // SAFETY: path is a valid C string; result is a static/owned C string.
2682    let ptr = unsafe { vips_sys::vips_foreign_find_load(path.as_ptr()) };
2683    if ptr.is_null() {
2684        return None;
2685    }
2686    // SAFETY: non-null NUL-terminated string from libvips.
2687    let s = unsafe { std::ffi::CStr::from_ptr(ptr) };
2688    s.to_str().ok().map(str::to_owned)
2689}
2690
2691/// Saver nickname libvips would use for `path` (e.g. `"VipsForeignSaveJpegFile"`).
2692pub fn find_save(path: impl AsRef<Path>) -> Option<String> {
2693    let path = path_to_cstring(path.as_ref()).ok()?;
2694    // SAFETY: path is a valid C string; result is a static/owned C string.
2695    let ptr = unsafe { vips_sys::vips_foreign_find_save(path.as_ptr()) };
2696    if ptr.is_null() {
2697        return None;
2698    }
2699    // SAFETY: non-null NUL-terminated string from libvips.
2700    let s = unsafe { std::ffi::CStr::from_ptr(ptr) };
2701    s.to_str().ok().map(str::to_owned)
2702}
2703
2704/// Create a one-band 8-bit black image (`vips_black`).
2705pub fn black(width: u32, height: u32) -> Result<VipsImage<'static>> {
2706    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2707    // SAFETY: pure constructor.
2708    let ret = unsafe {
2709        vips_sys::vips_black(
2710            &mut out_ptr,
2711            width as i32,
2712            height as i32,
2713            null() as *const c_char,
2714        )
2715    };
2716    result_with_ret(out_ptr, ret)
2717}
2718
2719/// Create a two-band coordinate image (`vips_xyz`).
2720pub fn xyz(width: u32, height: u32) -> Result<VipsImage<'static>> {
2721    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2722    // SAFETY: pure constructor.
2723    let ret = unsafe {
2724        vips_sys::vips_xyz(
2725            &mut out_ptr,
2726            width as i32,
2727            height as i32,
2728            null() as *const c_char,
2729        )
2730    };
2731    result_with_ret(out_ptr, ret)
2732}
2733
2734/// Create a grey ramp image (`vips_grey`).
2735pub fn grey(width: u32, height: u32) -> Result<VipsImage<'static>> {
2736    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2737    // SAFETY: pure constructor.
2738    let ret = unsafe {
2739        vips_sys::vips_grey(
2740            &mut out_ptr,
2741            width as i32,
2742            height as i32,
2743            null() as *const c_char,
2744        )
2745    };
2746    result_with_ret(out_ptr, ret)
2747}
2748
2749/// Create a sine-wave test image (`vips_sines`).
2750pub fn sines(width: u32, height: u32) -> Result<VipsImage<'static>> {
2751    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2752    // SAFETY: pure constructor.
2753    let ret = unsafe {
2754        vips_sys::vips_sines(
2755            &mut out_ptr,
2756            width as i32,
2757            height as i32,
2758            null() as *const c_char,
2759        )
2760    };
2761    result_with_ret(out_ptr, ret)
2762}
2763
2764/// Create a zone-plate test image (`vips_zone`).
2765pub fn zone(width: u32, height: u32) -> Result<VipsImage<'static>> {
2766    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2767    // SAFETY: pure constructor.
2768    let ret = unsafe {
2769        vips_sys::vips_zone(
2770            &mut out_ptr,
2771            width as i32,
2772            height as i32,
2773            null() as *const c_char,
2774        )
2775    };
2776    result_with_ret(out_ptr, ret)
2777}
2778
2779/// Create a Perlin noise image (`vips_perlin`).
2780pub fn perlin(width: u32, height: u32) -> Result<VipsImage<'static>> {
2781    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2782    // SAFETY: pure constructor.
2783    let ret = unsafe {
2784        vips_sys::vips_perlin(
2785            &mut out_ptr,
2786            width as i32,
2787            height as i32,
2788            null() as *const c_char,
2789        )
2790    };
2791    result_with_ret(out_ptr, ret)
2792}
2793
2794/// Create Gaussian noise (`vips_gaussnoise`).
2795pub fn gaussnoise(width: u32, height: u32) -> Result<VipsImage<'static>> {
2796    let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
2797    // SAFETY: pure constructor.
2798    let ret = unsafe {
2799        vips_sys::vips_gaussnoise(
2800            &mut out_ptr,
2801            width as i32,
2802            height as i32,
2803            null() as *const c_char,
2804        )
2805    };
2806    result_with_ret(out_ptr, ret)
2807}