Skip to main content

v_frame/
plane.rs

1// Copyright (c) 2017-2025, The rav1e contributors. All rights reserved
2//
3// This source code is subject to the terms of the BSD 2 Clause License and
4// the Alliance for Open Media Patent License 1.0. If the BSD 2 Clause License
5// was not distributed with this source code in the LICENSE file, you can
6// obtain it at www.aomedia.org/license/software. If the Alliance for Open
7// Media Patent License 1.0 was not distributed with this source code in the
8// PATENTS file, you can obtain it at www.aomedia.org/license/patent.
9
10//! Plane data structure for storing two-dimensional pixel data.
11//!
12//! This module provides the [`Plane`] type, which represents a single plane of pixel data
13//! with optional padding. Planes are the building blocks of video frames, with a YUV frame
14//! typically consisting of one luma (Y) plane and two chroma (U and V) planes.
15//!
16//! # Memory Layout
17//!
18//! Planes store data in a contiguous, aligned buffer with support for padding on all sides:
19//! - Data is aligned to 64 bytes on non-WASM platforms (SIMD-friendly)
20//! - Data is aligned to 8 bytes on WASM platforms
21//! - Padding pixels surround the visible area for codec algorithms that need border access
22//!
23//! # API Design
24//!
25//! The public API exposes only the "visible" pixels by default, abstracting away the padding.
26//! Methods like [`rows()`](Plane::rows), [`pixels()`](Plane::pixels), and indexing operations
27//! work only with the visible area. Low-level padding access is available via the
28//! `padding_api` feature flag.
29//!
30//! To ensure safety, planes must be instantiated by building a [`Frame`](crate::frame::Frame)
31//! through the [`FrameBuilder`](crate::frame::FrameBuilder) interface.
32
33#[cfg(test)]
34mod tests;
35
36use std::{
37    iter,
38    num::{NonZeroU8, NonZeroUsize},
39};
40
41use aligned_vec::{ABox, AVec, ConstAlign};
42
43use crate::{error::Error, pixel::Pixel};
44
45/// Alignment for plane data on WASM platforms (8 bytes).
46#[cfg(target_arch = "wasm32")]
47const DATA_ALIGNMENT: usize = 1 << 3;
48
49/// Alignment for plane data on non-WASM platforms (64 bytes for SIMD optimization).
50#[cfg(not(target_arch = "wasm32"))]
51const DATA_ALIGNMENT: usize = 1 << 6;
52
53/// A two-dimensional plane of pixel data with optional padding.
54///
55/// `Plane<T>` represents a rectangular array of pixels of type `T`, where `T` implements
56/// the [`Pixel`] trait (currently `u8` or `u16`). The plane supports arbitrary padding
57/// on all four sides, which is useful for video codec algorithms that need to access
58/// pixels beyond the visible frame boundaries.
59///
60/// # Memory Layout
61///
62/// The data is stored in a contiguous, aligned buffer:
63/// - 64-byte alignment on non-WASM platforms (optimized for SIMD operations)
64/// - 8-byte alignment on WASM platforms
65///
66/// The visible pixels are surrounded by optional padding pixels. The public API
67/// provides access only to the visible area by default; padding access requires
68/// the `padding_api` feature flag.
69///
70/// # Accessing Pixels
71///
72/// Planes provide several ways to access pixel data:
73/// - [`row()`](Plane::row) / [`row_mut()`](Plane::row_mut): Access a single row by index
74/// - [`rows()`](Plane::rows) / [`rows_mut()`](Plane::rows_mut): Iterate over all visible rows
75/// - [`pixel()`](Plane::pixel) / [`pixel_mut()`](Plane::pixel_mut): Access individual pixels
76/// - [`pixels()`](Plane::pixels) / [`pixels_mut()`](Plane::pixels_mut): Iterate over all visible pixels
77#[derive(Clone)]
78pub struct Plane<T: Pixel> {
79    /// The underlying pixel data buffer, including padding.
80    pub(crate) data: ABox<[T], ConstAlign<DATA_ALIGNMENT>>,
81    /// Geometry information describing dimensions and padding.
82    pub(crate) geometry: PlaneGeometry,
83}
84
85impl<T> Plane<T>
86where
87    T: Pixel,
88{
89    /// Creates a new plane with the given geometry, initialized with zero-valued pixels.
90    pub(crate) fn new(geometry: PlaneGeometry) -> Self {
91        let rows = geometry
92            .height
93            .saturating_add(geometry.pad_top)
94            .saturating_add(geometry.pad_bottom);
95        Self {
96            data: AVec::from_iter(
97                DATA_ALIGNMENT,
98                iter::repeat_n(T::zero(), geometry.stride.get() * rows.get()),
99            )
100            .into_boxed_slice(),
101            geometry,
102        }
103    }
104
105    /// Returns the visible width of the plane in pixels
106    #[inline]
107    #[must_use]
108    pub fn width(&self) -> NonZeroUsize {
109        self.geometry.width
110    }
111
112    /// Returns the visible height of the plane in pixels
113    #[inline]
114    #[must_use]
115    pub fn height(&self) -> NonZeroUsize {
116        self.geometry.height
117    }
118
119    /// Returns a slice containing the visible pixels in
120    /// the row at vertical index `y`.
121    #[inline]
122    #[must_use]
123    pub fn row(&self, y: usize) -> Option<&[T]> {
124        self.rows().nth(y)
125    }
126
127    /// Returns a mutable slice containing the visible pixels in
128    /// the row at vertical index `y`.
129    #[inline]
130    #[must_use]
131    pub fn row_mut(&mut self, y: usize) -> Option<&mut [T]> {
132        self.rows_mut().nth(y)
133    }
134
135    /// Returns an iterator over the visible pixels of each row
136    /// in the plane, from top to bottom.
137    #[inline]
138    #[must_use]
139    pub fn rows(&self) -> impl DoubleEndedIterator<Item = &[T]> + ExactSizeIterator {
140        let origin = self.geometry.stride.get() * self.geometry.pad_top;
141        // SAFETY: The plane creation interface ensures the data is large enough
142        let visible_data = unsafe { self.data.get_unchecked(origin..) };
143        visible_data
144            .chunks_exact(self.geometry.stride.get())
145            .take(self.geometry.height.get())
146            .map(|row| {
147                let start_idx = self.geometry.pad_left;
148                let end_idx = start_idx + self.geometry.width.get();
149                // SAFETY: The plane creation interface ensures the data is large enough
150                unsafe { row.get_unchecked(start_idx..end_idx) }
151            })
152    }
153
154    /// Returns a mutable iterator over the visible pixels of each row
155    /// in the plane, from top to bottom.
156    #[inline]
157    pub fn rows_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut [T]> + ExactSizeIterator {
158        let origin = self.geometry.stride.get() * self.geometry.pad_top;
159        // SAFETY: The plane creation interface ensures the data is large enough
160        let visible_data = unsafe { self.data.get_unchecked_mut(origin..) };
161        visible_data
162            .chunks_exact_mut(self.geometry.stride.get())
163            .take(self.geometry.height.get())
164            .map(|row| {
165                let start_idx = self.geometry.pad_left;
166                let end_idx = start_idx + self.geometry.width.get();
167                // SAFETY: The plane creation interface ensures the data is large enough
168                unsafe { row.get_unchecked_mut(start_idx..end_idx) }
169            })
170    }
171
172    /// Return the value of the pixel at the given `(x, y)` coordinate,
173    /// or `None` if the index is out of bounds.
174    ///
175    /// Since this performs bounds checking, it is likely less performant
176    /// and should not be used to iterate over rows and pixels.
177    #[inline]
178    #[must_use]
179    pub fn pixel(&self, x: usize, y: usize) -> Option<T> {
180        let index = self.data_origin() + self.geometry.stride.get() * y + x;
181        self.data.get(index).copied()
182    }
183
184    /// Return a mutable reference to the pixel at the given `(x, y)` coordinate,
185    /// or `None` if the index is out of bounds.
186    ///
187    /// Since this performs bounds checking, it is likely less performant
188    /// and should not be used to iterate over rows and pixels.
189    #[inline]
190    pub fn pixel_mut(&mut self, x: usize, y: usize) -> Option<&mut T> {
191        let index = self.data_origin() + self.geometry.stride.get() * y + x;
192        self.data.get_mut(index)
193    }
194
195    /// Returns an iterator over the visible pixels in the plane,
196    /// in row-major order.
197    #[inline]
198    #[must_use]
199    pub fn pixels(&self) -> impl DoubleEndedIterator<Item = T> + ExactSizeIterator {
200        let total = self.width().get() * self.height().get();
201        ExactSizeWrapper {
202            iter: self.rows().flatten().copied(),
203            len: total,
204        }
205    }
206
207    /// Returns a mutable iterator over the visible pixels in the plane,
208    /// in row-major order.
209    #[inline]
210    pub fn pixels_mut(&mut self) -> impl DoubleEndedIterator<Item = &mut T> + ExactSizeIterator {
211        let total = self.width().get() * self.height().get();
212        ExactSizeWrapper {
213            iter: self.rows_mut().flatten(),
214            len: total,
215        }
216    }
217
218    /// Returns an iterator over the visible byte data in the plane,
219    /// in row-major order. High-bit-depth data is converted to `u8`
220    /// using low endianness.
221    #[inline]
222    #[must_use]
223    pub fn byte_data(&self) -> impl DoubleEndedIterator<Item = u8> + ExactSizeIterator {
224        let byte_width = size_of::<T>();
225        assert!(
226            byte_width <= 2,
227            "unsupported pixel byte width: {byte_width}"
228        );
229
230        let total = self.width().get() * self.height().get() * byte_width;
231        ExactSizeWrapper {
232            iter: self.pixels().flat_map(move |pix| {
233                let bytes: [u8; 2] = if byte_width == 1 {
234                    [
235                        pix.to_u8()
236                            .expect("Pixel::byte_data only supports u8 and u16 pixels"),
237                        0,
238                    ]
239                } else {
240                    pix.to_u16()
241                        .expect("Pixel::byte_data only supports u8 and u16 pixels")
242                        .to_le_bytes()
243                };
244                bytes.into_iter().take(byte_width)
245            }),
246            len: total,
247        }
248    }
249
250    /// Copies the data from `src` into this plane's visible pixels.
251    ///
252    /// # Errors
253    /// - Returns `Error::Datalength` if the length of `src` does not match
254    ///   this plane's `width * height`
255    #[inline]
256    pub fn copy_from_slice(&mut self, src: &[T]) -> Result<(), Error> {
257        let pixel_count = self.width().get() * self.height().get();
258        if pixel_count != src.len() {
259            return Err(Error::DataLength {
260                expected: pixel_count,
261                found: src.len(),
262            });
263        }
264
265        for (dest, src) in self.pixels_mut().zip(src.iter()) {
266            *dest = *src;
267        }
268        Ok(())
269    }
270
271    /// Copies the data from `src` into this plane's visible pixels.
272    /// This differs from `copy_from_slice` in that it accepts a raw slice
273    /// of `u8` data, which is often what is provided by decoders even if
274    /// the pixel data is high-bit-depth. This will convert high-bit-depth
275    /// pixels to `u16`, assuming low endian encoding. For low-bit-depth data,
276    /// this is equivalent to `copy_from_slice`.
277    ///
278    /// # Errors
279    /// - Returns `Error::Datalength` if the length of `src` does not match
280    ///   this plane's `width * height * bytes_per_pixel`
281    #[inline]
282    pub fn copy_from_u8_slice(&mut self, src: &[u8]) -> Result<(), Error> {
283        self.copy_from_u8_slice_with_stride(
284            src,
285            self.width()
286                .saturating_mul(NonZeroUsize::new(size_of::<T>()).expect("size can't be zero")),
287        )
288    }
289
290    /// Copies the data from `src` into this plane's visible pixels.
291    /// This version accepts inputs where the row stride is longer than the visible data width.
292    /// The `input_stride` must be in bytes.
293    ///
294    /// # Errors
295    /// - Returns `Error::Datalength` if the length of `src` does not match
296    ///   this plane's `width * height * bytes_per_pixel`
297    /// - Returns `Error::InvalidStride` if the stride is shorter than the visible width
298    #[inline]
299    pub fn copy_from_u8_slice_with_stride(
300        &mut self,
301        src: &[u8],
302        input_stride: NonZeroUsize,
303    ) -> Result<(), Error> {
304        let byte_width = size_of::<T>();
305        assert!(
306            byte_width <= 2,
307            "unsupported pixel byte width: {byte_width}"
308        );
309
310        if input_stride < self.width() {
311            return Err(Error::InvalidStride {
312                stride: input_stride.get(),
313                width: self.width().get(),
314            });
315        }
316
317        let byte_count = input_stride.get() * self.height().get();
318        if byte_count != src.len() {
319            return Err(Error::DataLength {
320                expected: byte_count,
321                found: src.len(),
322            });
323        }
324
325        let width = self.width().get();
326        let stride = input_stride.get();
327
328        if byte_width == 1 {
329            // Fast path for u8 pixels
330            for (row_idx, dest_row) in self.rows_mut().enumerate() {
331                let src_offset = row_idx * stride;
332                let src_row = &src[src_offset..src_offset + width];
333                // SAFETY: we know that `T` is `u8`
334                let src_row_typed = unsafe { &*(src_row as *const [u8] as *const [T]) };
335                dest_row.copy_from_slice(src_row_typed);
336            }
337        } else {
338            // u16 pixels - need to convert from little-endian bytes
339            let row_byte_width = width * byte_width;
340            for (row_idx, dest_row) in self.rows_mut().enumerate() {
341                let src_offset = row_idx * stride;
342                let src_row = &src[src_offset..src_offset + row_byte_width];
343
344                for (dest_pixel, src_chunk) in dest_row.iter_mut().zip(src_row.chunks_exact(2)) {
345                    // SAFETY: we know that each chunk has 2 bytes
346                    let bytes =
347                        unsafe { [*src_chunk.get_unchecked(0), *src_chunk.get_unchecked(1)] };
348                    // SAFETY: we know that `T` is `u16`
349                    let dest = unsafe { &mut *(dest_pixel as *mut T as *mut u16) };
350                    *dest = u16::from_le_bytes(bytes);
351                }
352            }
353        }
354
355        Ok(())
356    }
357
358    /// Returns the geometry of the current plane.
359    ///
360    /// This is a low-level API intended only for functions that require access to the padding.
361    #[inline]
362    #[must_use]
363    #[cfg(feature = "padding_api")]
364    pub fn geometry(&self) -> PlaneGeometry {
365        self.geometry
366    }
367
368    /// Returns a reference to the current plane's data, including padding.
369    ///
370    /// This is a low-level API intended only for functions that require access to the padding.
371    #[inline]
372    #[must_use]
373    #[cfg(feature = "padding_api")]
374    pub fn data(&self) -> &[T] {
375        &self.data
376    }
377
378    /// Returns a mutable reference to the current plane's data, including padding.
379    ///
380    /// This is a low-level API intended only for functions that require access to the padding.
381    #[inline]
382    #[must_use]
383    #[cfg(feature = "padding_api")]
384    pub fn data_mut(&mut self) -> &mut [T] {
385        &mut self.data
386    }
387
388    /// Returns the index for the first visible pixel in `data`.
389    ///
390    /// This is a low-level API intended only for functions that require access to the padding.
391    #[inline]
392    #[must_use]
393    #[cfg_attr(not(feature = "padding_api"), doc(hidden))]
394    pub fn data_origin(&self) -> usize {
395        self.geometry.stride.get() * self.geometry.pad_top + self.geometry.pad_left
396    }
397}
398
399/// Describes the geometry of a plane, including dimensions and padding.
400///
401/// This struct contains all the information needed to interpret the layout of
402/// a plane's data buffer, including the visible dimensions and the padding on
403/// all four sides.
404///
405/// The `stride` represents the number of pixels per row in the data buffer,
406/// which is equal to `width + pad_left + pad_right`.
407#[derive(Debug, Clone, Copy, PartialEq, Eq)]
408#[cfg_attr(not(feature = "padding_api"), doc(hidden))]
409pub struct PlaneGeometry {
410    /// Width of the visible area in pixels.
411    pub width: NonZeroUsize,
412    /// Height of the visible area in pixels.
413    pub height: NonZeroUsize,
414    /// Data stride (pixels per row in the buffer, including padding).
415    pub stride: NonZeroUsize,
416    /// Number of padding pixels on the left side.
417    pub pad_left: usize,
418    /// Number of padding pixels on the right side.
419    pub pad_right: usize,
420    /// Number of padding pixels on the top.
421    pub pad_top: usize,
422    /// Number of padding pixels on the bottom.
423    pub pad_bottom: usize,
424    /// The horizontal subsampling ratio of this plane compared to the luma plane
425    /// Will be 1 if no subsampling
426    pub subsampling_x: NonZeroU8,
427    /// The horizontal subsampling ratio of this plane compared to the luma plane
428    /// Will be 1 if no subsampling
429    pub subsampling_y: NonZeroU8,
430}
431
432impl PlaneGeometry {
433    /// Returns the total height of the plane, including padding
434    #[inline]
435    #[must_use]
436    #[cfg_attr(not(feature = "padding_api"), doc(hidden))]
437    pub fn alloc_height(&self) -> NonZeroUsize {
438        self.height
439            .saturating_add(self.pad_top)
440            .saturating_add(self.pad_bottom)
441    }
442}
443
444/// Wrapper to add `ExactSizeIterator` implementation to iterators with known length.
445struct ExactSizeWrapper<I> {
446    iter: I,
447    len: usize,
448}
449
450impl<I: Iterator> Iterator for ExactSizeWrapper<I> {
451    type Item = I::Item;
452
453    #[inline]
454    fn next(&mut self) -> Option<Self::Item> {
455        if let Some(item) = self.iter.next() {
456            self.len = self.len.saturating_sub(1);
457            Some(item)
458        } else {
459            None
460        }
461    }
462
463    #[inline]
464    fn size_hint(&self) -> (usize, Option<usize>) {
465        (self.len, Some(self.len))
466    }
467}
468
469impl<I: DoubleEndedIterator> DoubleEndedIterator for ExactSizeWrapper<I> {
470    #[inline]
471    fn next_back(&mut self) -> Option<Self::Item> {
472        if let Some(item) = self.iter.next_back() {
473            self.len = self.len.saturating_sub(1);
474            Some(item)
475        } else {
476            None
477        }
478    }
479}
480
481impl<I: Iterator> ExactSizeIterator for ExactSizeWrapper<I> {
482    #[inline]
483    fn len(&self) -> usize {
484        self.len
485    }
486}