use crate::image::Image;
use crate::layout;
use crate::layout::{Layout, MismatchedPixelError, TexelLayout, TryMend};
use crate::texel::{AsTexel, Texel};
use core::ops::Range;
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct StrideSpec {
pub width: usize,
pub height: usize,
pub element: TexelLayout,
pub width_stride: usize,
pub height_stride: usize,
pub offset: usize,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct StridedBytes {
spec: StrideSpec,
total: usize,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct Strides<T> {
inner: StridedBytes,
texel: Texel<T>,
}
#[derive(Debug)]
pub struct BadStrideError {
#[allow(dead_code)]
kind: BadStrideKind,
}
#[derive(Debug)]
enum BadStrideKind {
UnalignedOffset,
UnalignedWidthStride,
UnalignedHeightStride,
OutOfMemory,
}
pub struct StridedBufferRef<'data> {
layout: StridedBytes,
data: &'data [u8],
}
pub struct StridedBufferMut<'data> {
layout: StridedBytes,
data: &'data mut [u8],
}
impl StrideSpec {
fn matches(&self, other: &Self) -> bool {
self.element.size() == other.element.size()
&& self.width == other.width
&& self.height == other.height
}
fn has_contiguous_rows(&self) -> bool {
self.element.size() == self.width_stride
}
fn has_contiguous_cols(&self) -> bool {
self.element.size() == self.height_stride
}
fn element_start(&self, row: usize, col: usize) -> usize {
(row * self.height_stride) + (col * self.width_stride) + self.offset
}
fn element(&self, row: usize, col: usize) -> Range<usize> {
let start = self.element_start(row, col);
start..start + self.element.size()
}
fn contiguous_row(&self, row: usize) -> Range<usize> {
let start = self.element_start(row, 0);
let length = self.width * self.element.size();
start..start + length
}
fn contiguous_col(&self, col: usize) -> Range<usize> {
let start = self.element_start(0, col);
let length = self.height * self.element.size();
start..start + length
}
fn end(&self) -> Option<usize> {
if self.height == 0 || self.width == 0 {
return Some(self.offset);
}
let max_w = self.width - 1;
let max_h = self.height - 1;
let max_w_offset = max_w.checked_mul(self.width_stride)?;
let max_h_offset = max_h.checked_mul(self.height_stride)?;
let relative_past_end = self
.element
.size()
.checked_add(max_h_offset)?
.checked_add(max_w_offset)?;
let total = relative_past_end.checked_add(self.offset)?;
Some(total)
}
}
impl StridedBytes {
pub fn new(spec: StrideSpec) -> Result<Self, BadStrideError> {
if spec.offset % spec.element.align() != 0 {
return Err(BadStrideKind::UnalignedOffset.into());
}
if spec.width_stride % spec.element.align() != 0 {
return Err(BadStrideKind::UnalignedWidthStride.into());
}
if spec.height_stride % spec.element.align() != 0 {
return Err(BadStrideKind::UnalignedHeightStride.into());
}
let total = spec.end().ok_or(BadStrideKind::OutOfMemory)?;
Ok(StridedBytes { spec, total })
}
pub fn with_repeated_width_and_height(
element: TexelLayout,
width: usize,
height: usize,
) -> Self {
StridedBytes {
spec: StrideSpec {
element,
width,
height,
height_stride: 0,
width_stride: 0,
offset: 0,
},
total: element.size(),
}
}
pub fn with_column_major(matrix: layout::MatrixBytes) -> Self {
StridedBytes {
spec: StrideSpec {
element: matrix.element(),
width: matrix.width(),
height: matrix.height(),
height_stride: matrix.element().size(),
width_stride: matrix.height() * matrix.element().size(),
offset: 0,
},
total: matrix.byte_len(),
}
}
pub fn with_row_major(matrix: layout::MatrixBytes) -> Self {
StridedBytes {
spec: StrideSpec {
element: matrix.element(),
width: matrix.width(),
height: matrix.height(),
height_stride: matrix.width() * matrix.element().size(),
width_stride: matrix.element().size(),
offset: 0,
},
total: matrix.byte_len(),
}
}
pub fn spec(&self) -> StrideSpec {
self.spec
}
pub fn shrink_element(&mut self, new: TexelLayout) {
self.spec.element = self.spec.element.infimum(new);
}
fn matches(&self, other: &Self) -> bool {
self.spec.matches(&other.spec)
}
fn contiguous_rows(&self) -> Option<impl Iterator<Item = Range<usize>> + '_> {
if self.spec.has_contiguous_rows() {
Some((0..self.spec.height).map(move |row| self.spec.contiguous_row(row)))
} else {
None
}
}
fn contiguous_columns(&self) -> Option<impl Iterator<Item = Range<usize>> + '_> {
if self.spec.has_contiguous_cols() {
Some((0..self.spec.width).map(move |row| self.spec.contiguous_col(row)))
} else {
None
}
}
fn pixel(&self, x: usize, y: usize) -> Range<usize> {
self.spec.element(x, y)
}
}
impl<T> Strides<T> {
pub fn with_texel(texel: Texel<T>, bytes: StridedBytes) -> Option<Self> {
if TexelLayout::from(texel) == bytes.spec.element {
Some(Strides {
inner: bytes,
texel,
})
} else {
None
}
}
pub fn spec(&self) -> StrideSpec {
self.inner.spec()
}
pub fn texel(&self) -> Texel<T> {
self.texel
}
}
impl<'data> StridedBufferRef<'data> {
pub fn new(image: &'data Image<impl StridedLayout>) -> Self {
let layout = image.layout().strided();
let data = &image.as_bytes()[..layout.total];
StridedBufferRef { layout, data }
}
pub fn with_bytes(layout: StridedBytes, content: &'data [u8]) -> Option<Self> {
let data = content
.get(..layout.total)
.filter(|data| data.as_ptr() as usize % layout.spec.element.align() == 0)?;
Some(StridedBufferRef { layout, data })
}
pub fn with_repeated_element<T: AsTexel>(el: &'data T, width: usize, height: usize) -> Self {
let texel = T::texel();
let layout = StridedBytes::with_repeated_width_and_height(texel.into(), width, height);
let data = texel.to_bytes(core::slice::from_ref(el));
StridedBufferRef { layout, data }
}
pub fn shrink_element(&mut self, new: TexelLayout) -> TexelLayout {
self.layout.shrink_element(new);
self.layout.spec.element
}
pub fn as_ref(&self) -> StridedBufferRef<'_> {
StridedBufferRef {
layout: self.layout,
data: &*self.data,
}
}
}
impl<'data> StridedBufferMut<'data> {
pub fn new(image: &'data mut Image<impl StridedLayout>) -> Self {
let layout = image.layout().strided();
let data = &mut image.as_bytes_mut()[..layout.total];
StridedBufferMut { layout, data }
}
pub fn with_bytes(layout: StridedBytes, content: &'data mut [u8]) -> Option<Self> {
let data = content
.get_mut(..layout.total)
.filter(|data| data.as_ptr() as usize % layout.spec.element.align() == 0)?;
Some(StridedBufferMut { layout, data })
}
pub fn shrink_element(&mut self, new: TexelLayout) -> TexelLayout {
self.layout.shrink_element(new);
self.layout.spec.element
}
pub fn copy_from_image(&mut self, source: StridedBufferRef<'_>) {
assert!(self.layout.matches(&source.layout), "Mismatching layouts.");
if let Some(rows) = self.layout.contiguous_rows() {
if let Some(src_rows) = source.layout.contiguous_rows() {
for (row, src) in rows.zip(src_rows) {
self.data[row].copy_from_slice(&source.data[src]);
}
return;
}
}
if let Some(cols) = self.layout.contiguous_columns() {
if let Some(src_cols) = source.layout.contiguous_columns() {
for (col, src) in cols.zip(src_cols) {
self.data[col].copy_from_slice(&source.data[src]);
}
return;
}
}
for x in 0..self.layout.spec.width {
for y in 0..self.layout.spec.height {
let into = self.layout.pixel(x, y);
let from = source.layout.pixel(x, y);
self.data[into].copy_from_slice(&source.data[from]);
}
}
}
pub fn as_ref(&self) -> StridedBufferRef<'_> {
StridedBufferRef {
layout: self.layout,
data: &*self.data,
}
}
pub fn into_ref(self) -> StridedBufferRef<'data> {
StridedBufferRef {
layout: self.layout,
data: self.data,
}
}
}
pub trait StridedLayout: Layout {
fn strided(&self) -> StridedBytes;
}
impl Layout for StridedBytes {
fn byte_len(&self) -> usize {
self.total
}
}
impl StridedLayout for StridedBytes {
fn strided(&self) -> StridedBytes {
*self
}
}
impl<T: StridedLayout> StridedLayout for &'_ T {
fn strided(&self) -> StridedBytes {
(**self).strided()
}
}
impl<T: StridedLayout> StridedLayout for &'_ mut T {
fn strided(&self) -> StridedBytes {
(**self).strided()
}
}
impl<T: StridedLayout> layout::Decay<T> for StridedBytes {
fn decay(from: T) -> Self {
from.strided()
}
}
impl<P: AsTexel> StridedLayout for layout::Matrix<P> {
fn strided(&self) -> StridedBytes {
let matrix: layout::MatrixBytes = self.clone().into();
StridedBytes::with_row_major(matrix)
}
}
impl<P> Layout for Strides<P> {
fn byte_len(&self) -> usize {
self.inner.total
}
}
impl<P> StridedLayout for Strides<P> {
fn strided(&self) -> StridedBytes {
self.inner.clone()
}
}
impl From<BadStrideKind> for BadStrideError {
fn from(kind: BadStrideKind) -> Self {
BadStrideError { kind }
}
}
impl From<&'_ StridedBytes> for StrideSpec {
fn from(layout: &'_ StridedBytes) -> Self {
layout.spec()
}
}
impl<P> TryMend<StridedBytes> for Texel<P> {
type Into = Strides<P>;
type Err = MismatchedPixelError;
fn try_mend(self, matrix: &StridedBytes) -> Result<Strides<P>, Self::Err> {
Strides::with_texel(self, *matrix).ok_or_else(MismatchedPixelError::default)
}
}
#[test]
fn align_validation() {
let matrix = layout::MatrixBytes::from_width_height(TexelLayout::from_pixel::<u16>(), 2, 2)
.expect("Valid matrix");
let layout = StridedBytes::with_row_major(matrix);
let bad_offset = StrideSpec {
offset: 1,
..layout.spec
};
assert!(StridedBytes::new(bad_offset).is_err());
let bad_pitch = StrideSpec {
width_stride: 5,
..layout.spec
};
assert!(StridedBytes::new(bad_pitch).is_err());
}
#[test]
fn image_copies() {
let matrix = layout::MatrixBytes::from_width_height(TexelLayout::from_pixel::<u8>(), 2, 2)
.expect("Valid matrix");
let row_layout = StridedBytes::with_row_major(matrix);
let col_layout = StridedBytes::with_column_major(matrix);
let src = Image::with_bytes(row_layout, &[0u8, 1, 2, 3]);
let mut dst = Image::new(row_layout);
StridedBufferMut::new(&mut dst).copy_from_image(StridedBufferRef::new(&src));
assert_eq!(dst.as_bytes(), &[0u8, 1, 2, 3], "Still in same order");
let mut dst = Image::new(col_layout);
StridedBufferMut::new(&mut dst).copy_from_image(StridedBufferRef::new(&src));
assert_eq!(
dst.as_bytes(),
&[0u8, 2, 1, 3],
"In transposed matrix order"
);
}