diff --git a/src/ImageSharp/Colors/Color.cs b/src/ImageSharp/Colors/Color.cs
index c4ea2d762..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.
@@ -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..5f8fe37db 100644
--- a/src/ImageSharp/Colors/ColorTransforms.cs
+++ b/src/ImageSharp/Colors/ColorTransforms.cs
@@ -5,7 +5,6 @@
namespace ImageSharp
{
- using System;
using System.Numerics;
///
@@ -18,6 +17,48 @@ 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));
+ }
+
+ ///
+ /// 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.
///
@@ -27,31 +68,171 @@ namespace ImageSharp
/// original color.
///
///
+ /// The backdrop color.
+ /// The source color.
+ ///
+ /// The .
+ ///
+ public static Color Multiply(Color backdrop, Color source)
+ {
+ Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.ToVector4(), source.ToVector4());
+ 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 .
+ ///
+ public static Color Screen(Color backdrop, Color source)
+ {
+ Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.ToVector4(), source.ToVector4());
+ return new Color(Pack(ref subtract));
+ }
+
+ ///
+ /// 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)
+ {
+ Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.ToVector4(), source.ToVector4());
+ return new Color(Pack(ref hardlight));
+ }
+
+ ///
+ /// 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)
+ {
+ Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.ToVector4(), source.ToVector4());
+ return new Color(Pack(ref overlay));
+ }
+
+ ///
+ /// 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 darken = Vector4BlendTransforms.Darken(backdrop.ToVector4(), source.ToVector4());
+ return new Color(Pack(ref darken));
+ }
+
+ ///
+ /// 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 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 destination color.
///
/// The .
///
- public static Color Multiply(Color source, Color destination)
+ public static Color Exclusion(Color backdrop, Color source)
{
- if (destination == Color.Black)
- {
- return Color.Black;
- }
-
- if (destination == Color.White)
- {
- return source;
- }
-
- // 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);
-
- return new Color(r, g, b, a);
+ Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.ToVector4(), source.ToVector4());
+ return new Color(Pack(ref exclusion));
}
///
@@ -71,4 +252,4 @@ namespace ImageSharp
return new Color(Vector4.Lerp(from.ToVector4(), to.ToVector4(), amount));
}
}
-}
+}
\ 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/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..064bdf2d0
--- /dev/null
+++ b/tests/ImageSharp.Tests/Colors/ColorTransformTests.cs
@@ -0,0 +1,116 @@
+//
+// 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. Test results match the output of CSS equivalents.
+ ///
+ ///
+ 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 Normal()
+ {
+ Color normal = Color.Normal(Backdrop, Source);
+ Assert.True(normal == Source);
+ }
+
+ [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));
+ }
+
+ [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));
+ }
+
+ [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));
+ }
+ }
+}