use ndarray::{Array3, ArrayView3};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChannelOrder {
RGB,
BGR,
}
#[derive(Debug, Clone)]
pub struct ImageBuffer {
data: Array3<u8>,
}
impl ImageBuffer {
pub fn from_ndarray(data: Array3<u8>, channel_order: ChannelOrder) -> Self {
let data = match channel_order {
ChannelOrder::RGB => data,
ChannelOrder::BGR => Self::bgr_to_rgb(data),
};
Self { data }
}
#[inline]
pub fn from_rgb(data: Array3<u8>) -> Self {
Self { data }
}
#[inline]
pub fn from_bgr(data: Array3<u8>) -> Self {
Self::from_ndarray(data, ChannelOrder::BGR)
}
pub fn zeros(height: usize, width: usize, channels: usize) -> Self {
Self {
data: Array3::zeros((height, width, channels)),
}
}
#[inline]
pub fn height(&self) -> usize {
self.data.shape()[0]
}
#[inline]
pub fn width(&self) -> usize {
self.data.shape()[1]
}
#[inline]
pub fn channels(&self) -> usize {
self.data.shape()[2]
}
#[inline]
pub fn shape(&self) -> (usize, usize, usize) {
(self.height(), self.width(), self.channels())
}
#[inline]
pub fn as_array(&self) -> ArrayView3<'_, u8> {
self.data.view()
}
#[inline]
pub fn data(&self) -> &Array3<u8> {
&self.data
}
#[inline]
pub fn into_array(self) -> Array3<u8> {
self.data
}
#[inline]
pub fn as_slice(&self) -> Option<&[u8]> {
self.data.as_slice()
}
fn bgr_to_rgb(mut data: Array3<u8>) -> Array3<u8> {
let shape = data.shape();
let height = shape[0];
let width = shape[1];
for h in 0..height {
for w in 0..width {
let b = data[[h, w, 0]];
let r = data[[h, w, 2]];
data[[h, w, 0]] = r;
data[[h, w, 2]] = b;
}
}
data
}
pub fn to_bgr(&self) -> Array3<u8> {
let mut bgr = self.data.clone();
let shape = bgr.shape();
let height = shape[0];
let width = shape[1];
for h in 0..height {
for w in 0..width {
let r = bgr[[h, w, 0]];
let b = bgr[[h, w, 2]];
bgr[[h, w, 0]] = b;
bgr[[h, w, 2]] = r;
}
}
bgr
}
}
#[cfg(any(feature = "ort-backend", feature = "rknn-backend", feature = "tensorrt-backend"))]
mod image_impl {
use super::*;
use image::{DynamicImage, RgbImage};
impl ImageBuffer {
pub fn from_dynamic_image(img: DynamicImage) -> Self {
let rgb = img.to_rgb8();
let (width, height) = rgb.dimensions();
let raw = rgb.into_raw();
let data = Array3::from_shape_vec(
(height as usize, width as usize, 3),
raw,
)
.expect("Shape mismatch when converting DynamicImage to ImageBuffer");
Self { data }
}
pub fn from_rgb_image(img: RgbImage) -> Self {
let (width, height) = img.dimensions();
let raw = img.into_raw();
let data = Array3::from_shape_vec(
(height as usize, width as usize, 3),
raw,
)
.expect("Shape mismatch when converting RgbImage to ImageBuffer");
Self { data }
}
pub fn to_rgb_image(&self) -> RgbImage {
let (height, width, _) = self.shape();
let raw: Vec<u8> = self.data.iter().cloned().collect();
RgbImage::from_raw(width as u32, height as u32, raw)
.expect("Failed to convert ImageBuffer to RgbImage")
}
pub fn to_dynamic_image(&self) -> DynamicImage {
DynamicImage::ImageRgb8(self.to_rgb_image())
}
}
impl From<DynamicImage> for ImageBuffer {
fn from(img: DynamicImage) -> Self {
Self::from_dynamic_image(img)
}
}
impl From<RgbImage> for ImageBuffer {
fn from(img: RgbImage) -> Self {
Self::from_rgb_image(img)
}
}
}
#[cfg(any(feature = "opencv-backend", feature = "ort-opencv-compat", feature = "tensorrt-opencv-compat"))]
mod opencv_impl {
use super::*;
use opencv::core::{Mat, MatTraitConst};
impl ImageBuffer {
pub fn from_mat(mat: &Mat) -> Result<Self, opencv::Error> {
let mat = if mat.is_continuous() {
mat.clone()
} else {
let mut continuous = Mat::default();
mat.copy_to(&mut continuous)?;
continuous
};
let rows = mat.rows() as usize;
let cols = mat.cols() as usize;
let channels = mat.channels() as usize;
let data_ptr = mat.data();
let total_bytes = rows * cols * channels;
let raw_data: Vec<u8> = unsafe {
std::slice::from_raw_parts(data_ptr, total_bytes).to_vec()
};
let data = Array3::from_shape_vec((rows, cols, channels), raw_data)
.map_err(|e| opencv::Error::new(
opencv::core::StsError,
format!("Failed to create ndarray: {}", e),
))?;
Ok(Self::from_ndarray(data, ChannelOrder::BGR))
}
pub fn to_mat(&self) -> Result<Mat, opencv::Error> {
let bgr_data = self.to_bgr();
let (height, width, channels) = (
bgr_data.shape()[0] as i32,
bgr_data.shape()[1] as i32,
bgr_data.shape()[2] as i32,
);
let raw: Vec<u8> = bgr_data.into_iter().collect();
let mat = Mat::from_slice(&raw)?;
mat.reshape_nd(channels, &[height, width])?.try_clone()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_image_buffer_zeros() {
let buf = ImageBuffer::zeros(480, 640, 3);
assert_eq!(buf.height(), 480);
assert_eq!(buf.width(), 640);
assert_eq!(buf.channels(), 3);
}
#[test]
fn test_image_buffer_from_rgb() {
let data = Array3::from_elem((100, 100, 3), 128u8);
let buf = ImageBuffer::from_rgb(data);
assert_eq!(buf.shape(), (100, 100, 3));
}
#[test]
fn test_bgr_to_rgb_conversion() {
let mut bgr_data = Array3::zeros((2, 2, 3));
bgr_data[[0, 0, 0]] = 1;
bgr_data[[0, 0, 1]] = 2;
bgr_data[[0, 0, 2]] = 3;
let buf = ImageBuffer::from_bgr(bgr_data);
assert_eq!(buf.data[[0, 0, 0]], 3);
assert_eq!(buf.data[[0, 0, 1]], 2);
assert_eq!(buf.data[[0, 0, 2]], 1);
}
}