pixel 0.1.3

Prototype! Pixel manipulation library (blend and convert colors)
Documentation
use super::*;

/// Various color blending operations.
/// Almost all blending modes use same alpha blending formula above.
/// See http://www.w3.org/TR/compositing-1/#blending
#[derive(Clone)]
#[derive(Copy)]
pub enum BlendMode {
    /// Select source color
    /// B(Cb, Cs) = Cs
    Normal,

    /// Multiply each channel values
    /// B(Cb, Cs) = Cb x Cs
    Multiply,

    /// Invert, multiply and invert again each channel
    /// B(Cb, Cs) = 1 - [(1 - Cb) x (1 - Cs)] = Cb + Cs -(Cb x Cs)
    Screen,

    /// Combination of Multiply and Screen
    /// B(Cb, Cs) = HardLight(Cs, Cb)
    Overlay,

    /// Make color darker by taking minimum of source and backdrop
    /// B(Cb, Cs) = min(Cb, Cs)
    Darken,

    /// Make color lighter by taking lighter of source and backdrop
    /// B(Cb, Cs) = max(Cb, Cs)
    Lighten,

    /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes.
    /// if(Cb == 0)
    ///     B(Cb, Cs) = 0
    /// else if(Cs == 1)
    ///     B(Cb, Cs) = 1
    /// else
    ///     B(Cb, Cs) = min(1, Cb / (1 - Cs))
    Dodge,


    /// Darkens the backdrop color to reflect the source color. Painting with white produces no change.
    /// if(Cb == 1)
    ///     B(Cb, Cs) = 1
    /// else if(Cs == 0)
    ///     B(Cb, Cs) = 0
    /// else
    ///     B(Cb, Cs) = 1 - min(1, (1 - Cb) / Cs)
    Burn,



    /// Multiplies or screens the colors, depending on the source color value. The effect is similar to shining a harsh spotlight on the backdrop.
    /// if(Cs <= 0.5)
    ///     B(Cb, Cs) = Multiply(Cb, 2 x Cs)
    /// else
    ///     B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
    HardLight,

    /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining a diffused spotlight on the backdrop.
    ///     if(Cs <= 0.5)
    ///         B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
    ///     else
    ///         B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
    /// with
    ///     if(Cb <= 0.25)
    ///         D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
    ///     else
    ///         D(Cb) = sqrt(Cb)
    SoftLight,

    /// Subtracts the darker of the two constituent colors from the lighter color.
    /// Painting with white inverts the backdrop color; painting with black produces no change.
    /// B(Cb, Cs) = | Cb - Cs |
    Difference,

    /// Produces an effect similar to that of the Difference mode but lower in contrast. Painting with white inverts the backdrop color; painting with black produces no change
    /// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
    Exclusion,

    /// Creates a color with the hue of the source color and the saturation and luminosity of the backdrop color.
    /// B(Cb, Cs) = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))
    Hue,

    /// Creates a color with the saturation of the source color and the hue and luminosity of the backdrop color. Painting with this mode in an area of the backdrop that is a pure gray (no saturation) produces no change.
    /// B(Cb, Cs) = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))
    Saturation,

    /// Creates a color with the hue and saturation of the source color and the luminosity of the backdrop color. This preserves the gray levels of the backdrop and is useful for coloring monochrome images or tinting color images.
    /// B(Cb, Cs) = SetLum(Cs, Lum(Cb))
    Color,

    /// Creates a color with the luminosity of the source color and the hue and saturation of the backdrop color. This produces an inverse effect to that of the Color mode.
    /// B(Cb, Cs) = SetLum(Cb, Lum(Cs))
    Luminosity,
}


fn op_on_channels<L: Color, R: Color, F: Fn(f32, f32)->f32>(lhs: &L, rhs: &R, f: F) -> RGB {
    let lhs = lhs.to_rgb();
    let rhs = rhs.to_rgb();
    RGB {
        red: f(lhs.red, rhs.red),
        green: f(lhs.green, rhs.green),
        blue: f(lhs.blue, rhs.blue)
    }
}


