Skip to content

Commit

Permalink
Merge pull request #9 from TooManyBees/avx
Browse files Browse the repository at this point in the history
Add experimental SIMD functions
  • Loading branch information
TooManyBees authored Mar 6, 2019
2 parents f0328f1 + 367cb6f commit b09d8c5
Show file tree
Hide file tree
Showing 8 changed files with 775 additions and 29 deletions.
16 changes: 15 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,19 @@ keywords = ["lab", "color", "pixel", "rgb"]
categories = ["multimedia::images", "multimedia::video"]
license = "MIT"

[lib]
bench = false

[dev-dependencies]
rand = "0.5" # used for benchmark tests
rand = "0.5"
criterion = { version = "0.2", default-features = false }
lazy_static = "*"
pretty_assertions = "0.5"

[[bench]]
name = "rgb_to_lab"
harness = false

[[bench]]
name = "lab_to_rgb"
harness = false
24 changes: 0 additions & 24 deletions benches/bench.rs

This file was deleted.

34 changes: 34 additions & 0 deletions benches/lab_to_rgb.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#[macro_use]
extern crate criterion;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate lab;

use criterion::Criterion;
use rand::Rng;
use rand::distributions::Standard;

lazy_static! {
static ref LABS: Vec<lab::Lab> = {
let rand_seed = [0u8;32];
let mut rng: rand::StdRng = rand::SeedableRng::from_seed(rand_seed);
let labs: Vec<[f32; 8]> = rng.sample_iter(&Standard).take(512).collect();
labs.iter().map(|lab| lab::Lab { l: lab[0], a: lab[1], b: lab[2] }).collect()
};
}

fn labs_to_rgbs(c: &mut Criterion) {
c.bench_function("labs_to_rgbs", move |b| {
b.iter(|| lab::labs_to_rgbs(&LABS))
});
}

fn labs_to_rgbs_simd(c: &mut Criterion) {
c.bench_function("labs_to_rgbs_simd", move |b| {
b.iter(|| unsafe { lab::simd::labs_to_rgbs(&LABS) })
});
}

criterion_group!(benches, labs_to_rgbs, labs_to_rgbs_simd);
criterion_main!(benches);
40 changes: 40 additions & 0 deletions benches/rgb_to_lab.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#[macro_use]
extern crate criterion;
#[macro_use]
extern crate lazy_static;
extern crate rand;
extern crate lab;

use criterion::Criterion;
use rand::Rng;
use rand::distributions::Standard;

lazy_static! {
static ref RGBS: Vec<[u8;3]> = {
let rand_seed = [0u8;32];
let mut rng: rand::StdRng = rand::SeedableRng::from_seed(rand_seed);
rng.sample_iter(&Standard).take(512).collect()
};
}

// fn rgb_to_lab(c: &mut Criterion) {
// let rgb = RGBS[0];
// c.bench_function("rgb_to_lab", move |b| {
// b.iter(|| lab::Lab::from_rgb(&rgb))
// });
// }

fn rgbs_to_labs(c: &mut Criterion) {
c.bench_function("rgbs_to_labs", move |b| {
b.iter(|| lab::rgbs_to_labs(&RGBS))
});
}

fn rgbs_to_labs_simd(c: &mut Criterion) {
c.bench_function("rgbs_to_labs_simd", move |b| {
b.iter(|| unsafe { lab::simd::rgbs_to_labs(&RGBS) })
});
}

criterion_group!(benches, rgbs_to_labs, rgbs_to_labs_simd);
criterion_main!(benches);
130 changes: 126 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,57 @@
//! # Lab
//!
//! Tools for converting RGB colors to L\*a\*b\* measurements.
//!
//! RGB colors, for this crate at least, are considered to be an array
//! of `u8` values (`[u8; 3]`), while L\*a\*b\* colors are represented
//! by its own struct that uses `f32` values.
//!
//! # Usage
//! ## Converting single values
//! To convert a single value, use one of the functions
//! * `lab::Lab::from_rgb(rgb: &[u8; 3]) -> Lab`
//! * `lab::Lab::from_rgba(rgba: &[u8; 4]) -> Lab` (drops the fourth alpha byte)
//! * `lab::Lab::to_rgb(&self) -> [u8; 3]`
//!
//! ## Converting multiple values
//! To convert slices of values
//! * `lab::rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab>`
//! * `lab::labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]>`
//! To convert slices using SIMD (AVX, SSE 4.1) operations
//! * `lab::simd::rgbs_to_labs`
//! * `lab::simd::labs_to_rgbs`
//!
//! ## Parallelization concerns
//! This crate makes no assumptions about how to parallelize work, so the above
//! functions that convert slices do so in serial. Presently, parallelizing the
//! functions that accept slices is a manual job of reimplementing
//! them using their fundamental work function, and replacing one iterator method
//! with its equivalent from Rayon.
//!
//! `lab::rgbs_to_labs` and `lab::labs_to_rgbs` are convenience functions for
//! `rgbs.iter().map(Lab::from_rgb).collect()`, which can easily be parallelized
//! with Rayon by replacing `iter()` with `par_iter()`.
//!
//! For the SIMD based functions, their smallest unit of work is done by the
//! functions `lab::simd::rgbs_to_labs_chunk` and `lab::simd::labs_to_rgbs_chunk`
//! which both accept exactly 8 elements. See their respective docs for examples
//! on where to add Rayon methods.
#![doc(html_root_url = "https://docs.rs/lab/0.6.0")]

