Skip to content

Commit

Permalink
Auto merge of #18 - sidred:generic_float, r=Ogeon
Browse files Browse the repository at this point in the history
Convert all colors to be generic over floats, f32 and f64

**Convert all colors to be generic over floats**

Change all colors (Color, Rgb, Hsl, Hsv, Lab, Xyz) and traits (Mix and Saturation etc) to be generic over floats and closes #13.

**_This is a breaking change. Type f32 or f64 must be explicitly annotated._**

```
// OLD
let new_color: Rgb = lch_color.shift_hue(180.0.into()).into();

// NEW
let new_color: Rgb<f32> = lch_color.shift_hue(180.0.into()).into();
```

Since Rust defaults to f64, most of the color types will default to f64. For example Rgb::linear_rgb(1.0, 0.0, 0.0) will be inferred as Rgb\<f64\>. To use f32, constants must be explicitly set like rgb::linear_rgb(1.0_f32, 0.0, 0.0) or the type explicitly annotated.

T::from( constant ).unwrap() etc is used in a number of places to convert constants to num::Float trait ( via the num::Numcast trait).

For Hues, the Into<T> trait is not generic over floats due to unwrap() and possibility of runtime panic and is separately implemented for f32 and f64 for Hue\<32\> and Hue\<64\>. Also the Partial Eq trait could not be implemented for Hues as float and was removed.

The RgbPixel could not be made generic for [T;3] and (T,T,T) and is separately implemented for f32, f64 and u8.

Using associated typed for Mix and Saturation is not ergonomic and the trait was made generic over float.
  • Loading branch information
homu committed Jan 23, 2016
2 parents 7d74089 + 5ae93ac commit 398bde6
Show file tree
Hide file tree
Showing 14 changed files with 958 additions and 835 deletions.
3 changes: 3 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ license = "MIT OR Apache-2.0"
[features]
strict = []

[dependencies]
num = "0.1"

[dev-dependencies]
image = "0.4"
approx = "0.1"
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ extern crate palette;
use palette::{Rgb, Lch, Hue};

