Skip to content

Commit

Permalink
Get rid of declarative macros
Browse files Browse the repository at this point in the history
Instead, make use of const generic parameters. This comes with the
nice benefit of always knowing exactly which pixel type is being
used at compile time. The same goes for better error messages.

Using pure Rust code instead of macros should make for more readable
code hopefully and encourage contributors.

Signed-off-by: Christopher N. Hesse <[email protected]>
  • Loading branch information
raymanfx committed Oct 29, 2023
1 parent f95356b commit 38ffd83
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 400 deletions.
148 changes: 94 additions & 54 deletions ffimage-yuv/src/yuv.rs
Original file line number Diff line number Diff line change
@@ -1,67 +1,107 @@
use core::{
cmp::Ord,
ops::{Deref, DerefMut},
};

use num_traits::{AsPrimitive, FromPrimitive};

use ffimage::color::bgr::*;
use ffimage::color::rgb::*;
use ffimage::traits::Pixel;
use ffimage::{create_pixel, define_pixel, impl_Pixel};

macro_rules! impl_from_rgb_to_yuv {
($src:ident, $dst:ident, $r:expr, $g:expr, $b:expr) => {
impl<I: AsPrimitive<i32>, O: FromPrimitive> From<$src<I>> for $dst<O> {
fn from(pix: $src<I>) -> Self {
let r = pix[$r].as_();
let g = pix[$g].as_();
let b = pix[$b].as_();

let y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
let u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
let v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;

let y = O::from_i32(y).unwrap();
let u = O::from_i32(u).unwrap();
let v = O::from_i32(v).unwrap();
$dst { 0: [y, u, v] }
}
}
};

/// YUV pixel
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Yuv<T, const Y: usize = 0, const U: usize = 1, const V: usize = 2>(pub [T; 3]);

impl<T, const Y: usize, const U: usize, const V: usize> Deref for Yuv<T, Y, U, V> {
type Target = [T; 3];

fn deref(&self) -> &Self::Target {
&self.0
}
}

macro_rules! impl_from_yuv_to_rgb {
($src:ident, $dst:ident, $r:expr, $g:expr, $b:expr) => {
impl<I: AsPrimitive<i32>, O: Copy + FromPrimitive> From<$src<I>> for $dst<O> {
fn from(pix: $src<I>) -> Self {
let y = pix[0].as_();
let u = pix[1].as_();
let v = pix[2].as_();
let c = y - 16;
let d = u - 128;
let e = v - 128;

let r = num_traits::clamp((298 * c + 409 * e + 128) >> 8, 0, 255);
let g = num_traits::clamp((298 * c - 100 * d - 208 * e + 128) >> 8, 0, 255);
let b = num_traits::clamp((298 * c + 516 * d + 128) >> 8, 0, 255);

let r = O::from_i32(r).unwrap();
let g = O::from_i32(g).unwrap();
let b = O::from_i32(b).unwrap();

let mut result = $dst { 0: [r, g, b] };
result[$r] = r;
result[$g] = g;
result[$b] = b;
result
}
}
};
impl<T, const Y: usize, const U: usize, const V: usize> DerefMut for Yuv<T, Y, U, V> {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

create_pixel!(Yuv, 3, #[doc = "YUV pixel"]);
impl<T, const Y: usize, const U: usize, const V: usize> Pixel for Yuv<T, Y, U, V>
where
T: Copy,
{
type T = T;

fn channels() -> u8 {
3
}

impl_from_rgb_to_yuv!(Bgr, Yuv, 2, 1, 0);
impl_from_rgb_to_yuv!(Rgb, Yuv, 0, 1, 2);
fn subpixels() -> u8 {
1
}
}

impl_from_yuv_to_rgb!(Yuv, Bgr, 2, 1, 0);
impl_from_yuv_to_rgb!(Yuv, Rgb, 0, 1, 2);
impl<
T,
const Y: usize,
const U: usize,
const V: usize,
const R: usize,
const G: usize,
const B: usize,
> From<Rgb<T, R, G, B>> for Yuv<T, Y, U, V>
where
T: Copy + Default + AsPrimitive<i32> + FromPrimitive,
{
fn from(rgb: Rgb<T, R, G, B>) -> Self {
let r = rgb[R].as_();
let g = rgb[G].as_();
let b = rgb[B].as_();

let y = ((66 * r + 129 * g + 25 * b + 128) >> 8) + 16;
let u = ((-38 * r - 74 * g + 112 * b + 128) >> 8) + 128;
let v = ((112 * r - 94 * g - 18 * b + 128) >> 8) + 128;

let mut yuv = Yuv::<T, Y, U, V>::default();
yuv[Y] = T::from_i32(y).unwrap();
yuv[U] = T::from_i32(u).unwrap();
yuv[V] = T::from_i32(v).unwrap();
yuv
}
}

impl<
T,
const R: usize,
const G: usize,
const B: usize,
const Y: usize,
const U: usize,
const V: usize,
> From<Yuv<T, Y, U, V>> for Rgb<T, R, G, B>
where
T: Copy + Default + AsPrimitive<i32> + FromPrimitive,
{
fn from(yuv: Yuv<T, Y, U, V>) -> Self {
let y = yuv[Y].as_();
let u = yuv[U].as_();
let v = yuv[V].as_();
let c = y - 16;
let d = u - 128;
let e = v - 128;

let r = ((298 * c + 409 * e + 128) >> 8).min(255).max(0);
let g = ((298 * c - 100 * d - 208 * e + 128) >> 8).min(255).max(0);
let b = ((298 * c + 516 * d + 128) >> 8).min(255).max(0);

let mut rgb = Rgb::<T, R, G, B>::default();
rgb[R] = T::from_i32(r).unwrap();
rgb[G] = T::from_i32(g).unwrap();
rgb[B] = T::from_i32(b).unwrap();
rgb
}
}

#[cfg(test)]
mod tests {
Expand Down
63 changes: 25 additions & 38 deletions ffimage-yuv/src/yuv422.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
use core::ops::{Deref, DerefMut};

use itertools::Itertools;
use num_traits::{AsPrimitive, FromPrimitive};

use ffimage::color::{Bgr, Rgb};
use ffimage::color::Rgb;
use ffimage::convert::MapPixels;
use ffimage::traits::Pixel;

Expand All @@ -13,26 +15,29 @@ pub type Uyvy<T> = Yuv422<T, 1, 3, 0, 2>;

#[repr(C)]
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq)]
pub struct Yuv422<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize>(pub [T; 4]);

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> Yuv422<T, Y0, Y1, U, V> {
/// Returns a new pixel
///
/// # Arguments
///
/// * `channels` - Channel values
pub fn new(channels: [T; 4]) -> Self {
Yuv422 { 0: channels }
pub struct Yuv422<
T,
const Y0: usize = 0,
const Y1: usize = 1,
const U: usize = 2,
const V: usize = 3,
>(pub [T; 4]);

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> Deref
for Yuv422<T, Y0, Y1, U, V>
{
type Target = [T; 4];

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> From<[T; 4]>
impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> DerefMut
for Yuv422<T, Y0, Y1, U, V>
where
T: Copy,
{
fn from(array: [T; 4]) -> Self {
Yuv422 { 0: array }
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}

Expand All @@ -52,24 +57,6 @@ where
}
}

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> core::ops::Index<usize>
for Yuv422<T, Y0, Y1, U, V>
{
type Output = T;

fn index(&self, i: usize) -> &Self::Output {
&self.0[i]
}
}

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize> core::ops::IndexMut<usize>
for Yuv422<T, Y0, Y1, U, V>
{
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
&mut self.0[i]
}
}

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize>
From<Yuv422<T, Y0, Y1, U, V>> for [Yuv<T>; 2]
where
Expand Down Expand Up @@ -168,23 +155,23 @@ where
}

impl<T, const Y0: usize, const Y1: usize, const U: usize, const V: usize>
MapPixels<Yuv422<T, Y0, Y1, U, V>, Bgr<T>> for Yuv422<T, Y0, Y1, U, V>
MapPixels<Yuv422<T, Y0, Y1, U, V>, Rgb<T, 2, 1, 0>> for Yuv422<T, Y0, Y1, U, V>
where
T: Copy + Default + AsPrimitive<i32> + FromPrimitive,
{
fn map_pixels<'a, I, O>(input: I, output: O)
where
I: IntoIterator<Item = &'a Yuv422<T, Y0, Y1, U, V>>,
O: IntoIterator<Item = &'a mut Bgr<T>>,
O: IntoIterator<Item = &'a mut Rgb<T, 2, 1, 0>>,
T: 'a,
{
input
.into_iter()
.zip(output.into_iter().tuples())
.for_each(|(t, (u1, u2))| {
let yuv = <[Yuv<T>; 2]>::from(*t);
*u1 = Bgr::<T>::from(yuv[0]);
*u2 = Bgr::<T>::from(yuv[1]);
*u1 = Rgb::<T, 2, 1, 0>::from(yuv[0]);
*u2 = Rgb::<T, 2, 1, 0>::from(yuv[1]);
})
}
}
Expand Down
2 changes: 1 addition & 1 deletion ffimage/benches/packed/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ pub fn rgb_to_bgr(c: &mut Criterion) {

for res in resolutions {
let rgb = Image::<Rgb<u8>, Vec<u8>>::new(res.0, res.1, 0u8);
let mut bgr = Image::<Bgr<u8>, _>::new(res.0, res.1, 0u8);
let mut bgr = Image::<Rgb<u8, 2, 1, 0>, _>::new(res.0, res.1, 0u8);
c.bench_function(&format!("Rgb[u8] -> Bgr[u8] ({}x{})", res.0, res.1), |b| {
b.iter(|| rgb.convert(black_box(&mut bgr)))
});
Expand Down
70 changes: 0 additions & 70 deletions ffimage/src/color/bgr.rs

This file was deleted.

Loading

0 comments on commit 38ffd83

Please sign in to comment.