v_frame 0.5.0

Video Frame data structures, originally part of rav1e
Documentation
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
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
// Copyright (c) 2017-2025, The rav1e contributors. All rights reserved
//
// This source code is subject to the terms of the BSD 2 Clause License and
// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
// was not distributed with this source code in the LICENSE file, you can
// obtain it at www.aomedia.org/license/software. If the Alliance for Open
// Media Patent License 1.0 was not distributed with this source code in the
// PATENTS file, you can obtain it at www.aomedia.org/license/patent.

//! Plane data structure for storing two-dimensional pixel data.
//!
//! This module provides the [`Plane`] type, which represents a single plane of pixel data
//! with optional padding. Planes are the building blocks of video frames, with a YUV frame
//! typically consisting of one luma (Y) plane and two chroma (U and V) planes.
//!
//! # Memory Layout
//!
//! Planes store data in a contiguous, aligned buffer with support for padding on all sides:
//! - Data is aligned to 64 bytes on non-WASM platforms (SIMD-friendly)
//! - Data is aligned to 8 bytes on WASM platforms
//! - Padding pixels surround the visible area for codec algorithms that need border access
//!
//! # API Design
//!
//! The public API exposes only the "visible" pixels by default, abstracting away the padding.
//! Methods like [`rows()`](Plane::rows), [`pixels()`](Plane::pixels), and indexing operations
//! work only with the visible area. Low-level padding access is available via the
//! `padding_api` feature flag.
//!
//! To ensure safety, planes must be instantiated by building a [`Frame`](crate::frame::Frame)
//! through the [`FrameBuilder`](crate::frame::FrameBuilder) interface.

#[cfg(test)]
mod tests;

use std::{
    iter,
    num::{NonZeroU8, NonZeroUsize},
};

use aligned_vec::{ABox, AVec, ConstAlign};

use crate::{error::Error, pixel::Pixel};

/// Alignment for plane data on WASM platforms (8 bytes).
#[cfg(target_arch = "wasm32")]
const DATA_ALIGNMENT: usize = 1 << 3;

/// Alignment for plane data on non-WASM platforms (64 bytes for SIMD optimization).
#[cfg(not(target_arch = "wasm32"))]
const DATA_ALIGNMENT: usize = 1 << 6;

/// A two-dimensional plane of pixel data with optional padding.
///
/// `Plane<T>` represents a rectangular array of pixels of type `T`, where `T` implements
/// the [`Pixel`] trait (currently `u8` or `u16`). The plane supports arbitrary padding
/// on all four sides, which is useful for video codec algorithms that need to access
/// pixels beyond the visible frame boundaries.
///
/// # Memory Layout
///
/// The data is stored in a contiguous, aligned buffer:
/// - 64-byte alignment on non-WASM platforms (optimized for SIMD operations)
/// - 8-byte alignment on WASM platforms
///
/// The visible pixels are surrounded by optional padding pixels. The public API
/// provides access only to the visible area by default; padding access requires
/// the `padding_api` feature flag.
///
/// # Accessing Pixels
///
/// Planes provide several ways to access pixel data:
/// - [`row()`](Plane::row) / [`row_mut()`](Plane::row_mut): Access a single row by index
/// - [`rows()`](Plane::rows) / [`rows_mut()`](Plane::rows_mut): Iterate over all visible rows
/// - [`pixel()`](Plane::pixel) / [`pixel_mut()`](Plane::pixel_mut): Access individual pixels
/// - [`pixels()`](Plane::pixels) / [`pixels_mut()`](Plane::pixels_mut): Iterate over all visible pixels
#[derive(Clone)]
pub struct Plane<T: Pixel> {
    /// The underlying pixel data buffer, including padding.
    pub(crate) data: ABox<[T], ConstAlign<DATA_ALIGNMENT>>,
    /// Geometry information describing dimensions and padding.
    pub(crate) geometry: PlaneGeometry,
}

