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