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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
#[cfg(test)]
mod tests;
use std::num::NonZeroUsize;
use anyhow::{Result, anyhow};
#[derive(Debug, Clone)]
/// Owned storage for a single frame plane and its stride metadata.
pub struct FramePlane<T> {
data: Box<[T]>,
width: NonZeroUsize,
height: NonZeroUsize,
stride: NonZeroUsize,
}
impl<T> FramePlane<T> {
/// Creates an owned plane from a backing buffer and logical dimensions.
///
/// Returns an error when `data.len()` is smaller than `stride * height`.
#[inline]
pub fn new(
data: Box<[T]>,
width: NonZeroUsize,
height: NonZeroUsize,
stride: NonZeroUsize,
) -> Result<Self> {
let required_len = stride.get() * height.get();
if data.len() < required_len {
return Err(anyhow!(
"plane data is shorter than stride * height: {} < {}",
data.len(),
required_len
));
}
Ok(Self {
data,
width,
height,
stride,
})
}
/// Returns the full backing storage, including any stride padding.
#[must_use]
#[inline]
pub fn data(&self) -> &[T] {
&self.data
}
/// Returns the logical plane width in pixels.
#[must_use]
#[inline]
pub const fn width(&self) -> NonZeroUsize {
self.width
}
/// Returns the logical plane height in pixels.
#[must_use]
#[inline]
pub const fn height(&self) -> NonZeroUsize {
self.height
}
/// Returns the number of samples between adjacent rows.
#[must_use]
#[inline]
pub const fn stride(&self) -> NonZeroUsize {
self.stride
}
}
#[derive(Debug, Clone)]
/// Owned frame data stored as up to three Y/U/V planes.
pub struct Frame<T> {
planes: [Option<FramePlane<T>>; 3],
}
impl<T> Frame<T> {
/// Creates an owned frame from optional Y, U, and V planes.
#[must_use]
#[inline]
pub const fn new(planes: [Option<FramePlane<T>>; 3]) -> Self {
Self { planes }
}
/// Returns the requested plane if it is present.
#[must_use]
#[inline]
pub fn plane(&self, plane: usize) -> Option<&FramePlane<T>> {
self.planes.get(plane).and_then(Option::as_ref)
}
/// Returns the raw Y/U/V plane array.
#[must_use]
#[inline]
pub const fn planes(&self) -> &[Option<FramePlane<T>>; 3] {
&self.planes
}
/// Returns per-plane stride metadata for the owned frame.
///
/// Returns an error if the luma plane is missing.
#[inline]
pub fn pitch(&self) -> Result<PlaneSizeTuple> {
Ok((
self.plane(0)
.ok_or_else(|| anyhow!("owned frame is missing luma plane"))?
.stride(),
self.plane(1).map(FramePlane::stride),
self.plane(2).map(FramePlane::stride),
))
}
/// Borrows this frame as immutable plane references.
#[must_use]
#[inline]
pub fn as_planes(&self) -> FramePlanes<'_, T> {
FramePlanes::new(
self.planes
.each_ref()
.map(|plane| plane.as_ref().map(|plane| PlaneRef::new(plane.data()))),
)
}
/// Borrows this frame as a [`FrameView`] with matching pitch metadata.
#[inline]
pub fn as_view(&self) -> Result<FrameView<'_, T>> {
Ok(FrameView::new(self.as_planes(), self.pitch()?))
}
}
#[derive(Debug)]
/// Borrowed frame planes paired with per-plane pitch information.
pub struct FrameView<'a, T> {
planes: FramePlanes<'a, T>,
pitch: PlaneSizeTuple,
}
impl<'a, T> FrameView<'a, T> {
/// Creates a borrowed frame view from plane slices and per-plane pitch.
/// CONTRACT: `planes` and `pitch` must describe the same logical plane layout.
/// Chroma pitch entries must be present if and only if the matching plane exists.
#[must_use]
#[inline]
pub fn new(planes: FramePlanes<'a, T>, pitch: PlaneSizeTuple) -> Self {
debug_assert!(
planes.planes[0].is_some(),
"FrameView requires a luma plane"
);
debug_assert_eq!(
planes.planes[1].is_some(),
pitch.1.is_some(),
"FrameView planes and pitch must agree for chroma U"
);
debug_assert_eq!(
planes.planes[2].is_some(),
pitch.2.is_some(),
"FrameView planes and pitch must agree for chroma V"
);
Self { planes, pitch }
}
/// Returns the stored per-plane pitch tuple.
#[must_use]
#[inline]
pub const fn pitch(&self) -> PlaneSizeTuple {
self.pitch
}
/// Returns the number of leading planes present in this view.
#[must_use]
#[inline]
pub fn plane_count(&self) -> usize {
self.planes
.planes
.iter()
.rposition(Option::is_some)
.map_or(0, |last_plane| last_plane + 1)
}
/// Returns the borrowed plane set backing this view.
#[must_use]
#[inline]
pub const fn planes(&self) -> &FramePlanes<'a, T> {
&self.planes
}
/// Returns the requested plane slice.
#[inline]
pub fn plane(&self, plane: usize) -> Result<&'a [T]> {
self.planes.plane(plane)
}
/// Returns the stride for the requested plane.
#[inline]
pub fn pitch_for_plane(&self, plane: usize) -> Result<NonZeroUsize> {
let pitch = match plane {
0 => Some(self.pitch.0),
1 => self.pitch.1,
2 => self.pitch.2,
_ => None,
};
pitch.ok_or_else(|| anyhow!("requested plane {plane} is not available"))
}
}
#[derive(Debug, Clone, Copy)]
/// Borrowed reference to one frame plane.
pub struct PlaneRef<'a, T> {
data: &'a [T],
}
impl<'a, T> PlaneRef<'a, T> {
/// Wraps a borrowed plane slice.
#[must_use]
#[inline]
pub const fn new(data: &'a [T]) -> Self {
Self { data }
}
/// Returns the borrowed plane slice.
#[must_use]
#[inline]
pub const fn data(&self) -> &'a [T] {
self.data
}
}
#[derive(Debug)]
/// Borrowed Y/U/V plane slices without stride metadata.
pub struct FramePlanes<'a, T> {
planes: [Option<PlaneRef<'a, T>>; 3],
}
impl<'a, T> FramePlanes<'a, T> {
/// Creates a borrowed plane set from optional Y, U, and V planes.
#[must_use]
#[inline]
pub const fn new(planes: [Option<PlaneRef<'a, T>>; 3]) -> Self {
Self { planes }
}
/// Returns the requested plane slice.
#[inline]
pub fn plane(&self, plane: usize) -> Result<&'a [T]> {
self.planes
.get(plane)
.and_then(|plane| plane.as_ref().map(PlaneRef::data))
.ok_or_else(|| anyhow!("requested plane {plane} is not available"))
}
}
#[derive(Debug)]
/// Mutable borrowed Y/U/V plane slices stored behind stable raw pointers.
pub struct FramePlanesMut<'a, T> {
planes: [Option<(*mut T, usize)>; 3],
_marker: std::marker::PhantomData<&'a mut [T]>,
}
impl<'a, T> FramePlanesMut<'a, T> {
/// Creates a mutable plane set from optional Y, U, and V slices.
#[must_use]
#[inline]
pub fn new(planes: [Option<&'a mut [T]>; 3]) -> Self {
let planes = planes.map(|plane| plane.map(|plane| (plane.as_mut_ptr(), plane.len())));
Self {
planes,
_marker: std::marker::PhantomData,
}
}
/// Returns an immutable view of the requested plane.
#[inline]
pub fn plane(&self, plane: usize) -> Result<&[T]> {
let (ptr, len) = self
.planes
.get(plane)
.and_then(|plane| *plane)
.ok_or_else(|| anyhow!("requested plane {plane} is not available"))?;
// SAFETY: `ptr,len` originate from a live mutable slice borrowed for `'a`.
Ok(unsafe { std::slice::from_raw_parts(ptr.cast_const(), len) })
}
/// Returns a mutable view of the requested plane.
#[inline]
pub fn plane_mut(&mut self, plane: usize) -> Result<&mut [T]> {
let (ptr, len) = self
.planes
.get(plane)
.and_then(|plane| *plane)
.ok_or_else(|| anyhow!("requested plane {plane} is not available"))?;
// SAFETY: `ptr,len` originate from a unique mutable slice borrowed for `'a`.
Ok(unsafe { std::slice::from_raw_parts_mut(ptr, len) })
}
/// Returns immutable and mutable views over the same plane backing storage.
///
/// # Safety
///
/// The caller must ensure the returned immutable and mutable slices are only used on
/// non-overlapping logical regions of the same underlying plane.
#[inline]
pub unsafe fn plane_split(&mut self, plane: usize) -> Result<(&[T], &mut [T])> {
let (ptr, len) = self
.planes
.get(plane)
.and_then(|plane| *plane)
.ok_or_else(|| anyhow!("requested plane {plane} is not available"))?;
// SAFETY: Caller guarantees the immutable and mutable views are used on non-overlapping
// logical regions, matching the previous VapourSynth-specific helper contract.
unsafe {
Ok((
std::slice::from_raw_parts(ptr.cast_const(), len),
std::slice::from_raw_parts_mut(ptr, len),
))
}
}
}
/// Per-plane stride tuple in Y/U/V order.
///
/// Chroma entries are `None` when the matching plane is absent.
pub type PlaneSizeTuple = (NonZeroUsize, Option<NonZeroUsize>, Option<NonZeroUsize>);