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}