impl<T> Plane<T>
where
    T: Pixel,
{
    /// Creates a new plane with the given geometry, initialized with zero-valued pixels.
    pub(crate) fn new(geometry: PlaneGeometry) -> Self {
        let rows = geometry
            .height
            .saturating_add(geometry.pad_top)
            .saturating_add(geometry.pad_bottom);
        Self {
            data: AVec::from_iter(
                DATA_ALIGNMENT,
                iter::repeat_n(T::zero(), geometry.stride.get() * rows.get()),
            )
            .into_boxed_slice(),
            geometry,
        }
    }

    /// Returns the visible width of the plane in pixels
    #[inline]
    #[must_use]
    pub fn width(&self) -> NonZeroUsize {
        self.geometry.width
    }

    /// Returns the visible height of the plane in pixels
    #[inline]
    #[must_use]
    pub fn height(&self) -> NonZeroUsize {
        self.geometry.height
    }

    /// Returns a slice containing the visible pixels in
    /// the row at vertical index `y`.
    #[inline]
    #[must_use]
    pub fn row(&self, y: usize) -> Option<&[T]> {
        self.rows().nth(y)
    }

    /// Returns a mutable slice containing the visible pixels in
    /// the row at vertical index `y`.
    #[inline]
    #[must_use]
    pub fn row_mut(&mut self, y: usize) -> Option<&mut [T]> {
        self.rows_mut().nth(y)
    }

    /// Returns an iterator over the visible pixels of each row
    /// in the plane, from top to bottom.
    #[inline]
    #[must_use]
    pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> + ExactSizeIterator {
        let origin = self.geometry.stride.get() * self.geometry.pad_top;
        // SAFETY: The plane creation interface ensures the data is large enough
        let visible_data = unsafe { self.data.get_unchecked(origin..) };
        visible_data
            .chunks_exact(self.geometry.stride.get())
            .take(self.geometry.height.get())
            .map(|row| {
                let start_idx = self.geometry.pad_left;
                let end_idx = start_idx + self.geometry.width.get();
                // SAFETY: The plane creation interface ensures the data is large enough
                unsafe { row.get_unchecked(start_idx..end_idx) }
            })
    }

    /// Returns a mutable iterator over the visible pixels of each row
    /// in the plane, from top to bottom.
    #[inline]
    pub fn rows_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut [T]> + ExactSizeIterator {
        let origin = self.geometry.stride.get() * self.geometry.pad_top;
        // SAFETY: The plane creation interface ensures the data is large enough
        let visible_data = unsafe { self.data.get_unchecked_mut(origin..) };
        visible_data
            .chunks_exact_mut(self.geometry.stride.get())
            .take(self.geometry.height.get())
            .map(|row| {
                let start_idx = self.geometry.pad_left;
                let end_idx = start_idx + self.geometry.width.get();
                // SAFETY: The plane creation interface ensures the data is large enough
                unsafe { row.get_unchecked_mut(start_idx..end_idx) }
            })
    }

    /// Return the value of the pixel at the given `(x, y)` coordinate,
    /// or `None` if the index is out of bounds.
    ///
    /// Since this performs bounds checking, it is likely less performant
    /// and should not be used to iterate over rows and pixels.
    #[inline]
    #[must_use]
    pub fn pixel(&self, x: usize, y: usize) -> Option<T> {
        let index = self.data_origin() + self.geometry.stride.get() * y + x;
        self.data.get(index).copied()
    }

    /// Return a mutable reference to the pixel at the given `(x, y)` coordinate,
    /// or `None` if the index is out of bounds.
    ///
    /// Since this performs bounds checking, it is likely less performant
    /// and should not be used to iterate over rows and pixels.
    #[inline]
    pub fn pixel_mut(&mut self, x: usize, y: usize) -> Option<&mut T> {
        let index = self.data_origin() + self.geometry.stride.get() * y + x;
        self.data.get_mut(index)
    }

    /// Returns an iterator over the visible pixels in the plane,
    /// in row-major order.
    #[inline]
    #[must_use]
    pub fn pixels(&self) -> impl DoubleEndedIterator<Item = T> + ExactSizeIterator {
        let total = self.width().get() * self.height().get();
        ExactSizeWrapper {
            iter: self.rows().flatten().copied(),
            len: total,
        }
    }

    /// Returns a mutable iterator over the visible pixels in the plane,
    /// in row-major order.
    #[inline]
    pub fn pixels_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> + ExactSizeIterator {
        let total = self.width().get() * self.height().get();
        ExactSizeWrapper {
            iter: self.rows_mut().flatten(),
            len: total,
        }
    }

    /// Returns an iterator over the visible byte data in the plane,
    /// in row-major order. High-bit-depth data is converted to `u8`
    /// using low endianness.
    #[inline]
    #[must_use]
    pub fn byte_data(&self) -> impl DoubleEndedIterator<Item = u8> + ExactSizeIterator {
        let byte_width = size_of::<T>();
        assert!(
            byte_width <= 2,
            "unsupported pixel byte width: {byte_width}"
        );

        let total = self.width().get() * self.height().get() * byte_width;
        ExactSizeWrapper {
            iter: self.pixels().flat_map(move |pix| {
                let bytes: [u8; 2] = if byte_width == 1 {
                    [
                        pix.to_u8()
                            .expect("Pixel::byte_data only supports u8 and u16 pixels"),
                        0,
                    ]
                } else {
                    pix.to_u16()
                        .expect("Pixel::byte_data only supports u8 and u16 pixels")
                        .to_le_bytes()
                };
                bytes.into_iter().take(byte_width)
            }),
            len: total,
        }
    }

    /// Copies the data from `src` into this plane's visible pixels.
    ///
    /// # Errors
    /// - Returns `Error::Datalength` if the length of `src` does not match
    ///   this plane's `width * height`
    #[inline]
    pub fn copy_from_slice(&mut self, src: &[T]) -> Result<(), Error> {
        let pixel_count = self.width().get() * self.height().get();
        if pixel_count != src.len() {
            return Err(Error::DataLength {
                expected: pixel_count,
                found: src.len(),
            });
        }

        for (dest, src) in self.pixels_mut().zip(src.iter()) {
            *dest = *src;
        }
        Ok(())
    }

    /// Copies the data from `src` into this plane's visible pixels.
    /// This differs from `copy_from_slice` in that it accepts a raw slice
    /// of `u8` data, which is often what is provided by decoders even if
    /// the pixel data is high-bit-depth. This will convert high-bit-depth
    /// pixels to `u16`, assuming low endian encoding. For low-bit-depth data,
    /// this is equivalent to `copy_from_slice`.
    ///
    /// # Errors
    /// - Returns `Error::Datalength` if the length of `src` does not match
    ///   this plane's `width * height * bytes_per_pixel`
    #[inline]
    pub fn copy_from_u8_slice(&mut self, src: &[u8]) -> Result<(), Error> {
        self.copy_from_u8_slice_with_stride(
            src,
            self.width()
                .saturating_mul(NonZeroUsize::new(size_of::<T>()).expect("size can't be zero")),
        )
    }

    /// Copies the data from `src` into this plane's visible pixels.
    /// This version accepts inputs where the row stride is longer than the visible data width.
    /// The `input_stride` must be in bytes.
    ///
    /// # Errors
    /// - Returns `Error::Datalength` if the length of `src` does not match
    ///   this plane's `width * height * bytes_per_pixel`
    /// - Returns `Error::InvalidStride` if the stride is shorter than the visible width
    #[inline]
    pub fn copy_from_u8_slice_with_stride(
        &mut self,
        src: &[u8],
        input_stride: NonZeroUsize,
    ) -> Result<(), Error> {
        let byte_width = size_of::<T>();
        assert!(
            byte_width <= 2,
            "unsupported pixel byte width: {byte_width}"
        );

        if input_stride < self.width() {
            return Err(Error::InvalidStride {
                stride: input_stride.get(),
                width: self.width().get(),
            });
        }

        let byte_count = input_stride.get() * self.height().get();
        if byte_count != src.len() {
            return Err(Error::DataLength {
                expected: byte_count,
                found: src.len(),
            });
        }

        let width = self.width().get();
        let stride = input_stride.get();

        if byte_width == 1 {
            // Fast path for u8 pixels
            for (row_idx, dest_row) in self.rows_mut().enumerate() {
                let src_offset = row_idx * stride;
                let src_row = &src[src_offset..src_offset + width];
                // SAFETY: we know that `T` is `u8`
                let src_row_typed = unsafe { &*(src_row as *const [u8] as *const [T]) };
                dest_row.copy_from_slice(src_row_typed);
            }
        } else {
            // u16 pixels - need to convert from little-endian bytes
            let row_byte_width = width * byte_width;
            for (row_idx, dest_row) in self.rows_mut().enumerate() {
                let src_offset = row_idx * stride;
                let src_row = &src[src_offset..src_offset + row_byte_width];

                for (dest_pixel, src_chunk) in dest_row.iter_mut().zip(src_row.chunks_exact(2)) {
                    // SAFETY: we know that each chunk has 2 bytes
                    let bytes =
                        unsafe { [*src_chunk.get_unchecked(0), *src_chunk.get_unchecked(1)] };
                    // SAFETY: we know that `T` is `u16`
                    let dest = unsafe { &mut *(dest_pixel as *mut T as *mut u16) };
                    *dest = u16::from_le_bytes(bytes);
                }
            }
        }

        Ok(())
    }

    /// Returns the geometry of the current plane.
    ///
    /// This is a low-level API intended only for functions that require access to the padding.
    #[inline]
    #[must_use]
    #[cfg(feature = "padding_api")]
    pub fn geometry(&self) -> PlaneGeometry {
        self.geometry
    }

    /// Returns a reference to the current plane's data, including padding.
    ///
    /// This is a low-level API intended only for functions that require access to the padding.
    #[inline]
    #[must_use]
    #[cfg(feature = "padding_api")]
    pub fn data(&self) -> &[T] {
        &self.data
    }

    /// Returns a mutable reference to the current plane's data, including padding.
    ///
    /// This is a low-level API intended only for functions that require access to the padding.
    #[inline]
    #[must_use]
    #[cfg(feature = "padding_api")]
    pub fn data_mut(&mut self) -> &mut [T] {
        &mut self.data
    }

    /// Returns the index for the first visible pixel in `data`.
    ///
    /// This is a low-level API intended only for functions that require access to the padding.
    #[inline]
    #[must_use]
    #[cfg_attr(not(feature = "padding_api"), doc(hidden))]
    pub fn data_origin(&self) -> usize {
        self.geometry.stride.get() * self.geometry.pad_top + self.geometry.pad_left
    }
}

