From 70ea546da78ba61ff0aa4e7a189683167bf1a1d9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 22 Nov 2016 01:33:40 +1100 Subject: [PATCH 1/5] Add initial transforms. Hardlight is broken. --- src/ImageSharp/Colors/Color.cs | 11 ++ src/ImageSharp/Colors/ColorDefinitions.cs | 2 +- src/ImageSharp/Colors/ColorTransforms.cs | 133 ++++++++++++++++-- tests/ImageSharp.Tests/Colors/ColorTests.cs | 4 +- .../Colors/ColorTransformTests.cs | 60 ++++++++ 5 files changed, 192 insertions(+), 18 deletions(-) create mode 100644 tests/ImageSharp.Tests/Colors/ColorTransformTests.cs diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index c4ea2d762..090a35d9e 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -107,6 +107,17 @@ namespace ImageSharp this.packedValue = Pack(ref vector); } + /// + /// Initializes a new instance of the struct. + /// + /// + /// The packed value. + /// + public Color(uint packed) + { + this.packedValue = packed; + } + /// /// Gets or sets the red component. /// diff --git a/src/ImageSharp/Colors/ColorDefinitions.cs b/src/ImageSharp/Colors/ColorDefinitions.cs index 46e0f8709..5c1c30fbd 100644 --- a/src/ImageSharp/Colors/ColorDefinitions.cs +++ b/src/ImageSharp/Colors/ColorDefinitions.cs @@ -581,7 +581,7 @@ namespace ImageSharp public static readonly Color Purple = new Color(128, 0, 128, 255); /// - /// Represents a matching the W3C definition that has an hex value of #0. + /// Represents a matching the W3C definition that has an hex value of #663399. /// public static readonly Color RebeccaPurple = new Color(102, 51, 153, 255); diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index e710b69e9..42a0ebc50 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -2,10 +2,8 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // - namespace ImageSharp { - using System; using System.Numerics; /// @@ -18,6 +16,34 @@ namespace ImageSharp /// public partial struct Color { + /// + /// Adds the second color to the first. + /// + /// The first source color. + /// The second source color. + /// + /// The . + /// + public static Color operator +(Color left, Color right) + { + Vector4 add = left.ToVector4() + right.ToVector4(); + return new Color(Pack(ref add)); + } + + /// + /// Subtracts the second color from the first. + /// + /// The first source color. + /// The second source color. + /// + /// The . + /// + public static Color operator -(Color left, Color right) + { + Vector4 sub = left.ToVector4() - right.ToVector4(); + return new Color(Pack(ref sub)); + } + /// /// Blends two colors by multiplication. /// @@ -27,31 +53,108 @@ namespace ImageSharp /// original color. /// /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Multiply(Color backdrop, Color source) + { + if (source == Black) + { + return Black; + } + + if (source == White) + { + return backdrop; + } + + Vector4 vb = backdrop.ToVector4(); + Vector4 vs = source.ToVector4(); + + Vector4 multiply = vb * vs; + multiply.W = vb.W; + return new Color(Pack(ref multiply)); + } + + /// + /// Multiplies the complements of the backdrop and source color values, then complements the result. + /// + /// The result color is always at least as light as either of the two constituent colors. Screening any + /// color with white produces white; screening with black leaves the original color unchanged. + /// The effect is similar to projecting multiple photographic slides simultaneously onto a single screen. + /// + /// + /// The backdrop color. /// The source color. - /// The destination color. /// /// The . /// - public static Color Multiply(Color source, Color destination) + public static Color Screen(Color backdrop, Color source) { - if (destination == Color.Black) + if (source == Black) { - return Color.Black; + return backdrop; } - if (destination == Color.White) + if (source == White) { - return source; + return White; } - // TODO: This will use less memory than using Vector4 - // but we should test speed vs memory to see which is best balance. - byte r = (byte)(source.R * destination.R).Clamp(0, 255); - byte g = (byte)(source.G * destination.G).Clamp(0, 255); - byte b = (byte)(source.B * destination.B).Clamp(0, 255); - byte a = (byte)(source.A * destination.A).Clamp(0, 255); + Vector4 vb = backdrop.ToVector4(); + Vector4 vs = source.ToVector4(); + + Vector4 subtract = Vector4.Clamp(vb + vs - (vb * vs), Vector4.Zero, Vector4.One); + subtract.W = vb.W; + return new Color(Pack(ref subtract)); + } - return new Color(r, g, b, a); + /// + /// Multiplies or screens the colors, depending on the source color value. The effect is similar to + /// shining a harsh spotlight on the backdrop. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color HardLight(Color backdrop, Color source) + { + // TODO: Why is this giving me nonsense? + // https://www.w3.org/TR/compositing-1/#blendinghardlight + // if(Cs <= 0.5) + // B(Cb, Cs) = Multiply(Cb, 2 x Cs) + // else + // B(Cb, Cs) = Screen(Cb, 2 x Cs -1) + Vector4 vs = source.ToVector4(); + Vector4 blend = 2F * vs; + if (vs.X <= 0.5F && vs.Y <= 0.5F && vs.Z <= 0.5F) + { + return Multiply(backdrop, new Color(Pack(ref blend))); + } + + blend = (2F * vs) - Vector4.One; + return Screen(backdrop, new Color(Pack(ref blend))); + } + + /// + /// Multiplies or screens the colors, depending on the backdrop color value. + /// + /// Source colors overlay the backdrop while preserving its highlights and shadows. + /// The backdrop color is not replaced but is mixed with the source color to reflect the lightness or darkness + /// of the backdrop. + /// + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Overlay(Color backdrop, Color source) + { + return HardLight(source, backdrop); } /// diff --git a/tests/ImageSharp.Tests/Colors/ColorTests.cs b/tests/ImageSharp.Tests/Colors/ColorTests.cs index 23ebbec98..ec75ae6f7 100644 --- a/tests/ImageSharp.Tests/Colors/ColorTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorTests.cs @@ -3,11 +3,11 @@ // Licensed under the Apache License, Version 2.0. // -using System.Numerics; - namespace ImageSharp.Tests { using System; + using System.Numerics; + using Xunit; /// diff --git a/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs new file mode 100644 index 000000000..dc710549b --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs @@ -0,0 +1,60 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests.Colors +{ + using Xunit; + + /// + /// Tests the color transform algorithms. + /// + /// + public class ColorTransformTests + { + /// + /// Orange backdrop + /// + private static readonly Color Backdrop = new Color(204, 102, 0); + + /// + /// Blue source + /// + private static readonly Color Source = new Color(0, 102, 153); + + [Fact] + public void Multiply() + { + Assert.True(Color.Multiply(Backdrop, Color.Black) == Color.Black); + Assert.True(Color.Multiply(Backdrop, Color.White) == Backdrop); + + Color multiply = Color.Multiply(Backdrop, Source); + Assert.True(multiply == new Color(0, 41, 0)); + } + + [Fact] + public void Screen() + { + Assert.True(Color.Screen(Backdrop, Color.Black) == Backdrop); + Assert.True(Color.Screen(Backdrop, Color.White) == Color.White); + + Color screen = Color.Screen(Backdrop, Source); + Assert.True(screen == new Color(204, 163, 153)); + } + + [Fact] + public void HardLight() + { + Color hardLight = Color.HardLight(Backdrop, Source); + Assert.True(hardLight == new Color(0, 82, 51)); + } + + [Fact] + public void Overlay() + { + Color overlay = Color.Overlay(Backdrop, Source); + Assert.True(overlay == new Color(153, 82, 0)); + } + } +} From 17a283f034091724276ca0f0169ebcddf7fa90c9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 22 Nov 2016 23:58:07 +1100 Subject: [PATCH 2/5] Fix hardlight and overlay --- src/ImageSharp/Colors/Color.cs | 2 +- src/ImageSharp/Colors/ColorTransforms.cs | 36 ++++++++++++++---------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs index 090a35d9e..1435377a5 100644 --- a/src/ImageSharp/Colors/Color.cs +++ b/src/ImageSharp/Colors/Color.cs @@ -32,7 +32,7 @@ namespace ImageSharp /// /// The half vector value. /// - private static readonly Vector4 Half = new Vector4(0.5f); + private static readonly Vector4 Half = new Vector4(0.5F); /// /// The packed value. diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index 42a0ebc50..11d0139be 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -2,6 +2,7 @@ // Copyright (c) James Jackson-South and contributors. // Licensed under the Apache License, Version 2.0. // + namespace ImageSharp { using System.Numerics; @@ -122,21 +123,10 @@ namespace ImageSharp /// public static Color HardLight(Color backdrop, Color source) { - // TODO: Why is this giving me nonsense? - // https://www.w3.org/TR/compositing-1/#blendinghardlight - // if(Cs <= 0.5) - // B(Cb, Cs) = Multiply(Cb, 2 x Cs) - // else - // B(Cb, Cs) = Screen(Cb, 2 x Cs -1) + Vector4 vb = backdrop.ToVector4(); Vector4 vs = source.ToVector4(); - Vector4 blend = 2F * vs; - if (vs.X <= 0.5F && vs.Y <= 0.5F && vs.Z <= 0.5F) - { - return Multiply(backdrop, new Color(Pack(ref blend))); - } - - blend = (2F * vs) - Vector4.One; - return Screen(backdrop, new Color(Pack(ref blend))); + Vector4 result = new Vector4(BlendOverlay(vs.X, vb.X), BlendOverlay(vs.Y, vb.Y), BlendOverlay(vs.Z, vb.Z), vb.W); + return new Color(Pack(ref result)); } /// @@ -154,7 +144,10 @@ namespace ImageSharp /// public static Color Overlay(Color backdrop, Color source) { - return HardLight(source, backdrop); + Vector4 vb = backdrop.ToVector4(); + Vector4 vs = source.ToVector4(); + Vector4 result = new Vector4(BlendOverlay(vb.X, vs.X), BlendOverlay(vb.Y, vs.Y), BlendOverlay(vb.Z, vs.Z), vb.W); + return new Color(Pack(ref result)); } /// @@ -173,5 +166,18 @@ namespace ImageSharp { return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount)); } + + /// + /// Multiplies or screens the color component, depending on the component value. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendOverlay(float b, float s) + { + return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); + } } } From 5e164f61a532727d2b12dcaad80bb007827dba10 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Nov 2016 00:41:38 +1100 Subject: [PATCH 3/5] Darken Lighten --- src/ImageSharp/Colors/ColorTransforms.cs | 36 +++++++++++++++++++ .../Colors/ColorTransformTests.cs | 14 ++++++++ 2 files changed, 50 insertions(+) diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index 11d0139be..5993ac699 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -150,6 +150,42 @@ namespace ImageSharp return new Color(Pack(ref result)); } + /// + /// Selects the darker of the backdrop and source colors. + /// The backdrop is replaced with the source where the source is darker; otherwise, it is left unchanged. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Darken(Color backdrop, Color source) + { + Vector4 vb = backdrop.ToVector4(); + Vector4 vs = source.ToVector4(); + Vector4 result = Vector4.Min(vb, vs); + result.W = vb.W; + return new Color(Pack(ref result)); + } + + /// + /// Selects the lighter of the backdrop and source colors. + /// The backdrop is replaced with the source where the source is lighter; otherwise, it is left unchanged. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Lighten(Color backdrop, Color source) + { + Vector4 vb = backdrop.ToVector4(); + Vector4 vs = source.ToVector4(); + Vector4 result = Vector4.Max(vb, vs); + result.W = vb.W; + return new Color(Pack(ref result)); + } + /// /// Linearly interpolates from one color to another based on the given weighting. /// diff --git a/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs index dc710549b..41835e7c6 100644 --- a/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs @@ -56,5 +56,19 @@ namespace ImageSharp.Tests.Colors Color overlay = Color.Overlay(Backdrop, Source); Assert.True(overlay == new Color(153, 82, 0)); } + + [Fact] + public void Darken() + { + Color darken = Color.Darken(Backdrop, Source); + Assert.True(darken == new Color(0, 102, 0)); + } + + [Fact] + public void Lighten() + { + Color lighten = Color.Lighten(Backdrop, Source); + Assert.True(lighten == new Color(204, 102, 153)); + } } } From 747614e0f6d7d35dc82a98aa036572d1fa16c4b5 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Wed, 23 Nov 2016 00:48:38 +1100 Subject: [PATCH 4/5] No need to clamp --- src/ImageSharp/Colors/ColorTransforms.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index 5993ac699..852cd196f 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -107,7 +107,7 @@ namespace ImageSharp Vector4 vb = backdrop.ToVector4(); Vector4 vs = source.ToVector4(); - Vector4 subtract = Vector4.Clamp(vb + vs - (vb * vs), Vector4.Zero, Vector4.One); + Vector4 subtract = vb + vs - (vb * vs); subtract.W = vb.W; return new Color(Pack(ref subtract)); } From dc4646eb00c714f52be92a2a7e67a1dda05f8a21 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 27 Nov 2016 23:07:01 +1100 Subject: [PATCH 5/5] Fix #16 --- src/ImageSharp/Colors/ColorTransforms.cs | 160 ++++++----- .../Common/Helpers/Vector4BlendTransforms.cs | 253 ++++++++++++++++++ .../Colors/ColorTransformTests.cs | 44 ++- 3 files changed, 394 insertions(+), 63 deletions(-) create mode 100644 src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs diff --git a/src/ImageSharp/Colors/ColorTransforms.cs b/src/ImageSharp/Colors/ColorTransforms.cs index 852cd196f..5f8fe37db 100644 --- a/src/ImageSharp/Colors/ColorTransforms.cs +++ b/src/ImageSharp/Colors/ColorTransforms.cs @@ -45,6 +45,20 @@ namespace ImageSharp return new Color(Pack(ref sub)); } + /// + /// The blending formula simply selects the source color. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Normal(Color backdrop, Color source) + { + Vector4 normal = Vector4BlendTransforms.Normal(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref normal)); + } + /// /// Blends two colors by multiplication. /// @@ -61,21 +75,7 @@ namespace ImageSharp /// public static Color Multiply(Color backdrop, Color source) { - if (source == Black) - { - return Black; - } - - if (source == White) - { - return backdrop; - } - - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - - Vector4 multiply = vb * vs; - multiply.W = vb.W; + Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4()); return new Color(Pack(ref multiply)); } @@ -94,21 +94,7 @@ namespace ImageSharp /// public static Color Screen(Color backdrop, Color source) { - if (source == Black) - { - return backdrop; - } - - if (source == White) - { - return White; - } - - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - - Vector4 subtract = vb + vs - (vb * vs); - subtract.W = vb.W; + Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4()); return new Color(Pack(ref subtract)); } @@ -123,10 +109,8 @@ namespace ImageSharp /// public static Color HardLight(Color backdrop, Color source) { - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - Vector4 result = new Vector4(BlendOverlay(vs.X, vb.X), BlendOverlay(vs.Y, vb.Y), BlendOverlay(vs.Z, vb.Z), vb.W); - return new Color(Pack(ref result)); + Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref hardlight)); } /// @@ -144,10 +128,8 @@ namespace ImageSharp /// public static Color Overlay(Color backdrop, Color source) { - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - Vector4 result = new Vector4(BlendOverlay(vb.X, vs.X), BlendOverlay(vb.Y, vs.Y), BlendOverlay(vb.Z, vs.Z), vb.W); - return new Color(Pack(ref result)); + Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref overlay)); } /// @@ -161,11 +143,8 @@ namespace ImageSharp /// public static Color Darken(Color backdrop, Color source) { - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - Vector4 result = Vector4.Min(vb, vs); - result.W = vb.W; - return new Color(Pack(ref result)); + Vector4 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref darken)); } /// @@ -179,11 +158,81 @@ namespace ImageSharp /// public static Color Lighten(Color backdrop, Color source) { - Vector4 vb = backdrop.ToVector4(); - Vector4 vs = source.ToVector4(); - Vector4 result = Vector4.Max(vb, vs); - result.W = vb.W; - return new Color(Pack(ref result)); + Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref lighten)); + } + + /// + /// Darkens or lightens the colors, depending on the source color value. The effect is similar to shining + /// a diffused spotlight on the backdrop. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color SoftLight(Color backdrop, Color source) + { + Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref softlight)); + } + + /// + /// Brightens the backdrop color to reflect the source color. Painting with black produces no changes. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color ColorDodge(Color backdrop, Color source) + { + Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref dodge)); + } + + /// + /// Darkens the backdrop color to reflect the source color. Painting with white produces no change. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color ColorBurn(Color backdrop, Color source) + { + Vector4 burn = Vector4BlendTransforms.Burn(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref burn)); + } + + /// + /// 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. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Difference(Color backdrop, Color source) + { + Vector4 difference = Vector4BlendTransforms.Difference(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref difference)); + } + + /// + /// Produces an effect similar to that of the mode but lower in contrast. Painting with white + /// inverts the backdrop color; painting with black produces no change + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static Color Exclusion(Color backdrop, Color source) + { + Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4()); + return new Color(Pack(ref exclusion)); } /// @@ -202,18 +251,5 @@ namespace ImageSharp { return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount)); } - - /// - /// Multiplies or screens the color component, depending on the component value. - /// - /// The backdrop component. - /// The source component. - /// - /// The . - /// - private static float BlendOverlay(float b, float s) - { - return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); - } } -} +} \ No newline at end of file diff --git a/src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs b/src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs new file mode 100644 index 000000000..a6d43a47a --- /dev/null +++ b/src/ImageSharp/Common/Helpers/Vector4BlendTransforms.cs @@ -0,0 +1,253 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System; + using System.Numerics; + + /// + /// Transform algorithms that match the equations defined in the W3C Compositing and Blending Level 1 specification. + /// + /// + public class Vector4BlendTransforms + { + /// + /// The epsilon for comparing floating point numbers. + /// + private const float Epsilon = 0.001F; + + /// + /// The blending formula simply selects the source vector. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Normal(Vector4 backdrop, Vector4 source) + { + return new Vector4(source.X, source.Y, source.Z, source.W); + } + + /// + /// Blends two vectors by multiplication. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Multiply(Vector4 backdrop, Vector4 source) + { + Vector4 multiply = backdrop * source; + multiply.W = backdrop.W; + return multiply; + } + + /// + /// Multiplies the complements of the backdrop and source vector values, then complements the result. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Screen(Vector4 backdrop, Vector4 source) + { + Vector4 subtract = backdrop + source - (backdrop * source); + subtract.W = backdrop.W; + return subtract; + } + + /// + /// Multiplies or screens the colors, depending on the source vector value. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 HardLight(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendOverlay(source.X, backdrop.X), BlendOverlay(source.Y, backdrop.Y), BlendOverlay(source.Z, backdrop.Z), backdrop.W); + } + + /// + /// Multiplies or screens the vectors, depending on the backdrop vector value. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Overlay(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendOverlay(backdrop.X, source.X), BlendOverlay(backdrop.Y, source.Y), BlendOverlay(backdrop.Z, source.Z), backdrop.W); + } + + /// + /// Selects the minimum of the backdrop and source vectors. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Darken(Vector4 backdrop, Vector4 source) + { + Vector4 result = Vector4.Min(backdrop, source); + result.W = backdrop.W; + return result; + } + + /// + /// Selects the max of the backdrop and source vector. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Lighten(Vector4 backdrop, Vector4 source) + { + Vector4 result = Vector4.Max(backdrop, source); + result.W = backdrop.W; + return result; + } + + /// + /// Selects the maximum or minimum of the vectors, depending on the source vector value. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 SoftLight(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendSoftLight(backdrop.X, source.X), BlendSoftLight(backdrop.Y, source.Y), BlendSoftLight(backdrop.Z, source.Z), backdrop.W); + } + + /// + /// Increases the backdrop vector to reflect the source vector. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Dodge(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendDodge(backdrop.X, source.X), BlendDodge(backdrop.Y, source.Y), BlendDodge(backdrop.Z, source.Z), backdrop.W); + } + + /// + /// Decreases the backdrop vector to reflect the source vector. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Burn(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendBurn(backdrop.X, source.X), BlendBurn(backdrop.Y, source.Y), BlendBurn(backdrop.Z, source.Z), backdrop.W); + } + + /// + /// Subtracts the minimum of the two constituent vectors from the maximum vector. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Difference(Vector4 backdrop, Vector4 source) + { + Vector4 result = Vector4.Abs(backdrop - source); + result.W = backdrop.W; + return result; + } + + /// + /// Produces an effect similar to that of the mode but lower in magnitude. + /// + /// The backdrop vector. + /// The source vector. + /// + /// The . + /// + public static Vector4 Exclusion(Vector4 backdrop, Vector4 source) + { + return new Vector4(BlendExclusion(backdrop.X, source.X), BlendExclusion(backdrop.Y, source.Y), BlendExclusion(backdrop.Z, source.Z), backdrop.W); + } + + /// + /// Multiplies or screens the color component, depending on the component value. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendOverlay(float b, float s) + { + return b <= .5F ? (2F * b * s) : (1F - (2F * (1F - b) * (1F - s))); + } + + /// + /// Darkens or lightens the backdrop component, depending on the source component value. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendSoftLight(float b, float s) + { + return s <= .5F ? (2F * b * s + b * b * (1F - 2F * s)) : (float)(Math.Sqrt(b) * (2F * s - 1F) + 2F * b * (1F - s)); + } + + /// + /// Brightens the backdrop component to reflect the source component. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendDodge(float b, float s) + { + return Math.Abs(s - 1F) < Epsilon ? s : Math.Min(b / (1F - s), 1F); + } + + /// + /// Darkens the backdrop component to reflect the source component. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendBurn(float b, float s) + { + return Math.Abs(s) < Epsilon ? s : Math.Max(1F - ((1F - b) / s), 0F); + } + + /// + /// Darkens the backdrop component to reflect the source component. + /// + /// The backdrop component. + /// The source component. + /// + /// The . + /// + private static float BlendExclusion(float b, float s) + { + return b + s - 2F * b * s; + } + } +} diff --git a/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs index 41835e7c6..064bdf2d0 100644 --- a/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs +++ b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs @@ -8,7 +8,7 @@ namespace ImageSharp.Tests.Colors using Xunit; /// - /// Tests the color transform algorithms. + /// Tests the color transform algorithms. Test results match the output of CSS equivalents. /// /// public class ColorTransformTests @@ -23,6 +23,13 @@ namespace ImageSharp.Tests.Colors /// private static readonly Color Source = new Color(0, 102, 153); + [Fact] + public void Normal() + { + Color normal = Color.Normal(Backdrop, Source); + Assert.True(normal == Source); + } + [Fact] public void Multiply() { @@ -70,5 +77,40 @@ namespace ImageSharp.Tests.Colors Color lighten = Color.Lighten(Backdrop, Source); Assert.True(lighten == new Color(204, 102, 153)); } + + [Fact] + public void SoftLight() + { + Color softLight = Color.SoftLight(Backdrop, Source); + Assert.True(softLight == new Color(163, 90, 0)); + } + + [Fact] + public void ColorDodge() + { + Color colorDodge = Color.ColorDodge(Backdrop, Source); + Assert.True(colorDodge == new Color(204, 170, 0)); + } + + [Fact] + public void ColorBurn() + { + Color colorBurn = Color.ColorBurn(Backdrop, Source); + Assert.True(colorBurn == new Color(0, 0, 0)); + } + + [Fact] + public void Difference() + { + Color difference = Color.Difference(Backdrop, Source); + Assert.True(difference == new Color(204, 0, 153)); + } + + [Fact] + public void Exclusion() + { + Color exclusion = Color.Exclusion(Backdrop, Source); + Assert.True(exclusion == new Color(204, 122, 153)); + } } }