use std::{
rc::Rc,
sync::atomic::{AtomicU64, Ordering},
};
use crate::runtime::{EngineHandle, Storage};
use crate::{DType, Error, Matrix, Result};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImageLayout {
Nchw,
Nhwc,
Chw,
Hwc,
Hw,
}
impl ImageLayout {
pub const fn rank(self) -> usize {
match self {
Self::Nchw | Self::Nhwc => 4,
Self::Chw | Self::Hwc => 3,
Self::Hw => 2,
}
}
const fn axes(self) -> ImageAxes {
match self {
Self::Nchw => ImageAxes {
batch: Some(0),
channel: Some(1),
height: 2,
width: 3,
},
Self::Nhwc => ImageAxes {
batch: Some(0),
channel: Some(3),
height: 1,
width: 2,
},
Self::Chw => ImageAxes {
batch: None,
channel: Some(0),
height: 1,
width: 2,
},
Self::Hwc => ImageAxes {
batch: None,
channel: Some(2),
height: 0,
width: 1,
},
Self::Hw => ImageAxes {
batch: None,
channel: None,
height: 0,
width: 1,
},
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ImageFormat {
Gray,
GrayAlpha,
Rgb,
Rgba,
Bgr,
Bgra,
}
impl ImageFormat {
pub const fn channels(self) -> usize {
match self {
Self::Gray => 1,
Self::GrayAlpha => 2,
Self::Rgb | Self::Bgr => 3,
Self::Rgba | Self::Bgra => 4,
}
}
}
#[derive(Clone)]
pub struct Image {
data: Matrix,
layout: ImageLayout,
format: ImageFormat,
semantic: Rc<ImageSemantic>,
}
pub(crate) struct ImageSemantic {
id: u64,
}
impl Image {
pub fn new(data: Matrix, layout: ImageLayout, format: ImageFormat) -> Result<Self> {
validate_image(&data, layout, format)?;
Ok(Self {
data,
layout,
format,
semantic: Rc::new(ImageSemantic {
id: next_image_value_id()?,
}),
})
}
pub const fn as_matrix(&self) -> &Matrix {
&self.data
}
pub fn into_matrix(self) -> Matrix {
self.data
}
pub fn width(&self) -> usize {
self.data.shape()[self.layout.axes().width]
}
pub fn height(&self) -> usize {
self.data.shape()[self.layout.axes().height]
}
pub const fn channels(&self) -> usize {
self.format.channels()
}
pub fn batch_size(&self) -> usize {
self
.layout
.axes()
.batch
.map_or(1, |axis| self.data.shape()[axis])
}
pub const fn layout(&self) -> ImageLayout {
self.layout
}
pub const fn format(&self) -> ImageFormat {
self.format
}
pub const fn dtype(&self) -> DType {
self.data.dtype()
}
pub fn grayscale(&self) -> Result<Self> {
crate::image::grayscale(self)
}
pub fn convert_color(&self, destination_format: crate::ImageFormat) -> Result<Self> {
crate::image::convert_color(self, destination_format)
}
pub fn flip(&self, horizontal: bool, vertical: bool) -> Result<Self> {
crate::image::flip(self, horizontal, vertical)
}
pub fn rotate(&self, degrees: u32) -> Result<Self> {
crate::image::rotate(self, degrees)
}
pub fn resize(
&self,
width: u32,
height: u32,
mode: crate::image::InterpolationMode,
) -> Result<Self> {
crate::image::resize(self, width, height, mode)
}
pub fn crop(&self, x: u32, y: u32, width: u32, height: u32) -> Result<Self> {
crate::image::crop(self, x, y, width, height)
}
pub fn center_crop(&self, width: u32, height: u32) -> Result<Self> {
crate::image::center_crop(self, width, height)
}
pub fn invert(&self, max_value: f32) -> Result<Self> {
crate::image::invert(self, max_value)
}
pub fn brightness_contrast(&self, brightness: f32, contrast: f32) -> Result<Self> {
crate::image::brightness_contrast(self, brightness, contrast)
}
pub fn gamma_contrast(&self, gamma: f32, gain: f32) -> Result<Self> {
crate::image::gamma_contrast(self, gamma, gain)
}
pub fn normalize(&self, parameters: crate::image::NormalizationParams) -> Result<Self> {
crate::image::normalize(self, parameters)
}
pub fn gaussian_blur(&self, sigma: f32, kernel_size: u32) -> Result<Self> {
crate::image::gaussian_blur(self, sigma, kernel_size)
}
pub fn laplacian(&self, border: crate::image::BorderMode) -> Result<Self> {
crate::image::laplacian(self, border)
}
pub fn sharpen(&self, amount: f32, border: crate::image::BorderMode) -> Result<Self> {
crate::image::sharpen(self, amount, border)
}
pub(crate) const fn engine_handle(&self) -> &EngineHandle {
self.data.engine_handle()
}
pub(crate) fn storage(&self) -> &Storage {
self.data.storage()
}
pub(crate) fn value_id(&self) -> u64 {
self.semantic.id
}
}
fn next_image_value_id() -> Result<u64> {
static NEXT_IMAGE_VALUE_ID: AtomicU64 = AtomicU64::new(1);
NEXT_IMAGE_VALUE_ID
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |value| {
value.checked_add(1)
})
.map_err(|_| Error::resource_exhausted("image semantic value identity space is exhausted"))
}
#[derive(Clone, Copy)]
struct ImageAxes {
batch: Option<usize>,
channel: Option<usize>,
height: usize,
width: usize,
}
fn validate_image(data: &Matrix, layout: ImageLayout, format: ImageFormat) -> Result<()> {
if data.shape().len() != layout.rank() {
return Err(Error::invalid_argument(format!(
"image layout {layout:?} requires rank {}, but the matrix has rank {}",
layout.rank(),
data.shape().len()
)));
}
let axes = layout.axes();
if let Some(channel_axis) = axes.channel {
let actual = data.shape()[channel_axis];
let required = format.channels();
if actual != required {
return Err(Error::invalid_argument(format!(
"image format {format:?} requires {required} channels, but axis {channel_axis} has {actual}"
)));
}
} else if format != ImageFormat::Gray {
return Err(Error::invalid_argument(format!(
"image layout {layout:?} has no channel axis and requires Gray format"
)));
}
Ok(())
}