/// Describes the geometry of a plane, including dimensions and padding.
///
/// This struct contains all the information needed to interpret the layout of
/// a plane's data buffer, including the visible dimensions and the padding on
/// all four sides.
///
/// The `stride` represents the number of pixels per row in the data buffer,
/// which is equal to `width + pad_left + pad_right`.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(not(feature = "padding_api"), doc(hidden))]
pub struct PlaneGeometry {
    /// Width of the visible area in pixels.
    pub width: NonZeroUsize,
    /// Height of the visible area in pixels.
    pub height: NonZeroUsize,
    /// Data stride (pixels per row in the buffer, including padding).
    pub stride: NonZeroUsize,
    /// Number of padding pixels on the left side.
    pub pad_left: usize,
    /// Number of padding pixels on the right side.
    pub pad_right: usize,
    /// Number of padding pixels on the top.
    pub pad_top: usize,
    /// Number of padding pixels on the bottom.
    pub pad_bottom: usize,
    /// The horizontal subsampling ratio of this plane compared to the luma plane
    /// Will be 1 if no subsampling
    pub subsampling_x: NonZeroU8,
    /// The horizontal subsampling ratio of this plane compared to the luma plane
    /// Will be 1 if no subsampling
    pub subsampling_y: NonZeroU8,
}

