use crate::Error;
use apriltag_sys as sys;
use std::{
ffi::{c_int, c_uint, CString},
iter,
mem::ManuallyDrop,
ops::{Index, IndexMut},
ptr::NonNull,
slice,
};
pub const DEFAULT_ALIGNMENT_U8: usize = 96;
#[derive(Debug)]
#[repr(transparent)]
pub struct Image {
pub(crate) ptr: NonNull<sys::image_u8_t>,
}
impl Image {
pub unsafe fn new_uinit(width: usize, height: usize) -> Result<Self, Error> {
let ptr = sys::image_u8_create(width as c_uint, height as c_uint);
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: "image_u8_create() failed".to_string(),
})?;
Ok(Self { ptr })
}
pub unsafe fn new_uinit_with_stride(
width: usize,
height: usize,
stride: usize,
) -> Result<Self, Error> {
let ptr = sys::image_u8_create_stride(width as c_uint, height as c_uint, stride as c_uint);
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: "image_u8_create() failed".to_string(),
})?;
Ok(Self { ptr })
}
pub fn from_pnm_file(path: &str) -> Result<Self, Error> {
let cstr = CString::new(path).map_err(|_| Error::CreateImageError {
reason: format!("the path '{path}' contains null byte(s)"),
})?;
let ptr = unsafe { sys::image_u8_create_from_pnm(cstr.as_ptr()) };
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: format!("failed to load '{path}' using image_u8_create_from_pnm()"),
})?;
Ok(Self { ptr })
}
pub fn from_pnm_file_with_alignment(path: &str, alignment: usize) -> Result<Self, Error> {
let cstr = CString::new(path).map_err(|_| Error::CreateImageError {
reason: format!("the path '{path}' contains null byte(s)"),
})?;
let ptr =
unsafe { sys::image_u8_create_from_pnm_alignment(cstr.as_ptr(), alignment as c_int) };
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: format!("failed to load '{path}' using image_u8_create_from_pnm()"),
})?;
Ok(Self { ptr })
}
pub fn zeros_with_stride(width: usize, height: usize, stride: usize) -> Result<Self, Error> {
if width > stride {
return Err(Error::CreateImageError {
reason: format!("width ({width}) must be less than or equal to stride ({stride})"),
});
}
let ptr = unsafe {
sys::image_u8_create_stride(width as c_uint, height as c_uint, stride as c_uint)
};
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: "image_u8_create_stride() failed".to_string(),
})?;
Ok(Self { ptr })
}
pub fn zeros_with_alignment(
width: usize,
height: usize,
alignment: usize,
) -> Result<Self, Error> {
if alignment == 0 {
return Err(Error::CreateImageError {
reason: format!("alignment ({alignment}) must positive"),
});
}
let ptr = unsafe {
sys::image_u8_create_alignment(width as c_uint, height as c_uint, alignment as c_uint)
};
let ptr = NonNull::new(ptr).ok_or_else(|| Error::CreateImageError {
reason: "image_u8_create_alignment() failed".to_string(),
})?;
Ok(Self { ptr })
}
pub fn samples_iter(&self) -> impl Iterator<Item = u8> + '_ {
let height = self.height();
let width = self.width();
let stride = self.stride();
let buffer = self.as_slice();
let row_offsets =
iter::successors(Some(0), move |&offset| Some(offset + stride)).take(height);
let pixel_offsets =
row_offsets.flat_map(move |row_offset| row_offset..(row_offset + width));
pixel_offsets.map(move |offset| buffer[offset])
}
pub fn indexed_samples_iter(&self) -> impl Iterator<Item = (usize, usize, u8)> + '_ {
let height = self.height();
let width = self.width();
let stride = self.stride();
let buffer = self.as_slice();
let row_offsets = iter::successors(Some(0), move |&offset| Some(offset + stride))
.take(height)
.enumerate();
let pixel_offsets = row_offsets.flat_map(move |(row, row_offset)| {
let offsets = row_offset..(row_offset + width);
offsets
.enumerate()
.map(move |(col, offset)| (row, col, offset))
});
pixel_offsets.map(move |(row, col, offset)| (col, row, buffer[offset]))
}
pub fn width(&self) -> usize {
unsafe { self.ptr.as_ref().width as usize }
}
pub fn height(&self) -> usize {
unsafe { self.ptr.as_ref().height as usize }
}
pub fn stride(&self) -> usize {
unsafe { self.ptr.as_ref().stride as usize }
}
pub fn as_slice(&self) -> &[u8] {
unsafe {
let image = self.ptr.as_ref();
let len = (image.height * image.stride) as usize;
slice::from_raw_parts(image.buf, len)
}
}
pub fn as_slice_mut(&mut self) -> &mut [u8] {
unsafe {
let image = self.ptr.as_ref();
let len = (image.height * image.stride) as usize;
slice::from_raw_parts_mut(image.buf, len)
}
}
pub unsafe fn from_raw(ptr: *mut sys::image_u8_t) -> Self {
Self {
ptr: NonNull::new(ptr).unwrap(),
}
}
pub fn into_raw(self) -> NonNull<sys::image_u8_t> {
ManuallyDrop::new(self).ptr
}
}
impl Index<(usize, usize)> for Image {
type Output = u8;
fn index(&self, (x, y): (usize, usize)) -> &Self::Output {
let stride = self.stride();
&self.as_ref()[x + y * stride]
}
}
impl IndexMut<(usize, usize)> for Image {
fn index_mut(&mut self, (x, y): (usize, usize)) -> &mut Self::Output {
let stride = self.stride();
&mut self.as_mut()[x + y * stride]
}
}
impl AsRef<[u8]> for Image {
fn as_ref(&self) -> &[u8] {
unsafe { slice::from_raw_parts(self.ptr.as_ref().buf, self.stride() * self.height()) }
}
}
impl AsMut<[u8]> for Image {
fn as_mut(&mut self) -> &mut [u8] {
unsafe { slice::from_raw_parts_mut(self.ptr.as_ref().buf, self.stride() * self.height()) }
}
}
impl Clone for Image {
fn clone(&self) -> Self {
let cloned_ptr = unsafe { sys::image_u8_copy(self.ptr.as_ptr()) };
Self {
ptr: NonNull::new(cloned_ptr).unwrap(),
}
}
}
impl Drop for Image {
fn drop(&mut self) {
unsafe {
sys::image_u8_destroy(self.ptr.as_ptr());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn image_clone() {
let width = 80;
let height = 60;
let from_image = diagonal_image(width, height);
let to_image = from_image.clone();
assert_eq!(from_image.width(), to_image.width());
assert_eq!(from_image.height(), to_image.height());
assert_eq!(from_image.stride(), to_image.stride());
(0..height)
.into_iter()
.flat_map(|y| (0..width).into_iter().map(move |x| (x, y)))
.for_each(|(x, y)| {
assert_eq!(from_image[(x, y)], to_image[(x, y)]);
});
}
fn diagonal_image(width: usize, height: usize) -> Image {
let mut image = Image::zeros_with_alignment(width, height, DEFAULT_ALIGNMENT_U8).unwrap();
(0..width.min(height)).into_iter().for_each(|index| {
image[(index, index)] = 255;
});
image
}
}