/// Blend two colors
pub fn blend<B: Color, S: Color>(backdrop: &B, source: &S, op: BlendMode) -> RGB {
    use self::BlendMode::*;
    match op {
        // B(Cb, Cs) = Cs
        Normal => source.to_rgb(),

        // B(Cb, Cs) = Cb x Cs
        Multiply => op_on_channels(backdrop, source, |b, s| b * s),
        /// B(Cb, Cs) = 1 - [(1 - Cb) x (1 - Cs)] = Cb + Cs -(Cb x Cs)
        Screen => op_on_channels(backdrop, source, |b, s| 1.0 - (1.0 - b) * (1.0 - s)),
        /// B(Cb, Cs) = HardLight(Cs, Cb)
        Overlay => blend(source, backdrop, BlendMode::HardLight),
        /// B(Cb, Cs) = min(Cb, Cs)
        Darken => op_on_channels(backdrop, source, |b, s| utils::min(b, s)),
        /// B(Cb, Cs) = max(Cb, Cs)
        Lighten => op_on_channels(backdrop, source, |b, s| utils::max(b, s)),
        /// if(Cb == 0)
        ///     B(Cb, Cs) = 0
        /// else if(Cs == 1)
        ///     B(Cb, Cs) = 1
        /// else
        ///     B(Cb, Cs) = min(1, Cb / (1 - Cs))
        Dodge => op_on_channels(backdrop, source, |b, s| if b == 0.0 { 0.0 } else if s == 1.0 { 1.0 } else { utils::min(1.0, b / (1.0 - s)) } ),
        /// if(Cb == 1)
        ///     B(Cb, Cs) = 1
        /// else if(Cs == 0)
        ///     B(Cb, Cs) = 0
        /// else
        ///     B(Cb, Cs) = 1 - min(1, (1 - Cb) / Cs)
        Burn => op_on_channels(backdrop, source, |b, s| if b == 1.0 { 1.0 } else if s == 0.0 { 0.0 } else { utils::min(1.0, (1.0 - b) / s) } ),
        /// if(Cs <= 0.5)
        ///     B(Cb, Cs) = Multiply(Cb, 2 x Cs)
        /// else
        ///     B(Cb, Cs) = Screen(Cb, 2 x Cs -1)
        HardLight => op_on_channels(backdrop, source, |b, s| if s <= 0.5 { b * s } else { (1.0 - (1.0 - b) * (1.0 - s)) } ),
        ///     if(Cs <= 0.5)
        ///         B(Cb, Cs) = Cb - (1 - 2 x Cs) x Cb x (1 - Cb)
        ///     else
        ///         B(Cb, Cs) = Cb + (2 x Cs - 1) x (D(Cb) - Cb)
        /// with
        ///     if(Cb <= 0.25)
        ///         D(Cb) = ((16 * Cb - 12) x Cb + 4) x Cb
        ///     else
        ///         D(Cb) = sqrt(Cb)
        SoftLight => op_on_channels(backdrop, source, |b, s| if s <= 0.5 { b - (1.0 - 2.0 * s) * b * (1.0 - b) } else { b + (2.0 + s - 1.0) * (if b <= 0.25 { ((16.0 * b - 12.0) * b + 4.0) * b } else { b.sqrt() }) - b }),
        /// B(Cb, Cs) = | Cb - Cs |
        Difference => op_on_channels(backdrop, source, |b, s| (b - s).abs()),
        /// B(Cb, Cs) = Cb + Cs - 2 x Cb x Cs
        Exclusion => op_on_channels(backdrop, source, |b, s| b + s - 2.0 * b * s ),
        /// B(Cb, Cs) = SetLum(SetSat(Cs, Sat(Cb)), Lum(Cb))
        Hue => {
            HSL{ hue: source.get_hue(), saturation: backdrop.get_hsl_saturation(), lightness: backdrop.get_lightness() }.to_rgb()
        },
        /// B(Cb, Cs) = SetLum(SetSat(Cb, Sat(Cs)), Lum(Cb))
        Saturation => {
            HSL{ hue: backdrop.get_hue(), saturation: source.get_hsl_saturation(), lightness: backdrop.get_lightness() }.to_rgb()
        },
        /// B(Cb, Cs) = SetLum(Cs, Lum(Cb))
        Color => {
            HSL{ hue: source.get_hue(), saturation: source.get_hsl_saturation(), lightness: backdrop.get_lightness() }.to_rgb()
        },
        /// B(Cb, Cs) = SetLum(Cb, Lum(Cs))
        Luminosity => {
            HSL{ hue: backdrop.get_hue(), saturation: backdrop.get_hsl_saturation(), lightness: source.get_lightness() }.to_rgb()
        },
    }
}

/// Blend two pixels with alpha channel using specified color blend function
pub fn alpha_blend<B: Pixel, S: Pixel, R: Color, F: FnOnce(&B, &S) -> R>(backdrop: &B, source: &S, factor: f32, f: F) -> RGBA {
    let use_backdrop = backdrop.get_alpha();
    let use_source = source.get_alpha() * factor;
    let affected = backdrop.get_alpha() * source.get_alpha() * factor;
    let blended = f(backdrop, source).to_rgb();
    let alpha = use_backdrop + use_source + affected;
    let backdrop = backdrop.to_rgb();
    let source = source.to_rgb();
    RGBA {
        rgb: RGB {
            red: (blended.red * affected + backdrop.red * use_backdrop + source.red * use_source) / alpha,
            green: (blended.green * affected + backdrop.green * use_backdrop + source.green * use_source) / alpha,
            blue: (blended.blue * affected + backdrop.blue * use_backdrop + source.blue * use_source) / alpha
        },
        alpha: alpha
    }
}