From 70ea546da78ba61ff0aa4e7a189683167bf1a1d9 Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 22 Nov 2016 01:33:40 +1100 Subject: [PATCH] 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)); + } + } +}