From 13455a0bb14cc789120c6ed8156e09c0e560fabe Mon Sep 17 00:00:00 2001 From: James Jackson-South Date: Tue, 18 Apr 2017 00:55:03 +1000 Subject: [PATCH] Add transforms plus additional tests --- .../Colors/ColorVector.Transforms.cs | 253 ++++++++++++++++++ .../Colors/ColorVectorTests.cs | 132 +++++++++ .../Colors/ColorVectorTransformTests.cs | 118 ++++++++ 3 files changed, 503 insertions(+) create mode 100644 src/ImageSharp/Colors/ColorVector.Transforms.cs create mode 100644 tests/ImageSharp.Tests/Colors/ColorVectorTests.cs create mode 100644 tests/ImageSharp.Tests/Colors/ColorVectorTransformTests.cs diff --git a/src/ImageSharp/Colors/ColorVector.Transforms.cs b/src/ImageSharp/Colors/ColorVector.Transforms.cs new file mode 100644 index 0000000000..e9666a351a --- /dev/null +++ b/src/ImageSharp/Colors/ColorVector.Transforms.cs @@ -0,0 +1,253 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp +{ + using System.Numerics; + + /// + /// Unpacked pixel type containing four 16-bit unsigned normalized values typically ranging from 0 to 1. + /// The color components are stored in red, green, blue, and alpha order. + /// + /// + /// This struct is fully mutable. This is done (against the guidelines) for the sake of performance, + /// as it avoids the need to create new values for modification operations. + /// + public partial struct ColorVector + { + /// + /// Adds the second color to the first. + /// + /// The first source color. + /// The second source color. + /// + /// The . + /// + public static ColorVector operator +(ColorVector left, ColorVector right) + { + return new ColorVector(left.backingVector + right.backingVector); + } + + /// + /// Subtracts the second color from the first. + /// + /// The first source color. + /// The second source color. + /// + /// The . + /// + public static ColorVector operator -(ColorVector left, ColorVector right) + { + return new ColorVector(left.backingVector - right.backingVector); + } + + /// + /// The blending formula simply selects the source color. + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static ColorVector Normal(ColorVector backdrop, ColorVector source) + { + Vector4 normal = Vector4BlendTransforms.Normal(backdrop.backingVector, source.backingVector); + return new ColorVector(normal); + } + + /// + /// Blends two colors by multiplication. + /// + /// The source color is multiplied by the destination color and replaces the destination. + /// The resultant color is always at least as dark as either the source or destination color. + /// Multiplying any color with black results in black. Multiplying any color with white preserves the + /// original color. + /// + /// + /// The backdrop color. + /// The source color. + /// + /// The . + /// + public static ColorVector Multiply(ColorVector backdrop, ColorVector source) + { + Vector4 multiply = Vector4BlendTransforms.Multiply(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Screen(ColorVector backdrop, ColorVector source) + { + Vector4 subtract = Vector4BlendTransforms.Screen(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector HardLight(ColorVector backdrop, ColorVector source) + { + Vector4 hardlight = Vector4BlendTransforms.HardLight(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Overlay(ColorVector backdrop, ColorVector source) + { + Vector4 overlay = Vector4BlendTransforms.Overlay(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Darken(ColorVector backdrop, ColorVector source) + { + Vector4 darken = Vector4BlendTransforms.Darken(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Lighten(ColorVector backdrop, ColorVector source) + { + Vector4 lighten = Vector4BlendTransforms.Lighten(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector SoftLight(ColorVector backdrop, ColorVector source) + { + Vector4 softlight = Vector4BlendTransforms.SoftLight(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector ColorDodge(ColorVector backdrop, ColorVector source) + { + Vector4 dodge = Vector4BlendTransforms.Dodge(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector ColorBurn(ColorVector backdrop, ColorVector source) + { + Vector4 burn = Vector4BlendTransforms.Burn(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Difference(ColorVector backdrop, ColorVector source) + { + Vector4 difference = Vector4BlendTransforms.Difference(backdrop.backingVector, source.backingVector); + return new ColorVector(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 ColorVector Exclusion(ColorVector backdrop, ColorVector source) + { + Vector4 exclusion = Vector4BlendTransforms.Exclusion(backdrop.backingVector, source.backingVector); + return new ColorVector(exclusion); + } + + /// + /// Linearly interpolates from one color to another based on the given weighting. + /// + /// The first color value. + /// The second color value. + /// + /// A value between 0 and 1 indicating the weight of the second source vector. + /// At amount = 0, "from" is returned, at amount = 1, "to" is returned. + /// + /// + /// The + /// + public static ColorVector Lerp(ColorVector from, ColorVector to, float amount) + { + return new ColorVector(Vector4.Lerp(from.backingVector, to.backingVector, amount)); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorVectorTests.cs b/tests/ImageSharp.Tests/Colors/ColorVectorTests.cs new file mode 100644 index 0000000000..4300b1b387 --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/ColorVectorTests.cs @@ -0,0 +1,132 @@ +// +// Copyright (c) James Jackson-South and contributors. +// Licensed under the Apache License, Version 2.0. +// + +namespace ImageSharp.Tests +{ + using System.Numerics; + using System.Runtime.CompilerServices; + + using Xunit; + + /// + /// Tests the struct. + /// + public class ColorVectorTests + { + /// + /// Tests the equality operators for equality. + /// + [Fact] + public void AreEqual() + { + ColorVector color1 = new ColorVector(0, 0, 0F); + ColorVector color2 = new ColorVector(0, 0, 0, 1F); + ColorVector color3 = ColorVector.FromHex("#000"); + ColorVector color4 = ColorVector.FromHex("#000F"); + ColorVector color5 = ColorVector.FromHex("#000000"); + ColorVector color6 = ColorVector.FromHex("#000000FF"); + + Assert.Equal(color1, color2); + Assert.Equal(color1, color3); + Assert.Equal(color1, color4); + Assert.Equal(color1, color5); + Assert.Equal(color1, color6); + } + + /// + /// Tests the equality operators for inequality. + /// + [Fact] + public void AreNotEqual() + { + ColorVector color1 = new ColorVector(1, 0, 0, 1); + ColorVector color2 = new ColorVector(0, 0, 0, 1); + ColorVector color3 = ColorVector.FromHex("#000"); + ColorVector color4 = ColorVector.FromHex("#000000"); + ColorVector color5 = ColorVector.FromHex("#FF000000"); + + Assert.NotEqual(color1, color2); + Assert.NotEqual(color1, color3); + Assert.NotEqual(color1, color4); + Assert.NotEqual(color1, color5); + } + + /// + /// Tests whether the color constructor correctly assign properties. + /// + [Fact] + public void ConstructorAssignsProperties() + { + ColorVector color1 = new ColorVector(1, .1F, .133F, .864F); + Assert.Equal(1F, color1.R); + Assert.Equal(.1F, color1.G); + Assert.Equal(.133F, color1.B); + Assert.Equal(.864F, color1.A); + + ColorVector color2 = new ColorVector(1, .1f, .133f); + Assert.Equal(1F, color2.R); + Assert.Equal(.1F, color2.G); + Assert.Equal(.133F, color2.B); + Assert.Equal(1F, color2.A); + + ColorVector color4 = new ColorVector(new Vector3(1, .1f, .133f)); + Assert.Equal(1F, color4.R); + Assert.Equal(.1F, color4.G); + Assert.Equal(.133F, color4.B); + Assert.Equal(1F, color4.A); + + ColorVector color5 = new ColorVector(new Vector4(1, .1f, .133f, .5f)); + Assert.Equal(1F, color5.R); + Assert.Equal(.1F, color5.G); + Assert.Equal(.133F, color5.B); + Assert.Equal(.5F, color5.A); + } + + /// + /// Tests whether FromHex and ToHex work correctly. + /// + [Fact] + public void FromAndToHex() + { + ColorVector color = ColorVector.FromHex("#AABBCCDD"); + Assert.Equal(170 / 255F, color.R); + Assert.Equal(187 / 255F, color.G); + Assert.Equal(204 / 255F, color.B); + Assert.Equal(221 / 255F, color.A); + + color.A = 170 / 255F; + color.B = 187 / 255F; + color.G = 204 / 255F; + color.R = 221 / 255F; + + Assert.Equal("DDCCBBAA", color.ToHex()); + + color.R = 0; + + Assert.Equal("00CCBBAA", color.ToHex()); + + color.A = 255 / 255F; + + Assert.Equal("00CCBBFF", color.ToHex()); + } + + /// + /// Tests that the individual float elements are layed out in RGBA order. + /// + [Fact] + public void FloatLayout() + { + ColorVector color = new ColorVector(1F, 2, 3, 4); + Vector4 colorBase = Unsafe.As(ref Unsafe.Add(ref color, 0)); + float[] ordered = new float[4]; + colorBase.CopyTo(ordered); + + Assert.Equal(1, ordered[0]); + Assert.Equal(2, ordered[1]); + Assert.Equal(3, ordered[2]); + Assert.Equal(4, ordered[3]); + } + } +} \ No newline at end of file diff --git a/tests/ImageSharp.Tests/Colors/ColorVectorTransformTests.cs b/tests/ImageSharp.Tests/Colors/ColorVectorTransformTests.cs new file mode 100644 index 0000000000..985e54998a --- /dev/null +++ b/tests/ImageSharp.Tests/Colors/ColorVectorTransformTests.cs @@ -0,0 +1,118 @@ +// +// 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 ColorVectorTransformTests + { + private static readonly ApproximateFloatComparer FloatComparer = new ApproximateFloatComparer(0.01F); + + /// + /// Orange backdrop + /// + private static readonly ColorVector Backdrop = new ColorVector(204, 102, 0); + + /// + /// Blue source + /// + private static readonly ColorVector Source = new ColorVector(0, 102, 153); + + [Fact] + public void Normal() + { + ColorVector normal = ColorVector.Normal(Backdrop, Source); + Assert.True(normal == Source); + } + + [Fact] + public void Multiply() + { + Assert.Equal(ColorVector.Multiply(Backdrop, ColorVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer); + Assert.Equal(ColorVector.Multiply(Backdrop, ColorVector.White).ToVector4(), ColorVector.White.ToVector4(), FloatComparer); + + ColorVector multiply = ColorVector.Multiply(Backdrop, Source); + Assert.Equal(multiply.ToVector4(), new ColorVector(0, 41, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void Screen() + { + Assert.Equal(ColorVector.Screen(Backdrop, ColorVector.Black).ToVector4(), Backdrop.ToVector4(), FloatComparer); + Assert.Equal(ColorVector.Screen(Backdrop, ColorVector.White).ToVector4(), ColorVector.White.ToVector4(), FloatComparer); + + ColorVector screen = ColorVector.Screen(Backdrop, Source); + Assert.Equal(screen.ToVector4(), new ColorVector(204, 163, 153).ToVector4(), FloatComparer); + } + + [Fact] + public void HardLight() + { + ColorVector hardLight = ColorVector.HardLight(Backdrop, Source); + Assert.Equal(hardLight.ToVector4(), new ColorVector(0, 82, 51).ToVector4(), FloatComparer); + } + + [Fact] + public void Overlay() + { + ColorVector overlay = ColorVector.Overlay(Backdrop, Source); + Assert.Equal(overlay.ToVector4(), new ColorVector(153, 82, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void Darken() + { + ColorVector darken = ColorVector.Darken(Backdrop, Source); + Assert.Equal(darken.ToVector4(), new ColorVector(0, 102, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void Lighten() + { + ColorVector lighten = ColorVector.Lighten(Backdrop, Source); + Assert.Equal(lighten.ToVector4(), new ColorVector(204, 102, 153).ToVector4(), FloatComparer); + } + + [Fact] + public void SoftLight() + { + ColorVector softLight = ColorVector.SoftLight(Backdrop, Source); + Assert.Equal(softLight.ToVector4(), new ColorVector(163, 90, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void ColorDodge() + { + ColorVector colorDodge = ColorVector.ColorDodge(Backdrop, Source); + Assert.Equal(colorDodge.ToVector4(), new ColorVector(204, 170, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void ColorBurn() + { + ColorVector colorBurn = ColorVector.ColorBurn(Backdrop, Source); + Assert.Equal(colorBurn.ToVector4(), new ColorVector(0, 0, 0).ToVector4(), FloatComparer); + } + + [Fact] + public void Difference() + { + ColorVector difference = ColorVector.Difference(Backdrop, Source); + Assert.Equal(difference.ToVector4(), new ColorVector(204, 0, 153).ToVector4(), FloatComparer); + } + + [Fact] + public void Exclusion() + { + ColorVector exclusion = ColorVector.Exclusion(Backdrop, Source); + Assert.Equal(exclusion.ToVector4(), new ColorVector(204, 122, 153).ToVector4(), FloatComparer); + } + } +}