v_frame-0.3.7/.cargo_vcs_info.json0000644000000001360000000000100124620ustar { "git": { "sha1": "a52f1d36fb7d352942915a22359014d19db42cf0" }, "path_in_vcs": "" }v_frame-0.3.7/.github/workflows/crate.yml000064400000000000000000000040331046102023000164700ustar 00000000000000name: Crate on: push: branches: [ "main" ] pull_request: branches: [ "main" ] env: CARGO_TERM_COLOR: always jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Build run: cargo build --verbose - name: Run tests run: cargo test --verbose miri: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust nightly and miri uses: dtolnay/rust-toolchain@stable with: toolchain: nightly components: miri, rust-src - name: Run miri env: RUSTFLAGS: -Zrandomize-layout MIRIFLAGS: -Zmiri-symbolic-alignment-check run: cargo miri test code-coverage: needs: [miri, build] runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install Rust stable uses: dtolnay/rust-toolchain@stable with: toolchain: stable - name: Install grcov env: GRCOV_LINK: https://github.com/mozilla/grcov/releases/download GRCOV_VERSION: v0.8.7 run: | curl -L "$GRCOV_LINK/$GRCOV_VERSION/grcov-x86_64-unknown-linux-musl.tar.bz2" | tar xj -C $HOME/.cargo/bin - name: Install llvm-tools-preview run: | rustup component add llvm-tools-preview - name: Run cargo clean run: | cargo clean - name: Run tests env: CARGO_INCREMENTAL: 0 LLVM_PROFILE_FILE: "{{ name }}-%p-%m.profraw" RUSTFLAGS: > -Cinstrument-coverage -Ccodegen-units=1 -Clink-dead-code -Coverflow-checks=off RUSTDOCFLAGS: > -Cinstrument-coverage -Ccodegen-units=1 -Clink-dead-code -Coverflow-checks=off run: | cargo test --verbose - name: Get coverage data for codecov run: | grcov . --binary-path ./target/debug/ -s . -t lcov --branch \ --ignore-not-existing --ignore "/*" --ignore "../*" -o lcov.info - name: Codecov upload uses: codecov/codecov-action@v3 with: files: lcov.info v_frame-0.3.7/.gitignore000064400000000000000000000000241046102023000132360ustar 00000000000000/target /Cargo.lock v_frame-0.3.7/CHANGELOG.md000064400000000000000000000011571046102023000130670ustar 00000000000000## Version 0.3.6 - Revert changes in downsampling in 0.3.4 which changed its behavior ## Version 0.3.5 - Bump num-derive to 0.4 ## Version 0.3.4 - Fix cases of unsoundness (#14) - Slight optimizations for downsampling ## Version 0.3.3 - Add `row_cropped` and `row_slice_cropped` methods to get rows without padding - Make `RowsIter` and `RowsIterMut` return rows without right-side padding for greater consistency/predictability - Fix clippy lints ## Version 0.3.1 - Add `rows_iter_mut` method to `Plane` ## Version 0.2.6 - Split into separate repository - Remove unused rayon dependency - Fix some clippy lints v_frame-0.3.7/Cargo.toml0000644000000025720000000000100104660ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "v_frame" version = "0.3.7" authors = ["Luca Barbato "] description = "Video Frame data structures, originally part of rav1e" readme = "README.md" license = "BSD-2-Clause" repository = "https://github.com/rust-av/v_frame" [[bench]] name = "bench" harness = false [dependencies.cfg-if] version = "1.0" [dependencies.noop_proc_macro] version = "0.3.0" [dependencies.num-derive] version = "0.4" [dependencies.num-traits] version = "0.2" [dependencies.profiling] version = "1" [dependencies.serde] version = "1.0" features = ["derive"] optional = true [dependencies.tracing] version = "0.1.40" optional = true [dependencies.wasm-bindgen] version = "0.2.63" optional = true [dev-dependencies.criterion] version = "0.5" features = ["html_reports"] [features] serialize = ["serde"] tracing = [ "profiling/profile-with-tracing", "dep:tracing", ] wasm = ["wasm-bindgen"] v_frame-0.3.7/Cargo.toml.orig000064400000000000000000000014401046102023000141400ustar 00000000000000[package] name = "v_frame" version = "0.3.7" description = "Video Frame data structures, originally part of rav1e" license = "BSD-2-Clause" authors = ["Luca Barbato "] edition = "2021" repository = "https://github.com/rust-av/v_frame" [features] serialize = ["serde"] wasm = ["wasm-bindgen"] tracing = [ "profiling/profile-with-tracing", "dep:tracing" ] [dependencies] cfg-if = "1.0" num-traits = "0.2" num-derive = "0.4" noop_proc_macro = "0.3.0" serde = { version = "1.0", features = ["derive"], optional = true } wasm-bindgen = { version = "0.2.63", optional = true } profiling = { version = "1" } tracing = { version = "0.1.40", optional = true } [dev-dependencies] criterion = { version = "0.5", features = ["html_reports"] } [[bench]] name = "bench" harness = false v_frame-0.3.7/LICENSE000064400000000000000000000024641046102023000122650ustar 00000000000000BSD 2-Clause License Copyright (c) 2017-2022, the rav1e contributors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. v_frame-0.3.7/README.md000064400000000000000000000013211046102023000125260ustar 00000000000000# v_frame [![docs.rs](https://img.shields.io/docsrs/v_frame)](https://docs.rs/v_frame) [![Crates.io](https://img.shields.io/crates/v/v_frame)](https://crates.io/crates/v_frame) [![LICENSE](https://img.shields.io/crates/l/v_frame)](https://github.com/rust-av/v_frame/blob/main/LICENSE) [![dependency status](https://deps.rs/repo/github/rust-av/v_frame/status.svg)](https://deps.rs/repo/github/rust-av/v_frame) [![codecov](https://codecov.io/github/rust-av/v_frame/branch/main/graph/badge.svg?token=MKT1AZREF0)](https://codecov.io/github/rust-av/v_frame) Crate providing structs and utilities for handling YUV Frames and Planes. Originally used by rav1e, now branched into its own crate for wider use throughout Rust AV. v_frame-0.3.7/benches/bench.rs000064400000000000000000000060521046102023000143110ustar 00000000000000use criterion::{black_box, criterion_group, criterion_main, Criterion}; use v_frame::frame::Frame; use v_frame::pixel::{CastFromPrimitive, ChromaSampling}; use v_frame::plane::Plane; fn frame(c: &mut Criterion) { c.bench_function("frame new_with_padding padding=0", |b| { b.iter(|| { Frame::::new_with_padding( black_box(640), black_box(480), black_box(ChromaSampling::Cs420), black_box(0), ) }) }); c.bench_function("frame new_with_padding padding!=0", |b| { b.iter(|| { Frame::::new_with_padding( black_box(640), black_box(480), black_box(ChromaSampling::Cs420), black_box(24), ) }) }); } fn plane(c: &mut Criterion) { c.bench_function("plane new padding=0", |b| { b.iter(|| { Plane::::new( black_box(640), black_box(480), black_box(1), black_box(1), black_box(0), black_box(0), ) }) }); c.bench_function("plane new padding!=0", |b| { b.iter(|| { Plane::::new( black_box(640), black_box(480), black_box(1), black_box(1), black_box(24), black_box(24), ) }) }); let p = Plane::::new( black_box(640), black_box(480), black_box(1), black_box(1), black_box(0), black_box(0), ); c.bench_function("plane pad", |b| { b.iter(|| p.clone().pad(black_box(680), black_box(520))) }); let data_8b: Vec = vec![2; 640 * 480]; c.bench_function("plane copy_from_raw_u8 8-bit", |b| { b.iter(|| { p.clone() .copy_from_raw_u8(black_box(&data_8b), black_box(640), black_box(1)) }) }); let p10b = Plane::::new( black_box(640), black_box(480), black_box(1), black_box(1), black_box(0), black_box(0), ); let data_10b: Vec = vec![2; 640 * 480 * 2]; c.bench_function("plane copy_from_raw_u8 10-bit", |b| { b.iter(|| { p10b.clone() .copy_from_raw_u8(black_box(&data_10b), black_box(640), black_box(2)) }) }); c.bench_function("plane downsampled", |b| { b.iter(|| p.downsampled(black_box(320), black_box(240))) }); c.bench_function("plane downscale", |b| b.iter(|| p.downscale::<2>())); // This may seem silly to benchmark, but there is some math in the iterator // that has been known to hinder compiler optimizations c.bench_function("plane rows_iter", |b| { b.iter(|| { p.rows_iter() .map(|r| r.iter().map(|v| u8::cast_from(*v) as u64).sum::()) .collect::>() }) }); } criterion_group!(benches, frame, plane); criterion_main!(benches); v_frame-0.3.7/clippy.toml000064400000000000000000000000161046102023000134440ustar 00000000000000msrv = "1.64" v_frame-0.3.7/src/frame.rs000064400000000000000000000044171046102023000135070ustar 00000000000000// Copyright (c) 2018-2020, 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. use crate::math::*; use crate::pixel::*; use crate::plane::*; use crate::serialize::{Deserialize, Serialize}; /// Represents a raw video frame #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct Frame { /// Planes constituting the frame. pub planes: [Plane; 3], } impl Frame { /// Creates a new frame with the given parameters. /// /// Allocates data for the planes. pub fn new_with_padding( width: usize, height: usize, chroma_sampling: ChromaSampling, luma_padding: usize, ) -> Self { let luma_width = width.align_power_of_two(3); let luma_height = height.align_power_of_two(3); let (chroma_decimation_x, chroma_decimation_y) = chroma_sampling.get_decimation().unwrap_or((0, 0)); let (chroma_width, chroma_height) = chroma_sampling.get_chroma_dimensions(luma_width, luma_height); let chroma_padding_x = luma_padding >> chroma_decimation_x; let chroma_padding_y = luma_padding >> chroma_decimation_y; Frame { planes: [ Plane::new(luma_width, luma_height, 0, 0, luma_padding, luma_padding), Plane::new( chroma_width, chroma_height, chroma_decimation_x, chroma_decimation_y, chroma_padding_x, chroma_padding_y, ), Plane::new( chroma_width, chroma_height, chroma_decimation_x, chroma_decimation_y, chroma_padding_x, chroma_padding_y, ), ], } } } v_frame-0.3.7/src/lib.rs000064400000000000000000000067331046102023000131660ustar 00000000000000// Copyright (c) 2020, 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. // Safety lints #![deny(bare_trait_objects)] #![deny(clippy::as_ptr_cast_mut)] #![deny(clippy::large_stack_arrays)] // Performance lints #![warn(clippy::inefficient_to_string)] #![warn(clippy::invalid_upcast_comparisons)] #![warn(clippy::iter_with_drain)] #![warn(clippy::linkedlist)] #![warn(clippy::mutex_integer)] #![warn(clippy::naive_bytecount)] #![warn(clippy::needless_bitwise_bool)] #![warn(clippy::needless_collect)] #![warn(clippy::or_fun_call)] #![warn(clippy::stable_sort_primitive)] #![warn(clippy::suboptimal_flops)] #![warn(clippy::trivial_regex)] #![warn(clippy::trivially_copy_pass_by_ref)] #![warn(clippy::unnecessary_join)] #![warn(clippy::unused_async)] #![warn(clippy::zero_sized_map_values)] // Correctness lints #![deny(clippy::case_sensitive_file_extension_comparisons)] #![deny(clippy::copy_iterator)] #![deny(clippy::expl_impl_clone_on_copy)] #![deny(clippy::float_cmp)] #![warn(clippy::imprecise_flops)] #![deny(clippy::manual_instant_elapsed)] #![deny(clippy::mem_forget)] #![deny(clippy::path_buf_push_overwrite)] #![deny(clippy::same_functions_in_if_condition)] #![deny(clippy::unchecked_duration_subtraction)] #![deny(clippy::unicode_not_nfc)] // Clarity/formatting lints #![warn(clippy::checked_conversions)] #![warn(clippy::derive_partial_eq_without_eq)] #![allow(clippy::enum_variant_names)] #![warn(clippy::explicit_deref_methods)] #![warn(clippy::filter_map_next)] #![warn(clippy::flat_map_option)] #![warn(clippy::fn_params_excessive_bools)] #![warn(clippy::implicit_clone)] #![warn(clippy::iter_not_returning_iterator)] #![warn(clippy::iter_on_empty_collections)] #![warn(clippy::macro_use_imports)] #![warn(clippy::manual_clamp)] #![warn(clippy::manual_let_else)] #![warn(clippy::manual_ok_or)] #![warn(clippy::manual_string_new)] #![warn(clippy::map_flatten)] #![warn(clippy::match_bool)] #![warn(clippy::mut_mut)] #![warn(clippy::needless_borrow)] #![warn(clippy::needless_continue)] #![warn(clippy::range_minus_one)] #![warn(clippy::range_plus_one)] #![warn(clippy::ref_binding_to_reference)] #![warn(clippy::ref_option_ref)] #![warn(clippy::trait_duplication_in_bounds)] #![warn(clippy::unused_peekable)] #![warn(clippy::unused_rounding)] #![warn(clippy::unused_self)] #![allow(clippy::upper_case_acronyms)] #![warn(clippy::verbose_bit_mask)] #![warn(clippy::verbose_file_reads)] // Documentation lints #![warn(clippy::doc_link_with_quotes)] #![warn(clippy::doc_markdown)] #![warn(clippy::missing_errors_doc)] #![warn(clippy::missing_panics_doc)] pub mod frame; pub mod math; pub mod pixel; pub mod plane; mod serialize { cfg_if::cfg_if! { if #[cfg(feature="serialize")] { pub use serde::*; } else { pub use noop_proc_macro::{Deserialize, Serialize}; } } } mod wasm_bindgen { cfg_if::cfg_if! { if #[cfg(feature="wasm")] { pub use wasm_bindgen::prelude::*; } else { pub use noop_proc_macro::wasm_bindgen; } } } pub mod prelude { pub use crate::math::*; pub use crate::pixel::*; } v_frame-0.3.7/src/math.rs000064400000000000000000000041321046102023000133400ustar 00000000000000// Copyright (c) 2017-2020, 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. use num_traits::PrimInt; use std::mem::size_of; pub trait Fixed { fn floor_log2(&self, n: usize) -> usize; fn ceil_log2(&self, n: usize) -> usize; fn align_power_of_two(&self, n: usize) -> usize; fn align_power_of_two_and_shift(&self, n: usize) -> usize; } impl Fixed for usize { #[inline] fn floor_log2(&self, n: usize) -> usize { self & !((1 << n) - 1) } #[inline] fn ceil_log2(&self, n: usize) -> usize { (self + (1 << n) - 1).floor_log2(n) } #[inline] fn align_power_of_two(&self, n: usize) -> usize { self.ceil_log2(n) } #[inline] fn align_power_of_two_and_shift(&self, n: usize) -> usize { (self + (1 << n) - 1) >> n } } pub fn clamp(input: T, min: T, max: T) -> T { if input < min { min } else if input > max { max } else { input } } pub trait ILog: PrimInt { // Integer binary logarithm of an integer value. // Returns floor(log2(self)) + 1, or 0 if self == 0. // This is the number of bits that would be required to represent self in two's // complement notation with all of the leading zeros stripped. // TODO: Mark const once trait functions can be constant fn ilog(self) -> usize { size_of::() * 8 - self.leading_zeros() as usize } } impl ILog for T where T: PrimInt {} #[inline(always)] pub fn msb(x: i32) -> i32 { debug_assert!(x > 0); 31 ^ (x.leading_zeros() as i32) } #[inline(always)] pub const fn round_shift(value: i32, bit: usize) -> i32 { (value + (1 << bit >> 1)) >> bit } v_frame-0.3.7/src/pixel.rs000064400000000000000000000133451046102023000135360ustar 00000000000000// Copyright (c) 2017-2021, 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. use crate::serialize::{Deserialize, Serialize}; use crate::wasm_bindgen::*; use num_derive::FromPrimitive; use num_traits::{AsPrimitive, PrimInt, Signed}; use std::fmt; use std::fmt::{Debug, Display}; use std::mem::size_of; use std::ops::AddAssign; /// Trait for casting between primitive types. pub trait CastFromPrimitive: Copy + 'static { /// Casts the given value into `Self`. fn cast_from(v: T) -> Self; } macro_rules! impl_cast_from_primitive { ( $T:ty => $U:ty ) => { impl CastFromPrimitive<$U> for $T { #[inline(always)] fn cast_from(v: $U) -> Self { v as Self } } }; ( $T:ty => { $( $U:ty ),* } ) => { $( impl_cast_from_primitive!($T => $U); )* }; } // casts to { u8, u16 } are implemented separately using Pixel, so that the // compiler understands that CastFromPrimitive is always implemented impl_cast_from_primitive!(u8 => { u32, u64, usize }); impl_cast_from_primitive!(u8 => { i8, i64, isize }); impl_cast_from_primitive!(u16 => { u32, u64, usize }); impl_cast_from_primitive!(u16 => { i8, i64, isize }); impl_cast_from_primitive!(i16 => { u32, u64, usize }); impl_cast_from_primitive!(i16 => { i8, i64, isize }); impl_cast_from_primitive!(i32 => { u32, u64, usize }); impl_cast_from_primitive!(i32 => { i8, i64, isize }); pub trait RegisteredPrimitive: PrimInt + AsPrimitive + AsPrimitive + AsPrimitive + AsPrimitive + AsPrimitive + AsPrimitive + CastFromPrimitive + CastFromPrimitive + CastFromPrimitive + CastFromPrimitive + CastFromPrimitive + CastFromPrimitive { } impl RegisteredPrimitive for u8 {} impl RegisteredPrimitive for u16 {} impl RegisteredPrimitive for i16 {} impl RegisteredPrimitive for i32 {} macro_rules! impl_cast_from_pixel_to_primitive { ( $T:ty ) => { impl CastFromPrimitive for $T { #[inline(always)] fn cast_from(v: T) -> Self { v.as_() } } }; } impl_cast_from_pixel_to_primitive!(u8); impl_cast_from_pixel_to_primitive!(i16); impl_cast_from_pixel_to_primitive!(u16); impl_cast_from_pixel_to_primitive!(i32); impl_cast_from_pixel_to_primitive!(u32); /// Types that can be used as pixel types. #[derive(PartialEq, Eq)] pub enum PixelType { /// 8 bits per pixel, stored in a `u8`. U8, /// 10 or 12 bits per pixel, stored in a `u16`. U16, } /// A type that can be used as a pixel type. pub trait Pixel: RegisteredPrimitive + Into + Into + Debug + Display + Send + Sync + 'static { type Coeff: Coefficient; /// Returns a [`PixelType`] variant corresponding to this type. /// /// [`PixelType`]: enum.PixelType.html fn type_enum() -> PixelType; /// Converts stride in pixels to stride in bytes. #[inline] #[allow(clippy::wrong_self_convention)] fn to_asm_stride(in_stride: usize) -> isize { (in_stride * size_of::()) as isize } } impl Pixel for u8 { type Coeff = i16; #[inline] fn type_enum() -> PixelType { PixelType::U8 } } impl Pixel for u16 { type Coeff = i32; #[inline] fn type_enum() -> PixelType { PixelType::U16 } } pub trait Coefficient: RegisteredPrimitive + Into + AddAssign + Signed + Debug + 'static { type Pixel: Pixel; } impl Coefficient for i16 { type Pixel = u8; } impl Coefficient for i32 { type Pixel = u16; } /// Chroma subsampling format #[wasm_bindgen] #[derive(Copy, Clone, Debug, Default, PartialEq, Eq, FromPrimitive, Serialize, Deserialize)] #[repr(C)] pub enum ChromaSampling { /// Both vertically and horizontally subsampled. #[default] Cs420, /// Horizontally subsampled. Cs422, /// Not subsampled. Cs444, /// Monochrome. Cs400, } impl fmt::Display for ChromaSampling { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!( f, "{}", match self { ChromaSampling::Cs420 => "4:2:0", ChromaSampling::Cs422 => "4:2:2", ChromaSampling::Cs444 => "4:4:4", ChromaSampling::Cs400 => "Monochrome", } ) } } impl ChromaSampling { /// Provides the amount to right shift the luma plane dimensions to get the /// chroma plane dimensions. /// Only values 0 or 1 are ever returned. /// The plane dimensions must also be rounded up to accommodate odd luma plane /// sizes. /// Cs400 returns None, as there are no chroma planes. pub const fn get_decimation(self) -> Option<(usize, usize)> { use self::ChromaSampling::*; match self { Cs420 => Some((1, 1)), Cs422 => Some((1, 0)), Cs444 => Some((0, 0)), Cs400 => None, } } /// Calculates the size of a chroma plane for this sampling type, given the luma plane dimensions. pub const fn get_chroma_dimensions( self, luma_width: usize, luma_height: usize, ) -> (usize, usize) { if let Some((ss_x, ss_y)) = self.get_decimation() { ((luma_width + ss_x) >> ss_x, (luma_height + ss_y) >> ss_y) } else { (0, 0) } } } v_frame-0.3.7/src/plane.rs000064400000000000000000001146451046102023000135210ustar 00000000000000// Copyright (c) 2017-2021, 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. use std::alloc::handle_alloc_error; use std::iter::FusedIterator; use std::marker::PhantomData; use std::mem; use std::ops::{Index, IndexMut, Range}; use std::{ alloc::{alloc, dealloc, Layout}, u32, }; use std::{ fmt::{Debug, Display, Formatter}, usize, }; use crate::math::*; use crate::pixel::*; use crate::serialize::{Deserialize, Serialize}; /// Plane-specific configuration. #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct PlaneConfig { /// Data stride. pub stride: usize, /// Allocated height in pixels. pub alloc_height: usize, /// Width in pixels. pub width: usize, /// Height in pixels. pub height: usize, /// Decimator along the X axis. /// /// For example, for chroma planes in a 4:2:0 configuration this would be 1. pub xdec: usize, /// Decimator along the Y axis. /// /// For example, for chroma planes in a 4:2:0 configuration this would be 1. pub ydec: usize, /// Number of padding pixels on the right. pub xpad: usize, /// Number of padding pixels on the bottom. pub ypad: usize, /// X where the data starts. pub xorigin: usize, /// Y where the data starts. pub yorigin: usize, } impl PlaneConfig { /// Stride alignment in bytes. const STRIDE_ALIGNMENT_LOG2: usize = 6; #[inline] pub fn new( width: usize, height: usize, xdec: usize, ydec: usize, xpad: usize, ypad: usize, type_size: usize, ) -> Self { let xorigin = xpad.align_power_of_two(Self::STRIDE_ALIGNMENT_LOG2 + 1 - type_size); let yorigin = ypad; let stride = (xorigin + width + xpad) .align_power_of_two(Self::STRIDE_ALIGNMENT_LOG2 + 1 - type_size); let alloc_height = yorigin + height + ypad; PlaneConfig { stride, alloc_height, width, height, xdec, ydec, xpad, ypad, xorigin, yorigin, } } } /// Absolute offset in pixels inside a plane #[derive(Clone, Copy, Debug, Default)] pub struct PlaneOffset { pub x: isize, pub y: isize, } /// Backing buffer for the Plane data /// /// The buffer is padded and aligned according to the architecture-specific /// SIMD constraints. #[derive(Debug, PartialEq, Eq)] #[cfg_attr(not(feature = "serialize"), derive(Serialize, Deserialize))] pub struct PlaneData { ptr: std::ptr::NonNull, _marker: PhantomData, len: usize, } unsafe impl Send for PlaneData {} unsafe impl Sync for PlaneData {} impl Clone for PlaneData { fn clone(&self) -> Self { // SAFETY: we initialize the plane data before returning let mut pd = unsafe { Self::new_uninitialized(self.len) }; pd.copy_from_slice(self); pd } } impl std::ops::Deref for PlaneData { type Target = [T]; fn deref(&self) -> &[T] { // SAFETY: we cannot reference out of bounds because we know the length of the data unsafe { let p = self.ptr.as_ptr(); std::slice::from_raw_parts(p, self.len) } } } impl std::ops::DerefMut for PlaneData { fn deref_mut(&mut self) -> &mut [T] { // SAFETY: we cannot reference out of bounds because we know the length of the data unsafe { let p = self.ptr.as_ptr(); std::slice::from_raw_parts_mut(p, self.len) } } } impl std::ops::Drop for PlaneData { fn drop(&mut self) { // SAFETY: we cannot dealloc too much because we know the length of the data unsafe { dealloc(self.ptr.as_ptr() as *mut u8, Self::layout(self.len)); } } } #[cfg(feature = "serialize")] impl Serialize for PlaneData { fn serialize(&self, serializer: S) -> Result where S: serde::Serializer, { use serde::ser::SerializeSeq; use std::ops::Deref; let mut data = serializer.serialize_seq(Some(self.len))?; for byte in self.deref() { data.serialize_element(&byte)?; } data.end() } } #[cfg(feature = "serialize")] impl<'de, T: Pixel + Deserialize<'de>> Deserialize<'de> for PlaneData { fn deserialize(deserializer: D) -> Result>::Error> where D: serde::Deserializer<'de>, { let deserialized = Vec::deserialize(deserializer)?; Ok(Self::from_slice(&deserialized)) } } impl PlaneData { // Data alignment in bytes. cfg_if::cfg_if! { if #[cfg(target_arch = "wasm32")] { // FIXME: wasm32 allocator fails for alignment larger than 3 const DATA_ALIGNMENT_LOG2: usize = 3; } else { const DATA_ALIGNMENT_LOG2: usize = 6; } } unsafe fn layout(len: usize) -> Layout { Layout::from_size_align(len * mem::size_of::(), 1 << Self::DATA_ALIGNMENT_LOG2) .expect("layout size too large") } unsafe fn new_uninitialized(len: usize) -> Self { let ptr = { let layout = Self::layout(len); let ptr = alloc(layout) as *mut T; if ptr.is_null() { handle_alloc_error(layout); } std::ptr::NonNull::new_unchecked(ptr) }; PlaneData { ptr, len, _marker: PhantomData, } } pub fn new(len: usize) -> Self { // SAFETY: we initialize the plane data before returning let mut pd = unsafe { Self::new_uninitialized(len) }; for v in pd.iter_mut() { *v = T::cast_from(128); } pd } fn from_slice(data: &[T]) -> Self { // SAFETY: we initialize the plane data before returning let mut pd = unsafe { Self::new_uninitialized(data.len()) }; pd.copy_from_slice(data); pd } } /// One data plane of a frame. /// /// For example, a plane can be a Y luma plane or a U or V chroma plane. #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] pub struct Plane { // TODO: it is used by encoder to copy by plane and by tiling, make it // private again once tiling is moved and a copy_plane fn is added. // pub data: PlaneData, /// Plane configuration. pub cfg: PlaneConfig, } impl Debug for Plane where T: Display, { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!( f, "Plane {{ data: [{}, ...], cfg: {:?} }}", self.data[0], self.cfg ) } } impl Plane { /// Allocates and returns a new plane. pub fn new( width: usize, height: usize, xdec: usize, ydec: usize, xpad: usize, ypad: usize, ) -> Self { let cfg = PlaneConfig::new(width, height, xdec, ydec, xpad, ypad, mem::size_of::()); let data = PlaneData::new(cfg.stride * cfg.alloc_height); Plane { data, cfg } } /// Allocates and returns an uninitialized plane. unsafe fn new_uninitialized( width: usize, height: usize, xdec: usize, ydec: usize, xpad: usize, ypad: usize, ) -> Self { let cfg = PlaneConfig::new(width, height, xdec, ydec, xpad, ypad, mem::size_of::()); let data = PlaneData::new_uninitialized(cfg.stride * cfg.alloc_height); Plane { data, cfg } } /// # Panics /// /// - If `len` is not a multiple of `stride` pub fn from_slice(data: &[T], stride: usize) -> Self { let len = data.len(); assert!(len % stride == 0); Self { data: PlaneData::from_slice(data), cfg: PlaneConfig { stride, alloc_height: len / stride, width: stride, height: len / stride, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 0, yorigin: 0, }, } } pub fn pad(&mut self, w: usize, h: usize) { let xorigin = self.cfg.xorigin; let yorigin = self.cfg.yorigin; let stride = self.cfg.stride; let alloc_height = self.cfg.alloc_height; let width = (w + self.cfg.xdec) >> self.cfg.xdec; let height = (h + self.cfg.ydec) >> self.cfg.ydec; if xorigin > 0 { for y in 0..height { let base = (yorigin + y) * stride; let fill_val = self.data[base + xorigin]; for val in &mut self.data[base..base + xorigin] { *val = fill_val; } } } if xorigin + width < stride { for y in 0..height { let base = (yorigin + y) * stride + xorigin + width; let fill_val = self.data[base - 1]; for val in &mut self.data[base..base + stride - (xorigin + width)] { *val = fill_val; } } } if yorigin > 0 { let (top, bottom) = self.data.split_at_mut(yorigin * stride); let src = &bottom[..stride]; for y in 0..yorigin { let dst = &mut top[y * stride..(y + 1) * stride]; dst.copy_from_slice(src); } } if yorigin + height < self.cfg.alloc_height { let (top, bottom) = self.data.split_at_mut((yorigin + height) * stride); let src = &top[(yorigin + height - 1) * stride..]; for y in 0..alloc_height - (yorigin + height) { let dst = &mut bottom[y * stride..(y + 1) * stride]; dst.copy_from_slice(src); } } } /// Minimally test that the plane has been padded. pub fn probe_padding(&self, w: usize, h: usize) -> bool { let PlaneConfig { xorigin, yorigin, stride, alloc_height, xdec, ydec, .. } = self.cfg; let width = (w + xdec) >> xdec; let height = (h + ydec) >> ydec; let corner = (yorigin + height - 1) * stride + xorigin + width - 1; let corner_value = self.data[corner]; self.data[(yorigin + height) * stride - 1] == corner_value && self.data[(alloc_height - 1) * stride + xorigin + width - 1] == corner_value && self.data[alloc_height * stride - 1] == corner_value } pub fn slice(&self, po: PlaneOffset) -> PlaneSlice<'_, T> { PlaneSlice { plane: self, x: po.x, y: po.y, } } pub fn mut_slice(&mut self, po: PlaneOffset) -> PlaneMutSlice<'_, T> { PlaneMutSlice { plane: self, x: po.x, y: po.y, } } #[inline] fn index(&self, x: usize, y: usize) -> usize { (y + self.cfg.yorigin) * self.cfg.stride + (x + self.cfg.xorigin) } /// This version of the function crops off the padding on the right side of the image #[inline] pub fn row_range_cropped(&self, x: isize, y: isize) -> Range { debug_assert!(self.cfg.yorigin as isize + y >= 0); debug_assert!(self.cfg.xorigin as isize + x >= 0); let base_y = (self.cfg.yorigin as isize + y) as usize; let base_x = (self.cfg.xorigin as isize + x) as usize; let base = base_y * self.cfg.stride + base_x; let width = (self.cfg.width as isize - x) as usize; base..base + width } /// This version of the function includes the padding on the right side of the image #[inline] pub fn row_range(&self, x: isize, y: isize) -> Range { debug_assert!(self.cfg.yorigin as isize + y >= 0); debug_assert!(self.cfg.xorigin as isize + x >= 0); let base_y = (self.cfg.yorigin as isize + y) as usize; let base_x = (self.cfg.xorigin as isize + x) as usize; let base = base_y * self.cfg.stride + base_x; let width = self.cfg.stride - base_x; base..base + width } /// Returns the pixel at the given coordinates. pub fn p(&self, x: usize, y: usize) -> T { self.data[self.index(x, y)] } /// Returns plane data starting from the origin. pub fn data_origin(&self) -> &[T] { &self.data[self.index(0, 0)..] } /// Returns mutable plane data starting from the origin. pub fn data_origin_mut(&mut self) -> &mut [T] { let i = self.index(0, 0); &mut self.data[i..] } /// Copies data into the plane from a pixel array. /// /// # Panics /// /// - If `source_bytewidth` does not match the generic `T` of `Plane` pub fn copy_from_raw_u8( &mut self, source: &[u8], source_stride: usize, source_bytewidth: usize, ) { let stride = self.cfg.stride; assert!(stride != 0); assert!(source_stride != 0); for (self_row, source_row) in self .data_origin_mut() .chunks_exact_mut(stride) .zip(source.chunks_exact(source_stride)) { match source_bytewidth { 1 => { for (self_pixel, source_pixel) in self_row.iter_mut().zip(source_row.iter()) { *self_pixel = T::cast_from(*source_pixel); } } 2 => { assert!( mem::size_of::() == 2, "source bytewidth ({}) cannot fit in Plane", source_bytewidth ); debug_assert!(T::type_enum() == PixelType::U16); // SAFETY: because of the assert it is safe to assume that T == u16 let self_row: &mut [u16] = unsafe { std::mem::transmute(self_row) }; // SAFETY: we reinterpret the slice of bytes as a slice of elements of // [u8; 2] to allow for more efficient codegen with from_le_bytes let source_row: &[[u8; 2]] = unsafe { std::slice::from_raw_parts(source_row.as_ptr().cast(), source_row.len() / 2) }; for (self_pixel, bytes) in self_row.iter_mut().zip(source_row) { *self_pixel = u16::from_le_bytes(*bytes); } } _ => {} } } } /// Copies data from a plane into a pixel array. /// /// # Panics /// /// - If `dest_bytewidth` does not match the generic `T` of `Plane` pub fn copy_to_raw_u8(&self, dest: &mut [u8], dest_stride: usize, dest_bytewidth: usize) { let stride = self.cfg.stride; for (self_row, dest_row) in self .data_origin() .chunks_exact(stride) .zip(dest.chunks_exact_mut(dest_stride)) { match dest_bytewidth { 1 => { for (self_pixel, dest_pixel) in self_row[..self.cfg.width].iter().zip(dest_row.iter_mut()) { *dest_pixel = u8::cast_from(*self_pixel); } } 2 => { assert!( mem::size_of::() >= 2, "dest bytewidth ({}) cannot fit in Plane", dest_bytewidth ); // SAFETY: we reinterpret the slice of bytes as a slice // of [u8; 2] with half the elements let dest_row: &mut [[u8; 2]] = unsafe { std::slice::from_raw_parts_mut( dest_row.as_mut_ptr().cast(), dest_row.len() / 2, ) }; for (self_pixel, bytes) in self_row[..self.cfg.width].iter().zip(dest_row) { *bytes = u16::cast_from(*self_pixel).to_le_bytes(); } } _ => {} } } } /// Returns plane with half the resolution for width and height. /// Downscaled with 2x2 box filter. /// Padded to dimensions with `frame_width` and `frame_height`. /// /// # Panics /// /// - If the requested width and height are > half the input width or height pub fn downsampled(&self, frame_width: usize, frame_height: usize) -> Plane { let src = self; // SAFETY: all pixels initialized in this function let mut new = unsafe { Plane::new_uninitialized( (src.cfg.width + 1) / 2, (src.cfg.height + 1) / 2, src.cfg.xdec + 1, src.cfg.ydec + 1, src.cfg.xpad / 2, src.cfg.ypad / 2, ) }; let width = new.cfg.width; let height = new.cfg.height; assert!(width * 2 <= src.cfg.stride - src.cfg.xorigin); assert!(height * 2 <= src.cfg.alloc_height - src.cfg.yorigin); let data_origin = src.data_origin(); for (row_idx, dst_row) in new .mut_slice(PlaneOffset::default()) .rows_iter_mut() .enumerate() .take(height) { let src_top_row = &data_origin[(src.cfg.stride * row_idx * 2)..][..(2 * width)]; let src_bottom_row = &data_origin[(src.cfg.stride * (row_idx * 2 + 1))..][..(2 * width)]; for ((dst, a), b) in dst_row .iter_mut() .zip(src_top_row.chunks_exact(2)) .zip(src_bottom_row.chunks_exact(2)) { let sum = u32::cast_from(a[0]) + u32::cast_from(a[1]) + u32::cast_from(b[0]) + u32::cast_from(b[1]); let avg = (sum + 2) >> 2; *dst = T::cast_from(avg); } } new.pad(frame_width, frame_height); new } /// Returns a plane downscaled from the source plane by a factor of `scale` (not padded) pub fn downscale(&self) -> Plane { // SAFETY: all pixels initialized when `downscale_in_place` is called let mut new_plane = unsafe { Plane::new_uninitialized(self.cfg.width / SCALE, self.cfg.height / SCALE, 0, 0, 0, 0) }; self.downscale_in_place::(&mut new_plane); new_plane } /// Downscales the source plane by a factor of `scale`, writing the result to `in_plane` (not padded) /// /// # Panics /// /// - If the current plane's width and height are not at least `SCALE` times the `in_plane`'s #[profiling::function(downscale_in_place)] pub fn downscale_in_place(&self, in_plane: &mut Plane) { let stride = in_plane.cfg.stride; let width = in_plane.cfg.width; let height = in_plane.cfg.height; if stride == 0 || self.cfg.stride == 0 { panic!("stride cannot be 0"); } assert!(width * SCALE <= self.cfg.stride - self.cfg.xorigin); assert!(height * SCALE <= self.cfg.alloc_height - self.cfg.yorigin); // SAFETY: Bounds checks have been removed for performance reasons unsafe { let src = self; let box_pixels = SCALE * SCALE; let half_box_pixels = box_pixels as u32 / 2; // Used for rounding int division let data_origin = src.data_origin(); let plane_data_mut_slice = &mut *in_plane.data; // Iter dst rows for row_idx in 0..height { let dst_row = plane_data_mut_slice.get_unchecked_mut(row_idx * stride..); // Iter dst cols for (col_idx, dst) in dst_row.get_unchecked_mut(..width).iter_mut().enumerate() { macro_rules! generate_inner_loop { ($x:ty) => { let mut sum = half_box_pixels as $x; // Sum box of size scale * scale // Iter src row for y in 0..SCALE { let src_row_idx = row_idx * SCALE + y; let src_row = data_origin.get_unchecked((src_row_idx * src.cfg.stride)..); // Iter src col for x in 0..SCALE { let src_col_idx = col_idx * SCALE + x; sum += <$x>::cast_from(*src_row.get_unchecked(src_col_idx)); } } // Box average let avg = sum as usize / box_pixels; *dst = T::cast_from(avg); }; } // Use 16 bit precision if overflow would not happen if T::type_enum() == PixelType::U8 && SCALE as u128 * SCALE as u128 * (u8::MAX as u128) + half_box_pixels as u128 <= u16::MAX as u128 { generate_inner_loop!(u16); } else { generate_inner_loop!(u32); } } } } } /// Iterates over the pixels in the plane, skipping the padding. pub fn iter(&self) -> PlaneIter<'_, T> { PlaneIter::new(self) } /// Iterates over the lines of the plane pub fn rows_iter(&self) -> RowsIter<'_, T> { RowsIter { plane: self, x: 0, y: 0, } } pub fn rows_iter_mut(&mut self) -> RowsIterMut<'_, T> { RowsIterMut { plane: self as *mut Plane, x: 0, y: 0, phantom: PhantomData, } } /// Return a line pub fn row(&self, y: isize) -> &[T] { let range = self.row_range(0, y); &self.data[range] } } /// Iterator over plane pixels, skipping padding. #[derive(Debug)] pub struct PlaneIter<'a, T: Pixel> { plane: &'a Plane, y: usize, x: usize, } impl<'a, T: Pixel> PlaneIter<'a, T> { /// Creates a new iterator. pub fn new(plane: &'a Plane) -> Self { Self { plane, y: 0, x: 0 } } fn width(&self) -> usize { self.plane.cfg.width } fn height(&self) -> usize { self.plane.cfg.height } } impl<'a, T: Pixel> Iterator for PlaneIter<'a, T> { type Item = T; fn next(&mut self) -> Option<::Item> { if self.y == self.height() { return None; } let pixel = self.plane.p(self.x, self.y); if self.x == self.width() - 1 { self.x = 0; self.y += 1; } else { self.x += 1; } Some(pixel) } } impl FusedIterator for PlaneIter<'_, T> {} // A Plane, PlaneSlice, or PlaneRegion is assumed to include or be able to include // padding on the edge of the frame #[derive(Clone, Copy, Debug)] pub struct PlaneSlice<'a, T: Pixel> { pub plane: &'a Plane, pub x: isize, pub y: isize, } // A RowsIter or RowsIterMut is assumed to crop the padding from the frame edges pub struct RowsIter<'a, T: Pixel> { plane: &'a Plane, x: isize, y: isize, } impl<'a, T: Pixel> Iterator for RowsIter<'a, T> { type Item = &'a [T]; fn next(&mut self) -> Option { if self.plane.cfg.height as isize > self.y { // cannot directly return self.ps.row(row) due to lifetime issue let range = self.plane.row_range_cropped(self.x, self.y); self.y += 1; Some(&self.plane.data[range]) } else { None } } fn size_hint(&self) -> (usize, Option) { let remaining = self.plane.cfg.height as isize - self.y; debug_assert!(remaining >= 0); let remaining = remaining as usize; (remaining, Some(remaining)) } } impl<'a, T: Pixel> ExactSizeIterator for RowsIter<'a, T> {} impl<'a, T: Pixel> FusedIterator for RowsIter<'a, T> {} impl<'a, T: Pixel> PlaneSlice<'a, T> { #[allow(unused)] pub fn as_ptr(&self) -> *const T { self[0].as_ptr() } pub fn rows_iter(&self) -> RowsIter<'_, T> { RowsIter { plane: self.plane, x: self.x, y: self.y, } } pub fn clamp(&self) -> PlaneSlice<'a, T> { PlaneSlice { plane: self.plane, x: self.x.clamp( -(self.plane.cfg.xorigin as isize), self.plane.cfg.width as isize, ), y: self.y.clamp( -(self.plane.cfg.yorigin as isize), self.plane.cfg.height as isize, ), } } pub fn subslice(&self, xo: usize, yo: usize) -> PlaneSlice<'a, T> { PlaneSlice { plane: self.plane, x: self.x + xo as isize, y: self.y + yo as isize, } } pub fn reslice(&self, xo: isize, yo: isize) -> PlaneSlice<'a, T> { PlaneSlice { plane: self.plane, x: self.x + xo, y: self.y + yo, } } /// A slice starting i pixels above the current one. pub fn go_up(&self, i: usize) -> PlaneSlice<'a, T> { PlaneSlice { plane: self.plane, x: self.x, y: self.y - i as isize, } } /// A slice starting i pixels to the left of the current one. pub fn go_left(&self, i: usize) -> PlaneSlice<'a, T> { PlaneSlice { plane: self.plane, x: self.x - i as isize, y: self.y, } } pub fn p(&self, add_x: usize, add_y: usize) -> T { let new_y = (self.y + add_y as isize + self.plane.cfg.yorigin as isize) as usize; let new_x = (self.x + add_x as isize + self.plane.cfg.xorigin as isize) as usize; self.plane.data[new_y * self.plane.cfg.stride + new_x] } /// Checks if `add_y` and `add_x` lies in the allocated bounds of the /// underlying plane. pub fn accessible(&self, add_x: usize, add_y: usize) -> bool { let y = (self.y + add_y as isize + self.plane.cfg.yorigin as isize) as usize; let x = (self.x + add_x as isize + self.plane.cfg.xorigin as isize) as usize; y < self.plane.cfg.alloc_height && x < self.plane.cfg.stride } /// Checks if -`sub_x` and -`sub_y` lies in the allocated bounds of the /// underlying plane. pub fn accessible_neg(&self, sub_x: usize, sub_y: usize) -> bool { let y = self.y - sub_y as isize + self.plane.cfg.yorigin as isize; let x = self.x - sub_x as isize + self.plane.cfg.xorigin as isize; y >= 0 && x >= 0 } /// This version of the function crops off the padding on the right side of the image pub fn row_cropped(&self, y: usize) -> &[T] { let y = (self.y + y as isize + self.plane.cfg.yorigin as isize) as usize; let x = (self.x + self.plane.cfg.xorigin as isize) as usize; let start = y * self.plane.cfg.stride + x; let width = (self.plane.cfg.width as isize - self.x) as usize; &self.plane.data[start..start + width] } /// This version of the function includes the padding on the right side of the image pub fn row(&self, y: usize) -> &[T] { let y = (self.y + y as isize + self.plane.cfg.yorigin as isize) as usize; let x = (self.x + self.plane.cfg.xorigin as isize) as usize; let start = y * self.plane.cfg.stride + x; let width = self.plane.cfg.stride - x; &self.plane.data[start..start + width] } } impl<'a, T: Pixel> Index for PlaneSlice<'a, T> { type Output = [T]; fn index(&self, index: usize) -> &Self::Output { let range = self.plane.row_range(self.x, self.y + index as isize); &self.plane.data[range] } } pub struct PlaneMutSlice<'a, T: Pixel> { pub plane: &'a mut Plane, pub x: isize, pub y: isize, } pub struct RowsIterMut<'a, T: Pixel> { plane: *mut Plane, x: isize, y: isize, phantom: PhantomData<&'a mut Plane>, } impl<'a, T: Pixel> Iterator for RowsIterMut<'a, T> { type Item = &'a mut [T]; fn next(&mut self) -> Option { // SAFETY: there could not be a concurrent call using a mutable reference to the plane let plane = unsafe { &mut *self.plane }; if plane.cfg.height as isize > self.y { // cannot directly return self.ps.row(row) due to lifetime issue let range = plane.row_range_cropped(self.x, self.y); self.y += 1; Some(&mut plane.data[range]) } else { None } } fn size_hint(&self) -> (usize, Option) { // SAFETY: there could not be a concurrent call using a mutable reference to the plane let plane = unsafe { &mut *self.plane }; let remaining = plane.cfg.height as isize - self.y; debug_assert!(remaining >= 0); let remaining = remaining as usize; (remaining, Some(remaining)) } } impl<'a, T: Pixel> ExactSizeIterator for RowsIterMut<'a, T> {} impl<'a, T: Pixel> FusedIterator for RowsIterMut<'a, T> {} impl<'a, T: Pixel> PlaneMutSlice<'a, T> { #[allow(unused)] pub fn rows_iter(&self) -> RowsIter<'_, T> { RowsIter { plane: self.plane, x: self.x, y: self.y, } } pub fn rows_iter_mut(&mut self) -> RowsIterMut<'_, T> { RowsIterMut { plane: self.plane as *mut Plane, x: self.x, y: self.y, phantom: PhantomData, } } #[allow(unused)] pub fn subslice(&mut self, xo: usize, yo: usize) -> PlaneMutSlice<'_, T> { PlaneMutSlice { plane: self.plane, x: self.x + xo as isize, y: self.y + yo as isize, } } } impl<'a, T: Pixel> Index for PlaneMutSlice<'a, T> { type Output = [T]; fn index(&self, index: usize) -> &Self::Output { let range = self.plane.row_range(self.x, self.y + index as isize); &self.plane.data[range] } } impl<'a, T: Pixel> IndexMut for PlaneMutSlice<'a, T> { fn index_mut(&mut self, index: usize) -> &mut Self::Output { let range = self.plane.row_range(self.x, self.y + index as isize); &mut self.plane.data[range] } } #[cfg(test)] pub mod test { use super::*; #[test] fn copy_from_raw_u8() { #[rustfmt::skip] let mut plane = Plane::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8, ); let input = vec![42u8; 64]; plane.copy_from_raw_u8(&input, 8, 1); println!("{:?}", &plane.data[..10]); assert_eq!(&input[..64], &plane.data[..64]); } #[test] fn copy_to_raw_u8() { #[rustfmt::skip] let plane = Plane::from_slice(&[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 8, ); let mut output = vec![42u8; 64]; plane.copy_to_raw_u8(&mut output, 8, 1); println!("{:?}", &plane.data[..10]); assert_eq!(&output[..64], &plane.data[..64]); } #[test] fn test_plane_downsample() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), cfg: PlaneConfig { stride: 8, alloc_height: 9, width: 4, height: 4, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 2, yorigin: 3, }, }; let downsampled = plane.downsampled(4, 4); #[rustfmt::skip] let expected = &[ 5, 5, 6, 6, ]; let v: Vec<_> = downsampled.iter().collect(); assert_eq!(&expected[..], &v[..]); } #[test] fn test_plane_downsample_odd() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), cfg: PlaneConfig { stride: 8, alloc_height: 9, width: 3, height: 3, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 2, yorigin: 3, }, }; let downsampled = plane.downsampled(3, 3); #[rustfmt::skip] let expected = &[ 5, 5, 6, 6, ]; let v: Vec<_> = downsampled.iter().collect(); assert_eq!(&expected[..], &v[..]); } #[test] fn test_plane_downscale() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4, 5, 0, 0, 0, 0, 2, 3, 6, 7, 0, 0, 0, 0, 8, 9, 7, 5, 0, 0, 0, 0, 9, 8, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), cfg: PlaneConfig { stride: 8, alloc_height: 9, width: 4, height: 4, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 2, yorigin: 3, }, }; let downscaled = plane.downscale::<2>(); #[rustfmt::skip] let expected = &[ 2, 6, 9, 4 ]; let v: Vec<_> = downscaled.iter().collect(); assert_eq!(&expected[..], &v[..]); } #[test] fn test_plane_downscale_odd() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 1, 2, 3, 4, 1, 2, 3, 4, 0, 0, 8, 7, 6, 5, 8, 7, 6, 5, 8, 7, 6, 5, 8, 7, 6, 5, 8, 7, 0, 0, 2, 3, 4, 5, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 2, 3, 4, 5, ]), cfg: PlaneConfig { stride: 8, alloc_height: 7, width: 8, height: 7, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 0, yorigin: 0, }, }; let downscaled = plane.downscale::<3>(); #[rustfmt::skip] let expected = &[ 4, 5, 3, 3 ]; let v: Vec<_> = downscaled.iter().collect(); assert_eq!(&expected[..], &v[..]); } #[test] fn test_plane_downscale_odd_2() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 9, 8, 3, 1, 0, 1, 4, 5, 0, 0, 0, 1, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 9, 0, 0, 2, 3, 6, 7, 0, 0, 0, 0, 0, 0, 0, 8, 9, 7, 5, 0, 0, 0, 0, 9, 8, 3, 1, 0, 1, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 3, 6, 7, 0, 0, 0, 0, 0, 0, 0, 8, 9, 7, 5, 0, 0, 0, 0, 9, 8, 3, 1, 0, 0 ]), cfg: PlaneConfig { stride: 10, alloc_height: 10, width: 10, height: 10, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 0, yorigin: 0, }, }; let downscaled = plane.downscale::<3>(); #[rustfmt::skip] let expected = &[ 3, 1, 2, 4, 4, 1, 0, 0, 4, ]; let v: Vec<_> = downscaled.iter().collect(); assert_eq!(&expected[..], &v[..]); } #[test] fn test_plane_pad() { #[rustfmt::skip] let mut plane = Plane:: { data: PlaneData::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), cfg: PlaneConfig { stride: 8, alloc_height: 9, width: 4, height: 4, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 2, yorigin: 3, }, }; plane.pad(4, 4); #[rustfmt::skip] assert_eq!( &[ 1, 1, 1, 2, 3, 4, 4, 4, 1, 1, 1, 2, 3, 4, 4, 4, 1, 1, 1, 2, 3, 4, 4, 4, 1, 1, 1, 2, 3, 4, 4, 4, 8, 8, 8, 7, 6, 5, 5, 5, 9, 9, 9, 8, 7, 6, 6, 6, 2, 2, 2, 3, 4, 5, 5, 5, 2, 2, 2, 3, 4, 5, 5, 5, 2, 2, 2, 3, 4, 5, 5, 5, ][..], &plane.data[..] ); } #[test] fn test_pixel_iterator() { #[rustfmt::skip] let plane = Plane:: { data: PlaneData::from_slice(&[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 2, 3, 4, 0, 0, 0, 0, 8, 7, 6, 5, 0, 0, 0, 0, 9, 8, 7, 6, 0, 0, 0, 0, 2, 3, 4, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), cfg: PlaneConfig { stride: 8, alloc_height: 9, width: 4, height: 4, xdec: 0, ydec: 0, xpad: 0, ypad: 0, xorigin: 2, yorigin: 3, }, }; let pixels: Vec = plane.iter().collect(); assert_eq!( &[1, 2, 3, 4, 8, 7, 6, 5, 9, 8, 7, 6, 2, 3, 4, 5,][..], &pixels[..] ); } }