1use alloc::sync::Arc;
18use alloc::vec;
19use alloc::vec::Vec;
20use core::fmt;
21use core::marker::PhantomData;
22
23use whereat::At;
24#[cfg(feature = "rgb")]
25use whereat::ResultAtExt;
26
27#[cfg(feature = "imgref")]
28use imgref::ImgRef;
29#[cfg(feature = "imgref")]
30use imgref::ImgVec;
31#[cfg(feature = "rgb")]
32use rgb::alt::BGRA;
33#[cfg(feature = "rgb")]
34use rgb::{Gray, Rgb, Rgba};
35
36use crate::color::ColorContext;
37use crate::descriptor::{
38 AlphaMode, ColorPrimaries, PixelDescriptor, SignalRange, TransferFunction,
39};
40#[cfg(feature = "rgb")]
41use crate::pixel_types::{GrayAlpha8, GrayAlpha16, GrayAlphaF32};
42
43#[derive(bytemuck::Zeroable, bytemuck::Pod, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
53#[repr(C)]
54pub struct Rgbx {
55 pub r: u8,
57 pub g: u8,
59 pub b: u8,
61 pub x: u8,
63}
64
65#[derive(bytemuck::Zeroable, bytemuck::Pod, Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
69#[repr(C)]
70pub struct Bgrx {
71 pub b: u8,
73 pub g: u8,
75 pub r: u8,
77 pub x: u8,
79}
80
81pub trait Pixel: bytemuck::Pod {
95 const DESCRIPTOR: PixelDescriptor;
97}
98
99impl Pixel for Rgbx {
100 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBX8;
101}
102
103impl Pixel for Bgrx {
104 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::BGRX8;
105}
106
107#[cfg(feature = "rgb")]
108impl Pixel for Rgb<u8> {
109 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGB8;
110}
111
112#[cfg(feature = "rgb")]
113impl Pixel for Rgba<u8> {
114 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBA8;
115}
116
117#[cfg(feature = "rgb")]
118impl Pixel for Gray<u8> {
119 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAY8;
120}
121
122#[cfg(feature = "rgb")]
123impl Pixel for Rgb<u16> {
124 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGB16;
125}
126
127#[cfg(feature = "rgb")]
128impl Pixel for Rgba<u16> {
129 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBA16;
130}
131
132#[cfg(feature = "rgb")]
133impl Pixel for Gray<u16> {
134 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAY16;
135}
136
137#[cfg(feature = "rgb")]
138impl Pixel for Rgb<f32> {
139 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBF32;
140}
141
142#[cfg(feature = "rgb")]
143impl Pixel for Rgba<f32> {
144 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::RGBAF32;
145}
146
147#[cfg(feature = "rgb")]
148impl Pixel for Gray<f32> {
149 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYF32;
150}
151
152#[cfg(feature = "rgb")]
153impl Pixel for BGRA<u8> {
154 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::BGRA8;
155}
156
157#[cfg(feature = "rgb")]
158impl Pixel for GrayAlpha8 {
159 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYA8;
160}
161
162#[cfg(feature = "rgb")]
163impl Pixel for GrayAlpha16 {
164 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYA16;
165}
166
167#[cfg(feature = "rgb")]
168impl Pixel for GrayAlphaF32 {
169 const DESCRIPTOR: PixelDescriptor = PixelDescriptor::GRAYAF32;
170}
171
172#[derive(Clone, Copy, Debug, PartialEq, Eq)]
178#[non_exhaustive]
179pub enum BufferError {
180 AlignmentViolation,
182 InsufficientData,
184 StrideTooSmall,
186 StrideNotPixelAligned,
191 InvalidDimensions,
193 IncompatibleDescriptor,
198 AllocationFailed,
200}
201
202impl fmt::Display for BufferError {
203 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204 match self {
205 Self::AlignmentViolation => write!(f, "data is not aligned for the channel type"),
206 Self::InsufficientData => {
207 write!(f, "data slice is too small for the given dimensions")
208 }
209 Self::StrideTooSmall => write!(f, "stride is smaller than width * bytes_per_pixel"),
210 Self::StrideNotPixelAligned => {
211 write!(f, "stride is not a multiple of bytes_per_pixel")
212 }
213 Self::InvalidDimensions => write!(f, "width or height is zero or causes overflow"),
214 Self::IncompatibleDescriptor => {
215 write!(f, "new descriptor has different bytes_per_pixel")
216 }
217 Self::AllocationFailed => write!(f, "buffer allocation failed"),
218 }
219 }
220}
221
222impl core::error::Error for BufferError {}
223
224const fn align_up(val: usize, align: usize) -> usize {
230 (val + align - 1) & !(align - 1)
231}
232
233fn align_offset(ptr: *const u8, align: usize) -> usize {
235 let addr = ptr as usize;
236 align_up(addr, align) - addr
237}
238
239fn try_alloc_zeroed(size: usize) -> Result<Vec<u8>, BufferError> {
243 let mut data = Vec::new();
244 data.try_reserve_exact(size)
245 .map_err(|_| BufferError::AllocationFailed)?;
246 data.resize(size, 0);
247 Ok(data)
248}
249
250fn validate_slice(
252 data_len: usize,
253 data_ptr: *const u8,
254 width: u32,
255 rows: u32,
256 stride_bytes: usize,
257 descriptor: &PixelDescriptor,
258) -> Result<(), BufferError> {
259 let bpp = descriptor.bytes_per_pixel();
260 let min_stride = (width as usize)
261 .checked_mul(bpp)
262 .ok_or(BufferError::InvalidDimensions)?;
263 if stride_bytes < min_stride {
264 return Err(BufferError::StrideTooSmall);
265 }
266 #[allow(clippy::manual_is_multiple_of)] if bpp > 0 && stride_bytes % bpp != 0 {
268 return Err(BufferError::StrideNotPixelAligned);
269 }
270 if rows > 0 {
271 let required = required_bytes(rows, stride_bytes, min_stride)?;
272 if data_len < required {
273 return Err(BufferError::InsufficientData);
274 }
275 }
276 let align = descriptor.min_alignment();
277 #[allow(clippy::manual_is_multiple_of)] if (data_ptr as usize) % align != 0 {
279 return Err(BufferError::AlignmentViolation);
280 }
281 Ok(())
282}
283
284fn required_bytes(rows: u32, stride: usize, min_stride: usize) -> Result<usize, BufferError> {
286 let preceding = (rows as usize - 1)
287 .checked_mul(stride)
288 .ok_or(BufferError::InvalidDimensions)?;
289 preceding
290 .checked_add(min_stride)
291 .ok_or(BufferError::InvalidDimensions)
292}
293
294#[cfg(feature = "rgb")]
297fn pixels_to_bytes<P: bytemuck::Pod>(pixels: Vec<P>) -> Vec<u8> {
298 match bytemuck::try_cast_vec(pixels) {
299 Ok(bytes) => bytes,
300 Err((_err, pixels)) => bytemuck::cast_slice::<P, u8>(&pixels).to_vec(),
301 }
302}
303
304#[non_exhaustive]
324pub struct PixelSlice<'a, P = ()> {
325 data: &'a [u8],
326 width: u32,
327 rows: u32,
328 stride: usize,
329 descriptor: PixelDescriptor,
330 color: Option<Arc<ColorContext>>,
331 _pixel: PhantomData<P>,
332}
333
334impl<'a> PixelSlice<'a> {
335 #[track_caller]
344 pub fn new(
345 data: &'a [u8],
346 width: u32,
347 rows: u32,
348 stride_bytes: usize,
349 descriptor: PixelDescriptor,
350 ) -> Result<Self, At<BufferError>> {
351 validate_slice(
352 data.len(),
353 data.as_ptr(),
354 width,
355 rows,
356 stride_bytes,
357 &descriptor,
358 )
359 .map_err(|e| whereat::at!(e))?;
360 Ok(Self {
361 data,
362 width,
363 rows,
364 stride: stride_bytes,
365 descriptor,
366
367 color: None,
368 _pixel: PhantomData,
369 })
370 }
371}
372
373impl<'a, P> PixelSlice<'a, P> {
374 pub fn erase(self) -> PixelSlice<'a> {
378 PixelSlice {
379 data: self.data,
380 width: self.width,
381 rows: self.rows,
382 stride: self.stride,
383 descriptor: self.descriptor,
384
385 color: self.color,
386 _pixel: PhantomData,
387 }
388 }
389
390 pub fn try_typed<Q: Pixel>(self) -> Option<PixelSlice<'a, Q>> {
396 if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
397 Some(PixelSlice {
398 data: self.data,
399 width: self.width,
400 rows: self.rows,
401 stride: self.stride,
402 descriptor: self.descriptor,
403
404 color: self.color,
405 _pixel: PhantomData,
406 })
407 } else {
408 None
409 }
410 }
411
412 #[inline]
428 #[must_use]
429 pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
430 assert!(
431 self.descriptor.layout_compatible(descriptor),
432 "with_descriptor() cannot change physical layout ({} -> {}); \
433 use reinterpret() for layout changes",
434 self.descriptor,
435 descriptor
436 );
437 self.descriptor = descriptor;
438 self
439 }
440
441 #[track_caller]
449 pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
450 if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
451 return Err(whereat::at!(BufferError::IncompatibleDescriptor));
452 }
453 self.descriptor = descriptor;
454 Ok(self)
455 }
456
457 #[inline]
459 #[must_use]
460 pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
461 self.descriptor.transfer = tf;
462 self
463 }
464
465 #[inline]
467 #[must_use]
468 pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
469 self.descriptor.primaries = cp;
470 self
471 }
472
473 #[inline]
475 #[must_use]
476 pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
477 self.descriptor.signal_range = sr;
478 self
479 }
480
481 #[inline]
483 #[must_use]
484 pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
485 self.descriptor.alpha = am;
486 self
487 }
488
489 #[inline]
491 pub fn width(&self) -> u32 {
492 self.width
493 }
494
495 #[inline]
497 pub fn rows(&self) -> u32 {
498 self.rows
499 }
500
501 #[inline]
503 pub fn stride(&self) -> usize {
504 self.stride
505 }
506
507 #[inline]
509 pub fn descriptor(&self) -> PixelDescriptor {
510 self.descriptor
511 }
512
513 #[inline]
515 pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
516 self.color.as_ref()
517 }
518
519 #[inline]
521 #[must_use]
522 pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
523 self.color = Some(ctx);
524 self
525 }
526
527 #[inline]
532 pub fn is_contiguous(&self) -> bool {
533 self.stride == self.width as usize * self.descriptor.bytes_per_pixel()
534 }
535
536 #[inline]
544 pub fn as_contiguous_bytes(&self) -> Option<&'a [u8]> {
545 if self.is_contiguous() {
546 let total = self.rows as usize * self.stride;
547 Some(&self.data[..total])
548 } else {
549 None
550 }
551 }
552
553 #[inline]
563 pub fn as_strided_bytes(&self) -> &'a [u8] {
564 if self.rows == 0 {
565 return &[];
566 }
567 let full = self.rows as usize * self.stride;
570 if full <= self.data.len() {
571 &self.data[..full]
572 } else {
573 let bpp = self.descriptor.bytes_per_pixel();
574 let trimmed = (self.rows as usize - 1) * self.stride + self.width as usize * bpp;
575 &self.data[..trimmed]
576 }
577 }
578
579 pub fn contiguous_bytes(&self) -> alloc::borrow::Cow<'a, [u8]> {
584 if let Some(bytes) = self.as_contiguous_bytes() {
585 alloc::borrow::Cow::Borrowed(bytes)
586 } else {
587 let bpp = self.descriptor.bytes_per_pixel();
588 let row_bytes = self.width as usize * bpp;
589 let mut buf = Vec::with_capacity(row_bytes * self.rows as usize);
590 for y in 0..self.rows {
591 buf.extend_from_slice(self.row(y));
592 }
593 alloc::borrow::Cow::Owned(buf)
594 }
595 }
596
597 #[inline]
603 pub fn row(&self, y: u32) -> &[u8] {
604 assert!(
605 y < self.rows,
606 "row index {y} out of bounds (rows: {})",
607 self.rows
608 );
609 let start = y as usize * self.stride;
610 let len = self.width as usize * self.descriptor.bytes_per_pixel();
611 &self.data[start..start + len]
612 }
613
614 #[inline]
622 pub fn row_with_stride(&self, y: u32) -> &[u8] {
623 assert!(
624 y < self.rows,
625 "row index {y} out of bounds (rows: {})",
626 self.rows
627 );
628 let start = y as usize * self.stride;
629 &self.data[start..start + self.stride]
630 }
631
632 pub fn sub_rows(&self, y: u32, count: u32) -> PixelSlice<'_, P> {
638 assert!(
639 y.checked_add(count).is_some_and(|end| end <= self.rows),
640 "sub_rows({y}, {count}) out of bounds (rows: {})",
641 self.rows
642 );
643 if count == 0 {
644 return PixelSlice {
645 data: &[],
646 width: self.width,
647 rows: 0,
648 stride: self.stride,
649 descriptor: self.descriptor,
650
651 color: self.color.clone(),
652 _pixel: PhantomData,
653 };
654 }
655 let bpp = self.descriptor.bytes_per_pixel();
656 let start = y as usize * self.stride;
657 let end = (y as usize + count as usize - 1) * self.stride + self.width as usize * bpp;
658 PixelSlice {
659 data: &self.data[start..end],
660 width: self.width,
661 rows: count,
662 stride: self.stride,
663 descriptor: self.descriptor,
664
665 color: self.color.clone(),
666 _pixel: PhantomData,
667 }
668 }
669
670 pub fn crop_view(&self, x: u32, y: u32, w: u32, h: u32) -> PixelSlice<'_, P> {
677 assert!(
678 x.checked_add(w).is_some_and(|end| end <= self.width),
679 "crop x={x} w={w} exceeds width {}",
680 self.width
681 );
682 assert!(
683 y.checked_add(h).is_some_and(|end| end <= self.rows),
684 "crop y={y} h={h} exceeds rows {}",
685 self.rows
686 );
687 if h == 0 || w == 0 {
688 return PixelSlice {
689 data: &[],
690 width: w,
691 rows: h,
692 stride: self.stride,
693 descriptor: self.descriptor,
694
695 color: self.color.clone(),
696 _pixel: PhantomData,
697 };
698 }
699 let bpp = self.descriptor.bytes_per_pixel();
700 let start = y as usize * self.stride + x as usize * bpp;
701 let end = (y as usize + h as usize - 1) * self.stride + (x as usize + w as usize) * bpp;
702 PixelSlice {
703 data: &self.data[start..end],
704 width: w,
705 rows: h,
706 stride: self.stride,
707 descriptor: self.descriptor,
708
709 color: self.color.clone(),
710 _pixel: PhantomData,
711 }
712 }
713}
714
715impl<'a, P: Pixel> PixelSlice<'a, P> {
716 #[track_caller]
726 pub fn new_typed(
727 data: &'a [u8],
728 width: u32,
729 rows: u32,
730 stride_pixels: u32,
731 ) -> Result<Self, At<BufferError>> {
732 const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
733 let stride_bytes = stride_pixels as usize * core::mem::size_of::<P>();
734 validate_slice(
735 data.len(),
736 data.as_ptr(),
737 width,
738 rows,
739 stride_bytes,
740 &P::DESCRIPTOR,
741 )
742 .map_err(|e| whereat::at!(e))?;
743 Ok(Self {
744 data,
745 width,
746 rows,
747 stride: stride_bytes,
748 descriptor: P::DESCRIPTOR,
749
750 color: None,
751 _pixel: PhantomData,
752 })
753 }
754}
755
756impl<P> fmt::Debug for PixelSlice<'_, P> {
757 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
758 write!(
759 f,
760 "PixelSlice({}x{}, {:?} {:?})",
761 self.width,
762 self.rows,
763 self.descriptor.layout(),
764 self.descriptor.channel_type()
765 )
766 }
767}
768
769#[non_exhaustive]
778pub struct PixelSliceMut<'a, P = ()> {
779 data: &'a mut [u8],
780 width: u32,
781 rows: u32,
782 stride: usize,
783 descriptor: PixelDescriptor,
784 color: Option<Arc<ColorContext>>,
785 _pixel: PhantomData<P>,
786}
787
788impl<'a> PixelSliceMut<'a> {
789 #[track_caller]
798 pub fn new(
799 data: &'a mut [u8],
800 width: u32,
801 rows: u32,
802 stride_bytes: usize,
803 descriptor: PixelDescriptor,
804 ) -> Result<Self, At<BufferError>> {
805 validate_slice(
806 data.len(),
807 data.as_ptr(),
808 width,
809 rows,
810 stride_bytes,
811 &descriptor,
812 )
813 .map_err(|e| whereat::at!(e))?;
814 Ok(Self {
815 data,
816 width,
817 rows,
818 stride: stride_bytes,
819 descriptor,
820
821 color: None,
822 _pixel: PhantomData,
823 })
824 }
825}
826
827impl<'a, P> PixelSliceMut<'a, P> {
828 pub fn erase(self) -> PixelSliceMut<'a> {
830 PixelSliceMut {
831 data: self.data,
832 width: self.width,
833 rows: self.rows,
834 stride: self.stride,
835 descriptor: self.descriptor,
836
837 color: self.color,
838 _pixel: PhantomData,
839 }
840 }
841
842 pub fn try_typed<Q: Pixel>(self) -> Option<PixelSliceMut<'a, Q>> {
846 if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
847 Some(PixelSliceMut {
848 data: self.data,
849 width: self.width,
850 rows: self.rows,
851 stride: self.stride,
852 descriptor: self.descriptor,
853
854 color: self.color,
855 _pixel: PhantomData,
856 })
857 } else {
858 None
859 }
860 }
861
862 #[inline]
866 #[must_use]
867 pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
868 assert!(
869 self.descriptor.layout_compatible(descriptor),
870 "with_descriptor() cannot change physical layout ({} -> {}); \
871 use reinterpret() for layout changes",
872 self.descriptor,
873 descriptor
874 );
875 self.descriptor = descriptor;
876 self
877 }
878
879 #[track_caller]
883 pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
884 if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
885 return Err(whereat::at!(BufferError::IncompatibleDescriptor));
886 }
887 self.descriptor = descriptor;
888 Ok(self)
889 }
890
891 #[inline]
893 #[must_use]
894 pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
895 self.descriptor.transfer = tf;
896 self
897 }
898
899 #[inline]
901 #[must_use]
902 pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
903 self.descriptor.primaries = cp;
904 self
905 }
906
907 #[inline]
909 #[must_use]
910 pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
911 self.descriptor.signal_range = sr;
912 self
913 }
914
915 #[inline]
917 #[must_use]
918 pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
919 self.descriptor.alpha = am;
920 self
921 }
922
923 #[inline]
925 pub fn width(&self) -> u32 {
926 self.width
927 }
928
929 #[inline]
931 pub fn rows(&self) -> u32 {
932 self.rows
933 }
934
935 #[inline]
937 pub fn stride(&self) -> usize {
938 self.stride
939 }
940
941 #[inline]
943 pub fn descriptor(&self) -> PixelDescriptor {
944 self.descriptor
945 }
946
947 #[inline]
949 pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
950 self.color.as_ref()
951 }
952
953 #[inline]
955 #[must_use]
956 pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
957 self.color = Some(ctx);
958 self
959 }
960
961 #[inline]
966 pub fn as_pixel_slice(&self) -> PixelSlice<'_, P> {
967 PixelSlice {
968 data: self.data,
969 width: self.width,
970 rows: self.rows,
971 stride: self.stride,
972 descriptor: self.descriptor,
973 color: self.color.clone(),
974 _pixel: PhantomData,
975 }
976 }
977
978 #[inline]
984 pub fn as_strided_bytes(&self) -> &[u8] {
985 self.data
986 }
987
988 #[inline]
992 pub fn as_strided_bytes_mut(&mut self) -> &mut [u8] {
993 self.data
994 }
995
996 #[inline]
1002 pub fn row(&self, y: u32) -> &[u8] {
1003 assert!(
1004 y < self.rows,
1005 "row index {y} out of bounds (rows: {})",
1006 self.rows
1007 );
1008 let start = y as usize * self.stride;
1009 let len = self.width as usize * self.descriptor.bytes_per_pixel();
1010 &self.data[start..start + len]
1011 }
1012
1013 #[inline]
1019 pub fn row_mut(&mut self, y: u32) -> &mut [u8] {
1020 assert!(
1021 y < self.rows,
1022 "row index {y} out of bounds (rows: {})",
1023 self.rows
1024 );
1025 let start = y as usize * self.stride;
1026 let len = self.width as usize * self.descriptor.bytes_per_pixel();
1027 &mut self.data[start..start + len]
1028 }
1029
1030 pub fn sub_rows_mut(&mut self, y: u32, count: u32) -> PixelSliceMut<'_, P> {
1036 assert!(
1037 y.checked_add(count).is_some_and(|end| end <= self.rows),
1038 "sub_rows_mut({y}, {count}) out of bounds (rows: {})",
1039 self.rows
1040 );
1041 if count == 0 {
1042 return PixelSliceMut {
1043 data: &mut [],
1044 width: self.width,
1045 rows: 0,
1046 stride: self.stride,
1047 descriptor: self.descriptor,
1048
1049 color: self.color.clone(),
1050 _pixel: PhantomData,
1051 };
1052 }
1053 let bpp = self.descriptor.bytes_per_pixel();
1054 let start = y as usize * self.stride;
1055 let end = (y as usize + count as usize - 1) * self.stride + self.width as usize * bpp;
1056 PixelSliceMut {
1057 data: &mut self.data[start..end],
1058 width: self.width,
1059 rows: count,
1060 stride: self.stride,
1061 descriptor: self.descriptor,
1062
1063 color: self.color.clone(),
1064 _pixel: PhantomData,
1065 }
1066 }
1067}
1068
1069impl<'a, P: Pixel> PixelSliceMut<'a, P> {
1070 #[track_caller]
1075 pub fn new_typed(
1076 data: &'a mut [u8],
1077 width: u32,
1078 rows: u32,
1079 stride_pixels: u32,
1080 ) -> Result<Self, At<BufferError>> {
1081 const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
1082 let stride_bytes = stride_pixels as usize * core::mem::size_of::<P>();
1083 validate_slice(
1084 data.len(),
1085 data.as_ptr(),
1086 width,
1087 rows,
1088 stride_bytes,
1089 &P::DESCRIPTOR,
1090 )
1091 .map_err(|e| whereat::at!(e))?;
1092 Ok(Self {
1093 data,
1094 width,
1095 rows,
1096 stride: stride_bytes,
1097 descriptor: P::DESCRIPTOR,
1098
1099 color: None,
1100 _pixel: PhantomData,
1101 })
1102 }
1103}
1104
1105impl<P> fmt::Debug for PixelSliceMut<'_, P> {
1106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1107 write!(
1108 f,
1109 "PixelSliceMut({}x{}, {:?} {:?})",
1110 self.width,
1111 self.rows,
1112 self.descriptor.layout(),
1113 self.descriptor.channel_type()
1114 )
1115 }
1116}
1117
1118fn for_each_pixel_4bpp(
1124 data: &mut [u8],
1125 width: u32,
1126 rows: u32,
1127 stride: usize,
1128 mut f: impl FnMut(&mut [u8; 4]),
1129) {
1130 let row_bytes = width as usize * 4;
1131 for y in 0..rows as usize {
1132 let row_start = y * stride;
1133 let row = &mut data[row_start..row_start + row_bytes];
1134 for chunk in row.chunks_exact_mut(4) {
1135 let px: &mut [u8; 4] = chunk.try_into().unwrap();
1136 f(px);
1137 }
1138 }
1139}
1140
1141impl<'a> PixelSliceMut<'a, Rgbx> {
1142 #[must_use]
1144 pub fn swap_to_bgrx(self) -> PixelSliceMut<'a, Bgrx> {
1145 let width = self.width;
1146 let rows = self.rows;
1147 let stride = self.stride;
1148
1149 let color = self.color;
1150 let data = self.data;
1151 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1152 px.swap(0, 2);
1153 });
1154 PixelSliceMut {
1155 data,
1156 width,
1157 rows,
1158 stride,
1159 descriptor: PixelDescriptor::BGRX8_SRGB,
1160
1161 color,
1162 _pixel: PhantomData,
1163 }
1164 }
1165}
1166
1167#[cfg(feature = "rgb")]
1168impl<'a> PixelSliceMut<'a, Rgbx> {
1169 #[must_use]
1171 pub fn upgrade_to_rgba(self) -> PixelSliceMut<'a, Rgba<u8>> {
1172 let width = self.width;
1173 let rows = self.rows;
1174 let stride = self.stride;
1175
1176 let color = self.color;
1177 let data = self.data;
1178 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1179 px[3] = 255;
1180 });
1181 PixelSliceMut {
1182 data,
1183 width,
1184 rows,
1185 stride,
1186 descriptor: PixelDescriptor::RGBA8_SRGB,
1187
1188 color,
1189 _pixel: PhantomData,
1190 }
1191 }
1192}
1193
1194impl<'a> PixelSliceMut<'a, Bgrx> {
1195 #[must_use]
1197 pub fn swap_to_rgbx(self) -> PixelSliceMut<'a, Rgbx> {
1198 let width = self.width;
1199 let rows = self.rows;
1200 let stride = self.stride;
1201
1202 let color = self.color;
1203 let data = self.data;
1204 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1205 px.swap(0, 2);
1206 });
1207 PixelSliceMut {
1208 data,
1209 width,
1210 rows,
1211 stride,
1212 descriptor: PixelDescriptor::RGBX8_SRGB,
1213
1214 color,
1215 _pixel: PhantomData,
1216 }
1217 }
1218}
1219
1220#[cfg(feature = "rgb")]
1221impl<'a> PixelSliceMut<'a, Bgrx> {
1222 #[must_use]
1224 pub fn upgrade_to_bgra(self) -> PixelSliceMut<'a, BGRA<u8>> {
1225 let width = self.width;
1226 let rows = self.rows;
1227 let stride = self.stride;
1228
1229 let color = self.color;
1230 let data = self.data;
1231 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1232 px[3] = 255;
1233 });
1234 PixelSliceMut {
1235 data,
1236 width,
1237 rows,
1238 stride,
1239 descriptor: PixelDescriptor::BGRA8_SRGB,
1240
1241 color,
1242 _pixel: PhantomData,
1243 }
1244 }
1245}
1246
1247#[cfg(feature = "rgb")]
1248impl<'a> PixelSliceMut<'a, Rgba<u8>> {
1249 #[must_use]
1254 pub fn matte_to_rgbx(self, bg: Rgb<u8>) -> PixelSliceMut<'a, Rgbx> {
1255 let width = self.width;
1256 let rows = self.rows;
1257 let stride = self.stride;
1258
1259 let color = self.color;
1260 let data = self.data;
1261 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1262 let a = px[3] as u16;
1263 let inv_a = 255 - a;
1264 px[0] = ((px[0] as u16 * a + bg.r as u16 * inv_a + 127) / 255) as u8;
1265 px[1] = ((px[1] as u16 * a + bg.g as u16 * inv_a + 127) / 255) as u8;
1266 px[2] = ((px[2] as u16 * a + bg.b as u16 * inv_a + 127) / 255) as u8;
1267 px[3] = 0;
1268 });
1269 PixelSliceMut {
1270 data,
1271 width,
1272 rows,
1273 stride,
1274 descriptor: PixelDescriptor::RGBX8_SRGB,
1275
1276 color,
1277 _pixel: PhantomData,
1278 }
1279 }
1280
1281 #[must_use]
1286 pub fn strip_alpha_to_rgbx(self) -> PixelSliceMut<'a, Rgbx> {
1287 PixelSliceMut {
1288 data: self.data,
1289 width: self.width,
1290 rows: self.rows,
1291 stride: self.stride,
1292 descriptor: PixelDescriptor::RGBX8_SRGB,
1293
1294 color: self.color,
1295 _pixel: PhantomData,
1296 }
1297 }
1298
1299 #[must_use]
1301 pub fn swap_to_bgra(self) -> PixelSliceMut<'a, BGRA<u8>> {
1302 let width = self.width;
1303 let rows = self.rows;
1304 let stride = self.stride;
1305
1306 let color = self.color;
1307 let data = self.data;
1308 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1309 px.swap(0, 2);
1310 });
1311 PixelSliceMut {
1312 data,
1313 width,
1314 rows,
1315 stride,
1316 descriptor: PixelDescriptor::BGRA8_SRGB,
1317
1318 color,
1319 _pixel: PhantomData,
1320 }
1321 }
1322}
1323
1324#[cfg(feature = "rgb")]
1325impl<'a> PixelSliceMut<'a, BGRA<u8>> {
1326 #[must_use]
1331 pub fn matte_to_bgrx(self, bg: Rgb<u8>) -> PixelSliceMut<'a, Bgrx> {
1332 let width = self.width;
1333 let rows = self.rows;
1334 let stride = self.stride;
1335
1336 let color = self.color;
1337 let data = self.data;
1338 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1339 let a = px[3] as u16;
1340 let inv_a = 255 - a;
1341 px[0] = ((px[0] as u16 * a + bg.b as u16 * inv_a + 127) / 255) as u8;
1343 px[1] = ((px[1] as u16 * a + bg.g as u16 * inv_a + 127) / 255) as u8;
1344 px[2] = ((px[2] as u16 * a + bg.r as u16 * inv_a + 127) / 255) as u8;
1345 px[3] = 0;
1346 });
1347 PixelSliceMut {
1348 data,
1349 width,
1350 rows,
1351 stride,
1352 descriptor: PixelDescriptor::BGRX8_SRGB,
1353
1354 color,
1355 _pixel: PhantomData,
1356 }
1357 }
1358
1359 #[must_use]
1361 pub fn strip_alpha_to_bgrx(self) -> PixelSliceMut<'a, Bgrx> {
1362 PixelSliceMut {
1363 data: self.data,
1364 width: self.width,
1365 rows: self.rows,
1366 stride: self.stride,
1367 descriptor: PixelDescriptor::BGRX8_SRGB,
1368
1369 color: self.color,
1370 _pixel: PhantomData,
1371 }
1372 }
1373
1374 #[must_use]
1376 pub fn swap_to_rgba(self) -> PixelSliceMut<'a, Rgba<u8>> {
1377 let width = self.width;
1378 let rows = self.rows;
1379 let stride = self.stride;
1380
1381 let color = self.color;
1382 let data = self.data;
1383 for_each_pixel_4bpp(data, width, rows, stride, |px| {
1384 px.swap(0, 2);
1385 });
1386 PixelSliceMut {
1387 data,
1388 width,
1389 rows,
1390 stride,
1391 descriptor: PixelDescriptor::RGBA8_SRGB,
1392
1393 color,
1394 _pixel: PhantomData,
1395 }
1396 }
1397}
1398
1399#[non_exhaustive]
1413pub struct PixelBuffer<P = ()> {
1414 data: Vec<u8>,
1415 offset: usize,
1417 width: u32,
1418 height: u32,
1419 stride: usize,
1420 descriptor: PixelDescriptor,
1421 color: Option<Arc<ColorContext>>,
1422 _pixel: PhantomData<P>,
1423}
1424
1425impl PixelBuffer {
1426 pub fn new(width: u32, height: u32, descriptor: PixelDescriptor) -> Self {
1438 Self::try_new(width, height, descriptor).expect("pixel buffer allocation failed")
1439 }
1440
1441 #[track_caller]
1446 pub fn try_new(
1447 width: u32,
1448 height: u32,
1449 descriptor: PixelDescriptor,
1450 ) -> Result<Self, At<BufferError>> {
1451 let stride = descriptor.aligned_stride(width);
1452 let total = stride
1453 .checked_mul(height as usize)
1454 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1455 let align = descriptor.min_alignment();
1456 let alloc_size = total
1457 .checked_add(align - 1)
1458 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1459 let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
1460 let offset = align_offset(data.as_ptr(), align);
1461 Ok(Self {
1462 data,
1463 offset,
1464 width,
1465 height,
1466 stride,
1467 descriptor,
1468
1469 color: None,
1470 _pixel: PhantomData,
1471 })
1472 }
1473
1474 pub fn new_simd_aligned(
1493 width: u32,
1494 height: u32,
1495 descriptor: PixelDescriptor,
1496 simd_align: usize,
1497 ) -> Self {
1498 Self::try_new_simd_aligned(width, height, descriptor, simd_align)
1499 .expect("pixel buffer SIMD-aligned allocation failed")
1500 }
1501
1502 #[track_caller]
1507 pub fn try_new_simd_aligned(
1508 width: u32,
1509 height: u32,
1510 descriptor: PixelDescriptor,
1511 simd_align: usize,
1512 ) -> Result<Self, At<BufferError>> {
1513 let stride = descriptor.simd_aligned_stride(width, simd_align);
1514 let total = stride
1515 .checked_mul(height as usize)
1516 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1517 let alloc_size = total
1518 .checked_add(simd_align - 1)
1519 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1520 let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
1521 let offset = align_offset(data.as_ptr(), simd_align);
1522 Ok(Self {
1523 data,
1524 offset,
1525 width,
1526 height,
1527 stride,
1528 descriptor,
1529
1530 color: None,
1531 _pixel: PhantomData,
1532 })
1533 }
1534
1535 #[track_caller]
1545 pub fn from_vec(
1546 data: Vec<u8>,
1547 width: u32,
1548 height: u32,
1549 descriptor: PixelDescriptor,
1550 ) -> Result<Self, At<BufferError>> {
1551 let stride = descriptor.aligned_stride(width);
1552 let total = stride
1553 .checked_mul(height as usize)
1554 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1555 let align = descriptor.min_alignment();
1556 let offset = align_offset(data.as_ptr(), align);
1557 if data.len() < offset + total {
1558 return Err(whereat::at!(BufferError::InsufficientData));
1559 }
1560 Ok(Self {
1561 data,
1562 offset,
1563 width,
1564 height,
1565 stride,
1566 descriptor,
1567
1568 color: None,
1569 _pixel: PhantomData,
1570 })
1571 }
1572}
1573
1574impl<P: Pixel> PixelBuffer<P> {
1575 pub fn new_typed(width: u32, height: u32) -> Self {
1589 Self::try_new_typed(width, height).expect("typed pixel buffer allocation failed")
1590 }
1591
1592 #[track_caller]
1597 pub fn try_new_typed(width: u32, height: u32) -> Result<Self, At<BufferError>> {
1598 const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
1599 let descriptor = P::DESCRIPTOR;
1600 let stride = descriptor.aligned_stride(width);
1601 let total = stride
1602 .checked_mul(height as usize)
1603 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1604 let align = descriptor.min_alignment();
1605 let alloc_size = total
1606 .checked_add(align - 1)
1607 .ok_or_else(|| whereat::at!(BufferError::InvalidDimensions))?;
1608 let data = try_alloc_zeroed(alloc_size).map_err(|e| whereat::at!(e))?;
1609 let offset = align_offset(data.as_ptr(), align);
1610 Ok(Self {
1611 data,
1612 offset,
1613 width,
1614 height,
1615 stride,
1616 descriptor,
1617
1618 color: None,
1619 _pixel: PhantomData,
1620 })
1621 }
1622}
1623
1624#[cfg(feature = "rgb")]
1625impl<P: Pixel> PixelBuffer<P> {
1626 #[track_caller]
1636 pub fn from_pixels(pixels: Vec<P>, width: u32, height: u32) -> Result<Self, At<BufferError>> {
1637 const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
1638 let expected = width as usize * height as usize;
1639 if pixels.len() != expected {
1640 return Err(whereat::at!(BufferError::InvalidDimensions));
1641 }
1642 let descriptor = P::DESCRIPTOR;
1643 let stride = descriptor.aligned_stride(width);
1644 let data: Vec<u8> = pixels_to_bytes(pixels);
1645 Ok(Self {
1646 data,
1647 offset: 0,
1648 width,
1649 height,
1650 stride,
1651 descriptor,
1652
1653 color: None,
1654 _pixel: PhantomData,
1655 })
1656 }
1657}
1658
1659#[cfg(feature = "imgref")]
1660impl<P: Pixel> PixelBuffer<P> {
1661 pub fn from_imgvec(img: ImgVec<P>) -> Self {
1675 const { assert!(core::mem::size_of::<P>() == P::DESCRIPTOR.bytes_per_pixel()) }
1676 let width = img.width() as u32;
1677 let height = img.height() as u32;
1678 let stride_pixels = img.stride();
1679 let descriptor = P::DESCRIPTOR;
1680 let stride_bytes = stride_pixels * core::mem::size_of::<P>();
1681 let (buf, ..) = img.into_contiguous_buf();
1682 let data: Vec<u8> = pixels_to_bytes(buf);
1683 Self {
1684 data,
1685 offset: 0,
1686 width,
1687 height,
1688 stride: stride_bytes,
1689 descriptor,
1690
1691 color: None,
1692 _pixel: PhantomData,
1693 }
1694 }
1695}
1696
1697#[cfg(feature = "imgref")]
1698impl<P: Pixel> PixelBuffer<P> {
1699 pub fn as_imgref(&self) -> ImgRef<'_, P> {
1709 let total_bytes = if self.height == 0 {
1710 0
1711 } else {
1712 (self.height as usize - 1) * self.stride
1713 + self.width as usize * core::mem::size_of::<P>()
1714 };
1715 let data = &self.data[self.offset..self.offset + total_bytes];
1716 let pixels: &[P] = bytemuck::cast_slice(data);
1717 let stride_px = self.stride / core::mem::size_of::<P>();
1718 imgref::Img::new_stride(pixels, self.width as usize, self.height as usize, stride_px)
1719 }
1720
1721 pub fn as_imgref_mut(&mut self) -> imgref::ImgRefMut<'_, P> {
1725 let total_bytes = if self.height == 0 {
1726 0
1727 } else {
1728 (self.height as usize - 1) * self.stride
1729 + self.width as usize * core::mem::size_of::<P>()
1730 };
1731 let offset = self.offset;
1732 let data = &mut self.data[offset..offset + total_bytes];
1733 let pixels: &mut [P] = bytemuck::cast_slice_mut(data);
1734 let stride_px = self.stride / core::mem::size_of::<P>();
1735 imgref::Img::new_stride(pixels, self.width as usize, self.height as usize, stride_px)
1736 }
1737}
1738
1739#[cfg(feature = "rgb")]
1741impl PixelBuffer {
1742 #[track_caller]
1751 pub fn from_pixels_erased<P: Pixel>(
1752 pixels: Vec<P>,
1753 width: u32,
1754 height: u32,
1755 ) -> Result<Self, At<BufferError>> {
1756 PixelBuffer::<P>::from_pixels(pixels, width, height)
1757 .at()
1758 .map(PixelBuffer::from)
1759 }
1760
1761 pub fn as_contiguous_pixels<P: Pixel>(&self) -> Option<&[P]> {
1770 if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
1771 return None;
1772 }
1773 let pixel_size = core::mem::size_of::<P>();
1774 let row_bytes = self.width as usize * pixel_size;
1775 if pixel_size == 0 || self.stride != row_bytes {
1776 return None;
1777 }
1778 let total = row_bytes * self.height as usize;
1779 let data = &self.data[self.offset..self.offset + total];
1780 Some(bytemuck::cast_slice(data))
1781 }
1782
1783 pub fn into_contiguous_pixels<P: Pixel>(self) -> Option<Vec<P>> {
1790 if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
1791 return None;
1792 }
1793 let pixel_size = core::mem::size_of::<P>();
1794 if pixel_size == 0 {
1795 return None;
1796 }
1797 let row_bytes = self.width as usize * pixel_size;
1798 let total_pixels = self.width as usize * self.height as usize;
1799
1800 if self.stride == row_bytes && self.offset == 0 {
1801 let mut data = self.data;
1803 data.truncate(total_pixels * pixel_size);
1804 match bytemuck::try_cast_vec(data) {
1805 Ok(pixels) => return Some(pixels),
1806 Err((_err, data)) => {
1807 return Some(
1809 bytemuck::cast_slice::<u8, P>(&data[..total_pixels * pixel_size]).to_vec(),
1810 );
1811 }
1812 }
1813 }
1814
1815 let mut out = Vec::with_capacity(total_pixels);
1817 for y in 0..self.height as usize {
1818 let row_start = self.offset + y * self.stride;
1819 let row_data = &self.data[row_start..row_start + row_bytes];
1820 out.extend_from_slice(bytemuck::cast_slice(row_data));
1821 }
1822 Some(out)
1823 }
1824}
1825
1826#[cfg(feature = "imgref")]
1828impl PixelBuffer {
1829 pub fn try_as_imgref<P: Pixel>(&self) -> Option<ImgRef<'_, P>> {
1833 if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
1834 return None;
1835 }
1836 let pixel_size = core::mem::size_of::<P>();
1837 #[allow(clippy::manual_is_multiple_of)] if pixel_size == 0 || self.stride % pixel_size != 0 {
1839 return None;
1840 }
1841 let total_bytes = if self.height == 0 {
1842 0
1843 } else {
1844 (self.height as usize - 1) * self.stride + self.width as usize * pixel_size
1845 };
1846 let data = &self.data[self.offset..self.offset + total_bytes];
1847 let pixels: &[P] = bytemuck::cast_slice(data);
1848 let stride_px = self.stride / pixel_size;
1849 Some(imgref::Img::new_stride(
1850 pixels,
1851 self.width as usize,
1852 self.height as usize,
1853 stride_px,
1854 ))
1855 }
1856
1857 pub fn try_as_imgref_mut<P: Pixel>(&mut self) -> Option<imgref::ImgRefMut<'_, P>> {
1861 if !self.descriptor.layout_compatible(P::DESCRIPTOR) {
1862 return None;
1863 }
1864 let pixel_size = core::mem::size_of::<P>();
1865 #[allow(clippy::manual_is_multiple_of)] if pixel_size == 0 || self.stride % pixel_size != 0 {
1867 return None;
1868 }
1869 let total_bytes = if self.height == 0 {
1870 0
1871 } else {
1872 (self.height as usize - 1) * self.stride + self.width as usize * pixel_size
1873 };
1874 let offset = self.offset;
1875 let data = &mut self.data[offset..offset + total_bytes];
1876 let pixels: &mut [P] = bytemuck::cast_slice_mut(data);
1877 let stride_px = self.stride / pixel_size;
1878 Some(imgref::Img::new_stride(
1879 pixels,
1880 self.width as usize,
1881 self.height as usize,
1882 stride_px,
1883 ))
1884 }
1885}
1886
1887impl<P> PixelBuffer<P> {
1888 pub fn erase(self) -> PixelBuffer {
1890 PixelBuffer {
1891 data: self.data,
1892 offset: self.offset,
1893 width: self.width,
1894 height: self.height,
1895 stride: self.stride,
1896 descriptor: self.descriptor,
1897
1898 color: self.color,
1899 _pixel: PhantomData,
1900 }
1901 }
1902
1903 pub fn try_typed<Q: Pixel>(self) -> Option<PixelBuffer<Q>> {
1907 if self.descriptor.layout_compatible(Q::DESCRIPTOR) {
1908 Some(PixelBuffer {
1909 data: self.data,
1910 offset: self.offset,
1911 width: self.width,
1912 height: self.height,
1913 stride: self.stride,
1914 descriptor: self.descriptor,
1915
1916 color: self.color,
1917 _pixel: PhantomData,
1918 })
1919 } else {
1920 None
1921 }
1922 }
1923
1924 #[inline]
1928 #[must_use]
1929 pub fn with_descriptor(mut self, descriptor: PixelDescriptor) -> Self {
1930 assert!(
1931 self.descriptor.layout_compatible(descriptor),
1932 "with_descriptor() cannot change physical layout ({} -> {}); \
1933 use reinterpret() for layout changes",
1934 self.descriptor,
1935 descriptor
1936 );
1937 self.descriptor = descriptor;
1938 self
1939 }
1940
1941 #[track_caller]
1945 pub fn reinterpret(mut self, descriptor: PixelDescriptor) -> Result<Self, At<BufferError>> {
1946 if self.descriptor.bytes_per_pixel() != descriptor.bytes_per_pixel() {
1947 return Err(whereat::at!(BufferError::IncompatibleDescriptor));
1948 }
1949 self.descriptor = descriptor;
1950 Ok(self)
1951 }
1952
1953 #[inline]
1955 #[must_use]
1956 pub fn with_transfer(mut self, tf: TransferFunction) -> Self {
1957 self.descriptor.transfer = tf;
1958 self
1959 }
1960
1961 #[inline]
1963 #[must_use]
1964 pub fn with_primaries(mut self, cp: ColorPrimaries) -> Self {
1965 self.descriptor.primaries = cp;
1966 self
1967 }
1968
1969 #[inline]
1971 #[must_use]
1972 pub fn with_signal_range(mut self, sr: SignalRange) -> Self {
1973 self.descriptor.signal_range = sr;
1974 self
1975 }
1976
1977 #[inline]
1979 #[must_use]
1980 pub fn with_alpha_mode(mut self, am: Option<AlphaMode>) -> Self {
1981 self.descriptor.alpha = am;
1982 self
1983 }
1984
1985 #[inline]
1987 pub fn has_alpha(&self) -> bool {
1988 self.descriptor.has_alpha()
1989 }
1990
1991 #[inline]
1993 pub fn is_grayscale(&self) -> bool {
1994 self.descriptor.is_grayscale()
1995 }
1996
1997 pub fn into_vec(self) -> Vec<u8> {
1999 self.data
2000 }
2001
2002 #[inline]
2007 pub fn as_contiguous_bytes(&self) -> Option<&[u8]> {
2008 let bpp = self.descriptor.bytes_per_pixel();
2009 let row_bytes = self.width as usize * bpp;
2010 if self.stride == row_bytes {
2011 let total = row_bytes * self.height as usize;
2012 Some(&self.data[self.offset..self.offset + total])
2013 } else {
2014 None
2015 }
2016 }
2017
2018 pub fn copy_to_contiguous_bytes(&self) -> Vec<u8> {
2024 let bpp = self.descriptor.bytes_per_pixel();
2025 let row_bytes = self.width as usize * bpp;
2026 let total = row_bytes * self.height as usize;
2027
2028 if self.stride == row_bytes {
2030 let start = self.offset;
2031 return self.data[start..start + total].to_vec();
2032 }
2033
2034 let mut out = Vec::with_capacity(total);
2036 let slice = self.as_slice();
2037 for y in 0..self.height {
2038 out.extend_from_slice(slice.row(y));
2039 }
2040 out
2041 }
2042
2043 #[inline]
2045 pub fn width(&self) -> u32 {
2046 self.width
2047 }
2048
2049 #[inline]
2051 pub fn height(&self) -> u32 {
2052 self.height
2053 }
2054
2055 #[inline]
2057 pub fn stride(&self) -> usize {
2058 self.stride
2059 }
2060
2061 #[inline]
2063 pub fn descriptor(&self) -> PixelDescriptor {
2064 self.descriptor
2065 }
2066
2067 #[inline]
2069 pub fn color_context(&self) -> Option<&Arc<ColorContext>> {
2070 self.color.as_ref()
2071 }
2072
2073 #[inline]
2075 #[must_use]
2076 pub fn with_color_context(mut self, ctx: Arc<ColorContext>) -> Self {
2077 self.color = Some(ctx);
2078 self
2079 }
2080
2081 pub fn as_slice(&self) -> PixelSlice<'_, P> {
2083 let total = self.stride * self.height as usize;
2084 PixelSlice {
2085 data: &self.data[self.offset..self.offset + total],
2086 width: self.width,
2087 rows: self.height,
2088 stride: self.stride,
2089 descriptor: self.descriptor,
2090
2091 color: self.color.clone(),
2092 _pixel: PhantomData,
2093 }
2094 }
2095
2096 pub fn as_slice_mut(&mut self) -> PixelSliceMut<'_, P> {
2098 let total = self.stride * self.height as usize;
2099 let offset = self.offset;
2100 PixelSliceMut {
2101 data: &mut self.data[offset..offset + total],
2102 width: self.width,
2103 rows: self.height,
2104 stride: self.stride,
2105 descriptor: self.descriptor,
2106
2107 color: self.color.clone(),
2108 _pixel: PhantomData,
2109 }
2110 }
2111
2112 pub fn rows(&self, y: u32, count: u32) -> PixelSlice<'_, P> {
2118 assert!(
2119 y.checked_add(count).is_some_and(|end| end <= self.height),
2120 "rows({y}, {count}) out of bounds (height: {})",
2121 self.height
2122 );
2123 if count == 0 {
2124 return PixelSlice {
2125 data: &[],
2126 width: self.width,
2127 rows: 0,
2128 stride: self.stride,
2129 descriptor: self.descriptor,
2130
2131 color: self.color.clone(),
2132 _pixel: PhantomData,
2133 };
2134 }
2135 let bpp = self.descriptor.bytes_per_pixel();
2136 let start = self.offset + y as usize * self.stride;
2137 let end = self.offset
2138 + (y as usize + count as usize - 1) * self.stride
2139 + self.width as usize * bpp;
2140 PixelSlice {
2141 data: &self.data[start..end],
2142 width: self.width,
2143 rows: count,
2144 stride: self.stride,
2145 descriptor: self.descriptor,
2146
2147 color: self.color.clone(),
2148 _pixel: PhantomData,
2149 }
2150 }
2151
2152 pub fn rows_mut(&mut self, y: u32, count: u32) -> PixelSliceMut<'_, P> {
2158 assert!(
2159 y.checked_add(count).is_some_and(|end| end <= self.height),
2160 "rows_mut({y}, {count}) out of bounds (height: {})",
2161 self.height
2162 );
2163 if count == 0 {
2164 return PixelSliceMut {
2165 data: &mut [],
2166 width: self.width,
2167 rows: 0,
2168 stride: self.stride,
2169 descriptor: self.descriptor,
2170
2171 color: self.color.clone(),
2172 _pixel: PhantomData,
2173 };
2174 }
2175 let bpp = self.descriptor.bytes_per_pixel();
2176 let start = self.offset + y as usize * self.stride;
2177 let end = self.offset
2178 + (y as usize + count as usize - 1) * self.stride
2179 + self.width as usize * bpp;
2180 PixelSliceMut {
2181 data: &mut self.data[start..end],
2182 width: self.width,
2183 rows: count,
2184 stride: self.stride,
2185 descriptor: self.descriptor,
2186
2187 color: self.color.clone(),
2188 _pixel: PhantomData,
2189 }
2190 }
2191
2192 pub fn crop_view(&self, x: u32, y: u32, w: u32, h: u32) -> PixelSlice<'_, P> {
2198 assert!(
2199 x.checked_add(w).is_some_and(|end| end <= self.width),
2200 "crop x={x} w={w} exceeds width {}",
2201 self.width
2202 );
2203 assert!(
2204 y.checked_add(h).is_some_and(|end| end <= self.height),
2205 "crop y={y} h={h} exceeds height {}",
2206 self.height
2207 );
2208 if h == 0 || w == 0 {
2209 return PixelSlice {
2210 data: &[],
2211 width: w,
2212 rows: h,
2213 stride: self.stride,
2214 descriptor: self.descriptor,
2215
2216 color: self.color.clone(),
2217 _pixel: PhantomData,
2218 };
2219 }
2220 let bpp = self.descriptor.bytes_per_pixel();
2221 let start = self.offset + y as usize * self.stride + x as usize * bpp;
2222 let end = self.offset
2223 + (y as usize + h as usize - 1) * self.stride
2224 + (x as usize + w as usize) * bpp;
2225 PixelSlice {
2226 data: &self.data[start..end],
2227 width: w,
2228 rows: h,
2229 stride: self.stride,
2230 descriptor: self.descriptor,
2231
2232 color: self.color.clone(),
2233 _pixel: PhantomData,
2234 }
2235 }
2236
2237 pub fn crop_copy(&self, x: u32, y: u32, w: u32, h: u32) -> PixelBuffer<P> {
2243 let src = self.crop_view(x, y, w, h);
2244 let stride = self.descriptor.aligned_stride(w);
2245 let total = stride * h as usize;
2246 let align = self.descriptor.min_alignment();
2247 let alloc_size = total + align - 1;
2248 let data = vec![0u8; alloc_size];
2249 let offset = align_offset(data.as_ptr(), align);
2250 let mut dst = PixelBuffer {
2251 data,
2252 offset,
2253 width: w,
2254 height: h,
2255 stride,
2256 descriptor: self.descriptor,
2257
2258 color: self.color.clone(),
2259 _pixel: PhantomData,
2260 };
2261 let bpp = self.descriptor.bytes_per_pixel();
2262 let row_bytes = w as usize * bpp;
2263 for row_y in 0..h {
2264 let src_row = src.row(row_y);
2265 let dst_start = dst.offset + row_y as usize * dst.stride;
2266 dst.data[dst_start..dst_start + row_bytes].copy_from_slice(&src_row[..row_bytes]);
2267 }
2268 dst
2269 }
2270}
2271
2272impl<P> fmt::Debug for PixelBuffer<P> {
2273 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2274 write!(
2275 f,
2276 "PixelBuffer({}x{}, {:?} {:?})",
2277 self.width,
2278 self.height,
2279 self.descriptor.layout(),
2280 self.descriptor.channel_type()
2281 )
2282 }
2283}
2284
2285#[cfg(feature = "imgref")]
2290macro_rules! impl_from_imgref {
2291 ($pixel:ty, $descriptor:expr) => {
2292 impl<'a> From<ImgRef<'a, $pixel>> for PixelSlice<'a, $pixel> {
2293 fn from(img: ImgRef<'a, $pixel>) -> Self {
2294 let bytes: &[u8] = bytemuck::cast_slice(img.buf());
2295 let byte_stride = img.stride() * core::mem::size_of::<$pixel>();
2296 PixelSlice {
2297 data: bytes,
2298 width: img.width() as u32,
2299 rows: img.height() as u32,
2300 stride: byte_stride,
2301 descriptor: $descriptor,
2302
2303 color: None,
2304 _pixel: PhantomData,
2305 }
2306 }
2307 }
2308 };
2309}
2310
2311#[cfg(feature = "imgref")]
2314impl_from_imgref!(Rgb<u8>, PixelDescriptor::RGB8_SRGB);
2315#[cfg(feature = "imgref")]
2316impl_from_imgref!(Rgba<u8>, PixelDescriptor::RGBA8_SRGB);
2317#[cfg(feature = "imgref")]
2318impl_from_imgref!(Rgb<u16>, PixelDescriptor::RGB16);
2319#[cfg(feature = "imgref")]
2320impl_from_imgref!(Rgba<u16>, PixelDescriptor::RGBA16);
2321#[cfg(feature = "imgref")]
2322impl_from_imgref!(Rgb<f32>, PixelDescriptor::RGBF32_LINEAR);
2323#[cfg(feature = "imgref")]
2324impl_from_imgref!(Rgba<f32>, PixelDescriptor::RGBAF32_LINEAR);
2325#[cfg(feature = "imgref")]
2326impl_from_imgref!(Gray<u8>, PixelDescriptor::GRAY8_SRGB);
2327#[cfg(feature = "imgref")]
2328impl_from_imgref!(Gray<u16>, PixelDescriptor::GRAY16);
2329#[cfg(feature = "imgref")]
2330impl_from_imgref!(Gray<f32>, PixelDescriptor::GRAYF32_LINEAR);
2331#[cfg(feature = "imgref")]
2332impl_from_imgref!(BGRA<u8>, PixelDescriptor::BGRA8_SRGB);
2333
2334#[cfg(feature = "imgref")]
2339macro_rules! impl_from_imgref_mut {
2340 ($pixel:ty, $descriptor:expr) => {
2341 impl<'a> From<imgref::ImgRefMut<'a, $pixel>> for PixelSliceMut<'a, $pixel> {
2342 fn from(img: imgref::ImgRefMut<'a, $pixel>) -> Self {
2343 let width = img.width() as u32;
2344 let rows = img.height() as u32;
2345 let byte_stride = img.stride() * core::mem::size_of::<$pixel>();
2346 let buf = img.into_buf();
2347 let bytes: &mut [u8] = bytemuck::cast_slice_mut(buf);
2348 PixelSliceMut {
2349 data: bytes,
2350 width,
2351 rows,
2352 stride: byte_stride,
2353 descriptor: $descriptor,
2354
2355 color: None,
2356 _pixel: PhantomData,
2357 }
2358 }
2359 }
2360 };
2361}
2362
2363#[cfg(feature = "imgref")]
2364impl_from_imgref_mut!(Rgb<u8>, PixelDescriptor::RGB8_SRGB);
2365#[cfg(feature = "imgref")]
2366impl_from_imgref_mut!(Rgba<u8>, PixelDescriptor::RGBA8_SRGB);
2367#[cfg(feature = "imgref")]
2368impl_from_imgref_mut!(Rgb<u16>, PixelDescriptor::RGB16);
2369#[cfg(feature = "imgref")]
2370impl_from_imgref_mut!(Rgba<u16>, PixelDescriptor::RGBA16);
2371#[cfg(feature = "imgref")]
2372impl_from_imgref_mut!(Rgb<f32>, PixelDescriptor::RGBF32_LINEAR);
2373#[cfg(feature = "imgref")]
2374impl_from_imgref_mut!(Rgba<f32>, PixelDescriptor::RGBAF32_LINEAR);
2375#[cfg(feature = "imgref")]
2376impl_from_imgref_mut!(Gray<u8>, PixelDescriptor::GRAY8_SRGB);
2377#[cfg(feature = "imgref")]
2378impl_from_imgref_mut!(Gray<u16>, PixelDescriptor::GRAY16);
2379#[cfg(feature = "imgref")]
2380impl_from_imgref_mut!(Gray<f32>, PixelDescriptor::GRAYF32_LINEAR);
2381#[cfg(feature = "imgref")]
2382impl_from_imgref_mut!(BGRA<u8>, PixelDescriptor::BGRA8_SRGB);
2383
2384impl<'a, P: Pixel> From<PixelSlice<'a, P>> for PixelSlice<'a> {
2389 fn from(typed: PixelSlice<'a, P>) -> Self {
2390 typed.erase()
2391 }
2392}
2393
2394impl<'a, P: Pixel> From<PixelSliceMut<'a, P>> for PixelSliceMut<'a> {
2395 fn from(typed: PixelSliceMut<'a, P>) -> Self {
2396 typed.erase()
2397 }
2398}
2399
2400impl<P: Pixel> From<PixelBuffer<P>> for PixelBuffer {
2401 fn from(typed: PixelBuffer<P>) -> Self {
2402 typed.erase()
2403 }
2404}
2405
2406impl<'a, P> From<PixelSliceMut<'a, P>> for PixelSlice<'a, P> {
2412 fn from(mut_slice: PixelSliceMut<'a, P>) -> Self {
2413 PixelSlice {
2414 data: mut_slice.data,
2415 width: mut_slice.width,
2416 rows: mut_slice.rows,
2417 stride: mut_slice.stride,
2418 descriptor: mut_slice.descriptor,
2419 color: mut_slice.color,
2420 _pixel: PhantomData,
2421 }
2422 }
2423}
2424
2425#[cfg(test)]
2430mod tests {
2431 use super::*;
2432 use crate::descriptor::{ChannelLayout, ChannelType};
2433 use alloc::format;
2434 use alloc::vec;
2435
2436 #[test]
2439 fn pixel_buffer_new_rgb8() {
2440 let buf = PixelBuffer::new(10, 5, PixelDescriptor::RGB8_SRGB);
2441 assert_eq!(buf.width(), 10);
2442 assert_eq!(buf.height(), 5);
2443 assert_eq!(buf.stride(), 30);
2444 assert_eq!(buf.descriptor(), PixelDescriptor::RGB8_SRGB);
2445 let slice = buf.as_slice();
2447 assert_eq!(slice.row(0), &[0u8; 30]);
2448 assert_eq!(slice.row(4), &[0u8; 30]);
2449 }
2450
2451 #[test]
2452 fn pixel_buffer_from_vec() {
2453 let data = vec![0u8; 30 * 5];
2454 let buf = PixelBuffer::from_vec(data, 10, 5, PixelDescriptor::RGB8_SRGB).unwrap();
2455 assert_eq!(buf.width(), 10);
2456 assert_eq!(buf.height(), 5);
2457 }
2458
2459 #[test]
2460 fn pixel_buffer_from_vec_too_small() {
2461 let data = vec![0u8; 10];
2462 let err = PixelBuffer::from_vec(data, 10, 5, PixelDescriptor::RGB8_SRGB);
2463 assert_eq!(*err.unwrap_err().error(), BufferError::InsufficientData);
2464 }
2465
2466 #[test]
2467 fn pixel_buffer_into_vec_roundtrip() {
2468 let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGBA8_SRGB);
2469 let v = buf.into_vec();
2470 let buf2 = PixelBuffer::from_vec(v, 4, 4, PixelDescriptor::RGBA8_SRGB).unwrap();
2472 assert_eq!(buf2.width(), 4);
2473 }
2474
2475 #[test]
2476 fn pixel_buffer_write_and_read() {
2477 let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2478 {
2479 let mut slice = buf.as_slice_mut();
2480 let row = slice.row_mut(0);
2481 row[0] = 255;
2482 row[1] = 128;
2483 row[2] = 64;
2484 }
2485 let slice = buf.as_slice();
2486 assert_eq!(&slice.row(0)[..3], &[255, 128, 64]);
2487 assert_eq!(&slice.row(1)[..3], &[0, 0, 0]);
2488 }
2489
2490 #[test]
2491 fn pixel_buffer_simd_aligned() {
2492 let buf = PixelBuffer::new_simd_aligned(10, 5, PixelDescriptor::RGBA8_SRGB, 64);
2493 assert_eq!(buf.width(), 10);
2494 assert_eq!(buf.height(), 5);
2495 assert_eq!(buf.stride(), 64);
2497 let slice = buf.as_slice();
2499 assert_eq!(slice.data.as_ptr() as usize % 64, 0);
2500 }
2501
2502 #[test]
2505 fn pixel_slice_crop_view() {
2506 let mut buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
2508 {
2509 let mut slice = buf.as_slice_mut();
2510 for y in 0..4u32 {
2511 let row = slice.row_mut(y);
2512 for byte in row.iter_mut() {
2513 *byte = y as u8;
2514 }
2515 }
2516 }
2517 let crop = buf.crop_view(1, 1, 2, 2);
2519 assert_eq!(crop.width(), 2);
2520 assert_eq!(crop.rows(), 2);
2521 assert_eq!(crop.row(0), &[1, 1, 1, 1, 1, 1]);
2523 assert_eq!(crop.row(1), &[2, 2, 2, 2, 2, 2]);
2525 }
2526
2527 #[test]
2528 fn pixel_slice_crop_copy() {
2529 let mut buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
2530 {
2531 let mut slice = buf.as_slice_mut();
2532 for y in 0..4u32 {
2533 let row = slice.row_mut(y);
2534 for (i, byte) in row.iter_mut().enumerate() {
2535 *byte = (y * 100 + i as u32) as u8;
2536 }
2537 }
2538 }
2539 let cropped = buf.crop_copy(1, 1, 2, 2);
2540 assert_eq!(cropped.width(), 2);
2541 assert_eq!(cropped.height(), 2);
2542 assert_eq!(cropped.as_slice().row(0), &[103, 104, 105, 106, 107, 108]);
2544 }
2545
2546 #[test]
2547 fn pixel_slice_sub_rows() {
2548 let mut buf = PixelBuffer::new(2, 4, PixelDescriptor::GRAY8_SRGB);
2549 {
2550 let mut slice = buf.as_slice_mut();
2551 for y in 0..4u32 {
2552 let row = slice.row_mut(y);
2553 row[0] = y as u8 * 10;
2554 row[1] = y as u8 * 10 + 1;
2555 }
2556 }
2557 let sub = buf.rows(1, 2);
2558 assert_eq!(sub.rows(), 2);
2559 assert_eq!(sub.row(0), &[10, 11]);
2560 assert_eq!(sub.row(1), &[20, 21]);
2561 }
2562
2563 #[test]
2566 fn pixel_slice_stride_too_small() {
2567 let data = [0u8; 100];
2568 let err = PixelSlice::new(&data, 10, 1, 2, PixelDescriptor::RGB8_SRGB);
2569 assert_eq!(*err.unwrap_err().error(), BufferError::StrideTooSmall);
2570 }
2571
2572 #[test]
2573 fn pixel_slice_insufficient_data() {
2574 let data = [0u8; 10];
2575 let err = PixelSlice::new(&data, 10, 1, 30, PixelDescriptor::RGB8_SRGB);
2576 assert_eq!(*err.unwrap_err().error(), BufferError::InsufficientData);
2577 }
2578
2579 #[test]
2580 fn pixel_slice_zero_rows() {
2581 let data = [0u8; 0];
2582 let slice = PixelSlice::new(&data, 10, 0, 30, PixelDescriptor::RGB8_SRGB).unwrap();
2583 assert_eq!(slice.rows(), 0);
2584 }
2585
2586 #[test]
2587 fn stride_not_pixel_aligned_rejected() {
2588 let data = [0u8; 128];
2590 let err = PixelSlice::new(&data, 10, 1, 32, PixelDescriptor::RGB8_SRGB);
2591 assert_eq!(
2592 *err.unwrap_err().error(),
2593 BufferError::StrideNotPixelAligned
2594 );
2595
2596 let ok = PixelSlice::new(&data, 10, 1, 33, PixelDescriptor::RGB8_SRGB);
2598 assert!(ok.is_ok());
2599 }
2600
2601 #[test]
2602 fn stride_pixel_aligned_accepted() {
2603 let data = [0u8; 256];
2605 let ok = PixelSlice::new(&data, 10, 2, 48, PixelDescriptor::RGBA8_SRGB);
2606 assert!(ok.is_ok());
2607 let s = ok.unwrap();
2608 assert_eq!(s.stride(), 48);
2609 }
2610
2611 #[test]
2614 fn debug_formats() {
2615 let buf = PixelBuffer::new(10, 5, PixelDescriptor::RGB8_SRGB);
2616 assert_eq!(format!("{buf:?}"), "PixelBuffer(10x5, Rgb U8)");
2617
2618 let slice = buf.as_slice();
2619 assert_eq!(format!("{slice:?}"), "PixelSlice(10x5, Rgb U8)");
2620
2621 let mut buf = PixelBuffer::new(3, 3, PixelDescriptor::RGBA16_SRGB);
2622 let slice_mut = buf.as_slice_mut();
2623 assert_eq!(format!("{slice_mut:?}"), "PixelSliceMut(3x3, Rgba U16)");
2624 }
2625
2626 #[test]
2629 fn buffer_error_display() {
2630 let msg = format!("{}", BufferError::StrideTooSmall);
2631 assert!(msg.contains("stride"));
2632 }
2633
2634 #[test]
2637 fn bgrx8_srgb_properties() {
2638 let d = PixelDescriptor::BGRX8_SRGB;
2639 assert_eq!(d.channel_type(), ChannelType::U8);
2640 assert_eq!(d.layout(), ChannelLayout::Bgra);
2641 assert_eq!(d.alpha(), Some(AlphaMode::Undefined));
2642 assert_eq!(d.transfer(), TransferFunction::Srgb);
2643 assert_eq!(d.bytes_per_pixel(), 4);
2644 assert_eq!(d.min_alignment(), 1);
2645 assert!(d.layout_compatible(PixelDescriptor::BGRA8_SRGB));
2647 assert!(!d.has_alpha());
2649 assert!(PixelDescriptor::BGRA8_SRGB.has_alpha());
2651 assert!(d.layout().has_alpha());
2653 }
2654
2655 #[test]
2656 fn zero_size_buffer() {
2657 let buf = PixelBuffer::new(0, 0, PixelDescriptor::RGB8_SRGB);
2658 assert_eq!(buf.width(), 0);
2659 assert_eq!(buf.height(), 0);
2660 let slice = buf.as_slice();
2661 assert_eq!(slice.rows(), 0);
2662 }
2663
2664 #[test]
2665 fn crop_empty() {
2666 let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
2667 let crop = buf.crop_view(0, 0, 0, 0);
2668 assert_eq!(crop.width(), 0);
2669 assert_eq!(crop.rows(), 0);
2670 }
2671
2672 #[test]
2673 fn sub_rows_empty() {
2674 let buf = PixelBuffer::new(4, 4, PixelDescriptor::RGB8_SRGB);
2675 let sub = buf.rows(2, 0);
2676 assert_eq!(sub.rows(), 0);
2677 }
2678
2679 #[test]
2682 fn with_descriptor_metadata_change_succeeds() {
2683 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2684 let buf2 = buf.with_descriptor(PixelDescriptor::RGB8);
2686 assert_eq!(buf2.descriptor(), PixelDescriptor::RGB8);
2687 }
2688
2689 #[test]
2690 #[should_panic(expected = "with_descriptor() cannot change physical layout")]
2691 fn with_descriptor_layout_change_panics() {
2692 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
2693 let _ = buf.with_descriptor(PixelDescriptor::RGBA8);
2695 }
2696
2697 #[test]
2698 fn with_descriptor_slice_assertion() {
2699 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2700 let slice = buf.as_slice();
2701 let s2 = slice.with_descriptor(PixelDescriptor::RGB8);
2703 assert_eq!(s2.descriptor(), PixelDescriptor::RGB8);
2704 }
2705
2706 #[test]
2707 #[should_panic(expected = "with_descriptor() cannot change physical layout")]
2708 fn with_descriptor_slice_layout_change_panics() {
2709 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
2710 let slice = buf.as_slice();
2711 let _ = slice.with_descriptor(PixelDescriptor::RGBA8);
2712 }
2713
2714 #[test]
2717 fn reinterpret_same_bpp_succeeds() {
2718 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8);
2720 let buf2 = buf.reinterpret(PixelDescriptor::BGRA8).unwrap();
2721 assert_eq!(buf2.descriptor().layout(), ChannelLayout::Bgra);
2722 }
2723
2724 #[test]
2725 fn reinterpret_different_bpp_fails() {
2726 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
2728 let err = buf.reinterpret(PixelDescriptor::RGBA8);
2729 assert_eq!(
2730 *err.unwrap_err().error(),
2731 BufferError::IncompatibleDescriptor
2732 );
2733 }
2734
2735 #[test]
2736 fn reinterpret_rgbx_to_rgba() {
2737 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBX8);
2739 let buf2 = buf.reinterpret(PixelDescriptor::RGBA8).unwrap();
2740 assert!(buf2.descriptor().has_alpha());
2741 }
2742
2743 #[test]
2746 fn per_field_setters() {
2747 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8);
2748 let buf = buf.with_transfer(TransferFunction::Srgb);
2749 assert_eq!(buf.descriptor().transfer(), TransferFunction::Srgb);
2750 let buf = buf.with_primaries(ColorPrimaries::DisplayP3);
2751 assert_eq!(buf.descriptor().primaries, ColorPrimaries::DisplayP3);
2752 let buf = buf.with_signal_range(SignalRange::Narrow);
2753 assert!(matches!(buf.descriptor().signal_range, SignalRange::Narrow));
2754 let buf = buf.with_alpha_mode(Some(AlphaMode::Premultiplied));
2755 assert_eq!(buf.descriptor().alpha(), Some(AlphaMode::Premultiplied));
2756 }
2757
2758 #[test]
2761 fn copy_to_contiguous_bytes_tight() {
2762 let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2763 {
2764 let mut s = buf.as_slice_mut();
2765 s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6]);
2766 s.row_mut(1).copy_from_slice(&[7, 8, 9, 10, 11, 12]);
2767 }
2768 let bytes = buf.copy_to_contiguous_bytes();
2769 assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
2770 }
2771
2772 #[test]
2773 fn copy_to_contiguous_bytes_padded() {
2774 let mut buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
2776 let stride = buf.stride();
2777 assert!(stride >= 6);
2779 {
2780 let mut s = buf.as_slice_mut();
2781 s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6]);
2782 s.row_mut(1).copy_from_slice(&[7, 8, 9, 10, 11, 12]);
2783 }
2784 let bytes = buf.copy_to_contiguous_bytes();
2785 assert_eq!(bytes.len(), 12);
2787 assert_eq!(bytes, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
2788 }
2789
2790 #[test]
2793 fn buffer_error_display_alignment_violation() {
2794 let msg = format!("{}", BufferError::AlignmentViolation);
2795 assert_eq!(msg, "data is not aligned for the channel type");
2796 }
2797
2798 #[test]
2799 fn buffer_error_display_insufficient_data() {
2800 let msg = format!("{}", BufferError::InsufficientData);
2801 assert_eq!(msg, "data slice is too small for the given dimensions");
2802 }
2803
2804 #[test]
2805 fn buffer_error_display_invalid_dimensions() {
2806 let msg = format!("{}", BufferError::InvalidDimensions);
2807 assert_eq!(msg, "width or height is zero or causes overflow");
2808 }
2809
2810 #[test]
2811 fn buffer_error_display_incompatible_descriptor() {
2812 let msg = format!("{}", BufferError::IncompatibleDescriptor);
2813 assert_eq!(msg, "new descriptor has different bytes_per_pixel");
2814 }
2815
2816 #[test]
2817 fn buffer_error_display_allocation_failed() {
2818 let msg = format!("{}", BufferError::AllocationFailed);
2819 assert_eq!(msg, "buffer allocation failed");
2820 }
2821
2822 #[test]
2825 fn pixel_slice_is_contiguous_tight() {
2826 let buf = PixelBuffer::new(4, 3, PixelDescriptor::RGBA8_SRGB);
2828 let slice = buf.as_slice();
2829 assert_eq!(slice.stride(), 16);
2831 assert!(slice.is_contiguous());
2832 }
2833
2834 #[test]
2837 fn pixel_slice_as_contiguous_bytes_tight() {
2838 let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
2839 {
2840 let mut s = buf.as_slice_mut();
2841 s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
2842 s.row_mut(1)
2843 .copy_from_slice(&[9, 10, 11, 12, 13, 14, 15, 16]);
2844 }
2845 let slice = buf.as_slice();
2846 let bytes = slice.as_contiguous_bytes();
2847 assert!(bytes.is_some());
2848 assert_eq!(
2849 bytes.unwrap(),
2850 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
2851 );
2852 }
2853
2854 #[test]
2855 fn pixel_slice_as_contiguous_bytes_padded_returns_none() {
2856 let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
2858 let slice = buf.as_slice();
2859 assert!(slice.stride() > 6);
2861 assert!(!slice.is_contiguous());
2862 assert!(slice.as_contiguous_bytes().is_none());
2863 }
2864
2865 #[test]
2868 fn pixel_slice_as_strided_bytes_tight() {
2869 let mut buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
2870 {
2871 let mut s = buf.as_slice_mut();
2872 s.row_mut(0).copy_from_slice(&[1, 2, 3, 4, 5, 6, 7, 8]);
2873 s.row_mut(1)
2874 .copy_from_slice(&[9, 10, 11, 12, 13, 14, 15, 16]);
2875 }
2876 let slice = buf.as_slice();
2877 let bytes = slice.as_strided_bytes();
2878 assert_eq!(
2880 bytes,
2881 &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
2882 );
2883 }
2884
2885 #[test]
2886 fn pixel_slice_as_strided_bytes_padded() {
2887 let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
2888 let slice = buf.as_slice();
2889 let stride = slice.stride();
2890 assert!(stride > 6, "expected padding for SIMD alignment");
2891 let bytes = slice.as_strided_bytes();
2892 assert_eq!(bytes.len(), stride * 2);
2894 }
2895
2896 #[test]
2897 fn pixel_slice_as_strided_bytes_sub_rows() {
2898 let buf = PixelBuffer::new_simd_aligned(2, 4, PixelDescriptor::RGB8_SRGB, 16);
2899 let slice = buf.as_slice();
2900 let stride = slice.stride();
2901 let sub = slice.sub_rows(1, 2);
2902 let bytes = sub.as_strided_bytes();
2903 let expected_len = stride + 2 * 3; assert_eq!(bytes.len(), expected_len);
2906 }
2907
2908 #[test]
2909 fn pixel_slice_mut_as_strided_bytes() {
2910 let mut buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGB8_SRGB, 16);
2911 let mut slice = buf.as_slice_mut();
2912 let stride = slice.stride();
2913 let bytes = slice.as_strided_bytes_mut();
2915 assert_eq!(bytes.len(), stride * 2);
2916 bytes[0] = 42;
2917 assert_eq!(slice.row(0)[0], 42);
2919 }
2920
2921 #[test]
2924 fn pixel_slice_row_with_stride_padded() {
2925 let buf = PixelBuffer::new_simd_aligned(2, 2, PixelDescriptor::RGBA8_SRGB, 64);
2927 let slice = buf.as_slice();
2928 let stride = slice.stride();
2929 assert!(stride >= 8);
2931 let full_row = slice.row_with_stride(0);
2933 assert_eq!(full_row.len(), stride);
2934 let pixel_row = slice.row(0);
2936 assert_eq!(pixel_row.len(), 8); }
2938
2939 #[test]
2942 fn pixel_buffer_has_alpha_rgba8() {
2943 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
2944 assert!(buf.has_alpha());
2945 }
2946
2947 #[test]
2948 fn pixel_buffer_has_alpha_rgb8_false() {
2949 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2950 assert!(!buf.has_alpha());
2951 }
2952
2953 #[test]
2956 fn pixel_buffer_is_grayscale_gray8() {
2957 let buf = PixelBuffer::new(2, 2, PixelDescriptor::GRAY8_SRGB);
2958 assert!(buf.is_grayscale());
2959 }
2960
2961 #[test]
2962 fn pixel_buffer_is_grayscale_rgb8_false() {
2963 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
2964 assert!(!buf.is_grayscale());
2965 }
2966
2967 #[cfg(feature = "rgb")]
2970 #[test]
2971 fn pixel_slice_try_typed_success() {
2972 use rgb::Rgba;
2973
2974 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
2975 let slice = buf.as_slice();
2976 let typed: Option<PixelSlice<'_, Rgba<u8>>> = slice.try_typed();
2977 assert!(typed.is_some());
2978 let typed = typed.unwrap();
2979 assert_eq!(typed.width(), 2);
2980 assert_eq!(typed.rows(), 2);
2981 }
2982
2983 #[cfg(feature = "rgb")]
2986 #[test]
2987 fn pixel_buffer_try_typed_success() {
2988 use rgb::Rgba;
2989
2990 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGBA8_SRGB);
2991 let typed: Option<PixelBuffer<Rgba<u8>>> = buf.try_typed();
2992 assert!(typed.is_some());
2993 let typed = typed.unwrap();
2994 assert_eq!(typed.width(), 2);
2995 assert_eq!(typed.height(), 2);
2996 }
2997
2998 #[cfg(feature = "rgb")]
2999 #[test]
3000 fn pixel_buffer_try_typed_failure_wrong_layout() {
3001 use rgb::Rgba;
3002
3003 let buf = PixelBuffer::new(2, 2, PixelDescriptor::RGB8_SRGB);
3005 let typed: Option<PixelBuffer<Rgba<u8>>> = buf.try_typed();
3006 assert!(typed.is_none());
3007 }
3008}
3009
3010#[cfg(all(test, feature = "imgref"))]
3011mod buffer_tests {
3012 use super::*;
3013 use alloc::vec;
3014 use alloc::vec::Vec;
3015 use rgb::{Gray, Rgb};
3016
3017 #[test]
3020 fn imgref_to_pixel_slice_rgb8() {
3021 let pixels: Vec<Rgb<u8>> = vec![
3022 Rgb {
3023 r: 10,
3024 g: 20,
3025 b: 30,
3026 },
3027 Rgb {
3028 r: 40,
3029 g: 50,
3030 b: 60,
3031 },
3032 Rgb {
3033 r: 70,
3034 g: 80,
3035 b: 90,
3036 },
3037 Rgb {
3038 r: 100,
3039 g: 110,
3040 b: 120,
3041 },
3042 ];
3043 let img = imgref::Img::new(pixels.as_slice(), 2, 2);
3044 let slice: PixelSlice<'_, Rgb<u8>> = img.into();
3045 assert_eq!(slice.width(), 2);
3046 assert_eq!(slice.rows(), 2);
3047 assert_eq!(slice.row(0), &[10, 20, 30, 40, 50, 60]);
3048 assert_eq!(slice.row(1), &[70, 80, 90, 100, 110, 120]);
3049 }
3050
3051 #[test]
3052 fn imgref_to_pixel_slice_gray16() {
3053 let pixels = vec![Gray::new(1000u16), Gray::new(2000u16)];
3054 let img = imgref::Img::new(pixels.as_slice(), 2, 1);
3055 let slice: PixelSlice<'_, Gray<u16>> = img.into();
3056 assert_eq!(slice.width(), 2);
3057 assert_eq!(slice.rows(), 1);
3058 assert_eq!(slice.descriptor(), PixelDescriptor::GRAY16);
3059 let row = slice.row(0);
3061 assert_eq!(row.len(), 4);
3062 let v0 = u16::from_ne_bytes([row[0], row[1]]);
3063 let v1 = u16::from_ne_bytes([row[2], row[3]]);
3064 assert_eq!(v0, 1000);
3065 assert_eq!(v1, 2000);
3066 }
3067
3068 #[test]
3071 fn from_pixels_erased_matches_manual() {
3072 let pixels1: Vec<Rgb<u8>> = vec![
3073 Rgb {
3074 r: 10,
3075 g: 20,
3076 b: 30,
3077 },
3078 Rgb {
3079 r: 40,
3080 g: 50,
3081 b: 60,
3082 },
3083 ];
3084 let pixels2 = pixels1.clone();
3085
3086 let manual: PixelBuffer = PixelBuffer::from_pixels(pixels1, 2, 1).unwrap().into();
3088
3089 let erased = PixelBuffer::from_pixels_erased(pixels2, 2, 1).unwrap();
3091
3092 assert_eq!(manual.width(), erased.width());
3093 assert_eq!(manual.height(), erased.height());
3094 assert_eq!(manual.descriptor(), erased.descriptor());
3095 assert_eq!(manual.as_slice().row(0), erased.as_slice().row(0));
3096 }
3097
3098 #[test]
3099 fn from_pixels_erased_dimension_mismatch() {
3100 let pixels: Vec<Rgb<u8>> = vec![Rgb { r: 1, g: 2, b: 3 }];
3101 let err = PixelBuffer::from_pixels_erased(pixels, 2, 1);
3102 assert_eq!(*err.unwrap_err().error(), BufferError::InvalidDimensions);
3103 }
3104}