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