#[cfg(test)]
#[macro_use]
extern crate pretty_assertions;
#[cfg(test)]
#[macro_use]
extern crate lazy_static;
#[cfg(test)]
extern crate rand;

#[cfg(target_arch = "x86_64")]
pub mod simd;

/// Struct representing a color in L\*a\*b\* space
#[derive(Debug, PartialEq, Copy, Clone, Default)]
pub struct Lab {
pub l: f32,
Expand All @@ -14,9 +62,9 @@ pub struct Lab {
// κ and ε parameters used in conversion between XYZ and La*b*. See
// http://www.brucelindbloom.com/LContinuity.html for explanation as to why
// those are different values than those provided by CIE standard.
const KAPPA: f32 = 24389.0 / 27.0;
const EPSILON: f32 = 216.0 / 24389.0;
const CBRT_EPSILON: f32 = 0.20689655172413796;
pub(crate) const KAPPA: f32 = 24389.0 / 27.0;
pub(crate) const EPSILON: f32 = 216.0 / 24389.0;
pub(crate) const CBRT_EPSILON: f32 = 0.20689655172413796;

fn rgb_to_xyz(rgb: &[u8; 3]) -> [f32; 3] {
let r = rgb_to_xyz_map(rgb[0]);
Expand Down Expand Up @@ -115,7 +163,66 @@ fn xyz_to_rgb_map(c: f32) -> u8 {
}) * 255.0).round().min(255.0).max(0.0) as u8
}

/// Convenience function to map a slice of RGB values to Lab values in serial
///
/// # Example
/// ```
/// # extern crate lab;
/// # use lab::{Lab, rgbs_to_labs};
/// let rgbs = &[[0u8, 127, 127], [127, 0, 127], [255, 0, 0]];
/// let labs = lab::rgbs_to_labs(rgbs);
/// assert_eq!(labs, vec![
/// Lab { l: 47.8919, a: -28.683678, b: -8.42911 },
/// Lab { l: 29.52658, a: 58.595745, b: -36.281406 },
/// Lab { l: 53.240784, a: 80.09252, b: 67.203186 }
/// ]);
/// ```
#[inline]
pub fn rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab> {
rgbs.iter().map(Lab::from_rgb).collect()
}

/// Convenience function to map a slice of Lab values to RGB values in serial
///
/// # Example
/// ```
/// # extern crate lab;
/// # use lab::{Lab, labs_to_rgbs};
/// let labs = &[
/// Lab { l: 91.11321, a: -48.08751, b: -14.131201 },
/// Lab { l: 60.32421, a: 98.23433, b: -60.824894 },
/// Lab { l: 97.13926, a: -21.553724, b: 94.47797 },
/// ];
/// let rgbs = lab::labs_to_rgbs(labs);
/// assert_eq!(rgbs, vec![[0u8, 255, 255], [255, 0, 255], [255, 255, 0]]);
/// ```
#[inline]
pub fn labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]> {
labs.iter().map(Lab::to_rgb).collect()
}

impl Lab {

// pub fn from_rgbs(rgbs: &[[u8; 3]]) -> Vec<Self> {
// #[cfg(target_arch = "x86_64")]
// {
// if is_x86_feature_detected!("avx") && is_x86_feature_detected!("sse4.1") {
// return simd::rgbs_to_labs(rgbs);
// }
// }
// rgbs_to_labs(rgbs)
// }

// pub fn to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]> {
// #[cfg(target_arch = "x86_64")]
// {
// if is_x86_feature_detected!("avx") && is_x86_feature_detected!("sse4.1") {
// return simd::labs_to_rgbs(labs);
// }
// }
// labs_to_rgbs(labs)
// }

/// Constructs a new `Lab` from a three-element array of `u8`s
///
/// # Examples
Expand Down Expand Up @@ -177,7 +284,10 @@ impl Lab {

#[cfg(test)]
mod tests {
use super::Lab;
use rand;
use rand::Rng;
use rand::distributions::Standard;
use super::{Lab, rgbs_to_labs, labs_to_rgbs};

const PINK: Lab = Lab { l: 66.639084, a: 52.251457, b: 14.860654 };

Expand Down Expand Up @@ -236,4 +346,16 @@ mod tests {
fn assert_sync<T: Sync>() {}
assert_sync::<Lab>();
}

#[test]
fn test_rgb_to_lab_to_rgb() {
let rgbs: Vec<[u8; 3]> = {
let rand_seed = [1u8;32];
let mut rng: rand::StdRng = rand::SeedableRng::from_seed(rand_seed);
rng.sample_iter(&Standard).take(2048).collect()
};
let labs = rgbs_to_labs(&rgbs);
let rgbs2 = labs_to_rgbs(&labs);
assert_eq!(rgbs2, rgbs);
}
}
Loading

0 comments on commit b09d8c5

Please sign in to comment.