picturium-libvips 0.1.3

Libvips bindings for picturium media server
Documentation
use crate::FromSvgOptions;
use crate::bindings::{
    VipsImage as CVipsImage, g_object_unref, vips_image_get_blob, vips_image_get_height,
    vips_image_get_n_pages, vips_image_get_typeof, vips_image_get_width, vips_image_hasalpha,
    vips_image_new_from_file, vips_image_new_from_image, vips_image_set_kill, vips_svgload,
};
use crate::options::FromFileOptions;
use crate::result::{Error, Result};
use crate::utils::c_string;
use crate::vips::Vips;
use std::os::raw::{c_int, c_void};
use std::ptr::null_mut;

#[derive(Debug, Clone)]
pub struct VipsImage(pub *mut CVipsImage, pub(crate) Option<Vec<VipsImage>>);

pub const NULL: *const std::os::raw::c_char = std::ptr::null();

impl VipsImage {
    pub fn new_from_file(filename: &str, options: Option<FromFileOptions>) -> Result<Self> {
        let filename = match c_string(filename) {
            Ok(filename) => filename,
            Err(e) => return Err(e),
        };

        let image = match options {
            Some(options) => unsafe {
                vips_image_new_from_file(
                    filename.as_ptr(),
                    c_string("memory")?.as_ptr(),
                    options.memory as c_int,
                    c_string("access")?.as_ptr(),
                    options.access,
                    NULL,
                )
            },
            None => unsafe { vips_image_new_from_file(filename.as_ptr(), NULL) },
        };

        if image.is_null() {
            return Err(Error::ImageLoadError(Vips::get_error()));
        }

        Ok(VipsImage(image, None))
    }

    pub fn new_from_svg(filename: &str, options: Option<FromSvgOptions>) -> Result<Self> {
        let filename = match c_string(filename) {
            Ok(filename) => filename,
            Err(e) => return Err(e),
        };

        let mut output_image: *mut crate::bindings::VipsImage = null_mut();

        let result = match options {
            Some(options) => unsafe {
                vips_svgload(
                    filename.as_ptr(),
                    &mut output_image,
                    c_string("dpi")?.as_ptr(),
                    options.dpi,
                    c_string("scale")?.as_ptr(),
                    options.scale,
                    c_string("unlimited")?.as_ptr(),
                    options.unlimited as c_int,
                    c_string("flags")?.as_ptr(),
                    options.flags,
                    c_string("memory")?.as_ptr(),
                    options.memory as c_int,
                    c_string("access")?.as_ptr(),
                    options.access,
                    c_string("fail_on")?.as_ptr(),
                    options.fail_on,
                    c_string("revalidate")?.as_ptr(),
                    options.revalidate as c_int,
                    NULL,
                )
            },
            None => unsafe { vips_svgload(filename.as_ptr(), &mut output_image, NULL) },
        };

        if result != 0 || output_image.is_null() {
            return Err(Error::ImageLoadError(Vips::get_error()));
        }

        Ok(VipsImage(output_image, None))
    }

    pub fn new_from_image(image: &VipsImage, bands: &[f64]) -> Result<Self> {
        let image =
            unsafe { vips_image_new_from_image(image.0, bands.as_ptr(), bands.len() as c_int) };

        if image.is_null() {
            return Err(Error::ImageLoadError(Vips::get_error()));
        }

        Ok(VipsImage(image, None))
    }

    pub fn new_from_self(&self, bands: &[f64]) -> Result<Self> {
        Self::new_from_image(self, bands)
    }

    pub fn get_width(&self) -> i32 {
        unsafe { vips_image_get_width(self.0) }
    }

    pub fn get_height(&self) -> i32 {
        unsafe { vips_image_get_height(self.0) }
    }

    pub fn get_dimensions(&self) -> (i32, i32) {
        (self.get_width(), self.get_height())
    }

    pub fn get_bands(&self) -> i32 {
        (unsafe { *self.0 }).Bands
    }

    pub fn get_page_count(&self) -> i32 {
        unsafe { vips_image_get_n_pages(self.0) }
    }

    pub fn has_property(&self, property: &str) -> Result<bool> {
        Ok(unsafe { vips_image_get_typeof(self.0, c_string(property)?.as_ptr()) > 0 })
    }

