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));
+ }
}
}