impl PlaneGeometry {
    /// Returns the total height of the plane, including padding
    #[inline]
    #[must_use]
    #[cfg_attr(not(feature = "padding_api"), doc(hidden))]
    pub fn alloc_height(&self) -> NonZeroUsize {
        self.height
            .saturating_add(self.pad_top)
            .saturating_add(self.pad_bottom)
    }
}

/// Wrapper to add `ExactSizeIterator` implementation to iterators with known length.
struct ExactSizeWrapper<I> {
    iter: I,
    len: usize,
}

impl<I: Iterator> Iterator for ExactSizeWrapper<I> {
    type Item = I::Item;

    #[inline]
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(item) = self.iter.next() {
            self.len = self.len.saturating_sub(1);
            Some(item)
        } else {
            None
        }
    }

    #[inline]
    fn size_hint(&self) -> (usize, Option<usize>) {
        (self.len, Some(self.len))
    }
}

impl<I: DoubleEndedIterator> DoubleEndedIterator for ExactSizeWrapper<I> {
    #[inline]
    fn next_back(&mut self) -> Option<Self::Item> {
        if let Some(item) = self.iter.next_back() {
            self.len = self.len.saturating_sub(1);
            Some(item)
        } else {
            None
        }
    }
}

impl<I: Iterator> ExactSizeIterator for ExactSizeWrapper<I> {
    #[inline]
    fn len(&self) -> usize {
        self.len
    }
}