    pub fn get_blob(&self, property: &str) -> Result<Vec<u8>> {
        let mut output: *const c_void = null_mut();
        let mut length = 0;

        let result = unsafe {
            vips_image_get_blob(
                self.0,
                c_string(property)?.as_ptr(),
                &mut output,
                &mut length,
            )
        };

        if result != 0 {
            return Err(Error::ImageMetadataError(Vips::get_error()));
        }

        let blob_data = unsafe { std::slice::from_raw_parts(output as *const u8, length).to_vec() };
        Ok(blob_data)
    }

    pub fn is_transparent(&self) -> bool {
        unsafe { vips_image_hasalpha(self.0) == 1 }
    }

    pub fn kill(&self) {
        unsafe { vips_image_set_kill(self.0, 1) }
    }

    pub(crate) fn keepalive(&mut self, image: VipsImage) {
        if let None = self.1 {
            self.1 = Some(Vec::new());
        }

        self.1.as_mut().unwrap().push(image);
    }

    pub(crate) fn cleanup(&self) {
        self.kill();

        if !self.0.is_null() {
            unsafe {
                g_object_unref(self.0 as *mut c_void);
            }
        }
    }
}

impl Drop for VipsImage {
    fn drop(&mut self) {
        self.cleanup();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::enums::VipsAccess;
    use crate::vips::Vips;

    #[test]
    fn it_creates_a_new_image_from_a_file() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_file(
            "data/example.jpg",
            FromFileOptions {
                access: VipsAccess::Last,
                memory: true,
            }
            .into(),
        );

        if let Err(e) = image {
            panic!("{e}");
        }
    }

    #[test]
    fn it_creates_a_new_image_from_svg() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_svg(
            "data/example.svg",
            FromSvgOptions {
                dpi: 300.0,
                revalidate: true,
                ..FromSvgOptions::default()
            }
            .into(),
        );

        if let Err(e) = image {
            panic!("{e}");
        }

        let image = image.unwrap();

        assert_eq!(image.get_width(), 3142);
        assert_eq!(image.get_height(), 1449);
    }

    #[test]
    fn it_creates_a_new_image_from_image() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_file("data/example.jpg", None);

        if let Err(e) = image {
            panic!("Original image: {e}");
        }

        let new_image = image.unwrap().new_from_self(&[255.0, 255.0, 255.0]);

        if let Err(e) = new_image {
            panic!("New image: {e}");
        }
    }

    #[test]
    fn it_returns_image_dimensions() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_file("data/example.jpg", None);

        if let Err(e) = image {
            panic!("Original image: {e}");
        }

        let image = image.unwrap();

        assert_eq!(image.get_width(), 4000);
        assert_eq!(image.get_height(), 5328);
        assert_eq!(image.get_dimensions(), (4000, 5328));
    }

    #[test]
    fn it_returns_number_of_bands() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_file("data/example.jpg", None);

        if let Err(e) = image {
            panic!("Opaque image: {e}");
        }

        let image = image.unwrap();
        assert_eq!(image.get_bands(), 3);

        let image = VipsImage::new_from_file("data/transparent.png", None);

        if let Err(e) = image {
            panic!("Transparent image: {e}");
        }

        let image = image.unwrap();
        assert_eq!(image.get_bands(), 4);
    }

    #[test]
    fn it_returns_transparency_information() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let image = VipsImage::new_from_file("data/example.jpg", None);

        if let Err(e) = image {
            panic!("Opaque image: {e}");
        }

        let image = image.unwrap();
        assert_eq!(image.is_transparent(), false);

        let image = VipsImage::new_from_file("data/transparent.png", None);

        if let Err(e) = image {
            panic!("Transparent image: {e}");
        }

        let image = image.unwrap();
        assert_eq!(image.is_transparent(), true);
    }

    #[test]
    fn it_returns_number_of_pages() {
        let vips = Vips::new("picturium").unwrap();
        vips.check_leaks();

        let document = VipsImage::new_from_file("data/document.pdf", None);

        if let Err(e) = document {
            panic!("PDF document: {e}");
        }

        let document = document.unwrap();
        assert_eq!(document.get_page_count(), 2);
    }
}