From 6b4f2cd41db7162066096cfd2ef4cf1de127b83d Mon Sep 17 00:00:00 2001 From: Erik Hedvall Date: Wed, 10 Feb 2016 10:32:00 +0100 Subject: [PATCH] Add common blend modes to Blend --- src/alpha.rs | 14 ++- src/blend.rs | 290 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/lib.rs | 17 ++- src/luma.rs | 9 ++ src/rgb.rs | 9 ++ 5 files changed, 330 insertions(+), 9 deletions(-) diff --git a/src/alpha.rs b/src/alpha.rs index 86fb21458..f4527e9f8 100644 --- a/src/alpha.rs +++ b/src/alpha.rs @@ -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)] @@ -104,9 +105,20 @@ impl Limited for Alpha { } } -impl Blend for Alpha { +impl, T: Float> Blend for Alpha where + C::Color: ComponentWise, + Alpha: Into> + From>, +{ type Color = C::Color; type Scalar = T; + + fn into_premultiplied(self) -> PreAlpha { + PreAlpha::from(self.into()) + } + + fn from_premultiplied(color: PreAlpha) -> Alpha { + color.into().into() + } } impl, T: Float> ComponentWise for Alpha { diff --git a/src/blend.rs b/src/blend.rs index 82a0a5301..69197076b 100644 --- a/src/blend.rs +++ b/src/blend.rs @@ -1,17 +1,58 @@ //!Color blending. -use num::Float; +use num::{Float, One, Zero}; -use {Alpha, ComponentWise}; +use {Alpha, ComponentWise, clamp}; + +///Premultiplied alpha wrapper. +pub struct PreAlpha(Alpha); + +impl, T: Float> PreAlpha { + ///Convert a regular alpha color to premultiplied alpha. + pub fn from(mut color: Alpha) -> PreAlpha { + 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 { + 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, T: Float> Blend for PreAlpha { + type Color = C; + type Scalar = T; + + fn into_premultiplied(self) -> PreAlpha { + self + } + + fn from_premultiplied(color: PreAlpha) -> PreAlpha { + 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; ///The scalar type for color components. type Scalar: Float; + ///Convert the color to premultiplied alpha. + fn into_premultiplied(self) -> PreAlpha::Scalar>; + + ///Convert the color from premultiplied alpha. + fn from_premultiplied(color: PreAlpha::Scalar>) -> Self; + ///Blend self, as the source color, with `destination`, using `blend_function. fn blend(self, destination: Self, blend_function: F) -> Self where Alpha::Scalar>: From + Into, @@ -19,6 +60,189 @@ pub trait Blend: Sized { { blend_function.apply_to(self.into(), destination.into()).into() } + + ///Place `self` over `other`. + fn over(self, other: Self) -> Self where Self::Color: ComponentWise::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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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> { + 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. @@ -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] @@ -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]); } } diff --git a/src/lib.rs b/src/lib.rs index 286cf4e10..85510e80d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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; @@ -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); )+ }) @@ -400,6 +403,14 @@ macro_rules! make_color { impl Blend for Color { type Color = Rgb; type Scalar = T; + + fn into_premultiplied(self) -> PreAlpha, T> { + PreAlpha::from(self.into()) + } + + fn from_premultiplied(color: PreAlpha, T>) -> Self { + color.into().into() + } } $( diff --git a/src/luma.rs b/src/luma.rs index 9c9fbeb39..438978296 100644 --- a/src/luma.rs +++ b/src/luma.rs @@ -6,6 +6,7 @@ use {Color, Alpha}; use {Rgb, Xyz, Yxy, Lab, Lch, Hsv, Hsl}; use {Limited, Mix, Shade, Blend, ComponentWise}; use {clamp, flt}; +use blend::PreAlpha; ///Linear luminance with an alpha component. See the [`Lumaa` implementation in `Alpha`](struct.Alpha.html#Lumaa). pub type Lumaa = Alpha, T>; @@ -99,6 +100,14 @@ impl Shade for Luma { impl Blend for Luma { type Color = Luma; type Scalar = T; + + fn into_premultiplied(self) -> PreAlpha, T> { + PreAlpha::from(self.into()) + } + + fn from_premultiplied(color: PreAlpha, T>) -> Self { + color.into().into() + } } impl ComponentWise for Luma { diff --git a/src/rgb.rs b/src/rgb.rs index 676f0d313..3b09d780c 100644 --- a/src/rgb.rs +++ b/src/rgb.rs @@ -6,6 +6,7 @@ use {Color, Alpha, Luma, Xyz, Yxy, Lab, Lch, Hsv, Hsl}; use {Limited, Mix, Shade, GetHue, RgbHue, Blend, ComponentWise}; use {clamp, flt}; use pixel::{RgbPixel, Srgb, GammaRgb}; +use blend::PreAlpha; ///Linear RGB with an alpha component. See the [`Rgba` implementation in `Alpha`](struct.Alpha.html#Rgba). pub type Rgba = Alpha, T>; @@ -189,6 +190,14 @@ impl GetHue for Rgb { impl Blend for Rgb { type Color = Rgb; type Scalar = T; + + fn into_premultiplied(self) -> PreAlpha, T> { + PreAlpha::from(self.into()) + } + + fn from_premultiplied(color: PreAlpha, T>) -> Self { + color.into().into() + } } impl ComponentWise for Rgb {