From faec94a52c63a81a505f527a5e6bfe224710d16c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 29 Apr 2026 22:26:47 +0200 Subject: [PATCH 01/12] feat: roll our own Pixel-to-integer conversions These are infallible and very cheap. Also removes our num-traits dependency. --- Cargo.toml | 3 --- src/pixel.rs | 55 ++++++++++++++++++++++++++++++++++++++++++++++++---- src/plane.rs | 12 +----------- 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 60dc90c..cca2af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,9 +9,6 @@ edition = "2024" repository = "https://github.com/rust-av/v_frame" include = ["Cargo.toml", "README.md", "LICENSE", "src"] -[dependencies] -num-traits = "0.2.19" - [features] padding_api = [] diff --git a/src/pixel.rs b/src/pixel.rs index f8a1c0e..5f54fab 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -22,7 +22,6 @@ //! - 8-bit frames must use `u8` //! - 9-16 bit frames must use `u16` -use num_traits::PrimInt; use std::fmt::Debug; mod private { @@ -56,14 +55,62 @@ mod private { /// i.e. using [`std::mem::zeroed`] must __not__ cause undefined behavior for /// implementing types. pub unsafe trait Pixel: - Debug + Copy + Clone + Default + Send + Sync + PrimInt + 'static + private::Sealed + Debug + Copy + Clone + Default + Send + Sync + 'static + private::Sealed { + /// Lossless and cheap conversion into a `u16`. + fn as_u16(self) -> u16; + + /// Lossless and cheap conversion into a `u32`. + #[inline] + fn as_u32(self) -> u32 { + u32::from(self.as_u16()) + } + + /// Lossless and cheap conversion into a `u64`. + #[inline] + fn as_u64(self) -> u64 { + u64::from(self.as_u16()) + } + + /// Lossless and cheap conversion into a `u128`. + #[inline] + fn as_u128(self) -> u128 { + u128::from(self.as_u16()) + } + + /// Lossless and cheap conversion into a `i32`. + #[inline] + fn as_i32(self) -> i32 { + i32::from(self.as_u16()) + } + + /// Lossless and cheap conversion into a `i64`. + #[inline] + fn as_i64(self) -> i64 { + i64::from(self.as_u16()) + } + + /// Lossless and cheap conversion into a `i128`. + #[inline] + fn as_i128(self) -> i128 { + i128::from(self.as_u16()) + } } /// Pixel implementation for 8-bit video data. // SAFETY: u8 is valid if represented by a zeroed byte. -unsafe impl Pixel for u8 {} +unsafe impl Pixel for u8 { + #[inline] + fn as_u16(self) -> u16 { + u16::from(self) + } +} /// Pixel implementation for high bit-depth (9-16 bit) video data. // SAFETY: u16 is valid if represented by zeroed bytes. -unsafe impl Pixel for u16 {} +unsafe impl Pixel for u16 { + #[inline] + fn as_u16(self) -> u16 { + self + } +} diff --git a/src/plane.rs b/src/plane.rs index bf701ee..c605ba8 100644 --- a/src/plane.rs +++ b/src/plane.rs @@ -317,17 +317,7 @@ impl Plane { 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() - }; + let bytes = pix.as_u16().to_le_bytes(); bytes.into_iter().take(byte_width) }), len: total, From 8b005897281bf83af0fc868660774fcf12822387 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 30 Apr 2026 11:29:50 +0200 Subject: [PATCH 02/12] test: exhaustive conversion tests --- src/pixel.rs | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/pixel.rs b/src/pixel.rs index 5f54fab..362da0f 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -114,3 +114,40 @@ unsafe impl Pixel for u16 { self } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn u8() { + for v in 0..=u8::MAX { + assert_eq!(v, u8::try_from(v.as_u16()).expect("u16 fits into u8")); + assert_eq!(v, u8::try_from(v.as_u32()).expect("u32 fits into u8")); + assert_eq!(v, u8::try_from(v.as_u64()).expect("u64 fits into u8")); + assert_eq!(v, u8::try_from(v.as_u64()).expect("u64 fits into u8")); + assert_eq!(v, u8::try_from(v.as_u128()).expect("u128 fits into u8")); + + assert_eq!(v, u8::try_from(v.as_i32()).expect("i32 fits into u8")); + assert_eq!(v, u8::try_from(v.as_i64()).expect("i64 fits into u8")); + assert_eq!(v, u8::try_from(v.as_i64()).expect("i64 fits into u8")); + assert_eq!(v, u8::try_from(v.as_i128()).expect("i128 fits into u8")); + } + } + + #[test] + fn u16() { + for v in 0..=u16::MAX { + assert_eq!(v, v.as_u16()); + assert_eq!(v, u16::try_from(v.as_u32()).expect("u32 fits into u16")); + assert_eq!(v, u16::try_from(v.as_u64()).expect("u64 fits into u16")); + assert_eq!(v, u16::try_from(v.as_u64()).expect("u64 fits into u16")); + assert_eq!(v, u16::try_from(v.as_u128()).expect("u128 fits into u16")); + + assert_eq!(v, u16::try_from(v.as_i32()).expect("i32 fits into u16")); + assert_eq!(v, u16::try_from(v.as_i64()).expect("i64 fits into u16")); + assert_eq!(v, u16::try_from(v.as_i64()).expect("i64 fits into u16")); + assert_eq!(v, u16::try_from(v.as_i128()).expect("i128 fits into u16")); + } + } +} From 6bd794df67f0b8baad8935db30114fc4f61a27b0 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 30 Apr 2026 20:01:23 +0200 Subject: [PATCH 03/12] feat: add integer-to-Pixel conversions --- src/pixel.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/pixel.rs b/src/pixel.rs index 362da0f..77b2dee 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -57,6 +57,24 @@ mod private { pub unsafe trait Pixel: Debug + Copy + Clone + Default + Send + Sync + 'static + private::Sealed { + /// Lossless and cheap conversion from a `u8` into `Self`. + fn from_u8(x: u8) -> Self; + + /// Conversion from a `u16` into `Self`. + /// + /// Returns `None` if the implementing type cannot losslessly + /// represent the value of `x`. + fn try_from_u16(x: u16) -> Option; + + /// Conversion from any integer type into `Self`. + /// + /// Returns `None` if the implementing type cannot losslessly + /// represent the value of `x`. + #[inline] + fn try_from_int>(x: T) -> Option { + x.try_into().ok().and_then(Self::try_from_u16) + } + /// Lossless and cheap conversion into a `u16`. fn as_u16(self) -> u16; @@ -100,6 +118,16 @@ pub unsafe trait Pixel: /// Pixel implementation for 8-bit video data. // SAFETY: u8 is valid if represented by a zeroed byte. unsafe impl Pixel for u8 { + #[inline] + fn from_u8(x: u8) -> Self { + x + } + + #[inline] + fn try_from_u16(x: u16) -> Option { + u8::try_from(x).ok().map(Self::from_u8) + } + #[inline] fn as_u16(self) -> u16 { u16::from(self) @@ -109,6 +137,16 @@ unsafe impl Pixel for u8 { /// Pixel implementation for high bit-depth (9-16 bit) video data. // SAFETY: u16 is valid if represented by zeroed bytes. unsafe impl Pixel for u16 { + #[inline] + fn from_u8(x: u8) -> Self { + Self::from(x) + } + + #[inline] + fn try_from_u16(x: u16) -> Option { + Some(x) + } + #[inline] fn as_u16(self) -> u16 { self From 118b5e6e86b2f977e00f2711acd003617b31f180 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 30 Apr 2026 20:10:34 +0200 Subject: [PATCH 04/12] (wip) tests --- src/pixel.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/pixel.rs b/src/pixel.rs index 77b2dee..788ebc3 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -173,6 +173,44 @@ mod tests { } } + #[test] + fn u8_from() { + for v in 0..=u8::MAX { + if v <= i8::MAX as u8 { + assert_eq!(v, u8::try_from_int(v as i8).expect("i8 fits into u8")); + } + + assert_eq!(v, u8::from_u8(v)); + assert_eq!(v, u8::try_from_u16(v as u16).expect("u16 fits into u8")); + assert_eq!(v, u8::try_from_int(v as i16).expect("i16 fits into u8")); + assert_eq!(v, u8::try_from_int(v as i32).expect("i32 fits into u8")); + assert_eq!(v, u8::try_from_int(v as i64).expect("i64 fits into u8")); + assert_eq!(v, u8::try_from_int(v as i128).expect("i128 fits into u8")); + assert_eq!(v, u8::try_from_int(v as u32).expect("u32 fits into u8")); + assert_eq!(v, u8::try_from_int(v as u64).expect("u64 fits into u8")); + assert_eq!(v, u8::try_from_int(v as u128).expect("u128 fits into u8")); + assert_eq!(v, u8::try_from_int(v as isize).expect("isize fits into u8")); + assert_eq!(v, u8::try_from_int(v as usize).expect("usize fits into u8")); + } + } + + #[test] + fn u16_from() { + for v in 0..=u16::MAX { + assert_eq!(v, u16::try_from_u16(v).expect("u16 fits into u16")); + assert_eq!(v, u16::try_from_int(v as i8).expect("i8 fits into u16")); + assert_eq!(v, u16::try_from_int(v as i16).expect("i16 fits into u16")); + assert_eq!(v, u16::try_from_int(v as i32).expect("i32 fits into u16")); + assert_eq!(v, u16::try_from_int(v as i64).expect("i64 fits into u16")); + assert_eq!(v, u16::try_from_int(v as i128).expect("i128 fits into u16")); + assert_eq!(v, u16::try_from_int(v as u32).expect("u32 fits into u16")); + assert_eq!(v, u16::try_from_int(v as u64).expect("u64 fits into u16")); + assert_eq!(v, u16::try_from_int(v as u128).expect("u128 fits into u16")); + assert_eq!(v, u16::try_from_int(v as isize).expect("isize fits into u16")); + assert_eq!(v, u16::try_from_int(v as usize).expect("usize fits into u16")); + } + } + #[test] fn u16() { for v in 0..=u16::MAX { From 90ee84219d0018117e342b76f367e55421772b4f Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 May 2026 19:05:31 +0200 Subject: [PATCH 05/12] don't roll our own, use From/Into --- src/pixel.rs | 184 ++++++--------------------------------------------- src/plane.rs | 4 +- 2 files changed, 22 insertions(+), 166 deletions(-) diff --git a/src/pixel.rs b/src/pixel.rs index 788ebc3..9f8b90f 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -55,175 +55,31 @@ mod private { /// i.e. using [`std::mem::zeroed`] must __not__ cause undefined behavior for /// implementing types. pub unsafe trait Pixel: - Debug + Copy + Clone + Default + Send + Sync + 'static + private::Sealed + Debug + + Copy + + Clone + + Default + + Send + + Sync + + Into + + Into + + Into + + Into + + Into + + Into + + Into + + Into + + From + + TryFrom + + 'static + + private::Sealed { - /// Lossless and cheap conversion from a `u8` into `Self`. - fn from_u8(x: u8) -> Self; - - /// Conversion from a `u16` into `Self`. - /// - /// Returns `None` if the implementing type cannot losslessly - /// represent the value of `x`. - fn try_from_u16(x: u16) -> Option; - - /// Conversion from any integer type into `Self`. - /// - /// Returns `None` if the implementing type cannot losslessly - /// represent the value of `x`. - #[inline] - fn try_from_int>(x: T) -> Option { - x.try_into().ok().and_then(Self::try_from_u16) - } - - /// Lossless and cheap conversion into a `u16`. - fn as_u16(self) -> u16; - - /// Lossless and cheap conversion into a `u32`. - #[inline] - fn as_u32(self) -> u32 { - u32::from(self.as_u16()) - } - - /// Lossless and cheap conversion into a `u64`. - #[inline] - fn as_u64(self) -> u64 { - u64::from(self.as_u16()) - } - - /// Lossless and cheap conversion into a `u128`. - #[inline] - fn as_u128(self) -> u128 { - u128::from(self.as_u16()) - } - - /// Lossless and cheap conversion into a `i32`. - #[inline] - fn as_i32(self) -> i32 { - i32::from(self.as_u16()) - } - - /// Lossless and cheap conversion into a `i64`. - #[inline] - fn as_i64(self) -> i64 { - i64::from(self.as_u16()) - } - - /// Lossless and cheap conversion into a `i128`. - #[inline] - fn as_i128(self) -> i128 { - i128::from(self.as_u16()) - } } /// Pixel implementation for 8-bit video data. // SAFETY: u8 is valid if represented by a zeroed byte. -unsafe impl Pixel for u8 { - #[inline] - fn from_u8(x: u8) -> Self { - x - } - - #[inline] - fn try_from_u16(x: u16) -> Option { - u8::try_from(x).ok().map(Self::from_u8) - } - - #[inline] - fn as_u16(self) -> u16 { - u16::from(self) - } -} +unsafe impl Pixel for u8 {} /// Pixel implementation for high bit-depth (9-16 bit) video data. // SAFETY: u16 is valid if represented by zeroed bytes. -unsafe impl Pixel for u16 { - #[inline] - fn from_u8(x: u8) -> Self { - Self::from(x) - } - - #[inline] - fn try_from_u16(x: u16) -> Option { - Some(x) - } - - #[inline] - fn as_u16(self) -> u16 { - self - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn u8() { - for v in 0..=u8::MAX { - assert_eq!(v, u8::try_from(v.as_u16()).expect("u16 fits into u8")); - assert_eq!(v, u8::try_from(v.as_u32()).expect("u32 fits into u8")); - assert_eq!(v, u8::try_from(v.as_u64()).expect("u64 fits into u8")); - assert_eq!(v, u8::try_from(v.as_u64()).expect("u64 fits into u8")); - assert_eq!(v, u8::try_from(v.as_u128()).expect("u128 fits into u8")); - - assert_eq!(v, u8::try_from(v.as_i32()).expect("i32 fits into u8")); - assert_eq!(v, u8::try_from(v.as_i64()).expect("i64 fits into u8")); - assert_eq!(v, u8::try_from(v.as_i64()).expect("i64 fits into u8")); - assert_eq!(v, u8::try_from(v.as_i128()).expect("i128 fits into u8")); - } - } - - #[test] - fn u8_from() { - for v in 0..=u8::MAX { - if v <= i8::MAX as u8 { - assert_eq!(v, u8::try_from_int(v as i8).expect("i8 fits into u8")); - } - - assert_eq!(v, u8::from_u8(v)); - assert_eq!(v, u8::try_from_u16(v as u16).expect("u16 fits into u8")); - assert_eq!(v, u8::try_from_int(v as i16).expect("i16 fits into u8")); - assert_eq!(v, u8::try_from_int(v as i32).expect("i32 fits into u8")); - assert_eq!(v, u8::try_from_int(v as i64).expect("i64 fits into u8")); - assert_eq!(v, u8::try_from_int(v as i128).expect("i128 fits into u8")); - assert_eq!(v, u8::try_from_int(v as u32).expect("u32 fits into u8")); - assert_eq!(v, u8::try_from_int(v as u64).expect("u64 fits into u8")); - assert_eq!(v, u8::try_from_int(v as u128).expect("u128 fits into u8")); - assert_eq!(v, u8::try_from_int(v as isize).expect("isize fits into u8")); - assert_eq!(v, u8::try_from_int(v as usize).expect("usize fits into u8")); - } - } - - #[test] - fn u16_from() { - for v in 0..=u16::MAX { - assert_eq!(v, u16::try_from_u16(v).expect("u16 fits into u16")); - assert_eq!(v, u16::try_from_int(v as i8).expect("i8 fits into u16")); - assert_eq!(v, u16::try_from_int(v as i16).expect("i16 fits into u16")); - assert_eq!(v, u16::try_from_int(v as i32).expect("i32 fits into u16")); - assert_eq!(v, u16::try_from_int(v as i64).expect("i64 fits into u16")); - assert_eq!(v, u16::try_from_int(v as i128).expect("i128 fits into u16")); - assert_eq!(v, u16::try_from_int(v as u32).expect("u32 fits into u16")); - assert_eq!(v, u16::try_from_int(v as u64).expect("u64 fits into u16")); - assert_eq!(v, u16::try_from_int(v as u128).expect("u128 fits into u16")); - assert_eq!(v, u16::try_from_int(v as isize).expect("isize fits into u16")); - assert_eq!(v, u16::try_from_int(v as usize).expect("usize fits into u16")); - } - } - - #[test] - fn u16() { - for v in 0..=u16::MAX { - assert_eq!(v, v.as_u16()); - assert_eq!(v, u16::try_from(v.as_u32()).expect("u32 fits into u16")); - assert_eq!(v, u16::try_from(v.as_u64()).expect("u64 fits into u16")); - assert_eq!(v, u16::try_from(v.as_u64()).expect("u64 fits into u16")); - assert_eq!(v, u16::try_from(v.as_u128()).expect("u128 fits into u16")); - - assert_eq!(v, u16::try_from(v.as_i32()).expect("i32 fits into u16")); - assert_eq!(v, u16::try_from(v.as_i64()).expect("i64 fits into u16")); - assert_eq!(v, u16::try_from(v.as_i64()).expect("i64 fits into u16")); - assert_eq!(v, u16::try_from(v.as_i128()).expect("i128 fits into u16")); - } - } -} +unsafe impl Pixel for u16 {} diff --git a/src/plane.rs b/src/plane.rs index c605ba8..b5eeb23 100644 --- a/src/plane.rs +++ b/src/plane.rs @@ -317,8 +317,8 @@ impl Plane { let total = self.width().get() * self.height().get() * byte_width; ExactSizeWrapper { iter: self.pixels().flat_map(move |pix| { - let bytes = pix.as_u16().to_le_bytes(); - bytes.into_iter().take(byte_width) + let pix: u16 = pix.into(); + pix.to_le_bytes().into_iter().take(byte_width) }), len: total, } From 49001c5f38173979f8e0e8272d6cc2fccdf3bc8d Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 May 2026 19:19:24 +0200 Subject: [PATCH 06/12] add TryInto and --- src/pixel.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pixel.rs b/src/pixel.rs index 9f8b90f..2699a19 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -61,10 +61,12 @@ pub unsafe trait Pixel: + Default + Send + Sync + + TryInto + Into + Into + Into + Into + + TryInto + Into + Into + Into From e49fe2b6a8f7638e568e4392a1b88df8e5ff69da Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 May 2026 20:09:14 +0200 Subject: [PATCH 07/12] bring back num_traits::AsPrimitive --- Cargo.toml | 3 +++ src/pixel.rs | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index cca2af2..051987a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,6 +12,9 @@ include = ["Cargo.toml", "README.md", "LICENSE", "src"] [features] padding_api = [] +[dependencies.num-traits] +version = "0.2" + [dev-dependencies.criterion] version = "0.8" default-features = false diff --git a/src/pixel.rs b/src/pixel.rs index 2699a19..a142b9d 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -24,6 +24,8 @@ use std::fmt::Debug; +use num_traits::AsPrimitive; + mod private { pub trait Sealed {} @@ -66,13 +68,15 @@ pub unsafe trait Pixel: + Into + Into + Into - + TryInto + Into + Into + Into + Into + From - + TryFrom + + AsPrimitive + + AsPrimitive + + AsPrimitive + + AsPrimitive + 'static + private::Sealed { From 2833a83dc8ef8d10c14782930a08f681fc31de2a Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 5 May 2026 21:25:22 +0200 Subject: [PATCH 08/12] Revert "bring back num_traits::AsPrimitive" This reverts commit e49fe2b6a8f7638e568e4392a1b88df8e5ff69da. --- Cargo.toml | 3 --- src/pixel.rs | 8 ++------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 051987a..cca2af2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,9 +12,6 @@ include = ["Cargo.toml", "README.md", "LICENSE", "src"] [features] padding_api = [] -[dependencies.num-traits] -version = "0.2" - [dev-dependencies.criterion] version = "0.8" default-features = false diff --git a/src/pixel.rs b/src/pixel.rs index a142b9d..2699a19 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -24,8 +24,6 @@ use std::fmt::Debug; -use num_traits::AsPrimitive; - mod private { pub trait Sealed {} @@ -68,15 +66,13 @@ pub unsafe trait Pixel: + Into + Into + Into + + TryInto + Into + Into + Into + Into + From - + AsPrimitive - + AsPrimitive - + AsPrimitive - + AsPrimitive + + TryFrom + 'static + private::Sealed { From 72f7755faddf4847459cb1297f79b28e1e70c2b6 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 5 May 2026 21:25:47 +0200 Subject: [PATCH 09/12] add Pixel::from_u16 --- src/pixel.rs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/pixel.rs b/src/pixel.rs index 2699a19..570de46 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -61,7 +61,6 @@ pub unsafe trait Pixel: + Default + Send + Sync - + TryInto + Into + Into + Into @@ -76,12 +75,25 @@ pub unsafe trait Pixel: + 'static + private::Sealed { + /// Conversion from `u16` to a pixel. + /// + /// If the pixel is a u8 (single byte), the conversion will be lossy. + /// Use [`From::::from`][From::from] instead if you want to create a pixel from an u8. + fn from_u16(value: u16) -> Self; } /// Pixel implementation for 8-bit video data. // SAFETY: u8 is valid if represented by a zeroed byte. -unsafe impl Pixel for u8 {} +unsafe impl Pixel for u8 { + fn from_u16(value: u16) -> Self { + value as Self + } +} /// Pixel implementation for high bit-depth (9-16 bit) video data. // SAFETY: u16 is valid if represented by zeroed bytes. -unsafe impl Pixel for u16 {} +unsafe impl Pixel for u16 { + fn from_u16(value: u16) -> Self { + value + } +} From 4e7796db31c71c2506ea928e50ce1081ac627f53 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 6 May 2026 12:24:42 +0200 Subject: [PATCH 10/12] Add unsafe PixelExt --- src/lib.rs | 1 + src/pixel.rs | 5 ++--- src/pixel_ext.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 3 deletions(-) create mode 100644 src/pixel_ext.rs diff --git a/src/lib.rs b/src/lib.rs index 5e26476..8ae738e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,4 +36,5 @@ pub mod chroma; pub mod frame; pub mod pixel; +pub mod pixel_ext; pub mod plane; diff --git a/src/pixel.rs b/src/pixel.rs index 570de46..7a2847f 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -55,7 +55,8 @@ mod private { /// i.e. using [`std::mem::zeroed`] must __not__ cause undefined behavior for /// implementing types. pub unsafe trait Pixel: - Debug + Sized + + Debug + Copy + Clone + Default @@ -65,13 +66,11 @@ pub unsafe trait Pixel: + Into + Into + Into - + TryInto + Into + Into + Into + Into + From - + TryFrom + 'static + private::Sealed { diff --git a/src/pixel_ext.rs b/src/pixel_ext.rs new file mode 100644 index 0000000..386144e --- /dev/null +++ b/src/pixel_ext.rs @@ -0,0 +1,35 @@ +//! Extensions for the [`Pixel`] trait. +//! +//! Currently only the [`PixelExt`] trait is provided, but more might be +//! added in the future. + +use crate::pixel::Pixel; + +/// Implements additional traits on `Pixel` types for performance-sensitive applications. +pub trait PixelExt { + /// Assume the reference is a reference to a `u8`. + /// + /// # Safety + /// + /// Callers must ensure that the implementing type is a `u8`, for example by + /// checking the size of the type (via `size_of::() == 1`). + unsafe fn assume_u8(&self) -> &u8; + + /// Assume the reference is a reference to a `u16`. + /// + /// # Safety + /// + /// Callers must ensure that the implementing type is a `u16`, for example by + /// checking the size of the type (via `size_of::() == 2`). + unsafe fn assume_u16(&self) -> &u16; +} + +impl PixelExt for T { + unsafe fn assume_u8(&self) -> &u8 { + unsafe { std::ptr::from_ref(self).cast::().as_ref_unchecked() } + } + + unsafe fn assume_u16(&self) -> &u16 { + unsafe { std::ptr::from_ref(self).cast::().as_ref_unchecked() } + } +} From 9cc83db089a72a69845eff8ba3e486a72fda381a Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 6 May 2026 12:27:01 +0200 Subject: [PATCH 11/12] does downstream detect there's only one `into`? --- src/pixel.rs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/pixel.rs b/src/pixel.rs index 7a2847f..054ca11 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -63,13 +63,6 @@ pub unsafe trait Pixel: + Send + Sync + Into - + Into - + Into - + Into - + Into - + Into - + Into - + Into + From + 'static + private::Sealed From 91f7ddb37955eccf39c9bb1b1b8e2ee84d69f004 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 6 May 2026 19:03:24 +0200 Subject: [PATCH 12/12] add error bound to TryFrom..? --- src/pixel.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/pixel.rs b/src/pixel.rs index 054ca11..51700ce 100644 --- a/src/pixel.rs +++ b/src/pixel.rs @@ -64,6 +64,7 @@ pub unsafe trait Pixel: + Sync + Into + From + + TryFrom + 'static + private::Sealed {