From dc4646eb00c714f52be92a2a7e67a1dda05f8a21 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Sun, 27 Nov 2016 23:07:01 +1100 Subject: [PATCH] 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)); + } } }