let lch_color = Lch::from(Rgb::srgb(0.8, 0.2, 0.1));
let new_color: Rgb = lch_color.shift_hue(180.0.into()).into();
let new_color: Rgb<f32> = lch_color.shift_hue(180.0.into()).into();
```

This results in the following two colors:
Expand Down
16 changes: 9 additions & 7 deletions examples/readme_examples.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
extern crate image;
extern crate palette;
extern crate num;

use image::{RgbImage, GenericImage};
use num::traits::Float;

use palette::{Rgb, Gradient, Mix};

Expand All @@ -11,7 +13,7 @@ mod color_spaces {

pub fn run() {
let lch_color = Lch::from(Rgb::srgb(0.8, 0.2, 0.1));
let new_color: Rgb = lch_color.shift_hue(180.0.into()).into();
let new_color: Rgb<f32> = lch_color.shift_hue(180.0.into()).into();

display_colors("examples/readme_color_spaces.png", &[Rgb::srgb(0.8, 0.2, 0.1).to_srgb(), new_color.to_srgb()]);
}
Expand Down Expand Up @@ -67,18 +69,18 @@ fn display_colors(filename: &str, colors: &[[u8; 3]]) {
}
}

fn display_gradients<A: Mix + Clone, B: Mix + Clone>(filename: &str, grad1: Gradient<A>, grad2: Gradient<B>) where
Rgb: From<A>,
Rgb: From<B>
fn display_gradients<T: Float, A: Mix<T> + Clone, B: Mix<T> + Clone>(filename: &str, grad1: Gradient<T, A>, grad2: Gradient<T, B>)
where Rgb<T>: From<A>,
Rgb<T>: From<B>
{
let mut image = RgbImage::new(256, 64);

for (x, _, pixel) in image.sub_image(0, 0, 256, 32).pixels_mut() {
pixel.data = Rgb::from(grad1.get(x as f32 / 255.0)).to_srgb();
pixel.data = Rgb::from(grad1.get(T::from(x).unwrap() / T::from(255.0).unwrap())).to_srgb();
}

for (x, _, pixel) in image.sub_image(0, 32, 256, 32).pixels_mut() {
pixel.data = Rgb::from(grad2.get(x as f32 / 255.0)).to_srgb();
pixel.data = Rgb::from(grad2.get(T::from(x).unwrap() / T::from(255.0).unwrap())).to_srgb();
}

match image.save(filename) {
Expand All @@ -91,4 +93,4 @@ fn main() {
color_spaces::run();
manipulation::run();
gradients::run();
}
}
99 changes: 50 additions & 49 deletions src/gradient.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//!Types for interpolation between multiple colors.
use num::traits::Float;
use std::cmp::max;

use Mix;
Expand All @@ -13,18 +14,18 @@ use Mix;
///the domain of the gradient will have the same color as the closest control
///point.
#[derive(Clone, Debug)]
pub struct Gradient<C: Mix + Clone>(Vec<(f32, C)>);
pub struct Gradient<T: Float, C: Mix<T> + Clone>(Vec<(T, C)>);

impl<C: Mix + Clone> Gradient<C> {
impl<T: Float, C: Mix<T> + Clone> Gradient<T, C> {
///Create a gradient of evenly spaced colors with the domain [0.0, 1.0].
///There must be at least one color.
pub fn new<I: IntoIterator<Item=C>>(colors: I) -> Gradient<C> {
let mut points: Vec<_> = colors.into_iter().map(|c| (0.0, c)).collect();
pub fn new<I: IntoIterator<Item = C>>(colors: I) -> Gradient<T, C> {
let mut points: Vec<_> = colors.into_iter().map(|c| (T::zero(), c)).collect();
assert!(points.len() > 0);
let step_size = 1.0 / max(points.len() - 1, 1) as f32;
let step_size = T::one() / T::from(max(points.len() - 1, 1) as f64).unwrap();

for (i, &mut (ref mut p, _)) in points.iter_mut().enumerate() {
*p = i as f32 * step_size;
*p = T::from(i).unwrap() * step_size;
}

Gradient(points)
Expand All @@ -33,7 +34,7 @@ impl<C: Mix + Clone> Gradient<C> {
///Create a gradient of colors with custom spacing and domain. There must be
///at least one color and they are expected to be ordered by their
///position value.
pub fn with_domain(colors: Vec<(f32, C)>) -> Gradient<C> {
pub fn with_domain(colors: Vec<(T, C)>) -> Gradient<T, C> {
assert!(colors.len() > 0);

//Maybe sort the colors?
Expand All @@ -42,7 +43,7 @@ impl<C: Mix + Clone> Gradient<C> {

///Get a color from the gradient. The color of the closest control point
///will be returned if `i` is outside the domain.
pub fn get(&self, i: f32) -> C {
pub fn get(&self, i: T) -> C {
let &(mut min, ref min_color) = self.0.get(0).expect("a Gradient must contain at least one color");
let mut min_color = min_color;
let mut min_index = 0;
Expand Down Expand Up @@ -81,7 +82,7 @@ impl<C: Mix + Clone> Gradient<C> {
}

///Take `n` evenly spaced colors from the gradient, as an iterator.
pub fn take(&self, n: usize) -> Take<C> {
pub fn take(&self, n: usize) -> Take<T, C> {
let (min, max) = self.domain();

Take {
Expand All @@ -94,36 +95,36 @@ impl<C: Mix + Clone> Gradient<C> {
}

///Slice this gradient to limit its domain.
pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
pub fn slice<R: Into<Range<T>>>(&self, range: R) -> Slice<T, C> {
Slice {
gradient: self,
range: range.into(),
}
}

///Get the limits of this gradient's domain.
pub fn domain(&self) -> (f32, f32) {
pub fn domain(&self) -> (T, T) {
let &(min, _) = self.0.get(0).expect("a Gradient must contain at least one color");
let &(max, _) = self.0.last().expect("a Gradient must contain at least one color");
(min, max)
}
}

///An iterator over interpolated colors.
pub struct Take<'a, C: Mix + Clone + 'a> {
gradient: MaybeSlice<'a, C>,
from: f32,
diff: f32,
pub struct Take<'a, T: Float + 'a, C: Mix<T> + Clone + 'a> {
gradient: MaybeSlice<'a, T, C>,
from: T,
diff: T,
len: usize,
current: usize,
}

impl<'a, C: Mix + Clone> Iterator for Take<'a, C> {
impl<'a, T: Float, C: Mix<T> + Clone> Iterator for Take<'a, T, C> {
type Item = C;

fn next(&mut self) -> Option<C> {
if self.current < self.len {
let i = self.from + self.current as f32 * (self.diff / self.len as f32);
let i = self.from + T::from(self.current).unwrap() * (self.diff / T::from(self.len).unwrap());
self.current += 1;
Some(self.gradient.get(i))
} else {
Expand All @@ -136,25 +137,25 @@ impl<'a, C: Mix + Clone> Iterator for Take<'a, C> {
}
}

impl<'a, C: Mix + Clone> ExactSizeIterator for Take<'a, C> { }
impl<'a, T: Float, C: Mix<T> + Clone> ExactSizeIterator for Take<'a, T, C> {}


///A slice of a Gradient that limits its domain.
#[derive(Clone, Debug)]
pub struct Slice<'a, C: Mix + Clone + 'a> {
gradient: &'a Gradient<C>,
range: Range,
pub struct Slice<'a, T: Float + 'a, C: Mix<T> + Clone + 'a> {
gradient: &'a Gradient<T, C>,
range: Range<T>,
}

impl<'a, C: Mix + Clone> Slice<'a, C> {
impl<'a, T: Float, C: Mix<T> + Clone> Slice<'a, T, C> {
///Get a color from the gradient slice. The color of the closest domain
///limit will be returned if `i` is outside the domain.
pub fn get(&self, i: f32) -> C {
pub fn get(&self, i: T) -> C {
self.gradient.get(self.range.clamp(i))
}

///Take `n` evenly spaced colors from the gradient slice, as an iterator.
pub fn take(&self, n: usize) -> Take<C> {
pub fn take(&self, n: usize) -> Take<T, C> {
let (min, max) = self.domain();

Take {
Expand All @@ -168,15 +169,15 @@ impl<'a, C: Mix + Clone> Slice<'a, C> {

///Slice this gradient slice to further limit its domain. Ranges outside
///the domain will be clamped to the nearest domain limit.
pub fn slice<R: Into<Range>>(&self, range: R) -> Slice<C> {
pub fn slice<R: Into<Range<T>>>(&self, range: R) -> Slice<T, C> {
Slice {
gradient: self.gradient,
range: self.range.constrain(&range.into())
}
}

///Get the limits of this gradient slice's domain.
pub fn domain(&self) -> (f32, f32) {
pub fn domain(&self) -> (T, T) {
if let Range { from: Some(from), to: Some(to) } = self.range {
(from, to)
} else {
Expand All @@ -188,18 +189,18 @@ impl<'a, C: Mix + Clone> Slice<'a, C> {

///A domain range for gradient slices.
#[derive(Clone, Debug, PartialEq)]
pub struct Range {
from: Option<f32>,
to: Option<f32>,
pub struct Range<T: Float> {
from: Option<T>,
to: Option<T>,
}

impl Range {
fn clamp(&self, mut x: f32) -> f32 {
impl<T: Float> Range<T> {
fn clamp(&self, mut x: T) -> T {
x = self.from.unwrap_or(x).max(x);
self.to.unwrap_or(x).min(x)
}

fn constrain(&self, other: &Range) -> Range {
fn constrain(&self, other: &Range<T>) -> Range<T> {
if let (Some(f), Some(t)) = (other.from, self.to) {
if f >= t {
return Range {
Expand All @@ -209,7 +210,7 @@ impl Range {
}
}


if let (Some(t), Some(f)) = (other.to, self.from) {
if t <= f {
return Range {
Expand All @@ -218,7 +219,7 @@ impl Range {
};
}
}

Range {
from: match (self.from, other.from) {
(Some(s), Some(o)) => Some(s.max(o)),
Expand All @@ -236,49 +237,49 @@ impl Range {
}
}

impl From<::std::ops::Range<f32>> for Range {
fn from(range: ::std::ops::Range<f32>) -> Range {
impl<T: Float> From<::std::ops::Range<T>> for Range<T> {
fn from(range: ::std::ops::Range<T>) -> Range<T> {
Range {
from: Some(range.start),
to: Some(range.end),
}
}
}

impl From<::std::ops::RangeFrom<f32>> for Range {
fn from(range: ::std::ops::RangeFrom<f32>) -> Range {
impl<T: Float> From<::std::ops::RangeFrom<T>> for Range<T> {
fn from(range: ::std::ops::RangeFrom<T>) -> Range<T> {
Range {
from: Some(range.start),
to: None,
}
}
}

impl From<::std::ops::RangeTo<f32>> for Range {
fn from(range: ::std::ops::RangeTo<f32>) -> Range {
impl<T: Float> From<::std::ops::RangeTo<T>> for Range<T> {
fn from(range: ::std::ops::RangeTo<T>) -> Range<T> {
Range {
from: None,
to: Some(range.end),
}
}
}

impl From<::std::ops::RangeFull> for Range {
fn from(_range: ::std::ops::RangeFull) -> Range {
impl<T: Float> From<::std::ops::RangeFull> for Range<T> {
fn from(_range: ::std::ops::RangeFull) -> Range<T> {
Range {
from: None,
to: None,
}
}
}

enum MaybeSlice<'a, C: Mix + Clone + 'a> {
NotSlice(&'a Gradient<C>),
Slice(Slice<'a, C>),
enum MaybeSlice<'a, T: Float + 'a, C: Mix<T> + Clone + 'a> {
NotSlice(&'a Gradient<T, C>),
Slice(Slice<'a, T, C>),
}

impl<'a, C: Mix + Clone> MaybeSlice<'a, C> {
fn get(&self, i: f32) -> C {
impl<'a, T: Float, C: Mix<T> + Clone> MaybeSlice<'a, T, C> {
fn get(&self, i: T) -> C {
match *self {
MaybeSlice::NotSlice(g) => g.get(i),
MaybeSlice::Slice(ref s) => s.get(i),
Expand All @@ -293,15 +294,15 @@ mod test {

#[test]
fn range_clamp() {
let range: Range = (0.0..1.0).into();
let range: Range<f64> = (0.0..1.0).into();
assert_eq!(range.clamp(-1.0), 0.0);
assert_eq!(range.clamp(2.0), 1.0);
assert_eq!(range.clamp(0.5), 0.5);
}

#[test]
fn range_constrain() {
let range: Range = (0.0..1.0).into();
let range: Range<f64> = (0.0..1.0).into();
assert_eq!(range.constrain(&(-3.0..-5.0).into()), (0.0..0.0).into());
assert_eq!(range.constrain(&(-3.0..0.8).into()), (0.0..0.8).into());

Expand Down
Loading

0 comments on commit 398bde6

Please sign in to comment.