1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
//! Validated source frame for the 8-bit indexed-color (`AV_PIX_FMT_PAL8`) format.
//!
//! Each pixel is a `u8` index into a 256-entry BGRA palette carried
//! alongside the pixel buffer. See [`Pal8Frame::try_new`] for layout details.
use super::{GeometryOverflow, InsufficientPlane, InsufficientStride, ZeroDimension};
use derive_more::{IsVariant, TryUnwrap, Unwrap};
use thiserror::Error;
/// Error returned by [`Pal8Frame::try_new`].
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, IsVariant, TryUnwrap, Unwrap, Error)]
#[non_exhaustive]
#[unwrap(ref, ref_mut)]
#[try_unwrap(ref, ref_mut)]
pub enum Pal8FrameError {
/// `width` or `height` was zero.
#[error(transparent)]
ZeroDimension(ZeroDimension),
/// `stride < width`.
#[error(transparent)]
InsufficientStride(InsufficientStride),
/// `stride * height` overflows `usize` (can only fire on 32-bit targets).
#[error(transparent)]
GeometryOverflow(GeometryOverflow),
/// Pixel data is shorter than `stride * height` bytes.
#[error(transparent)]
InsufficientPlane(InsufficientPlane),
}
/// A validated 8-bit indexed-color (`AV_PIX_FMT_PAL8`) source frame.
///
/// `data` holds one `u8` index per pixel (row-major, with `stride`
/// bytes per row). `palette` is the 256-entry BGRA lookup table:
/// each entry is `[B, G, R, A]` per FFmpeg's `AV_PIX_FMT_PAL8`
/// convention.
#[derive(Debug, Clone, Copy)]
pub struct Pal8Frame<'a> {
data: &'a [u8],
palette: &'a [[u8; 4]; 256],
width: u32,
height: u32,
stride: u32,
}
impl<'a> Pal8Frame<'a> {
/// Constructs and validates a [`Pal8Frame`].
///
/// Returns [`Pal8FrameError`] if any of:
/// - `width` or `height` is zero,
/// - `stride < width`,
/// - `stride * height` overflows `usize`, or
/// - `data.len() < stride * height`.
///
/// The `palette` slice is always exactly 256 entries — Rust's type
/// system enforces this; no runtime palette-length check is needed.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn try_new(
data: &'a [u8],
palette: &'a [[u8; 4]; 256],
width: u32,
height: u32,
stride: u32,
) -> Result<Self, Pal8FrameError> {
if width == 0 || height == 0 {
return Err(Pal8FrameError::ZeroDimension(ZeroDimension::new(
width, height,
)));
}
if stride < width {
return Err(Pal8FrameError::InsufficientStride(InsufficientStride::new(
stride, width,
)));
}
let min = match (stride as usize).checked_mul(height as usize) {
Some(v) => v,
None => {
return Err(Pal8FrameError::GeometryOverflow(GeometryOverflow::new(
stride, height,
)));
}
};
if data.len() < min {
return Err(Pal8FrameError::InsufficientPlane(InsufficientPlane::new(
min,
data.len(),
)));
}
Ok(Self {
data,
palette,
width,
height,
stride,
})
}
/// Constructs a new [`Pal8Frame`], panicking on invalid inputs.
/// Prefer [`Self::try_new`] when inputs may be invalid at runtime.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn new(
data: &'a [u8],
palette: &'a [[u8; 4]; 256],
width: u32,
height: u32,
stride: u32,
) -> Self {
match Self::try_new(data, palette, width, height, stride) {
Ok(frame) => frame,
Err(_) => panic!("invalid Pal8Frame dimensions or plane length"),
}
}
/// The pixel index buffer. Row `r` starts at byte offset `r * stride()`.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn data(&self) -> &'a [u8] {
self.data
}
/// The 256-entry BGRA palette. Each entry is `[B, G, R, A]` per
/// FFmpeg's `AV_PIX_FMT_PAL8` convention.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn palette(&self) -> &'a [[u8; 4]; 256] {
self.palette
}
/// Frame width in pixels.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn width(&self) -> u32 {
self.width
}
/// Frame height in pixels.
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn height(&self) -> u32 {
self.height
}
/// Byte stride of the pixel plane (`>= width`).
#[cfg_attr(not(tarpaulin), inline(always))]
pub const fn stride(&self) -> u32 {
self.stride
}
}