Skip to content

Commit

Permalink
Add common blend modes to Blend
Browse files Browse the repository at this point in the history
  • Loading branch information
Ogeon committed Feb 13, 2016
1 parent 139a1ef commit 6b4f2cd
Show file tree
Hide file tree
Showing 5 changed files with 330 additions and 9 deletions.
14 changes: 13 additions & 1 deletion src/alpha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut, Add, Sub, Mul, Div};
use num::Float;

use {Mix, Shade, GetHue, Hue, Saturate, Limited, Blend, ComponentWise, clamp};
use blend::PreAlpha;

///An alpha component wrapper for colors.
#[derive(Clone, Copy, Debug, PartialEq)]
Expand Down Expand Up @@ -104,9 +105,20 @@ impl<C: Limited, T: Float> Limited for Alpha<C, T> {
}
}

impl<C: Blend, T: Float> Blend for Alpha<C, T> {
impl<C: Blend<Scalar=T>, T: Float> Blend for Alpha<C, T> where
C::Color: ComponentWise<Scalar=T>,
Alpha<C, T>: Into<Alpha<C::Color, T>> + From<Alpha<C::Color, T>>,
{
type Color = C::Color;
type Scalar = T;

fn into_premultiplied(self) -> PreAlpha<C::Color, T> {
PreAlpha::from(self.into())
}

fn from_premultiplied(color: PreAlpha<C::Color, T>) -> Alpha<C, T> {
color.into().into()
}
}

impl<C: ComponentWise<Scalar=T>, T: Float> ComponentWise for Alpha<C, T> {
Expand Down
290 changes: 285 additions & 5 deletions src/blend.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,248 @@
//!Color blending.
use num::Float;
use num::{Float, One, Zero};

use {Alpha, ComponentWise};
use {Alpha, ComponentWise, clamp};

///Premultiplied alpha wrapper.
pub struct PreAlpha<C, T: Float>(Alpha<C, T>);

impl<C: ComponentWise<Scalar=T>, T: Float> PreAlpha<C, T> {
///Convert a regular alpha color to premultiplied alpha.
pub fn from(mut color: Alpha<C, T>) -> PreAlpha<C, T> {
color.color = color.color.component_wise_self(|a| a * color.alpha);
PreAlpha(color)
}

///Convert a premultiplied alpha color to regular alpha.
pub fn into(mut self) -> Alpha<C, T> {
self.0.color = self.0.color.component_wise_self(|a| if self.0.alpha > T::zero() {
a / self.0.alpha
} else {
a
});

self.0
}
}

impl<C: Blend<Scalar=T, Color=C>, T: Float> Blend for PreAlpha<C, T> {
type Color = C;
type Scalar = T;

fn into_premultiplied(self) -> PreAlpha<C, T> {
self
}

fn from_premultiplied(color: PreAlpha<C, T>) -> PreAlpha<C, T> {
color
}
}

///Colors that can be blended together.
pub trait Blend: Sized {
///The core color type. Typically `Self` for color types without alpha.
type Color: Blend;
type Color: Blend<Scalar=Self::Scalar>;

///The scalar type for color components.
type Scalar: Float;

///Convert the color to premultiplied alpha.
fn into_premultiplied(self) -> PreAlpha<Self::Color, <Self::Color as Blend>::Scalar>;

///Convert the color from premultiplied alpha.
fn from_premultiplied(color: PreAlpha<Self::Color, <Self::Color as Blend>::Scalar>) -> Self;

///Blend self, as the source color, with `destination`, using `blend_function.
fn blend<F>(self, destination: Self, blend_function: F) -> Self where
Alpha<Self::Color, <Self::Color as Blend>::Scalar>: From<Self> + Into<Self>,
F: BlendFunction<Self::Color>,
{
blend_function.apply_to(self.into(), destination.into()).into()
}

///Place `self` over `other`.
fn over(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| a + b - b * src.0.alpha),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))

}

///Add `self` and `other`. This uses the alpha component to regulate the
///effect, so it's not just plain component wise addition.
fn plus(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| a + b),
alpha: clamp(src.0.alpha + dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Multiply `self` with `other`. This uses the alpha component to regulate
///the effect, so it's not just plain component wise multiplication.
fn multiply(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| {
a * b + a * (one - dst.0.alpha) + b * (one - src.0.alpha)
}),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Make a color which is at least as light as `self` or `other`.
fn screen(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| a + b - a * b),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Multiply `self` or `other` if other is dark, or screen them if `other`
///is light. This results in an S curve.
fn overlay(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();
let two = one + one;

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| if b * two <= dst.0.alpha {
two * a * b + a * (one - dst.0.alpha) + b * (one - src.0.alpha)
} else {
a * (one + dst.0.alpha) + b * (one + src.0.alpha) - two * a * b - src.0.alpha * dst.0.alpha
}),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Return the darkest parts of `self` and `other`.
fn darken(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| (a * src.0.alpha).min(b * dst.0.alpha) + a * (one - dst.0.alpha) + b * (one - src.0.alpha)),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Return the lightest parts of `self` and `other`.
fn lighten(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| (a * src.0.alpha).max(b * dst.0.alpha) + a * (one - dst.0.alpha) + b * (one - src.0.alpha)),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Multiply `self` or `other` if other is dark, or screen them if `self`
///is light. This is similar to `overlay`, but depends on `self` instead
///of `other`.
fn hard_light(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();
let two = one + one;

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| if a * two <= src.0.alpha {
two * a * b + a * (one - dst.0.alpha) + b * (one - src.0.alpha)
} else {
a * (one + dst.0.alpha) + b * (one + src.0.alpha) - two * a * b - src.0.alpha * dst.0.alpha
}),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Return the absolute difference between `self` and `other`. It's
///basically `abs(self - other)`, but regulated by the alpha component.
fn difference(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();
let two = one + one;

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| a + b - two * (a * dst.0.alpha).min(b * src.0.alpha)),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}

