Skip to main content

vips_sys/
lib.rs

1//! Low-level FFI bindings to `libvips`.
2//!
3//! By default this crate uses **pregenerated** bindings (`src/bindings/prebuilt.rs`)
4//! so consumers do not need clang/bindgen at build time.
5//!
6//! Enable the `bindgen` feature to regenerate bindings against the headers found
7//! on the machine (system libvips or `vendor/libvips`).
8//!
9//! # License
10//!
11//! This crate’s Rust source is **MIT**. The linked [`libvips`](https://www.libvips.org/)
12//! library is **LGPL-2.1** — distributing binaries that link libvips requires
13//! complying with LGPL-2.1 for that library. See the README “License” section.
14
15#![allow(non_upper_case_globals, non_camel_case_types, non_snake_case)]
16#![allow(clippy::missing_safety_doc)]
17
18/// Pregenerated declarations from libvips 8.18.7. Callers must ensure that each
19/// symbol they use exists in the linked library; newer APIs need newer libvips.
20#[cfg(not(feature = "bindgen"))]
21#[allow(clippy::all)]
22mod ffi {
23    include!("bindings/prebuilt.rs");
24}
25
26/// Live bindgen output from `OUT_DIR` (feature `bindgen`).
27#[cfg(feature = "bindgen")]
28#[allow(clippy::all)]
29mod ffi {
30    include!(concat!(env!("OUT_DIR"), "/binding.rs"));
31}
32
33pub use ffi::*;
34
35// These must hold even for no-link Windows CI builds.
36const _: () = {
37    assert!(core::mem::size_of::<gsize>() == core::mem::size_of::<usize>());
38    assert!(core::mem::size_of::<gssize>() == core::mem::size_of::<isize>());
39    assert!(core::mem::size_of::<GType>() == core::mem::size_of::<usize>());
40};
41
42/// Optional minimal helpers for init / shutdown / version.
43#[cfg(feature = "helpers")]
44pub mod helpers {
45    use super::*;
46    use std::ffi::CString;
47    use std::sync::{Mutex, OnceLock};
48
49    static INIT: OnceLock<()> = OnceLock::new();
50    static INIT_LOCK: Mutex<()> = Mutex::new(());
51
52    /// Whether this crate has successfully initialized libvips.
53    pub fn is_initialized() -> bool {
54        INIT.get().is_some()
55    }
56
57    /// Initialize libvips. Later calls reuse the first successful result.
58    pub fn init(argv0: &str) -> Result<(), i32> {
59        // Avoid CString allocation when init already succeeded.
60        if INIT.get().is_some() {
61            return Ok(());
62        }
63        let _guard = INIT_LOCK.lock().unwrap_or_else(|e| e.into_inner());
64        if INIT.get().is_some() {
65            return Ok(());
66        }
67        let c = CString::new(argv0).map_err(|_| -1)?;
68        let rc = unsafe { vips_init(c.as_ptr()) };
69        if rc != 0 {
70            return Err(rc);
71        }
72        let _ = INIT.set(());
73        Ok(())
74    }
75
76    /// Shut down libvips permanently. It cannot be initialized again.
77    ///
78    /// # Safety
79    /// All images, regions, interpolators, workers and other libvips users
80    /// must have finished and been destroyed. No later libvips calls, including
81    /// `init`, may occur. Prefer leaving cleanup to process exit.
82    pub unsafe fn shutdown() {
83        // SAFETY: the caller guarantees that all libvips use has ended.
84        unsafe { vips_shutdown() }
85    }
86
87    /// `(major, minor, micro)` of the linked libvips. Cached after first call.
88    pub fn version() -> (i32, i32, i32) {
89        static VER: OnceLock<(i32, i32, i32)> = OnceLock::new();
90        *VER.get_or_init(|| unsafe { (vips_version(0), vips_version(1), vips_version(2)) })
91    }
92
93    /// `libvips` version string, e.g. `"8.18.6"`.
94    ///
95    /// The returned `&'static str` points at libvips-owned memory that lives
96    /// for the process lifetime.
97    pub fn version_string() -> &'static str {
98        static STR: OnceLock<&'static str> = OnceLock::new();
99        // Deref-coerce `&'static &'static str` → `&'static str` (no explicit `*`).
100        STR.get_or_init(|| unsafe {
101            let p = vips_version_string();
102            if p.is_null() {
103                ""
104            } else {
105                std::ffi::CStr::from_ptr(p).to_str().unwrap_or("")
106            }
107        })
108    }
109}
110
111#[cfg(all(test, feature = "helpers"))]
112mod tests {
113    use super::helpers;
114
115    #[test]
116    fn init_and_version() {
117        assert_eq!(helpers::init("invalid\0name"), Err(-1));
118        helpers::init("vips-sys-test").expect("vips_init");
119        let (maj, min, mic) = helpers::version();
120        assert!(maj >= 0 && min >= 0 && mic >= 0);
121        assert!(!helpers::version_string().is_empty());
122        // Cached: second call is free and consistent.
123        assert_eq!(helpers::version(), (maj, min, mic));
124        std::thread::scope(|scope| {
125            for _ in 0..8 {
126                scope.spawn(|| helpers::init("parallel-init").unwrap());
127            }
128        });
129    }
130}