use crate::ffi;
use crate::{Error, Result, VipsInterpolate, take_vips_error};
use std::ffi::CString;
use std::marker::PhantomData;
use std::os::raw::{c_char, c_int, c_void};
use std::path::Path;
use std::ptr::{null, null_mut};
use vips_sys::{VipsBandFormat, VipsCombineMode, VipsDirection, VipsKernel, VipsSize};
fn path_to_cstring(path: &Path) -> Result<CString> {
#[cfg(unix)]
{
use std::os::unix::ffi::OsStrExt;
CString::new(path.as_os_str().as_bytes())
.map_err(|e| Error::InitFailed(format!("invalid path: {e}")))
}
#[cfg(not(unix))]
{
CString::new(path.to_string_lossy().as_bytes())
.map_err(|e| Error::InitFailed(format!("invalid path: {e}")))
}
}
pub struct VipsImage<'a> {
c: std::ptr::NonNull<vips_sys::VipsImage>,
marker: PhantomData<&'a ()>,
}
impl<'a> Drop for VipsImage<'a> {
fn drop(&mut self) {
unsafe { ffi::unref(self.c.as_ptr().cast()) };
}
}
unsafe extern "C" fn image_postclose(_ptr: *mut vips_sys::VipsImage, user_data: *mut c_void) {
if user_data.is_null() {
return;
}
let b: Box<Box<[u8]>> = unsafe { Box::from_raw(user_data as *mut Box<[u8]>) };
drop(b);
}
impl<'a> VipsImage<'a> {
#[inline]
pub fn as_ptr(&self) -> *mut vips_sys::VipsImage {
self.c.as_ptr()
}
pub(crate) unsafe fn from_raw(ptr: *mut vips_sys::VipsImage) -> VipsImage<'a> {
let c = std::ptr::NonNull::new(ptr).expect("VipsImage::from_raw(null)");
VipsImage {
c,
marker: PhantomData,
}
}
pub fn new() -> Result<VipsImage<'a>> {
let c = unsafe { vips_sys::vips_image_new() };
result(c)
}
pub fn new_memory() -> Result<VipsImage<'a>> {
let c = unsafe { vips_sys::vips_image_new_memory() };
result(c)
}
pub fn from_file(path: impl AsRef<Path>) -> Result<VipsImage<'a>> {
let path = path_to_cstring(path.as_ref())?;
unsafe {
let ptr = vips_sys::vips_image_new_from_file(path.as_ptr(), null() as *const c_char);
result(ptr)
}
}
pub fn from_memory(
buf: Vec<u8>,
width: u32,
height: u32,
bands: u8,
format: VipsBandFormat,
) -> Result<VipsImage<'a>> {
let b: Box<[u8]> = buf.into_boxed_slice();
let c = unsafe {
vips_sys::vips_image_new_from_memory(
b.as_ptr() as *const c_void,
b.len(),
width as i32,
height as i32,
bands as i32,
format,
)
};
if c.is_null() {
return Err(Error::Vips(
take_vips_error().unwrap_or_else(|| "Unknown error from libvips".to_string()),
));
}
let bb: Box<Box<[u8]>> = Box::new(b);
let raw: *mut c_void = Box::into_raw(bb) as *mut c_void;
unsafe {
let callback: unsafe extern "C" fn() =
std::mem::transmute(image_postclose as *const ());
vips_sys::g_signal_connect_data(
c as *mut c_void,
c"postclose".as_ptr(),
Some(callback),
raw,
None,
vips_sys::GConnectFlags::G_CONNECT_AFTER,
);
};
Ok(unsafe { VipsImage::from_raw(c) })
}
pub fn from_memory_reference(
buf: &'a [u8],
width: u32,
height: u32,
bands: u8,
format: VipsBandFormat,
) -> Result<VipsImage<'a>> {
let c = unsafe {
vips_sys::vips_image_new_from_memory(
buf.as_ptr() as *const c_void,
buf.len(),
width as i32,
height as i32,
bands as i32,
format,
)
};
result(c)
}
pub fn from_buffer(buf: &'a [u8]) -> Result<VipsImage<'a>> {
let c = unsafe {
vips_sys::vips_image_new_from_buffer(
buf.as_ptr() as *const c_void,
buf.len(),
null(),
null() as *const c_char,
)
};
result(c)
}
pub fn draw_rect(
&mut self,
ink: &[f64],
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_rect(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
left as i32,
top as i32,
width as i32,
height as i32,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_rect1(
&mut self,
ink: f64,
left: u32,
top: u32,
width: u32,
height: u32,
) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_rect1(
self.c.as_ptr(),
ink,
left as i32,
top as i32,
width as i32,
height as i32,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_point(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_point(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
x,
y,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_point1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_point1(self.c.as_ptr(), ink, x, y, null() as *const c_char)
};
result_draw(ret)
}
pub fn draw_image(
&mut self,
img: &VipsImage,
x: i32,
y: i32,
mode: VipsCombineMode,
) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_image(
self.c.as_ptr(),
img.as_ptr(),
x,
y,
c"mode".as_ptr(),
mode,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_mask(&mut self, ink: &[f64], mask: &VipsImage, x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_mask(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
mask.as_ptr(),
x,
y,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_mask1(&mut self, ink: f64, mask: &VipsImage, x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_mask1(
self.c.as_ptr(),
ink,
mask.as_ptr(),
x,
y,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_line(&mut self, ink: &[f64], x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_line(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
x1,
y1,
x2,
y2,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_line1(&mut self, ink: f64, x1: i32, y1: i32, x2: i32, y2: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_line1(
self.c.as_ptr(),
ink,
x1,
y1,
x2,
y2,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_circle(&mut self, ink: &[f64], cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_circle(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
cx,
cy,
r,
c"fill".as_ptr(),
fill as i32,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_circle1(&mut self, ink: f64, cx: i32, cy: i32, r: i32, fill: bool) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_circle1(
self.c.as_ptr(),
ink,
cx,
cy,
r,
c"fill".as_ptr(),
fill as i32,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_flood(&mut self, ink: &[f64], x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_flood(
self.c.as_ptr(),
ink.as_ptr() as *mut f64,
ink.len() as i32,
x,
y,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn draw_flood1(&mut self, ink: f64, x: i32, y: i32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_flood1(self.c.as_ptr(), ink, x, y, null() as *const c_char)
};
result_draw(ret)
}
pub fn draw_smudge(&mut self, left: u32, top: u32, width: u32, height: u32) -> Result<()> {
let ret = unsafe {
vips_sys::vips_draw_smudge(
self.c.as_ptr(),
left as i32,
top as i32,
width as i32,
height as i32,
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn merge(
&self,
another: &VipsImage,
direction: VipsDirection,
dx: i32,
dy: i32,
mblend: Option<i32>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
match mblend {
Some(mblend) => vips_sys::vips_merge(
self.c.as_ptr(),
another.as_ptr(),
&mut out_ptr,
direction,
dx,
dy,
c"mblend".as_ptr(),
mblend,
null() as *const c_char,
),
None => vips_sys::vips_merge(
self.c.as_ptr(),
another.as_ptr(),
&mut out_ptr,
direction,
dx,
dy,
null() as *const c_char,
),
}
};
result_with_ret(out_ptr, ret)
}
#[allow(clippy::too_many_arguments)]
pub fn mosaic(
&self,
sec: &VipsImage,
direction: VipsDirection,
xref: i32,
yref: i32,
xsec: i32,
ysec: i32,
bandno: Option<i32>,
hwindow: Option<i32>,
harea: Option<i32>,
mblend: Option<i32>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_mosaic(
self.c.as_ptr(),
sec.as_ptr(),
&mut out_ptr,
direction,
xref,
yref,
xsec,
ysec,
c"bandno".as_ptr(),
bandno.unwrap_or(-1),
c"hwindow".as_ptr(),
hwindow.unwrap_or(5),
c"harea".as_ptr(),
harea.unwrap_or(2),
c"mblend".as_ptr(),
mblend.unwrap_or(-1),
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
#[allow(clippy::too_many_arguments)]
pub fn mosaic1(
&self,
sec: &VipsImage,
direction: VipsDirection,
xr1: i32,
yr1: i32,
xs1: i32,
ys1: i32,
xr2: i32,
yr2: i32,
xs2: i32,
ys2: i32,
search: Option<bool>,
hwindow: Option<i32>,
harea: Option<i32>,
interpolate: Option<VipsInterpolate>,
mblend: Option<i32>,
bandno: Option<i32>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
match interpolate {
Some(interpolate) => vips_sys::vips_mosaic1(
self.c.as_ptr(),
sec.as_ptr(),
&mut out_ptr,
direction,
xr1,
yr1,
xs1,
ys1,
xr2,
yr2,
xs2,
ys2,
c"search".as_ptr(),
search.unwrap_or(false) as i32,
c"hwindow".as_ptr(),
hwindow.unwrap_or(5),
c"harea".as_ptr(),
harea.unwrap_or(2),
c"interpolate".as_ptr(),
interpolate.as_ptr(),
c"mblend".as_ptr(),
mblend.unwrap_or(-1),
c"bandno".as_ptr(),
bandno.unwrap_or(-1),
null() as *const c_char,
),
None => vips_sys::vips_mosaic1(
self.c.as_ptr(),
sec.as_ptr(),
&mut out_ptr,
direction,
xr1,
yr1,
xs1,
ys1,
xr2,
yr2,
xs2,
ys2,
c"search".as_ptr(),
search.unwrap_or(false) as i32,
c"hwindow".as_ptr(),
hwindow.unwrap_or(5),
c"harea".as_ptr(),
harea.unwrap_or(2),
c"mblend".as_ptr(),
mblend.unwrap_or(-1),
c"bandno".as_ptr(),
bandno.unwrap_or(-1),
null() as *const c_char,
),
}
};
result_with_ret(out_ptr, ret)
}
#[allow(clippy::too_many_arguments)]
pub fn match_(
&self,
sec: &VipsImage,
xr1: i32,
yr1: i32,
xs1: i32,
ys1: i32,
xr2: i32,
yr2: i32,
xs2: i32,
ys2: i32,
search: Option<bool>,
hwindow: Option<i32>,
harea: Option<i32>,
interpolate: Option<VipsInterpolate>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
match interpolate {
Some(interpolate) => vips_sys::vips_match(
self.c.as_ptr(),
sec.as_ptr(),
&mut out_ptr,
xr1,
yr1,
xs1,
ys1,
xr2,
yr2,
xs2,
ys2,
c"search".as_ptr(),
search.unwrap_or(false) as i32,
c"hwindow".as_ptr(),
hwindow.unwrap_or(5),
c"harea".as_ptr(),
harea.unwrap_or(2),
c"interpolate".as_ptr(),
interpolate.as_ptr(),
null() as *const c_char,
),
None => vips_sys::vips_match(
self.c.as_ptr(),
sec.as_ptr(),
&mut out_ptr,
xr1,
yr1,
xs1,
ys1,
xr2,
yr2,
xs2,
ys2,
c"search".as_ptr(),
search.unwrap_or(false) as i32,
c"hwindow".as_ptr(),
hwindow.unwrap_or(5),
c"harea".as_ptr(),
harea.unwrap_or(2),
null() as *const c_char,
),
}
};
result_with_ret(out_ptr, ret)
}
pub fn globalbalance(
&self,
gamma: Option<f64>,
int_output: Option<bool>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_globalbalance(
self.c.as_ptr(),
&mut out_ptr,
c"gamma".as_ptr(),
gamma.unwrap_or(1.6),
c"int_output".as_ptr(),
int_output.unwrap_or(false) as i32,
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
pub fn remosaic(&self, old_str: &str, new_str: &str) -> Result<VipsImage<'a>> {
let old_str = CString::new(old_str)
.map_err(|e| Error::InitFailed(format!("invalid old_str: {e}")))?;
let new_str = CString::new(new_str)
.map_err(|e| Error::InitFailed(format!("invalid new_str: {e}")))?;
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_remosaic(
self.c.as_ptr(),
&mut out_ptr,
old_str.as_ptr(),
new_str.as_ptr(),
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
#[inline]
pub fn width(&self) -> u32 {
unsafe { (*self.c.as_ptr()).Xsize as u32 }
}
#[inline]
pub fn height(&self) -> u32 {
unsafe { (*self.c.as_ptr()).Ysize as u32 }
}
#[inline]
pub fn size(&self) -> (u32, u32) {
(self.width(), self.height())
}
#[inline]
pub fn bands(&self) -> u32 {
unsafe { (*self.c.as_ptr()).Bands as u32 }
}
pub fn thumbnail(&self, width: u32, height: u32, size: VipsSize) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_thumbnail_image(
self.c.as_ptr(),
&mut out_ptr,
width as i32,
c"height".as_ptr(),
height as i32,
c"size".as_ptr(),
size,
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
pub fn resize(
&self,
scale: f64,
vscale: Option<f64>,
kernel: Option<VipsKernel>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_resize(
self.c.as_ptr(),
&mut out_ptr,
scale,
c"vscale".as_ptr(),
vscale.unwrap_or(scale),
c"kernel".as_ptr(),
kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
pub fn resize_reduce(
&self,
scale: f64,
vscale: Option<f64>,
kernel: Option<VipsKernel>,
) -> Result<VipsImage<'a>> {
let vscale = vscale.unwrap_or(scale);
if scale >= 1.0 && vscale >= 1.0 {
return self.resize(scale, Some(vscale), kernel);
}
let hscale = scale.max(f64::EPSILON);
let vscale = vscale.max(f64::EPSILON);
let pixels = self.width() as u64 * self.height() as u64;
if pixels < 4_000_000 {
return self.resize(hscale, Some(vscale), kernel);
}
let hshrink = if hscale < 0.5 {
(1.0 / hscale / 2.0).floor().max(1.0)
} else {
1.0
};
let vshrink = if vscale < 0.5 {
(1.0 / vscale / 2.0).floor().max(1.0)
} else {
1.0
};
let pre = if hshrink > 1.0 || vshrink > 1.0 {
Some(self.shrink_box(hshrink, vshrink)?)
} else {
None
};
let base = pre.as_ref().unwrap_or(self);
base.resize(hscale * hshrink, Some(vscale * vshrink), kernel)
}
pub fn shrink_box(&self, xshrink: f64, yshrink: f64) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_shrink(
self.c.as_ptr(),
&mut out_ptr,
xshrink,
yshrink,
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
pub fn reduce(
&self,
hshrink: f64,
vshrink: f64,
kernel: Option<VipsKernel>,
centre: Option<bool>,
) -> Result<VipsImage<'a>> {
let mut out_ptr: *mut vips_sys::VipsImage = null_mut();
let ret = unsafe {
vips_sys::vips_reduce(
self.c.as_ptr(),
&mut out_ptr,
hshrink,
vshrink,
c"kernel".as_ptr(),
kernel.unwrap_or(VipsKernel::VIPS_KERNEL_LANCZOS3),
c"centre".as_ptr(),
centre.unwrap_or(false) as i32,
null() as *const c_char,
)
};
result_with_ret(out_ptr, ret)
}
pub fn write_to_file(&self, path: impl AsRef<Path>) -> Result<()> {
let path = path_to_cstring(path.as_ref())?;
let ret = unsafe {
vips_sys::vips_image_write_to_file(
self.c.as_ptr(),
path.as_ptr(),
null() as *const c_char,
)
};
result_draw(ret)
}
pub fn write_to_memory(&self) -> Result<Vec<u8>> {
unsafe {
let mut result_size: usize = 0;
let ptr = vips_sys::vips_image_write_to_memory(
self.c.as_ptr(),
&mut result_size as *mut usize,
) as *mut u8;
if ptr.is_null() {
return Err(Error::Vips(
take_vips_error().unwrap_or_else(|| "Unknown error from libvips".to_string()),
));
}
let slice = std::slice::from_raw_parts(ptr as *const u8, result_size);
let vec = slice.to_vec();
vips_sys::g_free(ptr as *mut c_void);
Ok(vec)
}
}
pub fn write_jpeg(&self, path: impl AsRef<Path>, q: Option<i32>) -> Result<()> {
let path = path_to_cstring(path.as_ref())?;
let ret = unsafe {
match q {
Some(q) => vips_sys::vips_jpegsave(
self.c.as_ptr(),
path.as_ptr(),
c"Q".as_ptr(),
q,
null() as *const c_char,
),
None => {
vips_sys::vips_jpegsave(self.c.as_ptr(), path.as_ptr(), null() as *const c_char)
}
}
};
result_draw(ret)
}
}
fn result<'a>(ptr: *mut vips_sys::VipsImage) -> Result<VipsImage<'a>> {
let owned = ffi::image_from_ptr(ptr)?;
Ok(unsafe { VipsImage::from_raw(owned.into_ptr()) })
}
fn result_with_ret<'a>(ptr: *mut vips_sys::VipsImage, ret: c_int) -> Result<VipsImage<'a>> {
let owned = ffi::image_from_ret(ptr, ret)?;
Ok(unsafe { VipsImage::from_raw(owned.into_ptr()) })
}
fn result_draw(ret: c_int) -> Result<()> {
ffi::ret_to_result(ret)
}