///Similar to `difference`, but appears to result in a lower contrast.
///`other` is inverted if `self` is white, and preserved if `self` is
///black.
fn exclusion(self, other: Self) -> Self where Self::Color: ComponentWise<Scalar=<Self as Blend>::Scalar> {
let one = Self::Scalar::one();
let zero = Self::Scalar::zero();
let two = one + one;

let src = self.into_premultiplied();
let dst = other.into_premultiplied();

let result = Alpha {
color: src.0.color.component_wise(&dst.0.color, |a, b| (a * dst.0.alpha + b * src.0.alpha - two * a * b) + a * (one - dst.0.alpha) + b * (one - src.0.alpha)),
alpha: clamp(src.0.alpha + dst.0.alpha - src.0.alpha * dst.0.alpha, zero, one),
};

Self::from_premultiplied(PreAlpha(result))
}
}

///Something that blends two colors.
Expand Down Expand Up @@ -247,7 +471,7 @@ mod test {
let b = Color::rgb(0.0, 0.0, 1.0);

let c: Rgb = a.blend(b, |a: Rgba, b: Rgba| a.component_wise(&b, |a, b| a + b)).into();
assert_eq!(Rgb::new(1.0, 0.0, 1.0), c);
assert_approx_eq!(Rgb::new(1.0, 0.0, 1.0), c, [red, green, blue]);
}

#[test]
Expand All @@ -256,6 +480,62 @@ mod test {
let b = Colora::rgb(0.0, 0.0, 1.0, 0.2);

let c: Rgba = a.blend(b, |a: Rgba, b: Rgba| a.component_wise(&b, |a, b| a + b)).into();
assert_eq!(Rgba::new(1.0, 0.0, 1.0, 0.4), c);
assert_approx_eq!(Rgba::new(1.0, 0.0, 1.0, 0.4), c, [red, green, blue, alpha]);
}

#[test]
fn over() {
let a = Rgb::new(0.5, 0.0, 0.0);
let b = Rgb::new(1.0, 0.0, 0.0);

assert_approx_eq!(Rgb::new(0.5, 0.0, 0.0), a.over(b), [red, green, blue]);
}

#[test]
fn plus() {
let a = Rgb::new(0.5, 0.0, 0.0);
let b = Rgb::new(1.0, 0.0, 0.0);

assert_approx_eq!(Rgb::new(1.5, 0.0, 0.0), a.plus(b), [red, green, blue]);
}

#[test]
fn multiply() {
let a = Rgb::new(0.5, 0.0, 0.0);
let b = Rgb::new(0.5, 0.0, 0.0);

assert_approx_eq!(Rgb::new(0.25, 0.0, 0.0), a.multiply(b), [red, green, blue]);
}

#[test]
fn darken() {
let a = Rgb::new(0.5, 0.0, 0.3);
let b = Rgb::new(1.0, 0.2, 0.0);

assert_approx_eq!(Rgb::new(0.5, 0.0, 0.0), a.darken(b), [red, green, blue]);
}

#[test]
fn lighten() {
let a = Rgb::new(0.5, 0.0, 0.3);
let b = Rgb::new(1.0, 0.2, 0.0);

assert_approx_eq!(Rgb::new(1.0, 0.2, 0.3), a.lighten(b), [red, green, blue]);
}

#[test]
fn difference() {
let a = Rgb::new(0.5, 0.0, 0.3);
let b = Rgb::new(1.0, 0.2, 0.0);

assert_approx_eq!(Rgb::new(0.5, 0.2, 0.3), a.difference(b), [red, green, blue]);
}

#[test]
fn exclusion() {
let a = Rgb::new(1.0, 0.5, 0.0);
let b = Rgb::new(0.8, 0.4, 0.3);

assert_approx_eq!(Rgb::new(0.2, 0.5, 0.3), a.exclusion(b), [red, green, blue]);
}
}
17 changes: 14 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ extern crate phf;
use num::{Float, ToPrimitive, NumCast};

use pixel::{Srgb, GammaRgb};
use blend::PreAlpha;

pub use gradient::Gradient;
pub use alpha::Alpha;
Expand Down Expand Up @@ -124,10 +125,12 @@ macro_rules! alpha_from {
//are in roughly the same ranges, so this epsilon should be alright.
#[cfg(test)]
macro_rules! assert_approx_eq {
($a:ident, $b:ident, [$($components:ident),+]) => ({
($a:expr, $b:expr, [$($components:ident),+]) => ({
let lhs = $a;
let rhs = $b;
$(
let a: f32 = $a.$components.into();
let b: f32 = $b.$components.into();
let a: f32 = lhs.$components.into();
let b: f32 = rhs.$components.into();
assert_relative_eq!(a, b, epsilon = 0.0001);
)+
})
Expand Down Expand Up @@ -400,6 +403,14 @@ macro_rules! make_color {
impl<T: Float> Blend for Color<T> {
type Color = Rgb<T>;
type Scalar = T;

fn into_premultiplied(self) -> PreAlpha<Rgb<T>, T> {
PreAlpha::from(self.into())
}

fn from_premultiplied(color: PreAlpha<Rgb<T>, T>) -> Self {
color.into().into()
}
}

$(
Expand Down
Loading

0 comments on commit 6b4f2cd

Please sign in to comment.