use std::ops::{Index, IndexMut};
use ironlab_ir::{Color, IrError, NdArray, Values};
use crate::error::Error;
use crate::matrix::Matrix;
#[derive(Debug, Clone, PartialEq)]
pub struct Pixels {
rows: usize,
cols: usize,
channels: usize,
bytes: Vec<u8>,
}
impl Pixels {
pub fn from_rgb8(rows: usize, cols: usize, bytes: &[u8]) -> Result<Self, Error> {
Self::from_bytes(rows, cols, 3, bytes)
}
pub fn from_rgba8(rows: usize, cols: usize, bytes: &[u8]) -> Result<Self, Error> {
Self::from_bytes(rows, cols, 4, bytes)
}
#[must_use]
pub fn rgb_from_fn(rows: usize, cols: usize, f: impl FnMut(usize, usize) -> Color) -> Self {
Self::from_fn(rows, cols, 3, f)
}
#[must_use]
pub fn rgba_from_fn(rows: usize, cols: usize, f: impl FnMut(usize, usize) -> Color) -> Self {
Self::from_fn(rows, cols, 4, f)
}
pub fn from_planes(r: &Matrix, g: &Matrix, b: &Matrix) -> Result<Self, Error> {
let expected = [r.rows(), r.cols()];
check_shape(expected, g)?;
check_shape(expected, b)?;
let mut bytes = Vec::with_capacity(r.values().len() * 3);
let pixels = r.values().iter().zip(g.values()).zip(b.values());
for (index, ((&red, &green), &blue)) in pixels.enumerate() {
let (row, col) = (index / r.cols(), index % r.cols());
for (channel, component) in [red, green, blue].into_iter().enumerate() {
bytes.push(checked_level(component, row, col, channel)?);
}
}
Ok(Self {
rows: r.rows(),
cols: r.cols(),
channels: 3,
bytes,
})
}
pub fn with_alpha(self, alpha: &Matrix) -> Result<Self, Error> {
check_shape([self.rows, self.cols], alpha)?;
let mut bytes = Vec::with_capacity(alpha.values().len() * 4);
let pixels = self.bytes.chunks_exact(self.channels).zip(alpha.values());
for (index, (pixel, &opacity)) in pixels.enumerate() {
bytes.extend_from_slice(&pixel[..3]);
bytes.push(checked_level(
opacity,
index / self.cols,
index % self.cols,
3,
)?);
}
Ok(Self {
rows: self.rows,
cols: self.cols,
channels: 4,
bytes,
})
}
#[must_use]
pub fn rows(&self) -> usize {
self.rows
}
#[must_use]
pub fn cols(&self) -> usize {
self.cols
}
#[must_use]
pub fn channels(&self) -> usize {
self.channels
}
#[must_use]
pub fn bytes(&self) -> &[u8] {
&self.bytes
}
fn from_bytes(rows: usize, cols: usize, channels: usize, bytes: &[u8]) -> Result<Self, Error> {
let expected = rows
.checked_mul(cols)
.and_then(|pixels| pixels.checked_mul(channels));
if expected == Some(bytes.len()) {
Ok(Self {
rows,
cols,
channels,
bytes: bytes.to_vec(),
})
} else {
Err(Error::Ir(IrError::InvalidShape {
shape: vec![rows, cols, channels],
len: bytes.len(),
}))
}
}
fn from_fn(
rows: usize,
cols: usize,
channels: usize,
mut f: impl FnMut(usize, usize) -> Color,
) -> Self {
let bytes = (0..rows)
.flat_map(|row| (0..cols).map(move |col| (row, col)))
.flat_map(|(row, col)| {
let Color { r, g, b, a } = f(row, col);
[r, g, b, a]
.into_iter()
.take(channels)
.map(|component| level(f64::from(component)))
})
.collect();
Self {
rows,
cols,
channels,
bytes,
}
}
pub(crate) fn to_array(&self) -> NdArray {
NdArray {
shape: vec![self.rows, self.cols, self.channels],
values: Values::U8(self.bytes.clone()),
}
}
}
fn level(component: f64) -> u8 {
(component.clamp(0.0, 1.0) * 255.0).round() as u8
}
fn checked_level(component: f64, row: usize, col: usize, channel: usize) -> Result<u8, Error> {
if component.is_finite() {
Ok(level(component))
} else {
Err(Error::NonFiniteComponent { row, col, channel })
}
}
fn check_shape(expected: [usize; 2], plane: &Matrix) -> Result<(), Error> {
let found = [plane.rows(), plane.cols()];
if found == expected {
Ok(())
} else {
Err(Error::PlaneShapeMismatch { expected, found })
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ByteMatrix {
rows: usize,
cols: usize,
values: Vec<u8>,
}
impl ByteMatrix {
#[must_use]
pub fn zeros(rows: usize, cols: usize) -> Self {
Self {
rows,
cols,
values: vec![0; rows * cols],
}
}
#[must_use]
pub fn from_fn(rows: usize, cols: usize, mut f: impl FnMut(usize, usize) -> u8) -> Self {
let values = (0..rows)
.flat_map(|row| (0..cols).map(move |col| (row, col)))
.map(|(row, col)| f(row, col))
.collect();
Self { rows, cols, values }
}
#[must_use]
pub fn from_rows<R: AsRef<[u8]>>(rows: &[R]) -> Self {
let cols = rows.first().map_or(0, |row| row.as_ref().len());
assert!(
rows.iter().all(|row| row.as_ref().len() == cols),
"matrix rows have different lengths"
);
Self {
rows: rows.len(),
cols,
values: rows.iter().flat_map(|row| row.as_ref()).copied().collect(),
}
}
pub fn from_vec(rows: usize, cols: usize, values: Vec<u8>) -> Result<Self, IrError> {
if rows.checked_mul(cols) == Some(values.len()) {
Ok(Self { rows, cols, values })
} else {
Err(IrError::InvalidShape {
shape: vec![rows, cols],
len: values.len(),
})
}
}
#[must_use]
pub fn rows(&self) -> usize {
self.rows
}
#[must_use]
pub fn cols(&self) -> usize {
self.cols
}
#[must_use]
pub fn values(&self) -> &[u8] {
&self.values
}
#[must_use]
pub fn into_values(self) -> Vec<u8> {
self.values
}
fn flat_index(&self, row: usize, col: usize) -> usize {
assert!(
row < self.rows && col < self.cols,
"index ({row}, {col}) out of range for a {}x{} matrix",
self.rows,
self.cols
);
row * self.cols + col
}
}
impl Index<(usize, usize)> for ByteMatrix {
type Output = u8;
fn index(&self, (row, col): (usize, usize)) -> &u8 {
&self.values[self.flat_index(row, col)]
}
}
impl IndexMut<(usize, usize)> for ByteMatrix {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut u8 {
let index = self.flat_index(row, col);
&mut self.values[index]
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ImageValues {
Floats(Matrix),
Bytes(ByteMatrix),
}
impl ImageValues {
pub(crate) fn into_array(self) -> NdArray {
match self {
ImageValues::Floats(matrix) => NdArray {
shape: vec![matrix.rows(), matrix.cols()],
values: Values::F64(matrix.into_values()),
},
ImageValues::Bytes(matrix) => NdArray {
shape: vec![matrix.rows(), matrix.cols()],
values: Values::U8(matrix.into_values()),
},
}
}
}
impl From<Matrix> for ImageValues {
fn from(matrix: Matrix) -> Self {
ImageValues::Floats(matrix)
}
}
impl From<&Matrix> for ImageValues {
fn from(matrix: &Matrix) -> Self {
ImageValues::Floats(matrix.clone())
}
}
impl From<ByteMatrix> for ImageValues {
fn from(matrix: ByteMatrix) -> Self {
ImageValues::Bytes(matrix)
}
}
impl From<&ByteMatrix> for ImageValues {
fn from(matrix: &ByteMatrix) -> Self {
ImageValues::Bytes(matrix.clone())
}
}