use std::alloc::{Layout, alloc_zeroed, dealloc, handle_alloc_error};
use std::marker::PhantomData;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::{
ErrorCategory, ErrorCode, FormatDescriptor, Metadata, MetadataSchema, PixelFlowError, Result,
SampleType,
};
pub trait Sample: sealed::Sealed + Copy + 'static {
const SAMPLE_TYPE: SampleType;
}
mod sealed {
pub trait Sealed {}
impl Sealed for u8 {}
impl Sealed for u16 {}
impl Sealed for f32 {}
}
impl Sample for u8 {
const SAMPLE_TYPE: SampleType = SampleType::U8;
}
impl Sample for u16 {
const SAMPLE_TYPE: SampleType = SampleType::U16;
}
impl Sample for f32 {
const SAMPLE_TYPE: SampleType = SampleType::F32;
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct AllocatorConfig {
alignment: usize,
}
impl AllocatorConfig {
#[must_use]
pub const fn new(alignment: usize) -> Self {
Self { alignment }
}
#[must_use]
pub const fn actual_alignment(self) -> usize {
let requested = if self.alignment < 64 {
64
} else {
self.alignment
};
if requested.is_power_of_two() {
requested
} else {
requested.next_power_of_two()
}
}
}
impl Default for AllocatorConfig {
fn default() -> Self {
Self { alignment: 64 }
}
}
#[derive(Debug)]
struct AlignedBuffer {
ptr: NonNull<u8>,
len: usize,
alignment: usize,
}
unsafe impl Send for AlignedBuffer {}
unsafe impl Sync for AlignedBuffer {}
impl AlignedBuffer {
fn new_zeroed(len: usize, config: AllocatorConfig) -> Self {
let alignment = config.actual_alignment();
let layout = Layout::from_size_align(len.max(1), alignment)
.expect("alignment must be non-zero power of two");
let ptr = unsafe { alloc_zeroed(layout) };
let ptr = NonNull::new(ptr).unwrap_or_else(|| handle_alloc_error(layout));
Self {
ptr,
len,
alignment,
}
}
const fn as_ptr(&self) -> *const u8 {
self.ptr.as_ptr()
}
const fn as_mut_ptr(&mut self) -> *mut u8 {
unsafe { self.ptr.as_mut() }
}
}
impl Drop for AlignedBuffer {
fn drop(&mut self) {
let layout = Layout::from_size_align(self.len.max(1), self.alignment)
.expect("stored allocation layout must remain valid");
unsafe {
dealloc(self.ptr.as_ptr(), layout);
}
}
}
#[derive(Clone, Debug)]
struct PlaneStorage {
buffer: Arc<AlignedBuffer>,
sample_type: SampleType,
}
#[derive(Clone, Debug)]
struct PlaneView {
storage: PlaneStorage,
offset_bytes: usize,
stride_bytes: usize,
width: usize,
height: usize,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RawPlane {
pub ptr: *const u8,
pub stride_bytes: usize,
pub width: usize,
pub height: usize,
pub sample_type: SampleType,
}
#[repr(C)]
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct RawPlaneMut {
pub ptr: *mut u8,
pub stride_bytes: usize,
pub width: usize,
pub height: usize,
pub sample_type: SampleType,
}
#[derive(Clone)]
pub struct Frame {
format: FormatDescriptor,
width: usize,
height: usize,
planes: Vec<PlaneView>,
metadata: Metadata,
alignment: usize,
}
pub struct FrameBuilder {
format: FormatDescriptor,
width: usize,
height: usize,
planes: Vec<PlaneView>,
metadata: Metadata,
alignment: usize,
}
#[derive(Debug)]
pub struct Plane<T: Sample> {
view: PlaneView,
_sample: PhantomData<T>,
}
#[derive(Debug)]
pub struct PlaneMut<'a, T: Sample> {
view: &'a mut PlaneView,
_sample: PhantomData<&'a mut T>,
}
pub struct PlaneRows<'a, T: Sample> {
plane: &'a Plane<T>,
next_row: usize,
}
impl FrameBuilder {
pub fn new(
format: FormatDescriptor,
width: usize,
height: usize,
schema: &MetadataSchema,
allocator: AllocatorConfig,
) -> Result<Self> {
if width == 0 || height == 0 {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_dimensions"),
"frame dimensions must be non-zero",
));
}
let alignment = allocator.actual_alignment();
let mut planes = Vec::with_capacity(format.planes().len());
for descriptor in format.planes() {
let plane_width = div_ceil(width, descriptor.width_divisor)?;
let plane_height = div_ceil(height, descriptor.height_divisor)?;
let row_bytes = plane_width
.checked_mul(descriptor.sample_type.bytes_per_sample())
.ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.allocation_overflow"),
"row byte size overflowed",
)
})?;
let stride_bytes = align_up(row_bytes, alignment)?;
let buffer_len = stride_bytes.checked_mul(plane_height).ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.allocation_overflow"),
"plane allocation size overflowed",
)
})?;
let storage = PlaneStorage {
buffer: Arc::new(AlignedBuffer::new_zeroed(buffer_len, allocator)),
sample_type: descriptor.sample_type,
};
planes.push(PlaneView {
storage,
offset_bytes: 0,
stride_bytes,
width: plane_width,
height: plane_height,
});
}
Ok(Self {
format,
width,
height,
planes,
metadata: Metadata::new(schema),
alignment,
})
}
#[must_use]
pub const fn actual_alignment(&self) -> usize {
self.alignment
}
pub fn plane_mut<T: Sample>(&mut self, index: usize) -> Result<PlaneMut<'_, T>> {
let view = self
.planes
.get_mut(index)
.ok_or_else(|| plane_index_error(index))?;
if view.storage.sample_type != T::SAMPLE_TYPE {
return Err(sample_mismatch_error(
view.storage.sample_type,
T::SAMPLE_TYPE,
));
}
Ok(PlaneMut {
view,
_sample: PhantomData,
})
}
#[must_use]
pub fn finish(self) -> Frame {
Frame {
format: self.format,
width: self.width,
height: self.height,
planes: self.planes,
metadata: self.metadata,
alignment: self.alignment,
}
}
}
impl Frame {
#[must_use]
pub const fn format(&self) -> &FormatDescriptor {
&self.format
}
#[must_use]
pub const fn width(&self) -> usize {
self.width
}
#[must_use]
pub const fn height(&self) -> usize {
self.height
}
#[must_use]
pub const fn metadata(&self) -> &Metadata {
&self.metadata
}
#[must_use]
pub const fn actual_alignment(&self) -> usize {
self.alignment
}
pub fn plane<T: Sample>(&self, index: usize) -> Result<Plane<T>> {
let view = self
.planes
.get(index)
.ok_or_else(|| plane_index_error(index))?;
if view.storage.sample_type != T::SAMPLE_TYPE {
return Err(sample_mismatch_error(
view.storage.sample_type,
T::SAMPLE_TYPE,
));
}
Ok(Plane {
view: view.clone(),
_sample: PhantomData,
})
}
#[must_use]
pub fn with_metadata(&self, metadata: Metadata) -> Self {
Self {
format: self.format.clone(),
width: self.width,
height: self.height,
planes: self.planes.clone(),
metadata,
alignment: self.alignment,
}
}
#[must_use]
pub fn shares_plane_storage(&self, other: &Self, plane_index: usize) -> bool {
self.shares_plane_storage_at(plane_index, other, plane_index)
}
#[must_use]
pub fn shares_plane_storage_at(
&self,
plane_index: usize,
other: &Self,
other_plane_index: usize,
) -> bool {
match (
self.planes.get(plane_index),
other.planes.get(other_plane_index),
) {
(Some(left), Some(right)) => Arc::ptr_eq(&left.storage.buffer, &right.storage.buffer),
_ => false,
}
}
pub fn view(&self, left: usize, top: usize, width: usize, height: usize) -> Result<Self> {
if width == 0 || height == 0 {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_view"),
"view dimensions must be non-zero",
));
}
if left.checked_add(width).is_none_or(|r| r > self.width)
|| top.checked_add(height).is_none_or(|b| b > self.height)
{
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_view"),
"view rectangle is outside frame bounds",
));
}
let mut planes = Vec::with_capacity(self.planes.len());
for (descriptor, source) in self.format.planes().iter().zip(&self.planes) {
let plane_left = left / descriptor.width_divisor;
let plane_top = top / descriptor.height_divisor;
let plane_width = div_ceil(width, descriptor.width_divisor)?;
let plane_height = div_ceil(height, descriptor.height_divisor)?;
let sample_offset = plane_left
.checked_mul(descriptor.sample_type.bytes_per_sample())
.ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.offset_overflow"),
"plane sample offset overflowed",
)
})?;
let row_offset = plane_top.checked_mul(source.stride_bytes).ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.offset_overflow"),
"plane row offset overflowed",
)
})?;
let offset_bytes = source
.offset_bytes
.checked_add(row_offset)
.and_then(|value| value.checked_add(sample_offset))
.ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.offset_overflow"),
"plane view offset overflowed",
)
})?;
planes.push(PlaneView {
storage: source.storage.clone(),
offset_bytes,
stride_bytes: source.stride_bytes,
width: plane_width,
height: plane_height,
});
}
Ok(Self {
format: self.format.clone(),
width,
height,
planes,
metadata: self.metadata.clone(),
alignment: self.alignment,
})
}
pub fn single_plane_view(&self, plane_index: usize, format: FormatDescriptor) -> Result<Self> {
let view = self.plane_view(plane_index)?;
Self::from_plane_sources(
format,
view.width,
view.height,
&[(self, plane_index)],
self.metadata.clone(),
)
}
pub fn from_plane_sources(
format: FormatDescriptor,
width: usize,
height: usize,
sources: &[(&Self, usize)],
metadata: Metadata,
) -> Result<Self> {
if width == 0 || height == 0 {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_dimensions"),
"frame dimensions must be non-zero",
));
}
if sources.len() != format.planes().len() {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.plane_count_mismatch"),
format!(
"format '{}' requires {} planes, got {}",
format.name(),
format.planes().len(),
sources.len()
),
));
}
let mut planes = Vec::with_capacity(sources.len());
let mut alignment = usize::MAX;
for (descriptor, (source, plane_index)) in format.planes().iter().zip(sources.iter()) {
let source_view = source.plane_view(*plane_index)?.clone();
let expected_width = div_ceil(width, descriptor.width_divisor)?;
let expected_height = div_ceil(height, descriptor.height_divisor)?;
if source_view.width != expected_width || source_view.height != expected_height {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.plane_shape_mismatch"),
format!(
"plane role {:?} requires {}x{}, got {}x{}",
descriptor.role,
expected_width,
expected_height,
source_view.width,
source_view.height
),
));
}
if source_view.storage.sample_type != descriptor.sample_type {
return Err(sample_mismatch_error(
source_view.storage.sample_type,
descriptor.sample_type,
));
}
alignment = alignment.min(source.actual_alignment());
planes.push(source_view);
}
Ok(Self {
format,
width,
height,
planes,
metadata,
alignment,
})
}
fn plane_view(&self, index: usize) -> Result<&PlaneView> {
self.planes
.get(index)
.ok_or_else(|| plane_index_error(index))
}
pub unsafe fn raw_plane(&self, index: usize) -> Result<RawPlane> {
let view = self
.planes
.get(index)
.ok_or_else(|| plane_index_error(index))?;
Ok(RawPlane {
ptr: view
.storage
.buffer
.as_ptr()
.wrapping_add(view.offset_bytes)
.cast::<u8>(),
stride_bytes: view.stride_bytes,
width: view.width,
height: view.height,
sample_type: view.storage.sample_type,
})
}
}
impl<T: Sample> Plane<T> {
#[must_use]
pub const fn width(&self) -> usize {
self.view.width
}
#[must_use]
pub const fn height(&self) -> usize {
self.view.height
}
#[must_use]
pub fn row(&self, row: usize) -> Option<&[T]> {
if row >= self.view.height {
return None;
}
Some(typed_row(&self.view, row))
}
#[must_use]
pub const fn rows(&self) -> PlaneRows<'_, T> {
PlaneRows {
plane: self,
next_row: 0,
}
}
}
impl<'a, T: Sample> PlaneMut<'a, T> {
#[must_use]
pub const fn width(&self) -> usize {
self.view.width
}
#[must_use]
pub const fn height(&self) -> usize {
self.view.height
}
#[must_use]
pub fn row_mut(&mut self, row: usize) -> Option<&mut [T]> {
if row >= self.view.height {
return None;
}
Some(typed_row_mut(self.view, row))
}
#[must_use]
pub fn raw_parts(&mut self) -> RawPlaneMut {
RawPlaneMut {
ptr: self.view.storage_mut_ptr(),
stride_bytes: self.view.stride_bytes,
width: self.view.width,
height: self.view.height,
sample_type: self.view.storage.sample_type,
}
}
}
impl<'a, T: Sample> Iterator for PlaneRows<'a, T> {
type Item = &'a [T];
fn next(&mut self) -> Option<Self::Item> {
let row = self.plane.row(self.next_row)?;
self.next_row += 1;
Some(row)
}
}
fn div_ceil(value: usize, divisor: usize) -> Result<usize> {
if divisor == 0 {
return Err(PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_divisor"),
"plane divisor must be non-zero",
));
}
Ok(value.div_ceil(divisor))
}
fn align_up(value: usize, alignment: usize) -> Result<usize> {
let mask = alignment.checked_sub(1).ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.invalid_alignment"),
"alignment underflowed",
)
})?;
value
.checked_add(mask)
.map(|sum| sum & !mask)
.ok_or_else(|| {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.allocation_overflow"),
"aligned row size overflowed",
)
})
}
fn plane_index_error(index: usize) -> PixelFlowError {
PixelFlowError::new(
ErrorCategory::Core,
ErrorCode::new("frame.plane_index"),
format!("plane index {index} is out of range"),
)
}
fn sample_mismatch_error(actual: SampleType, requested: SampleType) -> PixelFlowError {
PixelFlowError::new(
ErrorCategory::Format,
ErrorCode::new("format.sample_type_mismatch"),
format!(
"plane sample type is {:?}, requested {:?}",
actual, requested
),
)
}
fn typed_row<T: Sample>(view: &PlaneView, row: usize) -> &[T] {
let byte_offset = view.offset_bytes + row * view.stride_bytes;
unsafe {
std::slice::from_raw_parts(
view.storage.buffer.as_ptr().add(byte_offset).cast::<T>(),
view.width,
)
}
}
fn typed_row_mut<T: Sample>(view: &mut PlaneView, row: usize) -> &mut [T] {
let byte_offset = view.offset_bytes + row * view.stride_bytes;
let buffer = Arc::get_mut(&mut view.storage.buffer)
.expect("builder must uniquely own plane storage before finish");
unsafe {
std::slice::from_raw_parts_mut(buffer.as_mut_ptr().add(byte_offset).cast::<T>(), view.width)
}
}
impl PlaneView {
fn storage_mut_ptr(&mut self) -> *mut u8 {
let buffer = Arc::get_mut(&mut self.storage.buffer)
.expect("builder must uniquely own plane storage before finish");
buffer.as_mut_ptr().wrapping_add(self.offset_bytes)
}
}
#[cfg(test)]
mod tests {
use crate::{
ErrorCategory, ErrorCode, MetadataKind, MetadataSchema, MetadataValue, SampleType,
resolve_format_alias,
};
use super::{AllocatorConfig, Frame, FrameBuilder};
#[test]
fn frame_builder_allocates_aligned_plane_buffers() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let mut builder = FrameBuilder::new(format, 4, 3, &schema, AllocatorConfig::default())
.expect("builder should allocate");
assert!(builder.actual_alignment() >= 64);
let mut plane = builder.plane_mut::<u8>(0).expect("u8 plane should match");
let raw = plane.raw_parts();
assert_eq!(raw.width, 4);
assert_eq!(raw.height, 3);
assert_eq!(raw.sample_type, SampleType::U8);
assert_eq!((raw.ptr as usize) % builder.actual_alignment(), 0);
}
#[test]
fn builder_writes_rows_and_finish_returns_immutable_frame() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let mut builder = FrameBuilder::new(format, 3, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate");
{
let mut plane = builder.plane_mut::<u8>(0).expect("u8 plane should match");
plane
.row_mut(0)
.expect("row 0 should exist")
.copy_from_slice(&[1, 2, 3]);
plane
.row_mut(1)
.expect("row 1 should exist")
.copy_from_slice(&[4, 5, 6]);
}
let frame = builder.finish();
let plane = frame.plane::<u8>(0).expect("u8 plane should match");
assert_eq!(plane.row(0).expect("row 0 should exist"), &[1, 2, 3]);
assert_eq!(plane.row(1).expect("row 1 should exist"), &[4, 5, 6]);
assert_eq!(
frame.metadata().get("core:matrix"),
Some(&MetadataValue::None)
);
}
#[test]
fn prop_only_clone_shares_plane_storage_but_replaces_metadata() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let mut schema = MetadataSchema::core();
schema
.register_plugin_key("acme/filter:enabled", MetadataKind::Bool)
.expect("plugin key should register");
let mut builder = FrameBuilder::new(format, 2, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate");
builder
.plane_mut::<u8>(0)
.expect("u8 plane should match")
.row_mut(0)
.expect("row should exist")
.copy_from_slice(&[7, 8]);
let frame = builder.finish();
let mut metadata = frame.metadata().clone();
metadata
.set(&schema, "acme/filter:enabled", MetadataValue::Bool(true))
.expect("metadata write should pass");
let cloned = frame.with_metadata(metadata);
assert!(frame.shares_plane_storage(&cloned, 0));
assert_eq!(
cloned.metadata().get("acme/filter:enabled"),
Some(&MetadataValue::Bool(true))
);
assert_eq!(
cloned.plane::<u8>(0).expect("u8 plane").row(0),
Some(&[7, 8][..])
);
}
#[test]
fn crop_like_view_shares_storage_and_adjusts_visible_rows() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let mut builder = FrameBuilder::new(format, 4, 4, &schema, AllocatorConfig::default())
.expect("builder should allocate");
{
let mut plane = builder.plane_mut::<u8>(0).expect("u8 plane should match");
plane
.row_mut(0)
.expect("row")
.copy_from_slice(&[1, 2, 3, 4]);
plane
.row_mut(1)
.expect("row")
.copy_from_slice(&[5, 6, 7, 8]);
plane
.row_mut(2)
.expect("row")
.copy_from_slice(&[9, 10, 11, 12]);
plane
.row_mut(3)
.expect("row")
.copy_from_slice(&[13, 14, 15, 16]);
}
let frame = builder.finish();
let cropped = frame
.view(1, 1, 2, 2)
.expect("crop-like view should succeed");
assert!(frame.shares_plane_storage(&cropped, 0));
assert_eq!(cropped.width(), 2);
assert_eq!(cropped.height(), 2);
let plane = cropped.plane::<u8>(0).expect("u8 plane should match");
assert_eq!(plane.row(0), Some(&[6, 7][..]));
assert_eq!(plane.row(1), Some(&[10, 11][..]));
}
#[test]
fn frame_single_plane_view_shares_storage_and_preserves_metadata() {
let source_format = resolve_format_alias("yuv420p8").expect("format should resolve");
let gray_format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let mut builder =
FrameBuilder::new(source_format, 4, 4, &schema, AllocatorConfig::default())
.expect("builder should allocate");
{
let mut u = builder.plane_mut::<u8>(1).expect("u plane should match");
let row = u.row_mut(0).expect("row should exist");
*row.first_mut().expect("plane row should be non-empty") = 77;
}
let source = builder.finish();
let mut metadata = source.metadata().clone();
metadata
.set(&schema, "core:frame_number", MetadataValue::Int(9))
.expect("metadata should set");
let source = source.with_metadata(metadata);
let output = source
.single_plane_view(1, gray_format)
.expect("single plane view should build");
assert_eq!(output.width(), 2);
assert_eq!(output.height(), 2);
assert_eq!(
output.metadata().get("core:frame_number"),
Some(&MetadataValue::Int(9))
);
assert!(source.shares_plane_storage_at(1, &output, 0));
assert_eq!(
output.plane::<u8>(0).expect("u8 plane").row(0),
Some(&[77, 0][..])
);
}
#[test]
fn frame_from_plane_sources_combines_compatible_planes_without_copying() {
let schema = MetadataSchema::core();
let gray_format = resolve_format_alias("gray8").expect("format should resolve");
let target_format = resolve_format_alias("yuv420p8").expect("format should resolve");
let y = FrameBuilder::new(
gray_format.clone(),
4,
4,
&schema,
AllocatorConfig::default(),
)
.expect("y builder should allocate")
.finish();
let u = FrameBuilder::new(
gray_format.clone(),
2,
2,
&schema,
AllocatorConfig::default(),
)
.expect("u builder should allocate")
.finish();
let v = FrameBuilder::new(gray_format, 2, 2, &schema, AllocatorConfig::default())
.expect("v builder should allocate")
.finish();
let output = Frame::from_plane_sources(
target_format,
4,
4,
&[(&y, 0), (&u, 0), (&v, 0)],
y.metadata().clone(),
)
.expect("compatible planes should compose");
assert_eq!(output.width(), 4);
assert_eq!(output.height(), 4);
assert!(y.shares_plane_storage_at(0, &output, 0));
assert!(u.shares_plane_storage_at(0, &output, 1));
assert!(v.shares_plane_storage_at(0, &output, 2));
}
#[test]
fn frame_from_plane_sources_rejects_incompatible_shape_and_sample_type() {
let schema = MetadataSchema::core();
let gray8 = resolve_format_alias("gray8").expect("format should resolve");
let gray10 = resolve_format_alias("gray10").expect("format should resolve");
let target = resolve_format_alias("yuv420p8").expect("format should resolve");
let y = FrameBuilder::new(gray8.clone(), 4, 4, &schema, AllocatorConfig::default())
.expect("builder should allocate")
.finish();
let wrong_shape = FrameBuilder::new(gray8, 3, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate")
.finish();
let wrong_sample = FrameBuilder::new(gray10, 2, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate")
.finish();
let Err(shape_error) = Frame::from_plane_sources(
target.clone(),
4,
4,
&[(&y, 0), (&wrong_shape, 0), (&wrong_shape, 0)],
y.metadata().clone(),
) else {
panic!("wrong chroma shape should fail");
};
assert_eq!(shape_error.category(), ErrorCategory::Core);
assert_eq!(
shape_error.code(),
ErrorCode::new("frame.plane_shape_mismatch")
);
let Err(sample_error) = Frame::from_plane_sources(
target,
4,
4,
&[(&y, 0), (&wrong_sample, 0), (&wrong_sample, 0)],
y.metadata().clone(),
) else {
panic!("wrong sample type should fail");
};
assert_eq!(sample_error.category(), ErrorCategory::Format);
assert_eq!(
sample_error.code(),
ErrorCode::new("format.sample_type_mismatch")
);
}
#[test]
fn typed_plane_mismatch_returns_error_not_panic() {
let format = resolve_format_alias("gray10").expect("format should resolve");
let schema = MetadataSchema::core();
let frame = FrameBuilder::new(format, 2, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate")
.finish();
let error = frame
.plane::<u8>(0)
.expect_err("wrong typed access should fail");
assert_eq!(error.category(), ErrorCategory::Format);
assert_eq!(error.code(), ErrorCode::new("format.sample_type_mismatch"));
}
#[test]
fn unsafe_raw_plane_access_exposes_pointer_stride_and_contract_shape() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let frame = FrameBuilder::new(format, 5, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate")
.finish();
let raw = unsafe { frame.raw_plane(0) }.expect("raw plane should exist");
assert!(!raw.ptr.is_null());
assert!(raw.stride_bytes >= 5);
assert_eq!(raw.width, 5);
assert_eq!(raw.height, 2);
assert_eq!(raw.sample_type, SampleType::U8);
}
#[test]
fn typed_plane_rows_iterate_visible_slices() {
let format = resolve_format_alias("gray8").expect("format should resolve");
let schema = MetadataSchema::core();
let mut builder = FrameBuilder::new(format, 2, 2, &schema, AllocatorConfig::default())
.expect("builder should allocate");
{
let mut plane = builder.plane_mut::<u8>(0).expect("u8 plane should match");
plane.row_mut(0).expect("row").copy_from_slice(&[1, 2]);
plane.row_mut(1).expect("row").copy_from_slice(&[3, 4]);
}
let frame = builder.finish();
let plane = frame.plane::<u8>(0).expect("u8 plane should match");
let rows: Vec<&[u8]> = plane.rows().collect();
assert_eq!(rows, vec![&[1, 2][..], &[3, 4][..]]);
}
}