diff --git a/README.md b/README.md index 02f95fd1..4114f3e3 100644 --- a/README.md +++ b/README.md @@ -233,14 +233,18 @@ var achromatopsia = unicolour.SimulateAchromatopsia(); ## Examples ✨ -This repo contains an [example project](Unicolour.Example/Program.cs) that uses `Unicolour` to generate gradients through different colour spaces -and for different colour vision deficiencies... +This repo contains an [example project](Unicolour.Example/Program.cs) that uses `Unicolour` to: +1. Generate gradients through different colour spaces +2. Render the colour spectrum with different colour vision deficiencies +3. Demonstrate interpolation with and without premultiplied alpha ![Gradients through different colour spaces generated from Unicolour](Unicolour.Example/gradients.png) ![Gradients for different colour vision deficiencies generated from Unicolour](Unicolour.Example/vision-deficiency.png) -... and a [console application](Unicolour.Console/Program.cs) that uses `Unicolour` to show colour information for a given hex value. +![Interpolation from red to transparent to blue, with and without premultiplied alpha](Unicolour.Example/alpha-interpolation.png) + +There is also a [console application](Unicolour.Console/Program.cs) that uses `Unicolour` to show colour information for a given hex value: ![Colour information from hex value](Unicolour.Console/colour-info.png) diff --git a/Unicolour.Example/Gradient.cs b/Unicolour.Example/Gradient.cs index afad73c9..55686609 100644 --- a/Unicolour.Example/Gradient.cs +++ b/Unicolour.Example/Gradient.cs @@ -7,7 +7,7 @@ internal static class Gradient { private const bool RenderOutOfGamutAsTransparent = false; - internal delegate Unicolour Mix(Unicolour start, Unicolour end, double distance); + internal delegate Unicolour Mix(Unicolour start, Unicolour end, double amount); internal static Image Draw((string text, Unicolour colour) label, int width, int height, Unicolour[] colourPoints, Mix mix) @@ -61,7 +61,8 @@ private static void SetLabel(Image image, string text, Unicolour colour) private static Rgba32 AsRgba32(Unicolour unicolour) { var (r, g, b) = unicolour.Rgb.Byte255.ConstrainedTriplet; - var a = unicolour.IsInDisplayGamut || !RenderOutOfGamutAsTransparent ? 255 : 0; + var alpha = unicolour.Alpha.A255; + var a = unicolour.IsInDisplayGamut || !RenderOutOfGamutAsTransparent ? alpha : 0; return new Rgba32((byte) r, (byte) g, (byte) b, (byte) a); } } \ No newline at end of file diff --git a/Unicolour.Example/Program.cs b/Unicolour.Example/Program.cs index efadbddc..6b095632 100644 --- a/Unicolour.Example/Program.cs +++ b/Unicolour.Example/Program.cs @@ -3,6 +3,7 @@ GenerateColourSpaceGradients(); GenerateVisionDeficiencyGradients(); +GenerateAlphaInterpolation(); return; void GenerateColourSpaceGradients() @@ -36,27 +37,27 @@ void GenerateColourSpaceGradients() Image DrawColumn(Unicolour[] colourPoints) { - var rgb = Gradient.Draw(("RGB", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixRgb(end, distance)); - var rgbLinear = Gradient.Draw(("RGB Linear", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixRgbLinear(end, distance)); - var hsb = Gradient.Draw(("HSB", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHsb(end, distance)); - var hsl = Gradient.Draw(("HSL", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHsl(end, distance)); - var hwb = Gradient.Draw(("HWB", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHwb(end, distance)); - var xyz = Gradient.Draw(("XYZ", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixXyz(end, distance)); - var xyy = Gradient.Draw(("xyY", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixXyy(end, distance)); - var lab = Gradient.Draw(("LAB", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixLab(end, distance)); - var lchab = Gradient.Draw(("LCHab", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixLchab(end, distance)); - var luv = Gradient.Draw(("LUV", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixLuv(end, distance)); - var lchuv = Gradient.Draw(("LCHuv", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixLchuv(end, distance)); - var hsluv = Gradient.Draw(("HSLuv", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHsluv(end, distance)); - var hpluv = Gradient.Draw(("HPLuv", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHpluv(end, distance)); - var ictcp = Gradient.Draw(("ICtCp", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixIctcp(end, distance)); - var jzazbz = Gradient.Draw(("JzAzBz", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixJzazbz(end, distance)); - var jzczhz = Gradient.Draw(("JzCzHz", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixJzczhz(end, distance)); - var oklab = Gradient.Draw(("OKLAB", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixOklab(end, distance)); - var oklch = Gradient.Draw(("OKLCH", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixOklch(end, distance)); - var cam02 = Gradient.Draw(("CAM02", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixCam02(end, distance)); - var cam16 = Gradient.Draw(("CAM16", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixCam16(end, distance)); - var hct = Gradient.Draw(("HCT", light), columnWidth, rowHeight, colourPoints, (start, end, distance) => start.MixHct(end, distance)); + var rgb = Gradient.Draw(("RGB", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixRgb(end, amount)); + var rgbLinear = Gradient.Draw(("RGB Linear", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixRgbLinear(end, amount)); + var hsb = Gradient.Draw(("HSB", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHsb(end, amount)); + var hsl = Gradient.Draw(("HSL", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHsl(end, amount)); + var hwb = Gradient.Draw(("HWB", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHwb(end, amount)); + var xyz = Gradient.Draw(("XYZ", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixXyz(end, amount)); + var xyy = Gradient.Draw(("xyY", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixXyy(end, amount)); + var lab = Gradient.Draw(("LAB", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixLab(end, amount)); + var lchab = Gradient.Draw(("LCHab", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixLchab(end, amount)); + var luv = Gradient.Draw(("LUV", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixLuv(end, amount)); + var lchuv = Gradient.Draw(("LCHuv", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixLchuv(end, amount)); + var hsluv = Gradient.Draw(("HSLuv", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHsluv(end, amount)); + var hpluv = Gradient.Draw(("HPLuv", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHpluv(end, amount)); + var ictcp = Gradient.Draw(("ICtCp", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixIctcp(end, amount)); + var jzazbz = Gradient.Draw(("JzAzBz", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixJzazbz(end, amount)); + var jzczhz = Gradient.Draw(("JzCzHz", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixJzczhz(end, amount)); + var oklab = Gradient.Draw(("OKLAB", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixOklab(end, amount)); + var oklch = Gradient.Draw(("OKLCH", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixOklch(end, amount)); + var cam02 = Gradient.Draw(("CAM02", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixCam02(end, amount)); + var cam16 = Gradient.Draw(("CAM16", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixCam16(end, amount)); + var hct = Gradient.Draw(("HCT", light), columnWidth, rowHeight, colourPoints, (start, end, amount) => start.MixHct(end, amount)); var columnImage = new Image(columnWidth, rowHeight * rows); columnImage.Mutate(context => context @@ -105,15 +106,15 @@ void GenerateVisionDeficiencyGradients() var dark = Unicolour.FromHex("#404046"); var none = Gradient.Draw(("No deficiency", dark), width, rowHeight, colourPoints, - (start, end, distance) => start.MixHsb(end, distance)); + (start, end, amount) => start.MixHsb(end, amount)); var protanopia = Gradient.Draw(("Protanopia", dark), width, rowHeight, colourPoints, - (start, end, distance) => start.MixHsb(end, distance).SimulateProtanopia()); + (start, end, amount) => start.MixHsb(end, amount).SimulateProtanopia()); var deuteranopia = Gradient.Draw(("Deuteranopia", dark), width, rowHeight, colourPoints, - (start, end, distance) => start.MixHsb(end, distance).SimulateDeuteranopia()); + (start, end, amount) => start.MixHsb(end, amount).SimulateDeuteranopia()); var tritanopia = Gradient.Draw(("Tritanopia", dark), width, rowHeight, colourPoints, - (start, end, distance) => start.MixHsb(end, distance).SimulateTritanopia()); + (start, end, amount) => start.MixHsb(end, amount).SimulateTritanopia()); var achromatopsia = Gradient.Draw(("Achromatopsia", dark), width, rowHeight, colourPoints, - (start, end, distance) => start.MixHsb(end, distance).SimulateAchromatopsia()); + (start, end, amount) => start.MixHsb(end, amount).SimulateAchromatopsia()); var image = new Image(width, rowHeight * rows); image.Mutate(context => context @@ -125,4 +126,32 @@ void GenerateVisionDeficiencyGradients() ); image.Save("vision-deficiency.png"); +} + +void GenerateAlphaInterpolation() +{ + const int width = 1000; + const int rows = 2; + const int rowHeight = 120; + + var colourPoints = new[] + { + Unicolour.FromRgb(1, 0, 0, 1), + Unicolour.FromRgb(0, 0, 0, 0), + Unicolour.FromRgb(0, 0, 1, 1) + }; + + var black = Unicolour.FromHex("#000000"); + var premultiplied = Gradient.Draw(("With premultiplied alpha", black), width, rowHeight, colourPoints, + (start, end, amount) => start.MixRgb(end, amount, true)); + var notPremultiplied = Gradient.Draw(("Without premultiplied alpha", black), width, rowHeight, colourPoints, + (start, end, amount) => start.MixRgb(end, amount, false)); + + var image = new Image(width, rowHeight * rows); + image.Mutate(context => context + .DrawImage(premultiplied, new Point(0, rowHeight * 0), 1f) + .DrawImage(notPremultiplied, new Point(0, rowHeight * 1), 1f) + ); + + image.Save("alpha-interpolation.png"); } \ No newline at end of file diff --git a/Unicolour.Example/alpha-interpolation.png b/Unicolour.Example/alpha-interpolation.png new file mode 100644 index 00000000..989ed2e0 Binary files /dev/null and b/Unicolour.Example/alpha-interpolation.png differ diff --git a/Unicolour.Tests/ColourTripletTests.cs b/Unicolour.Tests/ColourTripletTests.cs new file mode 100644 index 00000000..ffa49986 --- /dev/null +++ b/Unicolour.Tests/ColourTripletTests.cs @@ -0,0 +1,153 @@ +namespace Wacton.Unicolour.Tests; + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class ColourTripletTests +{ + private static readonly List GetHueTestData = new() + { + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, null), null), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 0), 7.7), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 1), null), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 2), 9.9) + }; + + private static readonly List OverrideHueTestData = new() + { + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, null), 6.6, null), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 0), 6.6, new ColourTriplet(6.6, 8.8, 9.9, 0)), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 1), 6.6, null), + new TestCaseData(new ColourTriplet(7.7, 8.8, 9.9, 2), 6.6, new ColourTriplet(7.7, 8.8, 6.6, 0)) + }; + + private static readonly List ModuloHueTestData = new() + { + new TestCaseData(new ColourTriplet(-270, 450, 810, null), new ColourTriplet(-270, 450, 810, null)), + new TestCaseData(new ColourTriplet(-270, 450, 810, 0), new ColourTriplet(90, 450, 810, 0)), + new TestCaseData(new ColourTriplet(-270, 450, 810, 1), null), + new TestCaseData(new ColourTriplet(-270, 450, 810, 2), new ColourTriplet(-270, 450, 90, 2)) + }; + + private static readonly List PremultipliedAlphaTestData = new() + { + new TestCaseData(new ColourTriplet(2, 10, -8.8, null), 0.5, new ColourTriplet(1, 5, -4.4, null)), + new TestCaseData(new ColourTriplet(2, 10, -8.8, 0), 0.5, new ColourTriplet(2, 5, -4.4, 0)), + new TestCaseData(new ColourTriplet(2, 10, -8.8, 1), 0.5, null), + new TestCaseData(new ColourTriplet(2, 10, -8.8, 2), 0.5, new ColourTriplet(1, 5, -8.8, 2)) + }; + + private static readonly List UnpremultipliedAlphaTestData = new() + { + new TestCaseData(new ColourTriplet(1, 5, -4.4, null), 0.5, new ColourTriplet(2, 10, -8.8, null)), + new TestCaseData(new ColourTriplet(1, 5, -4.4, 0), 0.5, new ColourTriplet(1, 10, -8.8, 0)), + new TestCaseData(new ColourTriplet(1, 5, -4.4, 1), 0.5, null), + new TestCaseData(new ColourTriplet(1, 5, -4.4, 2), 0.5, new ColourTriplet(2, 10, -4.4, 2)) + }; + + [TestCase(0, 0, 0)] + [TestCase(0, 0.5, 1)] + [TestCase(1, 1, 1)] + [TestCase(double.MinValue, 0.5, 0.5)] + [TestCase(0.5, double.MinValue, 0.5)] + [TestCase(0.5, 0.5, double.MinValue)] + [TestCase(double.MaxValue, 0.5, 0.5)] + [TestCase(0.5, double.MaxValue, 0.5)] + [TestCase(0.5, 0.5, double.MaxValue)] + [TestCase(double.Epsilon, 0.5, 0.5)] + [TestCase(0.5, double.Epsilon, 0.5)] + [TestCase(0.5, 0.5, double.Epsilon)] + [TestCase(double.NegativeInfinity, 0.5, 0.5)] + [TestCase(0.5, double.NegativeInfinity, 0.5)] + [TestCase(0.5, 0.5, double.NegativeInfinity)] + [TestCase(double.PositiveInfinity, 0.5, 0.5)] + [TestCase(0.5, double.PositiveInfinity, 0.5)] + [TestCase(0.5, 0.5, double.PositiveInfinity)] + [TestCase(double.NaN, 0.5, 0.5)] + [TestCase(0.5, double.NaN, 0.5)] + [TestCase(0.5, 0.5, double.NaN)] + public void AsArray(double first, double second, double third) + { + var triplet = new ColourTriplet(first, second, third); + var array = triplet.AsArray(); + Assert.That(array[0], Is.EqualTo(first)); + Assert.That(array[1], Is.EqualTo(second)); + Assert.That(array[2], Is.EqualTo(third)); + Assert.That(array[0], Is.EqualTo(triplet.First)); + Assert.That(array[1], Is.EqualTo(triplet.Second)); + Assert.That(array[2], Is.EqualTo(triplet.Third)); + } + + [TestCaseSource(nameof(GetHueTestData))] + public void GetHue(ColourTriplet triplet, double? expectedHue) + { + if (triplet.HueIndex is null or 1) + { + Assert.Throws(() => triplet.HueValue()); + return; + } + + var hue = double.NaN; + Assert.DoesNotThrow(() => hue = triplet.HueValue()); + Assert.That(hue, Is.EqualTo(expectedHue)); + } + + [TestCaseSource(nameof(OverrideHueTestData))] + public void OverrideHue(ColourTriplet triplet, double hueOverride, ColourTriplet expectedTriplet) + { + if (triplet.HueIndex is null or 1) + { + Assert.Throws(() => triplet.WithHueOverride(hueOverride)); + return; + } + + ColourTriplet hueOverrideTriplet = null!; + Assert.DoesNotThrow(() => hueOverrideTriplet = triplet.WithHueOverride(hueOverride)); + Assert.That(hueOverrideTriplet.HueValue(), Is.EqualTo(hueOverride)); + AssertUtils.AssertTriplet(hueOverrideTriplet, expectedTriplet, 0.00000000001); + } + + [TestCaseSource(nameof(ModuloHueTestData))] + public void ModuloHue(ColourTriplet triplet, ColourTriplet expectedTriplet) + { + if (triplet.HueIndex is 1) + { + Assert.Throws(() => triplet.WithHueModulo()); + return; + } + + ColourTriplet hueModuloTriplet = null!; + Assert.DoesNotThrow(() => hueModuloTriplet = triplet.WithHueModulo()); + AssertUtils.AssertTriplet(hueModuloTriplet, expectedTriplet, 0.00000000001); + } + + [TestCaseSource(nameof(PremultipliedAlphaTestData))] + public void PremultipliedAlpha(ColourTriplet triplet, double alpha, ColourTriplet expectedTriplet) + { + if (triplet.HueIndex is 1) + { + Assert.Throws(() => triplet.WithPremultipliedAlpha(alpha)); + return; + } + + ColourTriplet premultipliedAlphaTriplet = null!; + Assert.DoesNotThrow(() => premultipliedAlphaTriplet = triplet.WithPremultipliedAlpha(alpha)); + AssertUtils.AssertTriplet(premultipliedAlphaTriplet, expectedTriplet, 0.00000000001); + } + + [TestCaseSource(nameof(UnpremultipliedAlphaTestData))] + public void UnpremultipliedAlpha(ColourTriplet triplet, double alpha, ColourTriplet expectedTriplet) + { + if (triplet.HueIndex is 1) + { + Assert.Throws(() => triplet.WithUnpremultipliedAlpha(alpha)); + return; + } + + ColourTriplet unpremultipliedAlphaTriplet = null!; + Assert.DoesNotThrow(() => unpremultipliedAlphaTriplet = triplet.WithUnpremultipliedAlpha(alpha)); + AssertUtils.AssertTriplet(unpremultipliedAlphaTriplet, expectedTriplet, 0.00000000001); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/CoordinateSpaceTests.cs b/Unicolour.Tests/CoordinateSpaceTests.cs index ef01e7d1..247bd942 100644 --- a/Unicolour.Tests/CoordinateSpaceTests.cs +++ b/Unicolour.Tests/CoordinateSpaceTests.cs @@ -27,14 +27,14 @@ public class CoordinateSpaceTests private readonly ColourTriplet rgbLowerInRange = new(0, 0, 0); [Test] - public void CartesianRgbToCylindricalHsb() + public void RectangularRgbToCylindricalHsb() { AssertRgbToHsb(rgbUpperInRange, rgbUpperOutRange); AssertRgbToHsb(rgbLowerInRange, rgbLowerOutRange); } [Test] - public void CylindricalHsbToCartesianRgb() + public void CylindricalHsbToRectangularRgb() { AssertHsbToRgb(hsxUpperInRange, hsxUpperOutRange); AssertHsbToRgb(hsxLowerInRange, hsxLowerOutRange); @@ -69,28 +69,28 @@ public void CylindricalHwbToCylindricalHsb() } [Test] - public void CylindricalLchabCartesianLab() + public void CylindricalLchabRectangularLab() { AssertLchabToLab(HueUpperInRange, HueUpperOutRange); AssertLchabToLab(HueLowerInRange, HueLowerOutRange); } [Test] - public void CylindricalLchuvCartesianLuv() + public void CylindricalLchuvRectangularLuv() { AssertLchuvToLuv(HueUpperInRange, HueUpperOutRange); AssertLchuvToLuv(HueLowerInRange, HueLowerOutRange); } [Test] - public void CylindricalJzczhzCartesianJzazbz() + public void CylindricalJzczhzRectangularJzazbz() { AssertJzczhzToJzazbz(HueUpperInRange, HueUpperOutRange); AssertJzczhzToJzazbz(HueLowerInRange, HueLowerOutRange); } [Test] - public void CylindricalOklchCartesianOklab() + public void CylindricalOklchRectangularOklab() { AssertOklchToOklab(HueUpperInRange, HueUpperOutRange); AssertOklchToOklab(HueLowerInRange, HueLowerOutRange); diff --git a/Unicolour.Tests/GreyscaleTests.cs b/Unicolour.Tests/GreyscaleTests.cs index bac94b5d..e9fa37f6 100644 --- a/Unicolour.Tests/GreyscaleTests.cs +++ b/Unicolour.Tests/GreyscaleTests.cs @@ -291,7 +291,7 @@ private static void AssertInitialRepresentation(ColourRepresentation initial, bo Assert.That(initial.Heritage, Is.EqualTo(ColourHeritage.None)); Assert.That(initial.IsGreyscale, Is.EqualTo(shouldBeGreyscale)); Assert.That(initial.UseAsGreyscale, Is.EqualTo(shouldBeGreyscale)); - Assert.That(initial.UseAsHued, Is.EqualTo(initial.HasHueAxis)); + Assert.That(initial.UseAsHued, Is.EqualTo(initial.HasHueComponent)); Assert.That(initial.UseAsNaN, Is.False); } diff --git a/Unicolour.Tests/HueOverrideTests.cs b/Unicolour.Tests/HueOverrideTests.cs deleted file mode 100644 index 0f96a0dd..00000000 --- a/Unicolour.Tests/HueOverrideTests.cs +++ /dev/null @@ -1,43 +0,0 @@ -namespace Wacton.Unicolour.Tests; - -using System; -using NUnit.Framework; - -public class HueOverrideTests -{ - [Test] - public void NoHue() - { - var triplet = new ColourTriplet(7.7, 8.8, 9.9, null); - Assert.Throws(() => triplet.HueValue()); - Assert.Throws(() => triplet.WithHueOverride(6.6)); - } - - [Test] - public void FirstHue() - { - var triplet = new ColourTriplet(7.7, 8.8, 9.9, 0); - Assert.That(triplet.HueValue(), Is.EqualTo(7.7)); - var hueOverrideTriplet = triplet.WithHueOverride(6.6); - Assert.That(hueOverrideTriplet.HueValue(), Is.EqualTo(6.6)); - Assert.That(hueOverrideTriplet.Tuple, Is.EqualTo((6.6, 8.8, 9.9))); - } - - [Test] - public void SecondHue() - { - var triplet = new ColourTriplet(7.7, 8.8, 9.9, 1); - Assert.Throws(() => triplet.HueValue()); - Assert.Throws(() => triplet.WithHueOverride(6.6)); - } - - [Test] - public void ThirdHue() - { - var triplet = new ColourTriplet(7.7, 8.8, 9.9, 2); - Assert.That(triplet.HueValue(), Is.EqualTo(9.9)); - var hueOverrideTriplet = triplet.WithHueOverride(6.6); - Assert.That(hueOverrideTriplet.HueValue(), Is.EqualTo(6.6)); - Assert.That(hueOverrideTriplet.Tuple, Is.EqualTo((7.7, 8.8, 6.6))); - } -} \ No newline at end of file diff --git a/Unicolour.Tests/InvalidInterpolationTests.cs b/Unicolour.Tests/InvalidInterpolationTests.cs index 9c64dc35..0eaa0d0a 100644 --- a/Unicolour.Tests/InvalidInterpolationTests.cs +++ b/Unicolour.Tests/InvalidInterpolationTests.cs @@ -16,7 +16,7 @@ public class InvalidInterpolationTests [Test] public void InvalidInterpolationInput() { - Assert.Throws(() => Interpolation.Mix(BadColourSpace, unicolour1, unicolour2, 0.5)); + Assert.Throws(() => Interpolation.Mix(BadColourSpace, unicolour1, unicolour2, 0.5, true)); } [Test] diff --git a/Unicolour.Tests/KnownHpluvTests.cs b/Unicolour.Tests/KnownHpluvTests.cs new file mode 100644 index 00000000..00ddb349 --- /dev/null +++ b/Unicolour.Tests/KnownHpluvTests.cs @@ -0,0 +1,44 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownHpluvTests +{ + private const double Tolerance = 0.075; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(12.177, 426.75, 53.237), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(127.72, 490.15, 87.736), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(265.87, 513.41, 32.301), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0.0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(180.0, 0.0, 100.0), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownHsbTests.cs b/Unicolour.Tests/KnownHsbTests.cs new file mode 100644 index 00000000..3299ab04 --- /dev/null +++ b/Unicolour.Tests/KnownHsbTests.cs @@ -0,0 +1,44 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownHsbTests +{ + private const double Tolerance = 0.00000000001; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(0, 1.0, 1.0), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(120, 1.0, 1.0), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(240, 1.0, 1.0), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(0, 0.0, 1.0), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownHslTests.cs b/Unicolour.Tests/KnownHslTests.cs new file mode 100644 index 00000000..8aa66eaf --- /dev/null +++ b/Unicolour.Tests/KnownHslTests.cs @@ -0,0 +1,44 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownHslTests +{ + private const double Tolerance = 0.00000000001; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(0, 1.0, 0.5), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(120, 1.0, 0.5), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(240, 1.0, 0.5), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(0, 0.0, 1.0), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownHsluvTests.cs b/Unicolour.Tests/KnownHsluvTests.cs index 36946f78..741b343d 100644 --- a/Unicolour.Tests/KnownHsluvTests.cs +++ b/Unicolour.Tests/KnownHsluvTests.cs @@ -5,6 +5,43 @@ public class KnownHsluvTests { + private const double Tolerance = 0.05; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(12.177, 100.00, 53.237), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(127.72, 100.00, 87.736), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(265.87, 100.00, 32.301), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0.0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(180.0, 0.0, 100.0), Tolerance); + } + /* * note that at each step of the conversion process, more tolerance is required to match the HSLuv test data set * this divergence is likely due to slight differences in the initial RGB -> XYZ matrix used @@ -24,15 +61,15 @@ public void SnapshotTestColour(TestColour testColour) var hpluv = HandleNoHue(unicolour.Hpluv.Triplet, unicolour.Hpluv.UseAsHued); // accuracy drops off when saturation goes beyond 100% (mostly at 1,500%+), so be slightly more tolerant for larger values - Assert(unicolour.Rgb.Triplet, testColour.Rgb!, 0.00000000001, info); - Assert(unicolour.Xyz.Triplet, testColour.Xyz!, 0.0005, info); - Assert(unicolour.Luv.Triplet, testColour.Luv!, 0.025, info); - Assert(lchuv, testColour.Lchuv!, 0.25, info); - Assert(hsluv, testColour.Hsluv!, tolerance: 0.06, info); - Assert(hpluv, testColour.Hpluv!, tolerance: hpluv.Second > 100 ? 0.135 : 0.06, info); + AssertSnapshot(unicolour.Rgb.Triplet, testColour.Rgb!, 0.00000000001, info); + AssertSnapshot(unicolour.Xyz.Triplet, testColour.Xyz!, 0.0005, info); + AssertSnapshot(unicolour.Luv.Triplet, testColour.Luv!, 0.025, info); + AssertSnapshot(lchuv, testColour.Lchuv!, 0.25, info); + AssertSnapshot(hsluv, testColour.Hsluv!, tolerance: 0.06, info); + AssertSnapshot(hpluv, testColour.Hpluv!, tolerance: hpluv.Second > 100 ? 0.135 : 0.06, info); } - private static void Assert(ColourTriplet actual, ColourTriplet expected, double tolerance, string info) + private static void AssertSnapshot(ColourTriplet actual, ColourTriplet expected, double tolerance, string info) { AssertUtils.AssertTriplet(actual, expected, tolerance, info); } diff --git a/Unicolour.Tests/KnownHwbTests.cs b/Unicolour.Tests/KnownHwbTests.cs new file mode 100644 index 00000000..dbb93b5b --- /dev/null +++ b/Unicolour.Tests/KnownHwbTests.cs @@ -0,0 +1,44 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownHwbTests +{ + private const double Tolerance = 0.00000000001; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(0, 0.0, 0.0), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(120, 0.0, 0.0), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(240, 0.0, 0.0), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0, 0.0, 1.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(0, 1.0, 0.0), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownIctcpTests.cs b/Unicolour.Tests/KnownIctcpTests.cs new file mode 100644 index 00000000..20d4c341 --- /dev/null +++ b/Unicolour.Tests/KnownIctcpTests.cs @@ -0,0 +1,45 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownIctcpTests +{ + private const double Tolerance = 0.0005; + private static readonly Configuration Ictcp203Config = new(ictcpScalar: 203); + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(Ictcp203Config); + AssertUtils.AssertTriplet(red, new(0.42785, -0.11574, 0.2788), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(Ictcp203Config); + AssertUtils.AssertTriplet(green, new(0.53975, -0.28121, -0.04946), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(Ictcp203Config); + AssertUtils.AssertTriplet(blue, new(0.35607, 0.26914, -0.16143), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(Ictcp203Config); + AssertUtils.AssertTriplet(black, new(0.00000, 0.00000, 0.00000), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(Ictcp203Config); + AssertUtils.AssertTriplet(white, new(0.58069, 0.00000, 0.00000), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownJzazbzTests.cs b/Unicolour.Tests/KnownJzazbzTests.cs new file mode 100644 index 00000000..54e3dbf9 --- /dev/null +++ b/Unicolour.Tests/KnownJzazbzTests.cs @@ -0,0 +1,45 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownJzazbzTests +{ + private const double Tolerance = 0.0005; + private static readonly Configuration Jzazbz203Config = new(jzazbzScalar: 203); + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(red, new(0.13438, 0.11789, 0.11188), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(green, new(0.17681, -0.10904, 0.11899), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(blue, new(0.09577, -0.04085, -0.18585), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(black, new(0.00000, 0.00000, 0.00000), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(white, new(0.22207, -0.00016, -0.00012), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownJzczhzTests.cs b/Unicolour.Tests/KnownJzczhzTests.cs new file mode 100644 index 00000000..279b2869 --- /dev/null +++ b/Unicolour.Tests/KnownJzczhzTests.cs @@ -0,0 +1,47 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownJzczhzTests +{ + private const double Tolerance = 0.0005; + private static readonly Configuration Jzazbz203Config = new(jzazbzScalar: 203); + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(red, new(0.13438, 0.16252, 43.502), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(green, new(0.17681, 0.1614, 132.5), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(Jzazbz203Config); + AssertUtils.AssertTriplet(blue, new(0.09577, 0.19029, 257.61), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(Jzazbz203Config); + Assert.That(black.Jzczhz.J, Is.EqualTo(0.0).Within(Tolerance)); + Assert.That(black.Jzczhz.C, Is.EqualTo(0.0).Within(Tolerance)); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(Jzazbz203Config); + Assert.That(white.Jzczhz.J, Is.EqualTo(0.22207).Within(Tolerance)); + Assert.That(white.Jzczhz.C, Is.EqualTo(0.0002).Within(Tolerance)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownLabTests.cs b/Unicolour.Tests/KnownLabTests.cs new file mode 100644 index 00000000..8677f39d --- /dev/null +++ b/Unicolour.Tests/KnownLabTests.cs @@ -0,0 +1,49 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownLabTests +{ + private const double Tolerance = 0.00005; + + [TestCase(Illuminant.D65, 53.2408, 80.0925, 67.2032)] + [TestCase(Illuminant.D50, 54.2917, 80.8125, 69.8851)] + public void Red(Illuminant illuminant, double l, double a, double b) + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(l, a, b), Tolerance); + } + + [TestCase(Illuminant.D65, 87.7347, -86.1827, 83.1793)] + [TestCase(Illuminant.D50, 87.8181, -79.2873, 80.9902)] + public void Green(Illuminant illuminant, double l, double a, double b) + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(l, a, b), Tolerance); + } + + [TestCase(Illuminant.D65, 32.2970, 79.1875, -107.8602)] + [TestCase(Illuminant.D50, 29.5676, 68.2986, -112.0294)] + public void Blue(Illuminant illuminant, double l, double a, double b) + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(l, a, b), Tolerance); + } + + [TestCase(Illuminant.D65, 0.0, 0.0, 0.0)] + [TestCase(Illuminant.D50, 0.0, 0.0, 0.0)] + public void Black(Illuminant illuminant, double l, double a, double b) + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(black, new(l, a, b), Tolerance); + } + + [TestCase(Illuminant.D65, 100.0000, 0.0, 0.0)] + [TestCase(Illuminant.D50, 100.0000, 0.0, 0.0)] + public void White(Illuminant illuminant, double l, double a, double b) + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(white, new(l, a, b), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownLchabTests.cs b/Unicolour.Tests/KnownLchabTests.cs new file mode 100644 index 00000000..f937fdcb --- /dev/null +++ b/Unicolour.Tests/KnownLchabTests.cs @@ -0,0 +1,49 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownLchabTests +{ + private const double Tolerance = 0.00005; + + [TestCase(Illuminant.D65, 53.2408, 104.5518, 39.9990)] + [TestCase(Illuminant.D50, 54.2917, 106.8390, 40.8526)] + public void Red(Illuminant illuminant, double l, double c, double h) + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 87.7347, 119.7759, 136.0160)] + [TestCase(Illuminant.D50, 87.8181, 113.3397, 134.3912)] + public void Green(Illuminant illuminant, double l, double c, double h) + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 32.2970, 133.8076, 306.2849)] + [TestCase(Illuminant.D50, 29.5676, 131.2070, 301.3685)] + public void Blue(Illuminant illuminant, double l, double c, double h) + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 0.0, 0.0, 0.0)] + [TestCase(Illuminant.D50, 0.0, 0.0, 0.0)] + public void Black(Illuminant illuminant, double l, double c, double h) + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(black, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 100.0000, 0.0, 270.0)] + [TestCase(Illuminant.D50, 100.0000, 0.0, 0.0)] + public void White(Illuminant illuminant, double l, double c, double h) + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(white, new(l, c, h), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownLchuvTests.cs b/Unicolour.Tests/KnownLchuvTests.cs new file mode 100644 index 00000000..e0a91bd6 --- /dev/null +++ b/Unicolour.Tests/KnownLchuvTests.cs @@ -0,0 +1,50 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownLchuvTests +{ + private const double Tolerance = 0.00005; + + [TestCase(Illuminant.D65, 53.2408, 179.0414, 12.1740)] + [TestCase(Illuminant.D50, 54.2917, 176.9569, 8.4352)] + public void Red(Illuminant illuminant, double l, double c, double h) + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 87.7347, 135.7804, 127.7236)] + [TestCase(Illuminant.D50, 87.8181, 121.7606, 134.2323)] + public void Green(Illuminant illuminant, double l, double c, double h) + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 32.2970, 130.6812, 265.8727)] + [TestCase(Illuminant.D50, 29.5676, 122.5132, 264.5953)] + public void Blue(Illuminant illuminant, double l, double c, double h) + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 0.0, 0.0, 0.0)] + [TestCase(Illuminant.D50, 0.0, 0.0, 0.0)] + public void Black(Illuminant illuminant, double l, double c, double h) + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(black, new(l, c, h), Tolerance); + } + + [TestCase(Illuminant.D65, 100.0000, 0.0)] + [TestCase(Illuminant.D50, 100.0000, 0.0)] + public void White(Illuminant illuminant, double l, double c) + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + Assert.That(white.Lchuv.L, Is.EqualTo(l).Within(Tolerance)); + Assert.That(white.Lchuv.C, Is.EqualTo(c).Within(Tolerance)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownLuvTests.cs b/Unicolour.Tests/KnownLuvTests.cs new file mode 100644 index 00000000..7ccc0792 --- /dev/null +++ b/Unicolour.Tests/KnownLuvTests.cs @@ -0,0 +1,49 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownLuvTests +{ + private const double Tolerance = 0.00005; + + [TestCase(Illuminant.D65, 53.2408, 175.0151, 37.7564)] + [TestCase(Illuminant.D50, 54.2917, 175.0426, 25.9580)] + public void Red(Illuminant illuminant, double l, double u, double v) + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(l, u, v), Tolerance); + } + + [TestCase(Illuminant.D65, 87.7347, -83.0776, 107.3985)] + [TestCase(Illuminant.D50, 87.8181, -84.9365, 87.2436)] + public void Green(Illuminant illuminant, double l, double u, double v) + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(l, u, v), Tolerance); + } + + [TestCase(Illuminant.D65, 32.2970, -9.4054, -130.3423)] + [TestCase(Illuminant.D50, 29.5676, -11.5396, -121.9686)] + public void Blue(Illuminant illuminant, double l, double u, double v) + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(l, u, v), Tolerance); + } + + [TestCase(Illuminant.D65, 0.0, 0.0, 0.0)] + [TestCase(Illuminant.D50, 0.0, 0.0, 0.0)] + public void Black(Illuminant illuminant, double l, double u, double v) + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(black, new(l, u, v), Tolerance); + } + + [TestCase(Illuminant.D65, 100.0000, 0.0, 0.0)] + [TestCase(Illuminant.D50, 100.0000, 0.0, 0.0)] + public void White(Illuminant illuminant, double l, double u, double v) + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(white, new(l, u, v), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownOklabTests.cs b/Unicolour.Tests/KnownOklabTests.cs index 4f40d44c..d3ae1774 100644 --- a/Unicolour.Tests/KnownOklabTests.cs +++ b/Unicolour.Tests/KnownOklabTests.cs @@ -3,11 +3,49 @@ using System; using System.Collections.Generic; using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; -// Oklab actually provides test values 🙌 (https://bottosson.github.io/posts/oklab/#table-of-example-xyz-and-oklab-pairs) -// so it has its own dedicated set of tests based on those public class KnownOklabTests { + private const double Tolerance = 0.0005; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(0.62796, 0.22486, 0.12585), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(0.86644, -0.23389, 0.1795), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(0.45201, -0.03246, -0.31153), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0.0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + AssertUtils.AssertTriplet(white, new(1.0, 0.0, 0.0), Tolerance); + } + + // Oklab actually provides test values 🙌 (https://bottosson.github.io/posts/oklab/#table-of-example-xyz-and-oklab-pairs) + // so it has its own dedicated set of tests based on those private static readonly List<(ColourTriplet xyz, ColourTriplet expectedOklab)> TestData = new() { (new(0.950, 1.000, 1.089), new(1.000, 0.000, 0.000)), diff --git a/Unicolour.Tests/KnownOklchTests.cs b/Unicolour.Tests/KnownOklchTests.cs new file mode 100644 index 00000000..c2498e54 --- /dev/null +++ b/Unicolour.Tests/KnownOklchTests.cs @@ -0,0 +1,45 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownOklchTests +{ + private const double Tolerance = 0.0005; + + [Test] + public void Red() + { + var red = ColourLimits.Rgb[ColourLimit.Red]; + AssertUtils.AssertTriplet(red, new(0.62796, 0.25768, 29.23), Tolerance); + } + + [Test] + public void Green() + { + var green = ColourLimits.Rgb[ColourLimit.Green]; + AssertUtils.AssertTriplet(green, new(0.86644, 0.29483, 142.50), Tolerance); + } + + [Test] + public void Blue() + { + var blue = ColourLimits.Rgb[ColourLimit.Blue]; + AssertUtils.AssertTriplet(blue, new(0.45201, 0.31321, 264.05), Tolerance); + } + + [Test] + public void Black() + { + var black = ColourLimits.Rgb[ColourLimit.Black]; + AssertUtils.AssertTriplet(black, new(0.0, 0.0, 0.0), Tolerance); + } + + [Test] + public void White() + { + var white = ColourLimits.Rgb[ColourLimit.White]; + Assert.That(white.Oklch.L, Is.EqualTo(1.0).Within(Tolerance)); + Assert.That(white.Oklch.C, Is.EqualTo(0.0).Within(Tolerance)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownRgbLinearTests.cs b/Unicolour.Tests/KnownRgbLinearTests.cs new file mode 100644 index 00000000..95d40d96 --- /dev/null +++ b/Unicolour.Tests/KnownRgbLinearTests.cs @@ -0,0 +1,81 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownRgbLinearTests +{ + private const double Tolerance = 0.0005; + + [TestCase(ConfigUtils.RgbType.Standard, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Standard, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.Standard, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.5, 0.705)] + [TestCase(ConfigUtils.RgbType.Rec2020, 1.0, 1.0)] + public void Red(ConfigUtils.RgbType rgbType, double linear, double nonlinear) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var red = Unicolour.FromRgbLinear(config, linear, 0, 0); + AssertUtils.AssertTriplet(red.Rgb.Triplet, new(nonlinear, 0, 0), Tolerance); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Standard, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.Standard, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.5, 0.705)] + [TestCase(ConfigUtils.RgbType.Rec2020, 1.0, 1.0)] + public void Green(ConfigUtils.RgbType rgbType, double linear, double nonlinear) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var green = Unicolour.FromRgbLinear(config, 0, linear, 0); + AssertUtils.AssertTriplet(green.Rgb.Triplet, new(0, nonlinear, 0), Tolerance); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Standard, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.Standard, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.5, 0.735)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 1.0, 1.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.0, 0.0)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.5, 0.705)] + [TestCase(ConfigUtils.RgbType.Rec2020, 1.0, 1.0)] + public void Blue(ConfigUtils.RgbType rgbType, double linear, double nonlinear) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var blue = Unicolour.FromRgbLinear(config, 0, 0, linear); + AssertUtils.AssertTriplet(blue.Rgb.Triplet, new(0, 0, nonlinear), Tolerance); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.3127, 0.3290)] + public void Black(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var black = Unicolour.FromRgbLinear(config, 0, 0, 0); + Assert.That(black.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(0.00005)); + Assert.That(black.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(0.00005)); + Assert.That(black.Xyy.Luminance, Is.EqualTo(0).Within(Tolerance)); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.3127, 0.3290)] + public void White(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var white = Unicolour.FromRgbLinear(config, 1, 1, 1); + Assert.That(white.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(0.00005)); + Assert.That(white.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(0.00005)); + Assert.That(white.Xyy.Luminance, Is.EqualTo(1).Within(Tolerance)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownRgbTests.cs b/Unicolour.Tests/KnownRgbTests.cs new file mode 100644 index 00000000..861d2c83 --- /dev/null +++ b/Unicolour.Tests/KnownRgbTests.cs @@ -0,0 +1,66 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownRgbTests +{ + private const double Tolerance = 0.00000000001; + + [TestCase(ConfigUtils.RgbType.Standard, 0.640, 0.330)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.680, 0.320)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.708, 0.292)] + public void Red(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var red = Unicolour.FromRgb(config, 1, 0, 0); + Assert.That(red.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(Tolerance)); + Assert.That(red.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(Tolerance)); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.300, 0.600)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.265, 0.690)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.170, 0.797)] + public void Green(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var green = Unicolour.FromRgb(config, 0, 1, 0); + Assert.That(green.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(Tolerance)); + Assert.That(green.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(Tolerance)); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.150, 0.060)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.150, 0.060)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.131, 0.046)] + public void Blue(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var blue = Unicolour.FromRgb(config, 0, 0, 1); + Assert.That(blue.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(Tolerance)); + Assert.That(blue.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(Tolerance)); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.3127, 0.3290)] + public void Black(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var black = Unicolour.FromRgb(config, 0, 0, 0); + Assert.That(black.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(0.00005)); + Assert.That(black.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(0.00005)); + Assert.That(black.Xyy.Luminance, Is.EqualTo(0).Within(Tolerance)); + } + + [TestCase(ConfigUtils.RgbType.Standard, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.DisplayP3, 0.3127, 0.3290)] + [TestCase(ConfigUtils.RgbType.Rec2020, 0.3127, 0.3290)] + public void White(ConfigUtils.RgbType rgbType, double expectedX, double expectedY) + { + var config = ConfigUtils.GetConfigWithXyzD65(rgbType); + var white = Unicolour.FromRgb(config, 1, 1, 1); + Assert.That(white.Xyy.Chromaticity.X, Is.EqualTo(expectedX).Within(0.00005)); + Assert.That(white.Xyy.Chromaticity.Y, Is.EqualTo(expectedY).Within(0.00005)); + Assert.That(white.Xyy.Luminance, Is.EqualTo(1).Within(Tolerance)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/KnownXyyTests.cs b/Unicolour.Tests/KnownXyyTests.cs index 48c01c88..8fd573b4 100644 --- a/Unicolour.Tests/KnownXyyTests.cs +++ b/Unicolour.Tests/KnownXyyTests.cs @@ -3,49 +3,70 @@ using NUnit.Framework; using Wacton.Unicolour.Tests.Utils; -// XYY has special handling for black, since otherwise it would result in a divide-by-zero -// some implementations would set all values to zero -// but I think it's more intuitive to set chromaticity to the same as white, and set only luminance to 0 public class KnownXyyTests { - private const double Tolerance = 0.000001; - - [TestCase(Illuminant.D65, 0.312727, 0.329023, 1.000000)] - [TestCase(Illuminant.D50, 0.345669, 0.358496, 1.000000)] - [TestCase(Illuminant.E, 0.333333, 0.333333, 1.000000)] - public void White(Illuminant illuminant, double expectedX, double expectedY, double expectedLuminance) + private const double Tolerance = 0.0000005; + + [TestCase(Illuminant.D65, 0.640000, 0.330000, 0.212673)] + [TestCase(Illuminant.D50, 0.648427, 0.330856, 0.222504)] + public void Red(Illuminant illuminant, double x, double y, double expectedZ) { - var unicolour = Unicolour.FromRgb(GetConfig(illuminant), 1, 1, 1); - AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(expectedX, expectedY, expectedLuminance), Tolerance); + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(x, y, expectedZ), Tolerance); } - [TestCase(Illuminant.D65, 0.312727, 0.329023, 0.214041)] - [TestCase(Illuminant.D50, 0.345669, 0.358496, 0.214041)] - [TestCase(Illuminant.E, 0.333333, 0.333333, 0.214041)] - public void Grey(Illuminant illuminant, double expectedX, double expectedY, double expectedLuminance) + [TestCase(Illuminant.D65, 0.300000, 0.600000, 0.715152)] + [TestCase(Illuminant.D50, 0.321142, 0.597873, 0.716879)] + public void Green(Illuminant illuminant, double x, double y, double expectedZ) { - var unicolour = Unicolour.FromRgb(GetConfig(illuminant), 0.5, 0.5, 0.5); - AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(expectedX, expectedY, expectedLuminance), Tolerance); + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(x, y, expectedZ), Tolerance); } - [TestCase(Illuminant.D65, 0.312727, 0.329023, 0.000001)] - [TestCase(Illuminant.D50, 0.345669, 0.358496, 0.000001)] - [TestCase(Illuminant.E, 0.333333, 0.333333, 0.000001)] - public void NearBlack(Illuminant illuminant, double expectedX, double expectedY, double expectedLuminance) + [TestCase(Illuminant.D65, 0.150000, 0.060000, 0.072175)] + [TestCase(Illuminant.D50, 0.155883, 0.066041, 0.060617)] + public void Blue(Illuminant illuminant, double x, double y, double expectedZ) { - var unicolour = Unicolour.FromRgb(GetConfig(illuminant), 0.00001, 0.00001, 0.00001); - AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(expectedX, expectedY, expectedLuminance), Tolerance); + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(x, y, expectedZ), Tolerance); } [TestCase(Illuminant.D65, 0.312727, 0.329023, 0.000000)] [TestCase(Illuminant.D50, 0.345669, 0.358496, 0.000000)] [TestCase(Illuminant.E, 0.333333, 0.333333, 0.000000)] - public void Black(Illuminant illuminant, double expectedX, double expectedY, double expectedLuminance) + public void Black(Illuminant illuminant, double x, double y, double luminance) + { + var unicolour = Unicolour.FromRgb(ConfigUtils.GetConfigWithStandardRgb(illuminant), 0, 0, 0); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + } + + [TestCase(Illuminant.D65, 0.312727, 0.329023, 0.000001)] + [TestCase(Illuminant.D50, 0.345669, 0.358496, 0.000001)] + [TestCase(Illuminant.E, 0.333333, 0.333333, 0.000001)] + public void NearBlack(Illuminant illuminant, double x, double y, double luminance) + { + var unicolour = Unicolour.FromRgb(ConfigUtils.GetConfigWithStandardRgb(illuminant), 0.00001, 0.00001, 0.00001); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + } + + [TestCase(Illuminant.D65, 0.312727, 0.329023, 0.214041)] + [TestCase(Illuminant.D50, 0.345669, 0.358496, 0.214041)] + [TestCase(Illuminant.E, 0.333333, 0.333333, 0.214041)] + public void Grey(Illuminant illuminant, double x, double y, double luminance) { - var unicolour = Unicolour.FromRgb(GetConfig(illuminant), 0, 0, 0); - AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(expectedX, expectedY, expectedLuminance), Tolerance); + var unicolour = Unicolour.FromRgb(ConfigUtils.GetConfigWithStandardRgb(illuminant), 0.5, 0.5, 0.5); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); } + [TestCase(Illuminant.D65, 0.312727, 0.329023, 1.000000)] + [TestCase(Illuminant.D50, 0.345669, 0.358496, 1.000000)] + [TestCase(Illuminant.E, 0.333333, 0.333333, 1.000000)] + public void White(Illuminant illuminant, double x, double y, double luminance) + { + var unicolour = Unicolour.FromRgb(ConfigUtils.GetConfigWithStandardRgb(illuminant), 1, 1, 1); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + } + [TestCase(-0.00000000001)] [TestCase(0)] [TestCase(0.00000000001)] @@ -59,10 +80,4 @@ public void ChromaticityY(double chromaticityY) Assert.That(xyz.Y, useZero ? Is.EqualTo(0) : Is.GreaterThan(0)); Assert.That(xyz.Z, useZero ? Is.EqualTo(0) : Is.GreaterThan(0)); } - - private static Configuration GetConfig(Illuminant illuminant) - { - var xyzConfig = new XyzConfiguration(WhitePoint.From(illuminant)); - return new Configuration(RgbConfiguration.StandardRgb, xyzConfig); - } } \ No newline at end of file diff --git a/Unicolour.Tests/KnownXyzTests.cs b/Unicolour.Tests/KnownXyzTests.cs new file mode 100644 index 00000000..e9421561 --- /dev/null +++ b/Unicolour.Tests/KnownXyzTests.cs @@ -0,0 +1,76 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class KnownXyzTests +{ + private const double Tolerance = 0.0000005; + + [TestCase(Illuminant.D65, 0.412456, 0.212673, 0.019334)] + [TestCase(Illuminant.D50, 0.436075, 0.222504, 0.013932)] + public void Red(Illuminant illuminant, double x, double y, double z) + { + var red = ColourLimits.Rgb[ColourLimit.Red].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(red, new(x, y, z), Tolerance); + } + + [TestCase(Illuminant.D65, 0.357576, 0.715152, 0.119192)] + [TestCase(Illuminant.D50, 0.385065, 0.716879, 0.097105)] + public void Green(Illuminant illuminant, double x, double y, double z) + { + var green = ColourLimits.Rgb[ColourLimit.Green].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(green, new(x, y, z), Tolerance); + } + + [TestCase(Illuminant.D65, 0.180437, 0.072175, 0.950304)] + [TestCase(Illuminant.D50, 0.143080, 0.060617, 0.714173)] + public void Blue(Illuminant illuminant, double x, double y, double z) + { + var blue = ColourLimits.Rgb[ColourLimit.Blue].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(blue, new(x, y, z), Tolerance); + } + + [TestCase(Illuminant.D65, 0.0, 0.0, 0.0)] + [TestCase(Illuminant.D50, 0.0, 0.0, 0.0)] + public void Black(Illuminant illuminant, double x, double y, double z) + { + var black = ColourLimits.Rgb[ColourLimit.Black].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(black, new(x, y, z), Tolerance); + } + + [TestCase(Illuminant.D65, 0.950470, 1.000000, 1.088830)] + [TestCase(Illuminant.D50, 0.964220, 1.000000, 0.825210)] + public void White(Illuminant illuminant, double x, double y, double z) + { + var white = ColourLimits.Rgb[ColourLimit.White].ConvertToConfiguration(ConfigUtils.GetConfigWithStandardRgb(illuminant)); + AssertUtils.AssertTriplet(white, new(x, y, z), Tolerance); + } + + [TestCase(Illuminant.D65, 0.312727, 0.329023)] + [TestCase(Illuminant.D50, 0.345669, 0.358496)] + [TestCase(Illuminant.E, 0.333333, 0.333333)] + public void BlackChromaticity(Illuminant illuminant, double x, double y) + { + var unicolour = Unicolour.FromXyz(ConfigUtils.GetConfigWithStandardRgb(illuminant), 0.0, 0.0, 0.0); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 0.0), Tolerance); + } + + [TestCase(Illuminant.D65, 0.333333, 0.333333)] + [TestCase(Illuminant.D50, 0.333333, 0.333333)] + [TestCase(Illuminant.E, 0.333333, 0.333333)] + public void GreyChromaticity(Illuminant illuminant, double x, double y) + { + var unicolour = Unicolour.FromXyz(ConfigUtils.GetConfigWithStandardRgb(illuminant), 0.5, 0.5, 0.5); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 0.5), Tolerance); + } + + [TestCase(Illuminant.D65, 0.333333, 0.333333)] + [TestCase(Illuminant.D50, 0.333333, 0.333333)] + [TestCase(Illuminant.E, 0.333333, 0.333333)] + public void WhiteChromaticity(Illuminant illuminant, double x, double y) + { + var unicolour = Unicolour.FromXyz(ConfigUtils.GetConfigWithStandardRgb(illuminant), 1, 1, 1); + AssertUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 1.0), Tolerance); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/LazyEvaluationTests.cs b/Unicolour.Tests/LazyEvaluationTests.cs index ce469a7b..5b0dfba5 100644 --- a/Unicolour.Tests/LazyEvaluationTests.cs +++ b/Unicolour.Tests/LazyEvaluationTests.cs @@ -63,7 +63,7 @@ public void AfterMix(Func unicolourFunction) var unicolour = unicolourFunction(); var other = unicolourFunction(); var initialColourSpace = unicolour.InitialColourSpace; - _ = Interpolation.Mix(initialColourSpace, unicolour, other, 0.5); + _ = Interpolation.Mix(initialColourSpace, unicolour, other, 0.5, true); AssertBackingFields(unicolour); } diff --git a/Unicolour.Tests/MixAlphaTests.cs b/Unicolour.Tests/MixAlphaTests.cs new file mode 100644 index 00000000..fb6a1f73 --- /dev/null +++ b/Unicolour.Tests/MixAlphaTests.cs @@ -0,0 +1,112 @@ +namespace Wacton.Unicolour.Tests; + +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +public class MixAlphaTests +{ + [Test] // https://www.w3.org/TR/css-color-4/#ex-gradient-transition-premultiply + public void RgbRedToTransparent() + { + var red = Unicolour.FromRgb(1, 0, 0, alpha: 1); + var transparentBlack = Unicolour.FromRgb(0, 0, 0, alpha: 0); + + var notPremultipliedAlpha1 = red.MixRgb(transparentBlack, premultiplyAlpha: false); + var notPremultipliedAlpha2 = transparentBlack.MixRgb(red, premultiplyAlpha: false); + var withPremultipliedAlpha1 = red.MixRgb(transparentBlack); + var withPremultipliedAlpha2 = transparentBlack.MixRgb(red); + + AssertUtils.AssertTriplet(notPremultipliedAlpha1, new(0.5, 0, 0), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(notPremultipliedAlpha2, new(0.5, 0, 0), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha1, new(1.0, 0, 0), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha2, new(1.0, 0, 0), AssertUtils.MixTolerance); + } + + [Test] // https://www.w3.org/TR/css-color-4/#ex-gradient-transition-premultiply + public void RgbTransparentToBlue() + { + var transparentBlack = Unicolour.FromRgb(0, 0, 0, alpha: 0); + var blue = Unicolour.FromRgb(0, 0, 1, alpha: 1); + + var notPremultipliedAlpha1 = transparentBlack.MixRgb(blue, premultiplyAlpha: false); + var notPremultipliedAlpha2 = blue.MixRgb(transparentBlack, premultiplyAlpha: false); + var withPremultipliedAlpha1 = transparentBlack.MixRgb(blue); + var withPremultipliedAlpha2 = blue.MixRgb(transparentBlack); + + AssertUtils.AssertTriplet(notPremultipliedAlpha1, new(0, 0, 0.5), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(notPremultipliedAlpha2, new(0, 0, 0.5), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha1, new(0, 0, 1.0), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha2, new(0, 0, 1.0), AssertUtils.MixTolerance); + } + + [Test] // https://www.w3.org/TR/css-color-4/#ex-premultiplied-srgb + public void RgbPurpleToPink() + { + var purple = Unicolour.FromRgb(0.24, 0.12, 0.98, alpha: 0.4); + var pink = Unicolour.FromRgb(0.62, 0.26, 0.64, alpha: 0.6); + + var notPremultipliedAlpha1 = purple.MixRgb(pink, premultiplyAlpha: false); + var notPremultipliedAlpha2 = pink.MixRgb(purple, premultiplyAlpha: false); + var withPremultipliedAlpha1 = purple.MixRgb(pink); + var withPremultipliedAlpha2 = pink.MixRgb(purple); + + AssertUtils.AssertTriplet(notPremultipliedAlpha1, new(0.43, 0.19, 0.81), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(notPremultipliedAlpha2, new(0.43, 0.19, 0.81), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha1, new(0.468, 0.204, 0.776), AssertUtils.MixTolerance); + AssertUtils.AssertTriplet(withPremultipliedAlpha2, new(0.468, 0.204, 0.776), AssertUtils.MixTolerance); + } + + [Test] // https://www.w3.org/TR/css-color-4/#ex-premultiplied-lab + public void LabYellowToPink() + { + var yellowConfig = new Configuration(RgbConfiguration.StandardRgb, XyzConfiguration.D50); + var pinkConfig = new Configuration(RgbConfiguration.DisplayP3, XyzConfiguration.D50); + var yellow = Unicolour.FromRgb(yellowConfig, 0.76, 0.62, 0.03, alpha: 0.4); + var pink = Unicolour.FromRgb(pinkConfig, 0.84, 0.19, 0.72, alpha: 0.6); + AssertUtils.AssertTriplet(yellow, new(66.93, 4.87, 68.62), 0.05); + AssertUtils.AssertTriplet(pink, new(53.50, 82.67, -33.90), 0.05); + + // unicolour does not support interpolation between colours of different configurations + // (because not clear what the result colour's configuration should be) + // so workaround: create new unicolours with same configurations with the LAB values + + yellow = Unicolour.FromLab(yellow.Lab.Triplet.Tuple, yellow.Alpha.A); + pink = Unicolour.FromLab(pink.Lab.Triplet.Tuple, pink.Alpha.A); + var notPremultipliedAlpha1 = yellow.MixLab(pink, premultiplyAlpha: false); + var notPremultipliedAlpha2 = pink.MixLab(yellow, premultiplyAlpha: false); + var withPremultipliedAlpha1 = yellow.MixLab(pink); + var withPremultipliedAlpha2 = pink.MixLab(yellow); + + AssertUtils.AssertTriplet(notPremultipliedAlpha1, new(60.22, 43.77, 17.36), 0.05); + AssertUtils.AssertTriplet(notPremultipliedAlpha2, new(60.22, 43.77, 17.36), 0.05); + AssertUtils.AssertTriplet(withPremultipliedAlpha1, new(58.87, 51.55, 7.11), 0.05); + AssertUtils.AssertTriplet(withPremultipliedAlpha2, new(58.87, 51.55, 7.11), 0.05); + } + + [Test] // https://www.w3.org/TR/css-color-4/#ex-premultiplied-lch + public void LchabYellowToPink() + { + var yellowConfig = new Configuration(RgbConfiguration.StandardRgb, XyzConfiguration.D50); + var pinkConfig = new Configuration(RgbConfiguration.DisplayP3, XyzConfiguration.D50); + var yellow = Unicolour.FromRgb(yellowConfig, 0.76, 0.62, 0.03, alpha: 0.4); + var pink = Unicolour.FromRgb(pinkConfig, 0.84, 0.19, 0.72, alpha: 0.6); + AssertUtils.AssertTriplet(yellow, new(66.93, 68.79, 85.94), 0.05); + AssertUtils.AssertTriplet(pink, new(53.50, 89.35, 337.7), 0.05); + + // unicolour does not support interpolation between colours of different configurations + // (because not clear what the result colour's configuration should be) + // so workaround: create new unicolours with same configurations with the LCHAB values + + yellow = Unicolour.FromLchab(yellow.Lchab.Triplet.Tuple, yellow.Alpha.A); + pink = Unicolour.FromLchab(pink.Lchab.Triplet.Tuple, pink.Alpha.A); + var notPremultipliedAlpha1 = yellow.MixLchab(pink, premultiplyAlpha: false); + var notPremultipliedAlpha2 = pink.MixLchab(yellow, premultiplyAlpha: false); + var withPremultipliedAlpha1 = yellow.MixLchab(pink); + var withPremultipliedAlpha2 = pink.MixLchab(yellow); + + AssertUtils.AssertTriplet(notPremultipliedAlpha1, new(60.22, 79.07, 31.82), 0.05); + AssertUtils.AssertTriplet(notPremultipliedAlpha2, new(60.22, 79.07, 31.82), 0.05); + AssertUtils.AssertTriplet(withPremultipliedAlpha1, new(58.87, 81.13, 31.82), 0.05); + AssertUtils.AssertTriplet(withPremultipliedAlpha2, new(58.87, 81.13, 31.82), 0.05); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/MixCam02Tests.cs b/Unicolour.Tests/MixCam02Tests.cs index 90fb6cdd..a0a2f52c 100644 --- a/Unicolour.Tests/MixCam02Tests.cs +++ b/Unicolour.Tests/MixCam02Tests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromCam02(50, -25, 25, 0.5); var unicolour2 = Unicolour.FromCam02(50, -25, 25, 0.5); - var mixed1 = unicolour1.MixCam02(unicolour2, 0.25); - var mixed2 = unicolour2.MixCam02(unicolour1, 0.75); - var mixed3 = unicolour1.MixCam02(unicolour2, 0.75); - var mixed4 = unicolour2.MixCam02(unicolour1, 0.25); + var mixed1 = unicolour1.MixCam02(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixCam02(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixCam02(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixCam02(unicolour1, 0.25, false); AssertMixed(mixed1, (50, -25, 25, 0.5)); AssertMixed(mixed2, (50, -25, 25, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromCam02(0, -50, -50, 0.0); var unicolour2 = Unicolour.FromCam02(50, 50, 50); - var mixed1 = unicolour1.MixCam02(unicolour2, 0.5); - var mixed2 = unicolour2.MixCam02(unicolour1, 0.5); + var mixed1 = unicolour1.MixCam02(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixCam02(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 0, 0, 0.5)); AssertMixed(mixed2, (25, 0, 0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromCam02(0, 50, -50); var unicolour2 = Unicolour.FromCam02(80, -50, 50, 0.5); - var mixed1 = unicolour1.MixCam02(unicolour2, 0.75); - var mixed2 = unicolour2.MixCam02(unicolour1, 0.75); + var mixed1 = unicolour1.MixCam02(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixCam02(unicolour1, 0.75, false); AssertMixed(mixed1, (60, -25, 25, 0.625)); AssertMixed(mixed2, (20, 25, -25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromCam02(0, 50, -50); var unicolour2 = Unicolour.FromCam02(80, -50, 50, 0.5); - var mixed1 = unicolour1.MixCam02(unicolour2, 0.25); - var mixed2 = unicolour2.MixCam02(unicolour1, 0.25); + var mixed1 = unicolour1.MixCam02(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixCam02(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 25, -25, 0.875)); AssertMixed(mixed2, (60, -25, 25, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromCam02(20, -10, 10, 0.8); var unicolour2 = Unicolour.FromCam02(30, 10, -10, 0.9); - var mixed1 = unicolour1.MixCam02(unicolour2, 1.5); - var mixed2 = unicolour2.MixCam02(unicolour1, 1.5); + var mixed1 = unicolour1.MixCam02(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixCam02(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 20, -20, 0.95)); AssertMixed(mixed2, (15, -20, 20, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromCam02(20, -10, 10, 0.8); var unicolour2 = Unicolour.FromCam02(30, 10, -10, 0.9); - var mixed1 = unicolour1.MixCam02(unicolour2, -0.5); - var mixed2 = unicolour2.MixCam02(unicolour1, -0.5); + var mixed1 = unicolour1.MixCam02(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixCam02(unicolour1, -0.5, false); AssertMixed(mixed1, (15, -20, 20, 0.75)); AssertMixed(mixed2, (35, 20, -20, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromCam02(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromCam02(0, 0, 0, 1.5); + var mixed = unicolour1.MixCam02(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromCam02(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromCam02(0, 0, 0, -0.5); + var mixed = unicolour1.MixCam02(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromCam02(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromCam02(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixCam02(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixCam16Tests.cs b/Unicolour.Tests/MixCam16Tests.cs index f99693f3..43a09ed8 100644 --- a/Unicolour.Tests/MixCam16Tests.cs +++ b/Unicolour.Tests/MixCam16Tests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromCam16(50, -25, 25, 0.5); var unicolour2 = Unicolour.FromCam16(50, -25, 25, 0.5); - var mixed1 = unicolour1.MixCam16(unicolour2, 0.25); - var mixed2 = unicolour2.MixCam16(unicolour1, 0.75); - var mixed3 = unicolour1.MixCam16(unicolour2, 0.75); - var mixed4 = unicolour2.MixCam16(unicolour1, 0.25); + var mixed1 = unicolour1.MixCam16(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixCam16(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixCam16(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixCam16(unicolour1, 0.25, false); AssertMixed(mixed1, (50, -25, 25, 0.5)); AssertMixed(mixed2, (50, -25, 25, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromCam16(0, -50, -50, 0.0); var unicolour2 = Unicolour.FromCam16(50, 50, 50); - var mixed1 = unicolour1.MixCam16(unicolour2, 0.5); - var mixed2 = unicolour2.MixCam16(unicolour1, 0.5); + var mixed1 = unicolour1.MixCam16(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixCam16(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 0, 0, 0.5)); AssertMixed(mixed2, (25, 0, 0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromCam16(0, 50, -50); var unicolour2 = Unicolour.FromCam16(80, -50, 50, 0.5); - var mixed1 = unicolour1.MixCam16(unicolour2, 0.75); - var mixed2 = unicolour2.MixCam16(unicolour1, 0.75); + var mixed1 = unicolour1.MixCam16(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixCam16(unicolour1, 0.75, false); AssertMixed(mixed1, (60, -25, 25, 0.625)); AssertMixed(mixed2, (20, 25, -25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromCam16(0, 50, -50); var unicolour2 = Unicolour.FromCam16(80, -50, 50, 0.5); - var mixed1 = unicolour1.MixCam16(unicolour2, 0.25); - var mixed2 = unicolour2.MixCam16(unicolour1, 0.25); + var mixed1 = unicolour1.MixCam16(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixCam16(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 25, -25, 0.875)); AssertMixed(mixed2, (60, -25, 25, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromCam16(20, -10, 10, 0.8); var unicolour2 = Unicolour.FromCam16(30, 10, -10, 0.9); - var mixed1 = unicolour1.MixCam16(unicolour2, 1.5); - var mixed2 = unicolour2.MixCam16(unicolour1, 1.5); + var mixed1 = unicolour1.MixCam16(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixCam16(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 20, -20, 0.95)); AssertMixed(mixed2, (15, -20, 20, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromCam16(20, -10, 10, 0.8); var unicolour2 = Unicolour.FromCam16(30, 10, -10, 0.9); - var mixed1 = unicolour1.MixCam16(unicolour2, -0.5); - var mixed2 = unicolour2.MixCam16(unicolour1, -0.5); + var mixed1 = unicolour1.MixCam16(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixCam16(unicolour1, -0.5, false); AssertMixed(mixed1, (15, -20, 20, 0.75)); AssertMixed(mixed2, (35, 20, -20, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromCam16(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromCam16(0, 0, 0, 1.5); + var mixed = unicolour1.MixCam16(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromCam16(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromCam16(0, 0, 0, -0.5); + var mixed = unicolour1.MixCam16(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromCam16(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromCam16(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixCam16(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixConfigurationTests.cs b/Unicolour.Tests/MixConfigurationTests.cs index 167ab8a1..39cf57db 100644 --- a/Unicolour.Tests/MixConfigurationTests.cs +++ b/Unicolour.Tests/MixConfigurationTests.cs @@ -48,18 +48,18 @@ private static Configuration GetConfig() private static void AssertNoError(Unicolour unicolour1, Unicolour unicolour2) { Assert.That(unicolour1.Config.Id, Is.EqualTo(unicolour2.Config.Id)); - Assert.DoesNotThrow(() => unicolour1.MixRgb(unicolour2, 0.5)); - Assert.DoesNotThrow(() => unicolour2.MixRgb(unicolour1, 0.5)); - Assert.DoesNotThrow(() => unicolour1.MixHsb(unicolour2, 0.5)); - Assert.DoesNotThrow(() => unicolour2.MixHsb(unicolour1, 0.5)); + Assert.DoesNotThrow(() => unicolour1.MixRgb(unicolour2, 0.5, false)); + Assert.DoesNotThrow(() => unicolour2.MixRgb(unicolour1, 0.5, false)); + Assert.DoesNotThrow(() => unicolour1.MixHsb(unicolour2, 0.5, false)); + Assert.DoesNotThrow(() => unicolour2.MixHsb(unicolour1, 0.5, false)); } private static void AssertError(Unicolour unicolour1, Unicolour unicolour2) { Assert.That(unicolour1.Config.Id, Is.Not.EqualTo(unicolour2.Config.Id)); - Assert.Throws(() => unicolour1.MixRgb(unicolour2, 0.5)); - Assert.Throws(() => unicolour2.MixRgb(unicolour1, 0.5)); - Assert.Throws(() => unicolour1.MixHsb(unicolour2, 0.5)); - Assert.Throws(() => unicolour2.MixHsb(unicolour1, 0.5)); + Assert.Throws(() => unicolour1.MixRgb(unicolour2, 0.5, false)); + Assert.Throws(() => unicolour2.MixRgb(unicolour1, 0.5, false)); + Assert.Throws(() => unicolour1.MixHsb(unicolour2, 0.5, false)); + Assert.Throws(() => unicolour2.MixHsb(unicolour1, 0.5, false)); } } \ No newline at end of file diff --git a/Unicolour.Tests/MixGreyscaleHctTests.cs b/Unicolour.Tests/MixGreyscaleHctTests.cs index 2263fdd9..fb1ccb11 100644 --- a/Unicolour.Tests/MixGreyscaleHctTests.cs +++ b/Unicolour.Tests/MixGreyscaleHctTests.cs @@ -3,7 +3,7 @@ namespace Wacton.Unicolour.Tests; using NUnit.Framework; using Wacton.Unicolour.Tests.Utils; -// note: HCT is a composite of LAB & CAM16, therefore there is no obvious cartesian/hueless space to compare against +// note: HCT is a composite of LAB & CAM16, therefore there is no obvious rectangular/hueless space to compare against // so using RGB to generate non-HCT greyscales // ---------- // greyscale RGB has no hue - shouldn't assume to start at red (0 degrees) when mixing @@ -21,10 +21,10 @@ public void GreyscaleStartColour() var hctWhite = Unicolour.FromHct(180, 100, 100); // full tone = white var green = Unicolour.FromHct(120, 100, 50); - var fromRgbBlack = rgbBlack.MixHct(green, 0.5); - var fromRgbWhite = rgbWhite.MixHct(green, 0.5); - var fromHctBlack = hctBlack.MixHct(green, 0.5); - var fromHctWhite = hctWhite.MixHct(green, 0.5); + var fromRgbBlack = rgbBlack.MixHct(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHct(green, 0.5, false); + var fromHctBlack = hctBlack.MixHct(green, 0.5, false); + var fromHctWhite = hctWhite.MixHct(green, 0.5, false); // no obvious way to create known HCT value when starting from non-HCT space // so need to calculate what the expected Chroma will be for RGB-white @@ -47,10 +47,10 @@ public void GreyscaleEndColour() var hctWhite = Unicolour.FromHct(180, 100, 100); // full tone = white var blue = Unicolour.FromHct(240, 100, 50); - var toRgbBlack = blue.MixHct(rgbBlack, 0.5); - var toRgbWhite = blue.MixHct(rgbWhite, 0.5); - var toHctBlack = blue.MixHct(hctBlack, 0.5); - var toHctWhite = blue.MixHct(hctWhite, 0.5); + var toRgbBlack = blue.MixHct(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHct(rgbWhite, 0.5, false); + var toHctBlack = blue.MixHct(hctBlack, 0.5, false); + var toHctWhite = blue.MixHct(hctWhite, 0.5, false); // no obvious way to create known HCT value when starting from non-HCT space // so need to calculate what the expected Chroma will be for RGB-white @@ -74,9 +74,9 @@ public void GreyscaleBothRgbColours() var white = Unicolour.FromRgb(1, 1, 1); var grey = Unicolour.FromRgb(0.5, 0.5, 0.5); - var blackToWhite = black.MixHct(white, 0.5); - var blackToGrey = black.MixHct(grey, 0.5); - var whiteToGrey = white.MixHct(grey, 0.5); + var blackToWhite = black.MixHct(white, 0.5, false); + var blackToGrey = black.MixHct(grey, 0.5, false); + var whiteToGrey = white.MixHct(grey, 0.5, false); // colours created from RGB therefore hue does not change // (except for HCT for RGB-black, which converts to a different hue than other greyscales) @@ -96,9 +96,9 @@ public void GreyscaleBothHctColours() var white = Unicolour.FromHct(300, 0, 100); var grey = Unicolour.FromHct(100, 0, 50); - var blackToWhite = black.MixHct(white, 0.5); - var blackToGrey = black.MixHct(grey, 0.5); - var whiteToGrey = white.MixHct(grey, 0.5); + var blackToWhite = black.MixHct(white, 0.5, false); + var blackToGrey = black.MixHct(grey, 0.5, false); + var whiteToGrey = white.MixHct(grey, 0.5, false); AssertGrey(blackToWhite.Rgb); AssertGrey(blackToGrey.Rgb); diff --git a/Unicolour.Tests/MixGreyscaleHpluvTests.cs b/Unicolour.Tests/MixGreyscaleHpluvTests.cs index fa900017..552aaf5f 100644 --- a/Unicolour.Tests/MixGreyscaleHpluvTests.cs +++ b/Unicolour.Tests/MixGreyscaleHpluvTests.cs @@ -3,7 +3,7 @@ namespace Wacton.Unicolour.Tests; using NUnit.Framework; using Wacton.Unicolour.Tests.Utils; -// note: HPLuv is a transformation from LCHuv, therefore there is no obvious cartesian/hueless space to compare against +// note: HPLuv is a transformation from LCHuv, therefore there is no obvious rectangular/hueless space to compare against // so using RGB for greyscale -> colour behaviour, and LUV for greyscale -> greyscale behaviour // ---------- // greyscale RGB & LUV have no hue - shouldn't assume to start at red (0 degrees) when mixing @@ -19,10 +19,10 @@ public void GreyscaleStartColour() var hpluvWhite = Unicolour.FromHpluv(180, 0, 100); // no saturation = greyscale var green = Unicolour.FromHpluv(120, 100, 50); - var fromRgbBlack = rgbBlack.MixHpluv(green, 0.5); - var fromRgbWhite = rgbWhite.MixHpluv(green, 0.5); - var fromHpluvBlack = hpluvBlack.MixHpluv(green, 0.5); - var fromHpluvWhite = hpluvWhite.MixHpluv(green, 0.5); + var fromRgbBlack = rgbBlack.MixHpluv(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHpluv(green, 0.5, false); + var fromHpluvBlack = hpluvBlack.MixHpluv(green, 0.5, false); + var fromHpluvWhite = hpluvWhite.MixHpluv(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromRgbBlack.Hpluv.Triplet, new(120, 50, 25)); @@ -40,10 +40,10 @@ public void GreyscaleEndColour() var hpluvWhite = Unicolour.FromHpluv(180, 0, 100); // no saturation = greyscale var blue = Unicolour.FromHpluv(240, 100, 50); - var toRgbBlack = blue.MixHpluv(rgbBlack, 0.5); - var toRgbWhite = blue.MixHpluv(rgbWhite, 0.5); - var toHpluvBlack = blue.MixHpluv(hpluvBlack, 0.5); - var toHpluvWhite = blue.MixHpluv(hpluvWhite, 0.5); + var toRgbBlack = blue.MixHpluv(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHpluv(rgbWhite, 0.5, false); + var toHpluvBlack = blue.MixHpluv(hpluvBlack, 0.5, false); + var toHpluvWhite = blue.MixHpluv(hpluvWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toRgbBlack.Hpluv.Triplet, new(240, 50, 25)); @@ -59,9 +59,9 @@ public void GreyscaleBothLuvColours() var white = Unicolour.FromLuv(100, 0, 0); var grey = Unicolour.FromLuv(50, 0, 0); - var blackToWhite = black.MixHpluv(white, 0.5); - var blackToGrey = black.MixHpluv(grey, 0.5); - var whiteToGrey = white.MixHpluv(grey, 0.5); + var blackToWhite = black.MixHpluv(white, 0.5, false); + var blackToGrey = black.MixHpluv(grey, 0.5, false); + var whiteToGrey = white.MixHpluv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); @@ -80,9 +80,9 @@ public void GreyscaleBothHpluvColours() var white = Unicolour.FromHpluv(300, 0, 100); var grey = Unicolour.FromHpluv(100, 0, 50); - var blackToWhite = black.MixHpluv(white, 0.5); - var blackToGrey = black.MixHpluv(grey, 0.5); - var whiteToGrey = white.MixHpluv(grey, 0.5); + var blackToWhite = black.MixHpluv(white, 0.5, false); + var blackToGrey = black.MixHpluv(grey, 0.5, false); + var whiteToGrey = white.MixHpluv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); diff --git a/Unicolour.Tests/MixGreyscaleHsbTests.cs b/Unicolour.Tests/MixGreyscaleHsbTests.cs index a9cd4446..21e8af3a 100644 --- a/Unicolour.Tests/MixGreyscaleHsbTests.cs +++ b/Unicolour.Tests/MixGreyscaleHsbTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var hsbWhite = Unicolour.FromHsb(180, 0, 1); // no saturation = greyscale var green = Unicolour.FromHsb(120, 1, 1); - var fromRgbBlack = rgbBlack.MixHsb(green, 0.5); - var fromRgbWhite = rgbWhite.MixHsb(green, 0.5); - var fromHsbBlack = hsbBlack.MixHsb(green, 0.5); - var fromHsbWhite = hsbWhite.MixHsb(green, 0.5); + var fromRgbBlack = rgbBlack.MixHsb(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHsb(green, 0.5, false); + var fromHsbBlack = hsbBlack.MixHsb(green, 0.5, false); + var fromHsbWhite = hsbWhite.MixHsb(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromRgbBlack.Hsb.Triplet, new(120, 0.5, 0.5)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var hsbWhite = Unicolour.FromHsb(180, 0, 1); // no saturation = greyscale var blue = Unicolour.FromHsb(240, 1, 1); - var toRgbBlack = blue.MixHsb(rgbBlack, 0.5); - var toRgbWhite = blue.MixHsb(rgbWhite, 0.5); - var toHsbBlack = blue.MixHsb(hsbBlack, 0.5); - var toHsbWhite = blue.MixHsb(hsbWhite, 0.5); + var toRgbBlack = blue.MixHsb(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHsb(rgbWhite, 0.5, false); + var toHsbBlack = blue.MixHsb(hsbBlack, 0.5, false); + var toHsbWhite = blue.MixHsb(hsbWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toRgbBlack.Hsb.Triplet, new(240, 0.5, 0.5)); @@ -56,9 +56,9 @@ public void GreyscaleBothRgbColours() var white = Unicolour.FromRgb(1.0, 1.0, 1.0); var grey = Unicolour.FromRgb(0.5, 0.5, 0.5); - var blackToWhite = black.MixHsb(white, 0.5); - var blackToGrey = black.MixHsb(grey, 0.5); - var whiteToGrey = white.MixHsb(grey, 0.5); + var blackToWhite = black.MixHsb(white, 0.5, false); + var blackToGrey = black.MixHsb(grey, 0.5, false); + var whiteToGrey = white.MixHsb(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); @@ -77,9 +77,9 @@ public void GreyscaleBothHsbColours() var white = Unicolour.FromHsb(300, 0, 1.0); var grey = Unicolour.FromHsb(100, 0, 0.5); - var blackToWhite = black.MixHsb(white, 0.5); - var blackToGrey = black.MixHsb(grey, 0.5); - var whiteToGrey = white.MixHsb(grey, 0.5); + var blackToWhite = black.MixHsb(white, 0.5, false); + var blackToGrey = black.MixHsb(grey, 0.5, false); + var whiteToGrey = white.MixHsb(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); diff --git a/Unicolour.Tests/MixGreyscaleHslTests.cs b/Unicolour.Tests/MixGreyscaleHslTests.cs index 2362ffa8..52fb0361 100644 --- a/Unicolour.Tests/MixGreyscaleHslTests.cs +++ b/Unicolour.Tests/MixGreyscaleHslTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var hslWhite = Unicolour.FromHsl(180, 0, 1); // no saturation = greyscale var green = Unicolour.FromHsl(120, 1, 0.5); - var fromRgbBlack = rgbBlack.MixHsl(green, 0.5); - var fromRgbWhite = rgbWhite.MixHsl(green, 0.5); - var fromHslBlack = hslBlack.MixHsl(green, 0.5); - var fromHslWhite = hslWhite.MixHsl(green, 0.5); + var fromRgbBlack = rgbBlack.MixHsl(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHsl(green, 0.5, false); + var fromHslBlack = hslBlack.MixHsl(green, 0.5, false); + var fromHslWhite = hslWhite.MixHsl(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromRgbBlack.Hsl.Triplet, new(120, 0.5, 0.25)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var hslWhite = Unicolour.FromHsl(180, 0, 1); // no saturation = greyscale var blue = Unicolour.FromHsl(240, 1, 0.5); - var toRgbBlack = blue.MixHsl(rgbBlack, 0.5); - var toRgbWhite = blue.MixHsl(rgbWhite, 0.5); - var toHslBlack = blue.MixHsl(hslBlack, 0.5); - var toHslWhite = blue.MixHsl(hslWhite, 0.5); + var toRgbBlack = blue.MixHsl(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHsl(rgbWhite, 0.5, false); + var toHslBlack = blue.MixHsl(hslBlack, 0.5, false); + var toHslWhite = blue.MixHsl(hslWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toRgbBlack.Hsl.Triplet, new(240, 0.5, 0.25)); @@ -56,9 +56,9 @@ public void GreyscaleBothRgbColours() var white = Unicolour.FromRgb(1.0, 1.0, 1.0); var grey = Unicolour.FromRgb(0.5, 0.5, 0.5); - var blackToWhite = black.MixHsl(white, 0.5); - var blackToGrey = black.MixHsl(grey, 0.5); - var whiteToGrey = white.MixHsl(grey, 0.5); + var blackToWhite = black.MixHsl(white, 0.5, false); + var blackToGrey = black.MixHsl(grey, 0.5, false); + var whiteToGrey = white.MixHsl(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); @@ -77,9 +77,9 @@ public void GreyscaleBothHslColours() var white = Unicolour.FromHsl(300, 0, 1.0); var grey = Unicolour.FromHsl(100, 0, 0.5); - var blackToWhite = black.MixHsl(white, 0.5); - var blackToGrey = black.MixHsl(grey, 0.5); - var whiteToGrey = white.MixHsl(grey, 0.5); + var blackToWhite = black.MixHsl(white, 0.5, false); + var blackToGrey = black.MixHsl(grey, 0.5, false); + var whiteToGrey = white.MixHsl(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); diff --git a/Unicolour.Tests/MixGreyscaleHsluvTests.cs b/Unicolour.Tests/MixGreyscaleHsluvTests.cs index be7bf8d9..be5f4700 100644 --- a/Unicolour.Tests/MixGreyscaleHsluvTests.cs +++ b/Unicolour.Tests/MixGreyscaleHsluvTests.cs @@ -3,7 +3,7 @@ namespace Wacton.Unicolour.Tests; using NUnit.Framework; using Wacton.Unicolour.Tests.Utils; -// note: HSLuv is a transformation from LCHuv, therefore there is no obvious cartesian/hueless space to compare against +// note: HSLuv is a transformation from LCHuv, therefore there is no obvious rectangular/hueless space to compare against // so using RGB for greyscale -> colour behaviour, and LUV for greyscale -> greyscale behaviour // ---------- // greyscale RGB & LUV have no hue - shouldn't assume to start at red (0 degrees) when mixing @@ -19,10 +19,10 @@ public void GreyscaleStartColour() var hsluvWhite = Unicolour.FromHsluv(180, 0, 100); // no saturation = greyscale var green = Unicolour.FromHsluv(120, 100, 50); - var fromRgbBlack = rgbBlack.MixHsluv(green, 0.5); - var fromRgbWhite = rgbWhite.MixHsluv(green, 0.5); - var fromHsluvBlack = hsluvBlack.MixHsluv(green, 0.5); - var fromHsluvWhite = hsluvWhite.MixHsluv(green, 0.5); + var fromRgbBlack = rgbBlack.MixHsluv(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHsluv(green, 0.5, false); + var fromHsluvBlack = hsluvBlack.MixHsluv(green, 0.5, false); + var fromHsluvWhite = hsluvWhite.MixHsluv(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromRgbBlack.Hsluv.Triplet, new(120, 50, 25)); @@ -40,10 +40,10 @@ public void GreyscaleEndColour() var hsluvWhite = Unicolour.FromHsluv(180, 0, 100); // no saturation = greyscale var blue = Unicolour.FromHsluv(240, 100, 50); - var toRgbBlack = blue.MixHsluv(rgbBlack, 0.5); - var toRgbWhite = blue.MixHsluv(rgbWhite, 0.5); - var toHsluvBlack = blue.MixHsluv(hsluvBlack, 0.5); - var toHsluvWhite = blue.MixHsluv(hsluvWhite, 0.5); + var toRgbBlack = blue.MixHsluv(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHsluv(rgbWhite, 0.5, false); + var toHsluvBlack = blue.MixHsluv(hsluvBlack, 0.5, false); + var toHsluvWhite = blue.MixHsluv(hsluvWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toRgbBlack.Hsluv.Triplet, new(240, 50, 25)); @@ -59,9 +59,9 @@ public void GreyscaleBothLuvColours() var white = Unicolour.FromLuv(100, 0, 0); var grey = Unicolour.FromLuv(50, 0, 0); - var blackToWhite = black.MixHsluv(white, 0.5); - var blackToGrey = black.MixHsluv(grey, 0.5); - var whiteToGrey = white.MixHsluv(grey, 0.5); + var blackToWhite = black.MixHsluv(white, 0.5, false); + var blackToGrey = black.MixHsluv(grey, 0.5, false); + var whiteToGrey = white.MixHsluv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); @@ -80,9 +80,9 @@ public void GreyscaleBothHsluvColours() var white = Unicolour.FromHsluv(300, 0, 100); var grey = Unicolour.FromHsluv(100, 0, 50); - var blackToWhite = black.MixHsluv(white, 0.5); - var blackToGrey = black.MixHsluv(grey, 0.5); - var whiteToGrey = white.MixHsluv(grey, 0.5); + var blackToWhite = black.MixHsluv(white, 0.5, false); + var blackToGrey = black.MixHsluv(grey, 0.5, false); + var whiteToGrey = white.MixHsluv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); diff --git a/Unicolour.Tests/MixGreyscaleHwbTests.cs b/Unicolour.Tests/MixGreyscaleHwbTests.cs index 0d9544c8..d1d587b0 100644 --- a/Unicolour.Tests/MixGreyscaleHwbTests.cs +++ b/Unicolour.Tests/MixGreyscaleHwbTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var hwbWhite = Unicolour.FromHwb(180, 1, 0); // full whiteness = white var green = Unicolour.FromHwb(120, 0, 0); - var fromRgbBlack = rgbBlack.MixHwb(green, 0.5); - var fromRgbWhite = rgbWhite.MixHwb(green, 0.5); - var fromHwbBlack = hwbBlack.MixHwb(green, 0.5); - var fromHwbWhite = hwbWhite.MixHwb(green, 0.5); + var fromRgbBlack = rgbBlack.MixHwb(green, 0.5, false); + var fromRgbWhite = rgbWhite.MixHwb(green, 0.5, false); + var fromHwbBlack = hwbBlack.MixHwb(green, 0.5, false); + var fromHwbWhite = hwbWhite.MixHwb(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromRgbBlack.Hwb.Triplet, new(120, 0, 0.5)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var hwbWhite = Unicolour.FromHwb(180, 1, 0); // full whiteness = white var blue = Unicolour.FromHwb(240, 0, 0); - var toRgbBlack = blue.MixHwb(rgbBlack, 0.5); - var toRgbWhite = blue.MixHwb(rgbWhite, 0.5); - var toHwbBlack = blue.MixHwb(hwbBlack, 0.5); - var toHwbWhite = blue.MixHwb(hwbWhite, 0.5); + var toRgbBlack = blue.MixHwb(rgbBlack, 0.5, false); + var toRgbWhite = blue.MixHwb(rgbWhite, 0.5, false); + var toHwbBlack = blue.MixHwb(hwbBlack, 0.5, false); + var toHwbWhite = blue.MixHwb(hwbWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toRgbBlack.Hwb.Triplet, new(240, 0, 0.5)); @@ -56,9 +56,9 @@ public void GreyscaleBothRgbColours() var white = Unicolour.FromRgb(1.0, 1.0, 1.0); var grey = Unicolour.FromRgb(0.5, 0.5, 0.5); - var blackToWhite = black.MixHwb(white, 0.5); - var blackToGrey = black.MixHwb(grey, 0.5); - var whiteToGrey = white.MixHwb(grey, 0.5); + var blackToWhite = black.MixHwb(white, 0.5, false); + var blackToGrey = black.MixHwb(grey, 0.5, false); + var whiteToGrey = white.MixHwb(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); @@ -77,9 +77,9 @@ public void GreyscaleBothHwbColours() var white = Unicolour.FromHwb(300, 1.0, 0); var grey = Unicolour.FromHwb(100, 0.5, 0.5); - var blackToWhite = black.MixHwb(white, 0.5); - var blackToGrey = black.MixHwb(grey, 0.5); - var whiteToGrey = white.MixHwb(grey, 0.5); + var blackToWhite = black.MixHwb(white, 0.5, false); + var blackToGrey = black.MixHwb(grey, 0.5, false); + var whiteToGrey = white.MixHwb(grey, 0.5, false); AssertTriplet(blackToWhite.Rgb.Triplet, new(0.5, 0.5, 0.5)); AssertTriplet(blackToGrey.Rgb.Triplet, new(0.25, 0.25, 0.25)); diff --git a/Unicolour.Tests/MixGreyscaleJzczhzTests.cs b/Unicolour.Tests/MixGreyscaleJzczhzTests.cs index f99ece04..b75b3bde 100644 --- a/Unicolour.Tests/MixGreyscaleJzczhzTests.cs +++ b/Unicolour.Tests/MixGreyscaleJzczhzTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var jchWhite = Unicolour.FromJzczhz(1, 0.5, 180); // full lightness = white var green = Unicolour.FromJzczhz(0.5, 0.5, 120); - var fromJabBlack = jabBlack.MixJzczhz(green, 0.5); - var fromJabWhite = jabWhite.MixJzczhz(green, 0.5); - var fromJchBlack = jchBlack.MixJzczhz(green, 0.5); - var fromJchWhite = jchWhite.MixJzczhz(green, 0.5); + var fromJabBlack = jabBlack.MixJzczhz(green, 0.5, false); + var fromJabWhite = jabWhite.MixJzczhz(green, 0.5, false); + var fromJchBlack = jchBlack.MixJzczhz(green, 0.5, false); + var fromJchWhite = jchWhite.MixJzczhz(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromJabBlack.Jzczhz.Triplet, new(0.25, 0.25, 120)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var jcWhite = Unicolour.FromJzczhz(1, 0.5, 180); // full lightness = white var blue = Unicolour.FromJzczhz(0.5, 0.5, 240); - var toJzazbzBlack = blue.MixJzczhz(jabBlack, 0.5); - var toJzazbzWhite = blue.MixJzczhz(jabWhite, 0.5); - var toJzczhzBlack = blue.MixJzczhz(jchBlack, 0.5); - var toJzczhzWhite = blue.MixJzczhz(jcWhite, 0.5); + var toJzazbzBlack = blue.MixJzczhz(jabBlack, 0.5, false); + var toJzazbzWhite = blue.MixJzczhz(jabWhite, 0.5, false); + var toJzczhzBlack = blue.MixJzczhz(jchBlack, 0.5, false); + var toJzczhzWhite = blue.MixJzczhz(jcWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toJzazbzBlack.Jzczhz.Triplet, new(0.25, 0.25, 240)); @@ -56,9 +56,9 @@ public void GreyscaleBothJzazbzColours() var white = Unicolour.FromJzazbz(1, 0, 0); var grey = Unicolour.FromJzazbz(0.5, 0, 0); - var blackToWhite = black.MixJzczhz(white, 0.5); - var blackToGrey = black.MixJzczhz(grey, 0.5); - var whiteToGrey = white.MixJzczhz(grey, 0.5); + var blackToWhite = black.MixJzczhz(white, 0.5, false); + var blackToGrey = black.MixJzczhz(grey, 0.5, false); + var whiteToGrey = white.MixJzczhz(grey, 0.5, false); AssertTriplet(blackToWhite.Jzazbz.Triplet, new(0.5, 0, 0)); AssertTriplet(blackToGrey.Jzazbz.Triplet, new(0.25, 0, 0)); @@ -77,9 +77,9 @@ public void GreyscaleBothJzczhzColours() var white = Unicolour.FromJzczhz(1, 0, 300); var grey = Unicolour.FromJzczhz(0.5, 0, 100); - var blackToWhite = black.MixJzczhz(white, 0.5); - var blackToGrey = black.MixJzczhz(grey, 0.5); - var whiteToGrey = white.MixJzczhz(grey, 0.5); + var blackToWhite = black.MixJzczhz(white, 0.5, false); + var blackToGrey = black.MixJzczhz(grey, 0.5, false); + var whiteToGrey = white.MixJzczhz(grey, 0.5, false); AssertTriplet(blackToWhite.Jzazbz.Triplet, new(0.5, 0, 0)); AssertTriplet(blackToGrey.Jzazbz.Triplet, new(0.25, 0, 0)); diff --git a/Unicolour.Tests/MixGreyscaleLchabTests.cs b/Unicolour.Tests/MixGreyscaleLchabTests.cs index 94949211..57bd6a8d 100644 --- a/Unicolour.Tests/MixGreyscaleLchabTests.cs +++ b/Unicolour.Tests/MixGreyscaleLchabTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var lchabWhite = Unicolour.FromLchab(100, 100, 180); // full lightness = white var green = Unicolour.FromLchab(50, 100, 120); - var fromLabBlack = labBlack.MixLchab(green, 0.5); - var fromLabWhite = labWhite.MixLchab(green, 0.5); - var fromLchabBlack = lchabBlack.MixLchab(green, 0.5); - var fromLchabWhite = lchabWhite.MixLchab(green, 0.5); + var fromLabBlack = labBlack.MixLchab(green, 0.5, false); + var fromLabWhite = labWhite.MixLchab(green, 0.5, false); + var fromLchabBlack = lchabBlack.MixLchab(green, 0.5, false); + var fromLchabWhite = lchabWhite.MixLchab(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromLabBlack.Lchab.Triplet, new(25, 50, 120)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var lchabWhite = Unicolour.FromLchab(100, 100, 180); // full lightness = white var blue = Unicolour.FromLchab(50, 100, 240); - var toLabBlack = blue.MixLchab(labBlack, 0.5); - var toLabWhite = blue.MixLchab(labWhite, 0.5); - var toLchabBlack = blue.MixLchab(lchabBlack, 0.5); - var toLchabWhite = blue.MixLchab(lchabWhite, 0.5); + var toLabBlack = blue.MixLchab(labBlack, 0.5, false); + var toLabWhite = blue.MixLchab(labWhite, 0.5, false); + var toLchabBlack = blue.MixLchab(lchabBlack, 0.5, false); + var toLchabWhite = blue.MixLchab(lchabWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toLabBlack.Lchab.Triplet, new(25, 50, 240)); @@ -56,9 +56,9 @@ public void GreyscaleBothLabColours() var white = Unicolour.FromLab(100, 0, 0); var grey = Unicolour.FromLab(50, 0, 0); - var blackToWhite = black.MixLchab(white, 0.5); - var blackToGrey = black.MixLchab(grey, 0.5); - var whiteToGrey = white.MixLchab(grey, 0.5); + var blackToWhite = black.MixLchab(white, 0.5, false); + var blackToGrey = black.MixLchab(grey, 0.5, false); + var whiteToGrey = white.MixLchab(grey, 0.5, false); AssertTriplet(blackToWhite.Lab.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Lab.Triplet, new(25, 0, 0)); @@ -77,9 +77,9 @@ public void GreyscaleBothLchabColours() var white = Unicolour.FromLchab(100, 0, 300); var grey = Unicolour.FromLchab(50, 0, 100); - var blackToWhite = black.MixLchab(white, 0.5); - var blackToGrey = black.MixLchab(grey, 0.5); - var whiteToGrey = white.MixLchab(grey, 0.5); + var blackToWhite = black.MixLchab(white, 0.5, false); + var blackToGrey = black.MixLchab(grey, 0.5, false); + var whiteToGrey = white.MixLchab(grey, 0.5, false); AssertTriplet(blackToWhite.Lab.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Lab.Triplet, new(25, 0, 0)); diff --git a/Unicolour.Tests/MixGreyscaleLchuvTests.cs b/Unicolour.Tests/MixGreyscaleLchuvTests.cs index 47ff3864..859ab9d4 100644 --- a/Unicolour.Tests/MixGreyscaleLchuvTests.cs +++ b/Unicolour.Tests/MixGreyscaleLchuvTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var lchuvWhite = Unicolour.FromLchuv(100, 100, 180); // full lightness = white var green = Unicolour.FromLchuv(50, 100, 120); - var fromLuvBlack = luvBlack.MixLchuv(green, 0.5); - var fromLuvWhite = luvWhite.MixLchuv(green, 0.5); - var fromLchuvBlack = lchuvBlack.MixLchuv(green, 0.5); - var fromLchuvWhite = lchuvWhite.MixLchuv(green, 0.5); + var fromLuvBlack = luvBlack.MixLchuv(green, 0.5, false); + var fromLuvWhite = luvWhite.MixLchuv(green, 0.5, false); + var fromLchuvBlack = lchuvBlack.MixLchuv(green, 0.5, false); + var fromLchuvWhite = lchuvWhite.MixLchuv(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromLuvBlack.Lchuv.Triplet, new(25, 50, 120)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var lchuvWhite = Unicolour.FromLchuv(100, 100, 180); // full lightness = white var blue = Unicolour.FromLchuv(50, 100, 240); - var toLuvBlack = blue.MixLchuv(luvBlack, 0.5); - var toLuvWhite = blue.MixLchuv(luvWhite, 0.5); - var toLchuvBlack = blue.MixLchuv(lchuvBlack, 0.5); - var toLchuvWhite = blue.MixLchuv(lchuvWhite, 0.5); + var toLuvBlack = blue.MixLchuv(luvBlack, 0.5, false); + var toLuvWhite = blue.MixLchuv(luvWhite, 0.5, false); + var toLchuvBlack = blue.MixLchuv(lchuvBlack, 0.5, false); + var toLchuvWhite = blue.MixLchuv(lchuvWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toLuvBlack.Lchuv.Triplet, new(25, 50, 240)); @@ -56,9 +56,9 @@ public void GreyscaleBothLuvColours() var white = Unicolour.FromLuv(100, 0, 0); var grey = Unicolour.FromLuv(50, 0, 0); - var blackToWhite = black.MixLchuv(white, 0.5); - var blackToGrey = black.MixLchuv(grey, 0.5); - var whiteToGrey = white.MixLchuv(grey, 0.5); + var blackToWhite = black.MixLchuv(white, 0.5, false); + var blackToGrey = black.MixLchuv(grey, 0.5, false); + var whiteToGrey = white.MixLchuv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); @@ -77,9 +77,9 @@ public void GreyscaleBothLchuvColours() var white = Unicolour.FromLchuv(100, 0, 300); var grey = Unicolour.FromLchuv(50, 0, 100); - var blackToWhite = black.MixLchuv(white, 0.5); - var blackToGrey = black.MixLchuv(grey, 0.5); - var whiteToGrey = white.MixLchuv(grey, 0.5); + var blackToWhite = black.MixLchuv(white, 0.5, false); + var blackToGrey = black.MixLchuv(grey, 0.5, false); + var whiteToGrey = white.MixLchuv(grey, 0.5, false); AssertTriplet(blackToWhite.Luv.Triplet, new(50, 0, 0)); AssertTriplet(blackToGrey.Luv.Triplet, new(25, 0, 0)); diff --git a/Unicolour.Tests/MixGreyscaleOklchTests.cs b/Unicolour.Tests/MixGreyscaleOklchTests.cs index 505297b2..9354bb5b 100644 --- a/Unicolour.Tests/MixGreyscaleOklchTests.cs +++ b/Unicolour.Tests/MixGreyscaleOklchTests.cs @@ -16,10 +16,10 @@ public void GreyscaleStartColour() var oklchWhite = Unicolour.FromOklch(1, 0.5, 180); // full lightness = white var green = Unicolour.FromOklch(0.5, 0.5, 120); - var fromOklabBlack = oklabBlack.MixOklch(green, 0.5); - var fromOklabWhite = oklabWhite.MixOklch(green, 0.5); - var fromOklchBlack = oklchBlack.MixOklch(green, 0.5); - var fromOklchWhite = oklchWhite.MixOklch(green, 0.5); + var fromOklabBlack = oklabBlack.MixOklch(green, 0.5, false); + var fromOklabWhite = oklabWhite.MixOklch(green, 0.5, false); + var fromOklchBlack = oklchBlack.MixOklch(green, 0.5, false); + var fromOklchWhite = oklchWhite.MixOklch(green, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(fromOklabBlack.Oklch.Triplet, new(0.25, 0.25, 120)); @@ -37,10 +37,10 @@ public void GreyscaleEndColour() var oklchWhite = Unicolour.FromOklch(1, 0.5, 180); // full lightness = white var blue = Unicolour.FromOklch(0.5, 0.5, 240); - var toOklabBlack = blue.MixOklch(oklabBlack, 0.5); - var toOklabWhite = blue.MixOklch(oklabWhite, 0.5); - var toOklchBlack = blue.MixOklch(oklchBlack, 0.5); - var toOklchWhite = blue.MixOklch(oklchWhite, 0.5); + var toOklabBlack = blue.MixOklch(oklabBlack, 0.5, false); + var toOklabWhite = blue.MixOklch(oklabWhite, 0.5, false); + var toOklchBlack = blue.MixOklch(oklchBlack, 0.5, false); + var toOklchWhite = blue.MixOklch(oklchWhite, 0.5, false); // greyscale mixes differently depending on the initial colour space AssertTriplet(toOklabBlack.Oklch.Triplet, new(0.25, 0.25, 240)); @@ -56,9 +56,9 @@ public void GreyscaleBothOklabColours() var white = Unicolour.FromOklab(1, 0, 0); var grey = Unicolour.FromOklab(0.5, 0, 0); - var blackToWhite = black.MixOklch(white, 0.5); - var blackToGrey = black.MixOklch(grey, 0.5); - var whiteToGrey = white.MixOklch(grey, 0.5); + var blackToWhite = black.MixOklch(white, 0.5, false); + var blackToGrey = black.MixOklch(grey, 0.5, false); + var whiteToGrey = white.MixOklch(grey, 0.5, false); AssertTriplet(blackToWhite.Oklab.Triplet, new(0.5, 0, 0)); AssertTriplet(blackToGrey.Oklab.Triplet, new(0.25, 0, 0)); @@ -77,9 +77,9 @@ public void GreyscaleBothOklchColours() var white = Unicolour.FromOklch(1, 0, 300); var grey = Unicolour.FromOklch(0.5, 0, 100); - var blackToWhite = black.MixOklch(white, 0.5); - var blackToGrey = black.MixOklch(grey, 0.5); - var whiteToGrey = white.MixOklch(grey, 0.5); + var blackToWhite = black.MixOklch(white, 0.5, false); + var blackToGrey = black.MixOklch(grey, 0.5, false); + var whiteToGrey = white.MixOklch(grey, 0.5, false); AssertTriplet(blackToWhite.Oklab.Triplet, new(0.5, 0, 0)); AssertTriplet(blackToGrey.Oklab.Triplet, new(0.25, 0, 0)); diff --git a/Unicolour.Tests/MixHctTests.cs b/Unicolour.Tests/MixHctTests.cs index a590babb..3768ab74 100644 --- a/Unicolour.Tests/MixHctTests.cs +++ b/Unicolour.Tests/MixHctTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHct(180, 30, 75, 0.5); var unicolour2 = Unicolour.FromHct(180, 30, 75, 0.5); - var mixed1 = unicolour1.MixHct(unicolour2, 0.25); - var mixed2 = unicolour2.MixHct(unicolour1, 0.75); - var mixed3 = unicolour1.MixHct(unicolour2, 0.75); - var mixed4 = unicolour2.MixHct(unicolour1, 0.25); + var mixed1 = unicolour1.MixHct(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHct(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHct(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 30, 75, 0.5)); AssertMixed(mixed2, (180, 30, 75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHct(0, 0, 0, 0); var unicolour2 = Unicolour.FromHct(180, 120, 100); - var mixed1 = unicolour1.MixHct(unicolour2, 0.5); - var mixed2 = unicolour2.MixHct(unicolour1, 0.5); + var mixed1 = unicolour1.MixHct(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 60, 50, 0.5)); AssertMixed(mixed2, (90, 60, 50, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHct(0, 0, 0, 0); var unicolour2 = Unicolour.FromHct(340, 60, 80, 0.2); - var mixed1 = unicolour1.MixHct(unicolour2, 0.5); - var mixed2 = unicolour2.MixHct(unicolour1, 0.5); + var mixed1 = unicolour1.MixHct(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 30, 40, 0.1)); AssertMixed(mixed2, (350, 30, 40, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHct(0, 120, 0); var unicolour2 = Unicolour.FromHct(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHct(unicolour2, 0.75); - var mixed2 = unicolour2.MixHct(unicolour1, 0.75); + var mixed1 = unicolour1.MixHct(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 30, 75, 0.625)); AssertMixed(mixed2, (45, 90, 25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHct(300, 120, 0); var unicolour2 = Unicolour.FromHct(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHct(unicolour2, 0.75); - var mixed2 = unicolour2.MixHct(unicolour1, 0.75); + var mixed1 = unicolour1.MixHct(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 30, 75, 0.625)); AssertMixed(mixed2, (330, 90, 25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHct(0, 120, 0); var unicolour2 = Unicolour.FromHct(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHct(unicolour2, 0.25); - var mixed2 = unicolour2.MixHct(unicolour1, 0.25); + var mixed1 = unicolour1.MixHct(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 90, 25, 0.875)); AssertMixed(mixed2, (135, 30, 75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHct(300, 120, 0); var unicolour2 = Unicolour.FromHct(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHct(unicolour2, 0.25); - var mixed2 = unicolour2.MixHct(unicolour1, 0.25); + var mixed1 = unicolour1.MixHct(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHct(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 90, 25, 0.875)); AssertMixed(mixed2, (30, 30, 75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHct(0, 48, 60, 0.8); var unicolour2 = Unicolour.FromHct(90, 72, 40, 0.9); - var mixed1 = unicolour1.MixHct(unicolour2, 1.5); - var mixed2 = unicolour2.MixHct(unicolour1, 1.5); + var mixed1 = unicolour1.MixHct(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHct(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 84, 30, 0.95)); AssertMixed(mixed2, (315, 36, 70, 0.75)); @@ -110,12 +110,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHct(0, 48, 60, 0.8); var unicolour2 = Unicolour.FromHct(90, 72, 40, 0.9); - var mixed1 = unicolour1.MixHct(unicolour2, -0.5); - var mixed2 = unicolour2.MixHct(unicolour1, -0.5); + var mixed1 = unicolour1.MixHct(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHct(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 36, 70, 0.75)); AssertMixed(mixed2, (135, 84, 30, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHct(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHct(0, 0, 0, 1.5); + var mixed = unicolour1.MixHct(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHct(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHct(0, 0, 0, -0.5); + var mixed = unicolour1.MixHct(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHct(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHct(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHct(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixHeritageTests.cs b/Unicolour.Tests/MixHeritageTests.cs index f5ae8069..e6037088 100644 --- a/Unicolour.Tests/MixHeritageTests.cs +++ b/Unicolour.Tests/MixHeritageTests.cs @@ -25,8 +25,8 @@ public void HuedToHued() AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); // initial mixed representation: no heritage, non-NaN, non-greyscale; - // all representations are used as hued where hue axis is present - var mixed = unicolour1.MixRgb(unicolour2, 0.5); + // all representations are used as hued where hue component is present + var mixed = unicolour1.MixRgb(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -34,7 +34,7 @@ public void HuedToHued() Assert.That(data.UseAsHued(NonHuedSpaces), Has.All.False); // representations of mixed colour has same behaviour when mixed via hued space - mixed = unicolour1.MixHsb(unicolour2, 0.5); + mixed = unicolour1.MixHsb(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.Hued, isHued: true, isGreyscale: false, isNotNumber: false); data = new ColourHeritageData(mixed); @@ -53,7 +53,7 @@ public void HuedToGreyscale() // initial mixed representation: no heritage, greyscale values; // all representations are used as greyscale, none are used as hued - var mixed = unicolour1.MixRgb(unicolour2, 0.5); + var mixed = unicolour1.MixRgb(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -71,8 +71,8 @@ public void GreyscaleToHued() AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); // initial mixed representation: no heritage, non-greyscale values; - // all representations are used as hued where hue axis is present - var mixed = unicolour1.MixRgb(unicolour2, 0.5); + // all representations are used as hued where hue component is present + var mixed = unicolour1.MixRgb(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -91,7 +91,7 @@ public void GreyscaleToGreyscale() // initial mixed representation: greyscale heritage, greyscale values; // all representations are used as greyscale, none are used as hued - var mixed = unicolour1.MixLab(unicolour2, 0.5); + var mixed = unicolour1.MixLab(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.Greyscale, isHued: false, isGreyscale: true, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -110,7 +110,7 @@ public void NotNumberToNotNumber() // initial mixed representation: NaN heritage, NaN values; // all representations are used as NaN, none are used as hued or greyscale - var mixed = unicolour1.MixRgb(unicolour2, 0.5); + var mixed = unicolour1.MixRgb(unicolour2, 0.5, false); AssertInitialHeritage(mixed, ColourHeritage.NaN, isHued: false, isGreyscale: false, isNotNumber: true); var data = new ColourHeritageData(mixed); @@ -132,22 +132,22 @@ public void GreyscaleAndHued() AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.75); // 90, 0, 0 - var mixed2 = unicolour1.MixHsb(unicolour2, 0.25); // 30, 0, 0 + var mixed1 = unicolour1.MixHsb(unicolour2, 0.75, false); // 90, 0, 0 + var mixed2 = unicolour1.MixHsb(unicolour2, 0.25, false); // 30, 0, 0 AssertInitialHeritage(mixed1, ColourHeritage.GreyscaleAndHued, isHued: true, isGreyscale: true, isNotNumber: false); AssertInitialHeritage(mixed2, ColourHeritage.GreyscaleAndHued, isHued: true, isGreyscale: true, isNotNumber: false); - var mixed3 = mixed1.MixHsb(mixed2, 0.5); // 60, 0, 0 + var mixed3 = mixed1.MixHsb(mixed2, 0.5, false); // 60, 0, 0 var unicolour3 = Unicolour.FromHsb(240, 1, 1); AssertInitialHeritage(mixed3, ColourHeritage.GreyscaleAndHued, isHued: true, isGreyscale: true, isNotNumber: false); AssertInitialHeritage(unicolour3, ColourHeritage.None, isHued: true, isGreyscale: false, isNotNumber: false); - var mixed4 = mixed3.MixHsb(unicolour3, 0.5); // 180, 0.5, 0.5 + var mixed4 = mixed3.MixHsb(unicolour3, 0.5, false); // 180, 0.5, 0.5 AssertInitialHeritage(mixed4, ColourHeritage.Hued, isHued: true, isGreyscale: false, isNotNumber: false); Assert.That(mixed4.Hsb.H, Is.EqualTo(150)); // HSL is a transform of HSB, hue information should also still be intact - var mixedHsl = mixed3.MixHsl(unicolour3, 0.5); + var mixedHsl = mixed3.MixHsl(unicolour3, 0.5, false); AssertInitialHeritage(mixed4, ColourHeritage.Hued, isHued: true, isGreyscale: false, isNotNumber: false); Assert.That(mixedHsl.Hsb.H, Is.EqualTo(150)); } diff --git a/Unicolour.Tests/MixHpluvTests.cs b/Unicolour.Tests/MixHpluvTests.cs index 26bced8a..3ff14e44 100644 --- a/Unicolour.Tests/MixHpluvTests.cs +++ b/Unicolour.Tests/MixHpluvTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHpluv(180, 25, 75, 0.5); var unicolour2 = Unicolour.FromHpluv(180, 25, 75, 0.5); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75); - var mixed3 = unicolour1.MixHpluv(unicolour2, 0.75); - var mixed4 = unicolour2.MixHpluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHpluv(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHpluv(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 25, 75, 0.5)); AssertMixed(mixed2, (180, 25, 75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHpluv(0, 0, 0, 0); var unicolour2 = Unicolour.FromHpluv(180, 100, 100); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.5); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.5); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 50, 50, 0.5)); AssertMixed(mixed2, (90, 50, 50, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHpluv(0, 0, 0, 0); var unicolour2 = Unicolour.FromHpluv(340, 50, 80, 0.2); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.5); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.5); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 25, 40, 0.1)); AssertMixed(mixed2, (350, 25, 40, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHpluv(0, 100, 0); var unicolour2 = Unicolour.FromHpluv(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.75); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 25, 75, 0.625)); AssertMixed(mixed2, (45, 75, 25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHpluv(300, 100, 0); var unicolour2 = Unicolour.FromHpluv(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.75); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 25, 75, 0.625)); AssertMixed(mixed2, (330, 75, 25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHpluv(0, 100, 0); var unicolour2 = Unicolour.FromHpluv(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 75, 25, 0.875)); AssertMixed(mixed2, (135, 25, 75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHpluv(300, 100, 0); var unicolour2 = Unicolour.FromHpluv(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHpluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHpluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 75, 25, 0.875)); AssertMixed(mixed2, (30, 25, 75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHpluv(0, 40, 60, 0.8); var unicolour2 = Unicolour.FromHpluv(90, 60, 40, 0.9); - var mixed1 = unicolour1.MixHpluv(unicolour2, 1.5); - var mixed2 = unicolour2.MixHpluv(unicolour1, 1.5); + var mixed1 = unicolour1.MixHpluv(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 70, 30, 0.95)); AssertMixed(mixed2, (315, 30, 70, 0.75)); @@ -110,13 +110,42 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHpluv(0, 40, 60, 0.8); var unicolour2 = Unicolour.FromHpluv(90, 60, 40, 0.9); - var mixed1 = unicolour1.MixHpluv(unicolour2, -0.5); - var mixed2 = unicolour2.MixHpluv(unicolour1, -0.5); + var mixed1 = unicolour1.MixHpluv(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHpluv(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 30, 70, 0.75)); AssertMixed(mixed2, (135, 70, 30, 0.95)); } + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHpluv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHpluv(0, 0, 0, 1.5); + var mixed = unicolour1.MixHpluv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHpluv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHpluv(0, 0, 0, -0.5); + var mixed = unicolour1.MixHpluv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHpluv(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHpluv(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHpluv(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } + private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { AssertUtils.AssertMixed(unicolour.Hpluv.Triplet, unicolour.Alpha.A, expected); diff --git a/Unicolour.Tests/MixHsbTests.cs b/Unicolour.Tests/MixHsbTests.cs index fa4c9a64..28001b41 100644 --- a/Unicolour.Tests/MixHsbTests.cs +++ b/Unicolour.Tests/MixHsbTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHsb(180, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromHsb(180, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.75); - var mixed3 = unicolour1.MixHsb(unicolour2, 0.75); - var mixed4 = unicolour2.MixHsb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHsb(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHsb(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (180, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHsb(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsb(180, 1, 1); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (90, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHsb(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsb(340, 0.5, 0.8, 0.2); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 0.25, 0.4, 0.1)); AssertMixed(mixed2, (350, 0.25, 0.4, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHsb(0, 1, 0); var unicolour2 = Unicolour.FromHsb(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (45, 0.75, 0.25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHsb(300, 1, 0); var unicolour2 = Unicolour.FromHsb(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (330, 0.75, 0.25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHsb(0, 1, 0); var unicolour2 = Unicolour.FromHsb(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (135, 0.25, 0.75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHsb(300, 1, 0); var unicolour2 = Unicolour.FromHsb(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (30, 0.25, 0.75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHsb(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHsb(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHsb(unicolour2, 1.5); - var mixed2 = unicolour2.MixHsb(unicolour1, 1.5); + var mixed1 = unicolour1.MixHsb(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHsb(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (315, 0.3, 0.7, 0.75)); @@ -110,12 +110,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHsb(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHsb(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHsb(unicolour2, -0.5); - var mixed2 = unicolour2.MixHsb(unicolour1, -0.5); + var mixed1 = unicolour1.MixHsb(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHsb(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (135, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHsb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsb(0, 0, 0, 1.5); + var mixed = unicolour1.MixHsb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHsb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsb(0, 0, 0, -0.5); + var mixed = unicolour1.MixHsb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHsb(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHsb(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHsb(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixHslTests.cs b/Unicolour.Tests/MixHslTests.cs index c4ab11d6..853b7030 100644 --- a/Unicolour.Tests/MixHslTests.cs +++ b/Unicolour.Tests/MixHslTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHsl(180, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromHsl(180, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.75); - var mixed3 = unicolour1.MixHsl(unicolour2, 0.75); - var mixed4 = unicolour2.MixHsl(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHsl(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHsl(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (180, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsl(180, 1, 1); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (90, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsl(340, 0.5, 0.8, 0.2); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 0.25, 0.4, 0.1)); AssertMixed(mixed2, (350, 0.25, 0.4, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHsl(0, 1, 0); var unicolour2 = Unicolour.FromHsl(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (45, 0.75, 0.25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHsl(300, 1, 0); var unicolour2 = Unicolour.FromHsl(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (330, 0.75, 0.25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHsl(0, 1, 0); var unicolour2 = Unicolour.FromHsl(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (135, 0.25, 0.75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHsl(300, 1, 0); var unicolour2 = Unicolour.FromHsl(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHsl(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsl(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsl(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (30, 0.25, 0.75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHsl(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHsl(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHsl(unicolour2, 1.5); - var mixed2 = unicolour2.MixHsl(unicolour1, 1.5); + var mixed1 = unicolour1.MixHsl(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHsl(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (315, 0.3, 0.7, 0.75)); @@ -110,13 +110,42 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHsl(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHsl(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHsl(unicolour2, -0.5); - var mixed2 = unicolour2.MixHsl(unicolour1, -0.5); + var mixed1 = unicolour1.MixHsl(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHsl(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (135, 0.7, 0.3, 0.95)); } + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsl(0, 0, 0, 1.5); + var mixed = unicolour1.MixHsl(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHsl(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsl(0, 0, 0, -0.5); + var mixed = unicolour1.MixHsl(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHsl(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHsl(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHsl(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } + private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { AssertUtils.AssertMixed(unicolour.Hsl.Triplet, unicolour.Alpha.A, expected); diff --git a/Unicolour.Tests/MixHsluvTests.cs b/Unicolour.Tests/MixHsluvTests.cs index 01c67d97..9ac7997e 100644 --- a/Unicolour.Tests/MixHsluvTests.cs +++ b/Unicolour.Tests/MixHsluvTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHsluv(180, 25, 75, 0.5); var unicolour2 = Unicolour.FromHsluv(180, 25, 75, 0.5); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75); - var mixed3 = unicolour1.MixHsluv(unicolour2, 0.75); - var mixed4 = unicolour2.MixHsluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHsluv(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHsluv(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 25, 75, 0.5)); AssertMixed(mixed2, (180, 25, 75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHsluv(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsluv(180, 100, 100); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 50, 50, 0.5)); AssertMixed(mixed2, (90, 50, 50, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHsluv(0, 0, 0, 0); var unicolour2 = Unicolour.FromHsluv(340, 50, 80, 0.2); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.5); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.5); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 25, 40, 0.1)); AssertMixed(mixed2, (350, 25, 40, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHsluv(0, 100, 0); var unicolour2 = Unicolour.FromHsluv(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 25, 75, 0.625)); AssertMixed(mixed2, (45, 75, 25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHsluv(300, 100, 0); var unicolour2 = Unicolour.FromHsluv(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.75); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 25, 75, 0.625)); AssertMixed(mixed2, (330, 75, 25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHsluv(0, 100, 0); var unicolour2 = Unicolour.FromHsluv(180, 0, 100, 0.5); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 75, 25, 0.875)); AssertMixed(mixed2, (135, 25, 75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHsluv(300, 100, 0); var unicolour2 = Unicolour.FromHsluv(60, 0, 100, 0.5); - var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25); - var mixed2 = unicolour2.MixHsluv(unicolour1, 0.25); + var mixed1 = unicolour1.MixHsluv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 75, 25, 0.875)); AssertMixed(mixed2, (30, 25, 75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHsluv(0, 40, 60, 0.8); var unicolour2 = Unicolour.FromHsluv(90, 60, 40, 0.9); - var mixed1 = unicolour1.MixHsluv(unicolour2, 1.5); - var mixed2 = unicolour2.MixHsluv(unicolour1, 1.5); + var mixed1 = unicolour1.MixHsluv(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 70, 30, 0.95)); AssertMixed(mixed2, (315, 30, 70, 0.75)); @@ -110,13 +110,42 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHsluv(0, 40, 60, 0.8); var unicolour2 = Unicolour.FromHsluv(90, 60, 40, 0.9); - var mixed1 = unicolour1.MixHsluv(unicolour2, -0.5); - var mixed2 = unicolour2.MixHsluv(unicolour1, -0.5); + var mixed1 = unicolour1.MixHsluv(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHsluv(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 30, 70, 0.75)); AssertMixed(mixed2, (135, 70, 30, 0.95)); } + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHsluv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsluv(0, 0, 0, 1.5); + var mixed = unicolour1.MixHsluv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHsluv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHsluv(0, 0, 0, -0.5); + var mixed = unicolour1.MixHsluv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHsluv(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHsluv(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHsluv(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } + private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { AssertUtils.AssertMixed(unicolour.Hsluv.Triplet, unicolour.Alpha.A, expected); diff --git a/Unicolour.Tests/MixHwbTests.cs b/Unicolour.Tests/MixHwbTests.cs index f9fa04d0..cca63bb8 100644 --- a/Unicolour.Tests/MixHwbTests.cs +++ b/Unicolour.Tests/MixHwbTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromHwb(180, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromHwb(180, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.75); - var mixed3 = unicolour1.MixHwb(unicolour2, 0.75); - var mixed4 = unicolour2.MixHwb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixHwb(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixHwb(unicolour1, 0.25, false); AssertMixed(mixed1, (180, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (180, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromHwb(0, 0, 0, 0); var unicolour2 = Unicolour.FromHwb(180, 1, 1); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.5); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.5); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.5, false); AssertMixed(mixed1, (90, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (90, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromHwb(0, 0, 0, 0); var unicolour2 = Unicolour.FromHwb(340, 0.5, 0.8, 0.2); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.5); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.5); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.5, false); AssertMixed(mixed1, (350, 0.25, 0.4, 0.1)); AssertMixed(mixed2, (350, 0.25, 0.4, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromHwb(0, 1, 0); var unicolour2 = Unicolour.FromHwb(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.75); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.75); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.75, false); AssertMixed(mixed1, (135, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (45, 0.75, 0.25, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromHwb(300, 1, 0); var unicolour2 = Unicolour.FromHwb(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.75); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.75); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.75, false); AssertMixed(mixed1, (30, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (330, 0.75, 0.25, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromHwb(0, 1, 0); var unicolour2 = Unicolour.FromHwb(180, 0, 1, 0.5); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.25, false); AssertMixed(mixed1, (45, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (135, 0.25, 0.75, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromHwb(300, 1, 0); var unicolour2 = Unicolour.FromHwb(60, 0, 1, 0.5); - var mixed1 = unicolour1.MixHwb(unicolour2, 0.25); - var mixed2 = unicolour2.MixHwb(unicolour1, 0.25); + var mixed1 = unicolour1.MixHwb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 0.25, false); AssertMixed(mixed1, (330, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (30, 0.25, 0.75, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromHwb(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHwb(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHwb(unicolour2, 1.5); - var mixed2 = unicolour2.MixHwb(unicolour1, 1.5); + var mixed1 = unicolour1.MixHwb(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixHwb(unicolour1, 1.5, false); AssertMixed(mixed1, (135, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (315, 0.3, 0.7, 0.75)); @@ -110,12 +110,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromHwb(0, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromHwb(90, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixHwb(unicolour2, -0.5); - var mixed2 = unicolour2.MixHwb(unicolour1, -0.5); + var mixed1 = unicolour1.MixHwb(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixHwb(unicolour1, -0.5, false); AssertMixed(mixed1, (315, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (135, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromHwb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHwb(0, 0, 0, 1.5); + var mixed = unicolour1.MixHwb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromHwb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromHwb(0, 0, 0, -0.5); + var mixed = unicolour1.MixHwb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex0))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromHwb(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromHwb(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixHwb(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixIctcpTests.cs b/Unicolour.Tests/MixIctcpTests.cs index 08bebea9..b3a78d08 100644 --- a/Unicolour.Tests/MixIctcpTests.cs +++ b/Unicolour.Tests/MixIctcpTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromIctcp(0.5, -0.25, 0.25, 0.5); var unicolour2 = Unicolour.FromIctcp(0.5, -0.25, 0.25, 0.5); - var mixed1 = unicolour1.MixIctcp(unicolour2, 0.25); - var mixed2 = unicolour2.MixIctcp(unicolour1, 0.75); - var mixed3 = unicolour1.MixIctcp(unicolour2, 0.75); - var mixed4 = unicolour2.MixIctcp(unicolour1, 0.25); + var mixed1 = unicolour1.MixIctcp(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixIctcp(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixIctcp(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, -0.25, 0.25, 0.5)); AssertMixed(mixed2, (0.5, -0.25, 0.25, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromIctcp(0.0, -0.5, -0.5, 0.0); var unicolour2 = Unicolour.FromIctcp(0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixIctcp(unicolour2, 0.5); - var mixed2 = unicolour2.MixIctcp(unicolour1, 0.5); + var mixed1 = unicolour1.MixIctcp(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.0, 0.0, 0.5)); AssertMixed(mixed2, (0.25, 0.0, 0.0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromIctcp(0, 0.5, -0.5); var unicolour2 = Unicolour.FromIctcp(0.8, -0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixIctcp(unicolour2, 0.75); - var mixed2 = unicolour2.MixIctcp(unicolour1, 0.75); + var mixed1 = unicolour1.MixIctcp(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, -0.25, 0.25, 0.625)); AssertMixed(mixed2, (0.2, 0.25, -0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromIctcp(0, 0.5, -0.5); var unicolour2 = Unicolour.FromIctcp(0.8, -0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixIctcp(unicolour2, 0.25); - var mixed2 = unicolour2.MixIctcp(unicolour1, 0.25); + var mixed1 = unicolour1.MixIctcp(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.25, -0.25, 0.875)); AssertMixed(mixed2, (0.6, -0.25, 0.25, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromIctcp(0.2, -0.125, 0.125, 0.8); var unicolour2 = Unicolour.FromIctcp(0.3, 0.125, -0.125, 0.9); - var mixed1 = unicolour1.MixIctcp(unicolour2, 1.5); - var mixed2 = unicolour2.MixIctcp(unicolour1, 1.5); + var mixed1 = unicolour1.MixIctcp(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.25, -0.25, 0.95)); AssertMixed(mixed2, (0.15, -0.25, 0.25, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromIctcp(0.2, -0.125, 0.125, 0.8); var unicolour2 = Unicolour.FromIctcp(0.3, 0.125, -0.125, 0.9); - var mixed1 = unicolour1.MixIctcp(unicolour2, -0.5); - var mixed2 = unicolour2.MixIctcp(unicolour1, -0.5); + var mixed1 = unicolour1.MixIctcp(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixIctcp(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, -0.25, 0.25, 0.75)); AssertMixed(mixed2, (0.35, 0.25, -0.25, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromIctcp(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromIctcp(0, 0, 0, 1.5); + var mixed = unicolour1.MixIctcp(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromIctcp(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromIctcp(0, 0, 0, -0.5); + var mixed = unicolour1.MixIctcp(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromIctcp(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromIctcp(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixIctcp(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixJzazbzTests.cs b/Unicolour.Tests/MixJzazbzTests.cs index 1755fb71..b34333ee 100644 --- a/Unicolour.Tests/MixJzazbzTests.cs +++ b/Unicolour.Tests/MixJzazbzTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromJzazbz(0.08, -0.05, 0.05, 0.5); var unicolour2 = Unicolour.FromJzazbz(0.08, -0.05, 0.05, 0.5); - var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.25); - var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.75); - var mixed3 = unicolour1.MixJzazbz(unicolour2, 0.75); - var mixed4 = unicolour2.MixJzazbz(unicolour1, 0.25); + var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixJzazbz(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixJzazbz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.08, -0.05, 0.05, 0.5)); AssertMixed(mixed2, (0.08, -0.05, 0.05, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromJzazbz(0.0, -0.1, -0.1, 0.0); var unicolour2 = Unicolour.FromJzazbz(0.08, 0.1, 0.1); - var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.5); - var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.5); + var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.5, false); AssertMixed(mixed1, (0.04, 0.0, 0.0, 0.5)); AssertMixed(mixed2, (0.04, 0.0, 0.0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromJzazbz(0, 0.1, -0.1); var unicolour2 = Unicolour.FromJzazbz(0.12, -0.1, 0.1, 0.5); - var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.75); - var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.75); + var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.75, false); AssertMixed(mixed1, (0.09, -0.05, 0.05, 0.625)); AssertMixed(mixed2, (0.03, 0.05, -0.05, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromJzazbz(0, 0.1, -0.1); var unicolour2 = Unicolour.FromJzazbz(0.12, -0.1, 0.1, 0.5); - var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.25); - var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.25); + var mixed1 = unicolour1.MixJzazbz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.03, 0.05, -0.05, 0.875)); AssertMixed(mixed2, (0.09, -0.05, 0.05, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromJzazbz(0.02, -0.025, 0.025, 0.8); var unicolour2 = Unicolour.FromJzazbz(0.03, 0.025, -0.025, 0.9); - var mixed1 = unicolour1.MixJzazbz(unicolour2, 1.5); - var mixed2 = unicolour2.MixJzazbz(unicolour1, 1.5); + var mixed1 = unicolour1.MixJzazbz(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, 1.5, false); AssertMixed(mixed1, (0.035, 0.05, -0.05, 0.95)); AssertMixed(mixed2, (0.015, -0.05, 0.05, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromJzazbz(0.02, -0.025, 0.025, 0.8); var unicolour2 = Unicolour.FromJzazbz(0.03, 0.025, -0.025, 0.9); - var mixed1 = unicolour1.MixJzazbz(unicolour2, -0.5); - var mixed2 = unicolour2.MixJzazbz(unicolour1, -0.5); + var mixed1 = unicolour1.MixJzazbz(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixJzazbz(unicolour1, -0.5, false); AssertMixed(mixed1, (0.015, -0.05, 0.05, 0.75)); AssertMixed(mixed2, (0.035, 0.05, -0.05, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromJzazbz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromJzazbz(0, 0, 0, 1.5); + var mixed = unicolour1.MixJzazbz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromJzazbz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromJzazbz(0, 0, 0, -0.5); + var mixed = unicolour1.MixJzazbz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromJzazbz(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromJzazbz(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixJzazbz(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixJzczhzTests.cs b/Unicolour.Tests/MixJzczhzTests.cs index 1454557b..9e9d6ef7 100644 --- a/Unicolour.Tests/MixJzczhzTests.cs +++ b/Unicolour.Tests/MixJzczhzTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromJzczhz(0.08, 0.08, 180, 0.5); var unicolour2 = Unicolour.FromJzczhz(0.08, 0.08, 180, 0.5); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75); - var mixed3 = unicolour1.MixJzczhz(unicolour2, 0.75); - var mixed4 = unicolour2.MixJzczhz(unicolour1, 0.25); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixJzczhz(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixJzczhz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.08, 0.08, 180, 0.5)); AssertMixed(mixed2, (0.08, 0.08, 180, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromJzczhz(0, 0, 0, 0.0); var unicolour2 = Unicolour.FromJzczhz(0.08, 0.08, 180); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.5); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.5); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.5, false); AssertMixed(mixed1, (0.04, 0.04, 90, 0.5)); AssertMixed(mixed2, (0.04, 0.04, 90, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromJzczhz(0, 0, 0, 0); var unicolour2 = Unicolour.FromJzczhz(0.12, 0.04, 340, 0.2); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.5); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.5); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.5, false); AssertMixed(mixed1, (0.06, 0.02, 350, 0.1)); AssertMixed(mixed2, (0.06, 0.02, 350, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromJzczhz(0, 0.08, 0); var unicolour2 = Unicolour.FromJzczhz(0.12, 0, 180, 0.5); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.75); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75, false); AssertMixed(mixed1, (0.09, 0.02, 135, 0.625)); AssertMixed(mixed2, (0.03, 0.06, 45, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromJzczhz(0, 0.08, 300); var unicolour2 = Unicolour.FromJzczhz(0.12, 0, 60, 0.5); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.75); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.75, false); AssertMixed(mixed1, (0.09, 0.02, 30, 0.625)); AssertMixed(mixed2, (0.03, 0.06, 330, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromJzczhz(0, 0.08, 0); var unicolour2 = Unicolour.FromJzczhz(0.12, 0, 180, 0.5); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.25); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.03, 0.06, 45, 0.875)); AssertMixed(mixed2, (0.09, 0.02, 135, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromJzczhz(0, 0.08, 300); var unicolour2 = Unicolour.FromJzczhz(0.12, 0, 60, 0.5); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.25); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.03, 0.06, 330, 0.875)); AssertMixed(mixed2, (0.09, 0.02, 30, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromJzczhz(0.02, 0.02, 0, 0.8); var unicolour2 = Unicolour.FromJzczhz(0.03, 0.03, 90, 0.9); - var mixed1 = unicolour1.MixJzczhz(unicolour2, 1.5); - var mixed2 = unicolour2.MixJzczhz(unicolour1, 1.5); + var mixed1 = unicolour1.MixJzczhz(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, 1.5, false); AssertMixed(mixed1, (0.035, 0.035, 135, 0.95)); AssertMixed(mixed2, (0.015, 0.015, 315, 0.75)); @@ -110,12 +110,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromJzczhz(0.02, 0.02, 0, 0.8); var unicolour2 = Unicolour.FromJzczhz(0.03, 0.03, 90, 0.9); - var mixed1 = unicolour1.MixJzczhz(unicolour2, -0.5); - var mixed2 = unicolour2.MixJzczhz(unicolour1, -0.5); + var mixed1 = unicolour1.MixJzczhz(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixJzczhz(unicolour1, -0.5, false); AssertMixed(mixed1, (0.015, 0.015, 315, 0.75)); AssertMixed(mixed2, (0.035, 0.035, 135, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromJzczhz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromJzczhz(0, 0, 0, 1.5); + var mixed = unicolour1.MixJzczhz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromJzczhz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromJzczhz(0, 0, 0, -0.5); + var mixed = unicolour1.MixJzczhz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex2))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromJzczhz(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromJzczhz(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixJzczhz(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixLabTests.cs b/Unicolour.Tests/MixLabTests.cs index 0795759c..30e4ffcc 100644 --- a/Unicolour.Tests/MixLabTests.cs +++ b/Unicolour.Tests/MixLabTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromLab(50, -64, 64, 0.5); var unicolour2 = Unicolour.FromLab(50, -64, 64, 0.5); - var mixed1 = unicolour1.MixLab(unicolour2, 0.25); - var mixed2 = unicolour2.MixLab(unicolour1, 0.75); - var mixed3 = unicolour1.MixLab(unicolour2, 0.75); - var mixed4 = unicolour2.MixLab(unicolour1, 0.25); + var mixed1 = unicolour1.MixLab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLab(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixLab(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixLab(unicolour1, 0.25, false); AssertMixed(mixed1, (50, -64, 64, 0.5)); AssertMixed(mixed2, (50, -64, 64, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromLab(0, -128, -128, 0.0); var unicolour2 = Unicolour.FromLab(50, 128, 128); - var mixed1 = unicolour1.MixLab(unicolour2, 0.5); - var mixed2 = unicolour2.MixLab(unicolour1, 0.5); + var mixed1 = unicolour1.MixLab(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLab(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 0, 0, 0.5)); AssertMixed(mixed2, (25, 0, 0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromLab(0, 128, -128); var unicolour2 = Unicolour.FromLab(80, -128, 128, 0.5); - var mixed1 = unicolour1.MixLab(unicolour2, 0.75); - var mixed2 = unicolour2.MixLab(unicolour1, 0.75); + var mixed1 = unicolour1.MixLab(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLab(unicolour1, 0.75, false); AssertMixed(mixed1, (60, -64, 64, 0.625)); AssertMixed(mixed2, (20, 64, -64, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromLab(0, 128, -128); var unicolour2 = Unicolour.FromLab(80, -128, 128, 0.5); - var mixed1 = unicolour1.MixLab(unicolour2, 0.25); - var mixed2 = unicolour2.MixLab(unicolour1, 0.25); + var mixed1 = unicolour1.MixLab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLab(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 64, -64, 0.875)); AssertMixed(mixed2, (60, -64, 64, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromLab(20, -25.6, 25.6, 0.8); var unicolour2 = Unicolour.FromLab(30, 25.6, -25.6, 0.9); - var mixed1 = unicolour1.MixLab(unicolour2, 1.5); - var mixed2 = unicolour2.MixLab(unicolour1, 1.5); + var mixed1 = unicolour1.MixLab(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixLab(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 51.2, -51.2, 0.95)); AssertMixed(mixed2, (15, -51.2, 51.2, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromLab(20, -25.6, 25.6, 0.8); var unicolour2 = Unicolour.FromLab(30, 25.6, -25.6, 0.9); - var mixed1 = unicolour1.MixLab(unicolour2, -0.5); - var mixed2 = unicolour2.MixLab(unicolour1, -0.5); + var mixed1 = unicolour1.MixLab(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixLab(unicolour1, -0.5, false); AssertMixed(mixed1, (15, -51.2, 51.2, 0.75)); AssertMixed(mixed2, (35, 51.2, -51.2, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromLab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLab(0, 0, 0, 1.5); + var mixed = unicolour1.MixLab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromLab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLab(0, 0, 0, -0.5); + var mixed = unicolour1.MixLab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromLab(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromLab(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixLab(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixLchabTests.cs b/Unicolour.Tests/MixLchabTests.cs index 0e3911f0..471729a6 100644 --- a/Unicolour.Tests/MixLchabTests.cs +++ b/Unicolour.Tests/MixLchabTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromLchab(50, 50, 180, 0.5); var unicolour2 = Unicolour.FromLchab(50, 50, 180, 0.5); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.75); - var mixed3 = unicolour1.MixLchab(unicolour2, 0.75); - var mixed4 = unicolour2.MixLchab(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixLchab(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixLchab(unicolour1, 0.25, false); AssertMixed(mixed1, (50, 50, 180, 0.5)); AssertMixed(mixed2, (50, 50, 180, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromLchab(0, 0, 0, 0.0); var unicolour2 = Unicolour.FromLchab(50, 100, 180); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.5); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.5); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 50, 90, 0.5)); AssertMixed(mixed2, (25, 50, 90, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromLchab(0, 0, 0, 0); var unicolour2 = Unicolour.FromLchab(80, 50, 340, 0.2); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.5); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.5); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.5, false); AssertMixed(mixed1, (40, 25, 350, 0.1)); AssertMixed(mixed2, (40, 25, 350, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromLchab(0, 100, 0); var unicolour2 = Unicolour.FromLchab(80, 0, 180, 0.5); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.75); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.75); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.75, false); AssertMixed(mixed1, (60, 25, 135, 0.625)); AssertMixed(mixed2, (20, 75, 45, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromLchab(0, 100, 300); var unicolour2 = Unicolour.FromLchab(80, 0, 60, 0.5); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.75); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.75); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.75, false); AssertMixed(mixed1, (60, 25, 30, 0.625)); AssertMixed(mixed2, (20, 75, 330, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromLchab(0, 100, 0); var unicolour2 = Unicolour.FromLchab(80, 0, 180, 0.5); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 75, 45, 0.875)); AssertMixed(mixed2, (60, 25, 135, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromLchab(0, 100, 300); var unicolour2 = Unicolour.FromLchab(80, 0, 60, 0.5); - var mixed1 = unicolour1.MixLchab(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchab(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 75, 330, 0.875)); AssertMixed(mixed2, (60, 25, 30, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromLchab(20, 40, 0, 0.8); var unicolour2 = Unicolour.FromLchab(30, 60, 90, 0.9); - var mixed1 = unicolour1.MixLchab(unicolour2, 1.5); - var mixed2 = unicolour2.MixLchab(unicolour1, 1.5); + var mixed1 = unicolour1.MixLchab(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixLchab(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 70, 135, 0.95)); AssertMixed(mixed2, (15, 30, 315, 0.75)); @@ -110,12 +110,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromLchab(20, 40, 0, 0.8); var unicolour2 = Unicolour.FromLchab(30, 60, 90, 0.9); - var mixed1 = unicolour1.MixLchab(unicolour2, -0.5); - var mixed2 = unicolour2.MixLchab(unicolour1, -0.5); + var mixed1 = unicolour1.MixLchab(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixLchab(unicolour1, -0.5, false); AssertMixed(mixed1, (15, 30, 315, 0.75)); AssertMixed(mixed2, (35, 70, 135, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromLchab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLchab(0, 0, 0, 1.5); + var mixed = unicolour1.MixLchab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromLchab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLchab(0, 0, 0, -0.5); + var mixed = unicolour1.MixLchab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex2))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromLchab(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromLchab(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixLchab(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixLchuvTests.cs b/Unicolour.Tests/MixLchuvTests.cs index 0daf8801..8a2d894b 100644 --- a/Unicolour.Tests/MixLchuvTests.cs +++ b/Unicolour.Tests/MixLchuvTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromLchuv(50, 50, 180, 0.5); var unicolour2 = Unicolour.FromLchuv(50, 50, 180, 0.5); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75); - var mixed3 = unicolour1.MixLchuv(unicolour2, 0.75); - var mixed4 = unicolour2.MixLchuv(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixLchuv(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixLchuv(unicolour1, 0.25, false); AssertMixed(mixed1, (50, 50, 180, 0.5)); AssertMixed(mixed2, (50, 50, 180, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromLchuv(0, 0, 0, 0.0); var unicolour2 = Unicolour.FromLchuv(50, 100, 180); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.5); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.5); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 50, 90, 0.5)); AssertMixed(mixed2, (25, 50, 90, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromLchuv(0, 0, 0, 0); var unicolour2 = Unicolour.FromLchuv(80, 50, 340, 0.2); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.5); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.5); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.5, false); AssertMixed(mixed1, (40, 25, 350, 0.1)); AssertMixed(mixed2, (40, 25, 350, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromLchuv(0, 100, 0); var unicolour2 = Unicolour.FromLchuv(80, 0, 180, 0.5); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.75); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75, false); AssertMixed(mixed1, (60, 25, 135, 0.625)); AssertMixed(mixed2, (20, 75, 45, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromLchuv(0, 100, 300); var unicolour2 = Unicolour.FromLchuv(80, 0, 60, 0.5); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.75); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.75, false); AssertMixed(mixed1, (60, 25, 30, 0.625)); AssertMixed(mixed2, (20, 75, 330, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromLchuv(0, 100, 0); var unicolour2 = Unicolour.FromLchuv(80, 0, 180, 0.5); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 75, 45, 0.875)); AssertMixed(mixed2, (60, 25, 135, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromLchuv(0, 100, 300); var unicolour2 = Unicolour.FromLchuv(80, 0, 60, 0.5); - var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25); - var mixed2 = unicolour2.MixLchuv(unicolour1, 0.25); + var mixed1 = unicolour1.MixLchuv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 75, 330, 0.875)); AssertMixed(mixed2, (60, 25, 30, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromLchuv(20, 40, 0, 0.8); var unicolour2 = Unicolour.FromLchuv(30, 60, 90, 0.9); - var mixed1 = unicolour1.MixLchuv(unicolour2, 1.5); - var mixed2 = unicolour2.MixLchuv(unicolour1, 1.5); + var mixed1 = unicolour1.MixLchuv(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 70, 135, 0.95)); AssertMixed(mixed2, (15, 30, 315, 0.75)); @@ -110,13 +110,42 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromLchuv(20, 40, 0, 0.8); var unicolour2 = Unicolour.FromLchuv(30, 60, 90, 0.9); - var mixed1 = unicolour1.MixLchuv(unicolour2, -0.5); - var mixed2 = unicolour2.MixLchuv(unicolour1, -0.5); + var mixed1 = unicolour1.MixLchuv(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixLchuv(unicolour1, -0.5, false); AssertMixed(mixed1, (15, 30, 315, 0.75)); AssertMixed(mixed2, (35, 70, 135, 0.95)); } - + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromLchuv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLchuv(0, 0, 0, 1.5); + var mixed = unicolour1.MixLchuv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromLchuv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLchuv(0, 0, 0, -0.5); + var mixed = unicolour1.MixLchuv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex2))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromLchuv(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromLchuv(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixLchuv(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } + private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { AssertUtils.AssertMixed(unicolour.Lchuv.Triplet, unicolour.Alpha.A, expected); diff --git a/Unicolour.Tests/MixLuvTests.cs b/Unicolour.Tests/MixLuvTests.cs index 83bfeab1..fcedf292 100644 --- a/Unicolour.Tests/MixLuvTests.cs +++ b/Unicolour.Tests/MixLuvTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromLuv(50, -50, 50, 0.5); var unicolour2 = Unicolour.FromLuv(50, -50, 50, 0.5); - var mixed1 = unicolour1.MixLuv(unicolour2, 0.25); - var mixed2 = unicolour2.MixLuv(unicolour1, 0.75); - var mixed3 = unicolour1.MixLuv(unicolour2, 0.75); - var mixed4 = unicolour2.MixLuv(unicolour1, 0.25); + var mixed1 = unicolour1.MixLuv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLuv(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixLuv(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixLuv(unicolour1, 0.25, false); AssertMixed(mixed1, (50, -50, 50, 0.5)); AssertMixed(mixed2, (50, -50, 50, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromLuv(0, -100, -100, 0.0); var unicolour2 = Unicolour.FromLuv(50, 100, 100); - var mixed1 = unicolour1.MixLuv(unicolour2, 0.5); - var mixed2 = unicolour2.MixLuv(unicolour1, 0.5); + var mixed1 = unicolour1.MixLuv(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixLuv(unicolour1, 0.5, false); AssertMixed(mixed1, (25, 0, 0, 0.5)); AssertMixed(mixed2, (25, 0, 0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromLuv(0, 100, -100); var unicolour2 = Unicolour.FromLuv(80, -100, 100, 0.5); - var mixed1 = unicolour1.MixLuv(unicolour2, 0.75); - var mixed2 = unicolour2.MixLuv(unicolour1, 0.75); + var mixed1 = unicolour1.MixLuv(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixLuv(unicolour1, 0.75, false); AssertMixed(mixed1, (60, -50, 50, 0.625)); AssertMixed(mixed2, (20, 50, -50, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromLuv(0, 100, -100); var unicolour2 = Unicolour.FromLuv(80, -100, 100, 0.5); - var mixed1 = unicolour1.MixLuv(unicolour2, 0.25); - var mixed2 = unicolour2.MixLuv(unicolour1, 0.25); + var mixed1 = unicolour1.MixLuv(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixLuv(unicolour1, 0.25, false); AssertMixed(mixed1, (20, 50, -50, 0.875)); AssertMixed(mixed2, (60, -50, 50, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromLuv(20, -25.6, 25.6, 0.8); var unicolour2 = Unicolour.FromLuv(30, 25.6, -25.6, 0.9); - var mixed1 = unicolour1.MixLuv(unicolour2, 1.5); - var mixed2 = unicolour2.MixLuv(unicolour1, 1.5); + var mixed1 = unicolour1.MixLuv(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixLuv(unicolour1, 1.5, false); AssertMixed(mixed1, (35, 51.2, -51.2, 0.95)); AssertMixed(mixed2, (15, -51.2, 51.2, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromLuv(20, -25.6, 25.6, 0.8); var unicolour2 = Unicolour.FromLuv(30, 25.6, -25.6, 0.9); - var mixed1 = unicolour1.MixLuv(unicolour2, -0.5); - var mixed2 = unicolour2.MixLuv(unicolour1, -0.5); + var mixed1 = unicolour1.MixLuv(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixLuv(unicolour1, -0.5, false); AssertMixed(mixed1, (15, -51.2, 51.2, 0.75)); AssertMixed(mixed2, (35, 51.2, -51.2, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromLuv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLuv(0, 0, 0, 1.5); + var mixed = unicolour1.MixLuv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromLuv(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromLuv(0, 0, 0, -0.5); + var mixed = unicolour1.MixLuv(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromLuv(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromLuv(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixLuv(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixOklabTests.cs b/Unicolour.Tests/MixOklabTests.cs index 7d043118..605ce892 100644 --- a/Unicolour.Tests/MixOklabTests.cs +++ b/Unicolour.Tests/MixOklabTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromOklab(0.5, -0.25, 0.25, 0.5); var unicolour2 = Unicolour.FromOklab(0.5, -0.25, 0.25, 0.5); - var mixed1 = unicolour1.MixOklab(unicolour2, 0.25); - var mixed2 = unicolour2.MixOklab(unicolour1, 0.75); - var mixed3 = unicolour1.MixOklab(unicolour2, 0.75); - var mixed4 = unicolour2.MixOklab(unicolour1, 0.25); + var mixed1 = unicolour1.MixOklab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixOklab(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixOklab(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixOklab(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, -0.25, 0.25, 0.5)); AssertMixed(mixed2, (0.5, -0.25, 0.25, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromOklab(0.0, -0.5, -0.5, 0.0); var unicolour2 = Unicolour.FromOklab(0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixOklab(unicolour2, 0.5); - var mixed2 = unicolour2.MixOklab(unicolour1, 0.5); + var mixed1 = unicolour1.MixOklab(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixOklab(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.0, 0.0, 0.5)); AssertMixed(mixed2, (0.25, 0.0, 0.0, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromOklab(0, 0.5, -0.5); var unicolour2 = Unicolour.FromOklab(0.8, -0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixOklab(unicolour2, 0.75); - var mixed2 = unicolour2.MixOklab(unicolour1, 0.75); + var mixed1 = unicolour1.MixOklab(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixOklab(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, -0.25, 0.25, 0.625)); AssertMixed(mixed2, (0.2, 0.25, -0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromOklab(0, 0.5, -0.5); var unicolour2 = Unicolour.FromOklab(0.8, -0.5, 0.5, 0.5); - var mixed1 = unicolour1.MixOklab(unicolour2, 0.25); - var mixed2 = unicolour2.MixOklab(unicolour1, 0.25); + var mixed1 = unicolour1.MixOklab(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixOklab(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.25, -0.25, 0.875)); AssertMixed(mixed2, (0.6, -0.25, 0.25, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromOklab(0.2, -0.125, 0.125, 0.8); var unicolour2 = Unicolour.FromOklab(0.3, 0.125, -0.125, 0.9); - var mixed1 = unicolour1.MixOklab(unicolour2, 1.5); - var mixed2 = unicolour2.MixOklab(unicolour1, 1.5); + var mixed1 = unicolour1.MixOklab(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixOklab(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.25, -0.25, 0.95)); AssertMixed(mixed2, (0.15, -0.25, 0.25, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromOklab(0.2, -0.125, 0.125, 0.8); var unicolour2 = Unicolour.FromOklab(0.3, 0.125, -0.125, 0.9); - var mixed1 = unicolour1.MixOklab(unicolour2, -0.5); - var mixed2 = unicolour2.MixOklab(unicolour1, -0.5); + var mixed1 = unicolour1.MixOklab(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixOklab(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, -0.25, 0.25, 0.75)); AssertMixed(mixed2, (0.35, 0.25, -0.25, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromOklab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromOklab(0, 0, 0, 1.5); + var mixed = unicolour1.MixOklab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromOklab(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromOklab(0, 0, 0, -0.5); + var mixed = unicolour1.MixOklab(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromOklab(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromOklab(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixOklab(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixOklchTests.cs b/Unicolour.Tests/MixOklchTests.cs index 85a384dd..e8453f86 100644 --- a/Unicolour.Tests/MixOklchTests.cs +++ b/Unicolour.Tests/MixOklchTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromOklch(0.5, 0.25, 180, 0.5); var unicolour2 = Unicolour.FromOklch(0.5, 0.25, 180, 0.5); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.25); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.75); - var mixed3 = unicolour1.MixOklch(unicolour2, 0.75); - var mixed4 = unicolour2.MixOklch(unicolour1, 0.25); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixOklch(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixOklch(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, 0.25, 180, 0.5)); AssertMixed(mixed2, (0.5, 0.25, 180, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromOklch(0, 0, 0, 0.0); var unicolour2 = Unicolour.FromOklch(0.5, 0.5, 180); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.5); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.5); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.25, 90, 0.5)); AssertMixed(mixed2, (0.25, 0.25, 90, 0.5)); @@ -38,8 +38,8 @@ public void EquidistantViaZero() { var unicolour1 = Unicolour.FromOklch(0, 0, 0, 0); var unicolour2 = Unicolour.FromOklch(0.8, 0.25, 340, 0.2); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.5); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.5); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.5, false); AssertMixed(mixed1, (0.4, 0.125, 350, 0.1)); AssertMixed(mixed2, (0.4, 0.125, 350, 0.1)); @@ -50,8 +50,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromOklch(0, 0.5, 0); var unicolour2 = Unicolour.FromOklch(0.8, 0, 180, 0.5); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.75); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.75); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.125, 135, 0.625)); AssertMixed(mixed2, (0.2, 0.375, 45, 0.875)); @@ -62,8 +62,8 @@ public void CloserToEndColourViaZero() { var unicolour1 = Unicolour.FromOklch(0, 0.5, 300); var unicolour2 = Unicolour.FromOklch(0.8, 0, 60, 0.5); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.75); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.75); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.125, 30, 0.625)); AssertMixed(mixed2, (0.2, 0.375, 330, 0.875)); @@ -74,8 +74,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromOklch(0, 0.5, 0); var unicolour2 = Unicolour.FromOklch(0.8, 0, 180, 0.5); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.25); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.25); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.375, 45, 0.875)); AssertMixed(mixed2, (0.6, 0.125, 135, 0.625)); @@ -86,8 +86,8 @@ public void CloserToStartColourViaZero() { var unicolour1 = Unicolour.FromOklch(0, 0.5, 300); var unicolour2 = Unicolour.FromOklch(0.8, 0, 60, 0.5); - var mixed1 = unicolour1.MixOklch(unicolour2, 0.25); - var mixed2 = unicolour2.MixOklch(unicolour1, 0.25); + var mixed1 = unicolour1.MixOklch(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.375, 330, 0.875)); AssertMixed(mixed2, (0.6, 0.125, 30, 0.625)); @@ -98,8 +98,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromOklch(0.2, 0.2, 0, 0.8); var unicolour2 = Unicolour.FromOklch(0.3, 0.3, 90, 0.9); - var mixed1 = unicolour1.MixOklch(unicolour2, 1.5); - var mixed2 = unicolour2.MixOklch(unicolour1, 1.5); + var mixed1 = unicolour1.MixOklch(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixOklch(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.35, 135, 0.95)); AssertMixed(mixed2, (0.15, 0.15, 315, 0.75)); @@ -110,13 +110,42 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromOklch(0.2, 0.2, 0, 0.8); var unicolour2 = Unicolour.FromOklch(0.3, 0.3, 90, 0.9); - var mixed1 = unicolour1.MixOklch(unicolour2, -0.5); - var mixed2 = unicolour2.MixOklch(unicolour1, -0.5); + var mixed1 = unicolour1.MixOklch(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixOklch(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, 0.15, 315, 0.75)); AssertMixed(mixed2, (0.35, 0.35, 135, 0.95)); } + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromOklch(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromOklch(0, 0, 0, 1.5); + var mixed = unicolour1.MixOklch(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromOklch(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromOklch(0, 0, 0, -0.5); + var mixed = unicolour1.MixOklch(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedHueIndex2))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromOklch(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromOklch(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixOklch(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } + private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { AssertUtils.AssertMixed(unicolour.Oklch.Triplet, unicolour.Alpha.A, expected); diff --git a/Unicolour.Tests/MixRgbLinearTests.cs b/Unicolour.Tests/MixRgbLinearTests.cs index b9dd6b41..7149339c 100644 --- a/Unicolour.Tests/MixRgbLinearTests.cs +++ b/Unicolour.Tests/MixRgbLinearTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromRgbLinear(0.5, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromRgbLinear(0.5, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.25); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.75); - var mixed3 = unicolour1.MixRgbLinear(unicolour2, 0.75); - var mixed4 = unicolour2.MixRgbLinear(unicolour1, 0.25); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixRgbLinear(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixRgbLinear(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (0.5, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromRgbLinear(0.0, 0.0, 0.0, 0.0); var unicolour2 = Unicolour.FromRgbLinear(0.5, 1.0, 1.0); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.5); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.5); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (0.25, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromRgbLinear(0, 1, 0); var unicolour2 = Unicolour.FromRgbLinear(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.75); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.75); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (0.2, 0.75, 0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromRgbLinear(0.0, 1.0, 0.0); var unicolour2 = Unicolour.FromRgbLinear(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.25); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.25); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (0.6, 0.25, 0.75, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromRgbLinear(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromRgbLinear(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, 1.5); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, 1.5); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (0.15, 0.3, 0.7, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromRgbLinear(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromRgbLinear(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixRgbLinear(unicolour2, -0.5); - var mixed2 = unicolour2.MixRgbLinear(unicolour1, -0.5); + var mixed1 = unicolour1.MixRgbLinear(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixRgbLinear(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (0.35, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromRgbLinear(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromRgbLinear(0, 0, 0, 1.5); + var mixed = unicolour1.MixRgbLinear(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromRgbLinear(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromRgbLinear(0, 0, 0, -0.5); + var mixed = unicolour1.MixRgbLinear(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromRgbLinear(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromRgbLinear(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixRgbLinear(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixRgbTests.cs b/Unicolour.Tests/MixRgbTests.cs index e8ec4fc1..60e9d801 100644 --- a/Unicolour.Tests/MixRgbTests.cs +++ b/Unicolour.Tests/MixRgbTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromRgb(0.5, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromRgb(0.5, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixRgb(unicolour2, 0.25); - var mixed2 = unicolour2.MixRgb(unicolour1, 0.75); - var mixed3 = unicolour1.MixRgb(unicolour2, 0.75); - var mixed4 = unicolour2.MixRgb(unicolour1, 0.25); + var mixed1 = unicolour1.MixRgb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixRgb(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixRgb(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixRgb(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (0.5, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromRgb(0.0, 0.0, 0.0, 0.0); var unicolour2 = Unicolour.FromRgb(0.5, 1.0, 1.0); - var mixed1 = unicolour1.MixRgb(unicolour2, 0.5); - var mixed2 = unicolour2.MixRgb(unicolour1, 0.5); + var mixed1 = unicolour1.MixRgb(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixRgb(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (0.25, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromRgb(0, 1, 0); var unicolour2 = Unicolour.FromRgb(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixRgb(unicolour2, 0.75); - var mixed2 = unicolour2.MixRgb(unicolour1, 0.75); + var mixed1 = unicolour1.MixRgb(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixRgb(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (0.2, 0.75, 0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromRgb(0.0, 1.0, 0.0); var unicolour2 = Unicolour.FromRgb(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixRgb(unicolour2, 0.25); - var mixed2 = unicolour2.MixRgb(unicolour1, 0.25); + var mixed1 = unicolour1.MixRgb(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixRgb(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (0.6, 0.25, 0.75, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromRgb(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromRgb(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixRgb(unicolour2, 1.5); - var mixed2 = unicolour2.MixRgb(unicolour1, 1.5); + var mixed1 = unicolour1.MixRgb(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixRgb(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (0.15, 0.3, 0.7, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromRgb(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromRgb(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixRgb(unicolour2, -0.5); - var mixed2 = unicolour2.MixRgb(unicolour1, -0.5); + var mixed1 = unicolour1.MixRgb(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixRgb(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (0.35, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromRgb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromRgb(0, 0, 0, 1.5); + var mixed = unicolour1.MixRgb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromRgb(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromRgb(0, 0, 0, -0.5); + var mixed = unicolour1.MixRgb(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromRgb(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromRgb(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixRgb(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixXyyTests.cs b/Unicolour.Tests/MixXyyTests.cs index 0770e900..5bd241da 100644 --- a/Unicolour.Tests/MixXyyTests.cs +++ b/Unicolour.Tests/MixXyyTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromXyy(0.5, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromXyy(0.5, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixXyy(unicolour2, 0.25); - var mixed2 = unicolour2.MixXyy(unicolour1, 0.75); - var mixed3 = unicolour1.MixXyy(unicolour2, 0.75); - var mixed4 = unicolour2.MixXyy(unicolour1, 0.25); + var mixed1 = unicolour1.MixXyy(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixXyy(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixXyy(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixXyy(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (0.5, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromXyy(0.0, 0.0, 0.0, 0.0); var unicolour2 = Unicolour.FromXyy(0.5, 1.0, 1.0); - var mixed1 = unicolour1.MixXyy(unicolour2, 0.5); - var mixed2 = unicolour2.MixXyy(unicolour1, 0.5); + var mixed1 = unicolour1.MixXyy(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixXyy(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (0.25, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromXyy(0, 1, 0); var unicolour2 = Unicolour.FromXyy(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixXyy(unicolour2, 0.75); - var mixed2 = unicolour2.MixXyy(unicolour1, 0.75); + var mixed1 = unicolour1.MixXyy(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixXyy(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (0.2, 0.75, 0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromXyy(0.0, 1.0, 0.0); var unicolour2 = Unicolour.FromXyy(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixXyy(unicolour2, 0.25); - var mixed2 = unicolour2.MixXyy(unicolour1, 0.25); + var mixed1 = unicolour1.MixXyy(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixXyy(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (0.6, 0.25, 0.75, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromXyy(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromXyy(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixXyy(unicolour2, 1.5); - var mixed2 = unicolour2.MixXyy(unicolour1, 1.5); + var mixed1 = unicolour1.MixXyy(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixXyy(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (0.15, 0.3, 0.7, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromXyy(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromXyy(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixXyy(unicolour2, -0.5); - var mixed2 = unicolour2.MixXyy(unicolour1, -0.5); + var mixed1 = unicolour1.MixXyy(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixXyy(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (0.35, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromXyy(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromXyy(0, 0, 0, 1.5); + var mixed = unicolour1.MixXyy(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromXyy(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromXyy(0, 0, 0, -0.5); + var mixed = unicolour1.MixXyy(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromXyy(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromXyy(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixXyy(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/MixXyzTests.cs b/Unicolour.Tests/MixXyzTests.cs index 1db8a0ee..e52ffce9 100644 --- a/Unicolour.Tests/MixXyzTests.cs +++ b/Unicolour.Tests/MixXyzTests.cs @@ -10,10 +10,10 @@ public void SameColour() { var unicolour1 = Unicolour.FromXyz(0.5, 0.25, 0.75, 0.5); var unicolour2 = Unicolour.FromXyz(0.5, 0.25, 0.75, 0.5); - var mixed1 = unicolour1.MixXyz(unicolour2, 0.25); - var mixed2 = unicolour2.MixXyz(unicolour1, 0.75); - var mixed3 = unicolour1.MixXyz(unicolour2, 0.75); - var mixed4 = unicolour2.MixXyz(unicolour1, 0.25); + var mixed1 = unicolour1.MixXyz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixXyz(unicolour1, 0.75, false); + var mixed3 = unicolour1.MixXyz(unicolour2, 0.75, false); + var mixed4 = unicolour2.MixXyz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.5, 0.25, 0.75, 0.5)); AssertMixed(mixed2, (0.5, 0.25, 0.75, 0.5)); @@ -26,8 +26,8 @@ public void Equidistant() { var unicolour1 = Unicolour.FromXyz(0.0, 0.0, 0.0, 0.0); var unicolour2 = Unicolour.FromXyz(0.5, 1.0, 1.0); - var mixed1 = unicolour1.MixXyz(unicolour2, 0.5); - var mixed2 = unicolour2.MixXyz(unicolour1, 0.5); + var mixed1 = unicolour1.MixXyz(unicolour2, 0.5, false); + var mixed2 = unicolour2.MixXyz(unicolour1, 0.5, false); AssertMixed(mixed1, (0.25, 0.5, 0.5, 0.5)); AssertMixed(mixed2, (0.25, 0.5, 0.5, 0.5)); @@ -38,8 +38,8 @@ public void CloserToEndColour() { var unicolour1 = Unicolour.FromXyz(0, 1, 0); var unicolour2 = Unicolour.FromXyz(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixXyz(unicolour2, 0.75); - var mixed2 = unicolour2.MixXyz(unicolour1, 0.75); + var mixed1 = unicolour1.MixXyz(unicolour2, 0.75, false); + var mixed2 = unicolour2.MixXyz(unicolour1, 0.75, false); AssertMixed(mixed1, (0.6, 0.25, 0.75, 0.625)); AssertMixed(mixed2, (0.2, 0.75, 0.25, 0.875)); @@ -50,8 +50,8 @@ public void CloserToStartColour() { var unicolour1 = Unicolour.FromXyz(0.0, 1.0, 0.0); var unicolour2 = Unicolour.FromXyz(0.8, 0.0, 1.0, 0.5); - var mixed1 = unicolour1.MixXyz(unicolour2, 0.25); - var mixed2 = unicolour2.MixXyz(unicolour1, 0.25); + var mixed1 = unicolour1.MixXyz(unicolour2, 0.25, false); + var mixed2 = unicolour2.MixXyz(unicolour1, 0.25, false); AssertMixed(mixed1, (0.2, 0.75, 0.25, 0.875)); AssertMixed(mixed2, (0.6, 0.25, 0.75, 0.625)); @@ -62,8 +62,8 @@ public void BeyondEndColour() { var unicolour1 = Unicolour.FromXyz(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromXyz(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixXyz(unicolour2, 1.5); - var mixed2 = unicolour2.MixXyz(unicolour1, 1.5); + var mixed1 = unicolour1.MixXyz(unicolour2, 1.5, false); + var mixed2 = unicolour2.MixXyz(unicolour1, 1.5, false); AssertMixed(mixed1, (0.35, 0.7, 0.3, 0.95)); AssertMixed(mixed2, (0.15, 0.3, 0.7, 0.75)); @@ -74,12 +74,41 @@ public void BeyondStartColour() { var unicolour1 = Unicolour.FromXyz(0.2, 0.4, 0.6, 0.8); var unicolour2 = Unicolour.FromXyz(0.3, 0.6, 0.4, 0.9); - var mixed1 = unicolour1.MixXyz(unicolour2, -0.5); - var mixed2 = unicolour2.MixXyz(unicolour1, -0.5); + var mixed1 = unicolour1.MixXyz(unicolour2, -0.5, false); + var mixed2 = unicolour2.MixXyz(unicolour1, -0.5, false); AssertMixed(mixed1, (0.15, 0.3, 0.7, 0.75)); AssertMixed(mixed2, (0.35, 0.7, 0.3, 0.95)); } + + [Test] + public void BeyondMaxAlpha() + { + var unicolour1 = Unicolour.FromXyz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromXyz(0, 0, 0, 1.5); + var mixed = unicolour1.MixXyz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); + } + + [Test] + public void BeyondMinAlpha() + { + var unicolour1 = Unicolour.FromXyz(0, 0, 0, 0.5); + var unicolour2 = Unicolour.FromXyz(0, 0, 0, -0.5); + var mixed = unicolour1.MixXyz(unicolour2, 3); + Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); + Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); + } + + [TestCaseSource(typeof(AlphaInterpolationUtils), nameof(AlphaInterpolationUtils.PremultipliedNoHueComponent))] + public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) + { + var unicolour1 = Unicolour.FromXyz(start.Triplet.Tuple, start.Alpha); + var unicolour2 = Unicolour.FromXyz(end.Triplet.Tuple, end.Alpha); + var mixed = unicolour1.MixXyz(unicolour2, amount, premultiplyAlpha: true); + AssertMixed(mixed, expected.Tuple); + } private static void AssertMixed(Unicolour unicolour, (double first, double second, double third, double alpha) expected) { diff --git a/Unicolour.Tests/Utils/AlphaInterpolationUtils.cs b/Unicolour.Tests/Utils/AlphaInterpolationUtils.cs new file mode 100644 index 00000000..dc54c2b0 --- /dev/null +++ b/Unicolour.Tests/Utils/AlphaInterpolationUtils.cs @@ -0,0 +1,40 @@ +namespace Wacton.Unicolour.Tests.Utils; + +using System.Collections.Generic; +using NUnit.Framework; + +public class AlphaInterpolationUtils +{ + public static readonly List PremultipliedNoHueComponent = new() + { + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 0.5), 0.25), new AlphaTriplet(new(1.0, 0.5, 1.0), 0.75), 0.00, new AlphaTriplet(new(0.500, 1.000, 0.500), 0.250)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 0.5), 0.25), new AlphaTriplet(new(1.0, 0.5, 1.0), 0.75), 0.25, new AlphaTriplet(new(0.750, 0.750, 0.750), 0.375)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 0.5), 0.25), new AlphaTriplet(new(1.0, 0.5, 1.0), 0.75), 0.50, new AlphaTriplet(new(0.875, 0.625, 0.875), 0.500)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 0.5), 0.25), new AlphaTriplet(new(1.0, 0.5, 1.0), 0.75), 0.75, new AlphaTriplet(new(0.950, 0.550, 0.950), 0.625)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 0.5), 0.25), new AlphaTriplet(new(1.0, 0.5, 1.0), 0.75), 1.00, new AlphaTriplet(new(1.000, 0.500, 1.000), 0.750)) + }; + + public static readonly List PremultipliedHueIndex0 = new() + { + new TestCaseData(new AlphaTriplet(new(90, 1.0, 0.5), 0.25), new AlphaTriplet(new(270, 0.5, 1.0), 0.75), 0.00, new AlphaTriplet(new(90, 1.000, 0.500), 0.250)), + new TestCaseData(new AlphaTriplet(new(90, 1.0, 0.5), 0.25), new AlphaTriplet(new(270, 0.5, 1.0), 0.75), 0.25, new AlphaTriplet(new(135, 0.750, 0.750), 0.375)), + new TestCaseData(new AlphaTriplet(new(90, 1.0, 0.5), 0.25), new AlphaTriplet(new(270, 0.5, 1.0), 0.75), 0.50, new AlphaTriplet(new(180, 0.625, 0.875), 0.500)), + new TestCaseData(new AlphaTriplet(new(90, 1.0, 0.5), 0.25), new AlphaTriplet(new(270, 0.5, 1.0), 0.75), 0.75, new AlphaTriplet(new(225, 0.550, 0.950), 0.625)), + new TestCaseData(new AlphaTriplet(new(90, 1.0, 0.5), 0.25), new AlphaTriplet(new(270, 0.5, 1.0), 0.75), 1.00, new AlphaTriplet(new(270, 0.500, 1.000), 0.750)) + }; + + public static readonly List PremultipliedHueIndex2 = new() + { + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 90), 0.25), new AlphaTriplet(new(1.0, 0.5, 270), 0.75), 0.00, new AlphaTriplet(new(0.500, 1.000, 90), 0.250)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 90), 0.25), new AlphaTriplet(new(1.0, 0.5, 270), 0.75), 0.25, new AlphaTriplet(new(0.750, 0.750, 135), 0.375)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 90), 0.25), new AlphaTriplet(new(1.0, 0.5, 270), 0.75), 0.50, new AlphaTriplet(new(0.875, 0.625, 180), 0.500)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 90), 0.25), new AlphaTriplet(new(1.0, 0.5, 270), 0.75), 0.75, new AlphaTriplet(new(0.950, 0.550, 225), 0.625)), + new TestCaseData(new AlphaTriplet(new(0.5, 1.0, 90), 0.25), new AlphaTriplet(new(1.0, 0.5, 270), 0.75), 1.00, new AlphaTriplet(new(1.000, 0.500, 270), 0.750)) + }; +} + +public record AlphaTriplet(ColourTriplet Triplet, double Alpha) +{ + public (double first, double second, double third, double alpha) Tuple => (Triplet.First, Triplet.Second, Triplet.Third, Alpha); + public override string ToString() => Tuple.ToString(); +} diff --git a/Unicolour.Tests/Utils/ConfigUtils.cs b/Unicolour.Tests/Utils/ConfigUtils.cs new file mode 100644 index 00000000..c99ca4fc --- /dev/null +++ b/Unicolour.Tests/Utils/ConfigUtils.cs @@ -0,0 +1,34 @@ +namespace Wacton.Unicolour.Tests.Utils; + +using System; + +public static class ConfigUtils +{ + private static readonly Configuration StandardRgbConfig = new(RgbConfiguration.StandardRgb); + private static readonly Configuration DisplayP3Config = new(RgbConfiguration.DisplayP3); + private static readonly Configuration Rec2020Config = new(RgbConfiguration.Rec2020); + + internal static Configuration GetConfigWithXyzD65(RgbType rgbType) + { + return rgbType switch + { + RgbType.Standard => StandardRgbConfig, + RgbType.DisplayP3 => DisplayP3Config, + RgbType.Rec2020 => Rec2020Config, + _ => throw new ArgumentOutOfRangeException(nameof(rgbType), rgbType, null) + }; + } + + internal static Configuration GetConfigWithStandardRgb(Illuminant illuminant) + { + var xyzConfig = new XyzConfiguration(WhitePoint.From(illuminant)); + return new Configuration(RgbConfiguration.StandardRgb, xyzConfig); + } + + public enum RgbType + { + Standard, + DisplayP3, + Rec2020 + } +} \ No newline at end of file diff --git a/Unicolour/Alpha.cs b/Unicolour/Alpha.cs index fe3d8520..09aef5b9 100644 --- a/Unicolour/Alpha.cs +++ b/Unicolour/Alpha.cs @@ -3,7 +3,8 @@ public record Alpha(double A) { public double A { get; } = A; - public int A255 => double.IsNaN(A) ? 0 : (int)Math.Round(A * 255).Clamp(0, 255); + public double ConstrainedA => double.IsNaN(A) ? 0 : A.Clamp(0.0, 1.0); + public int A255 => (int)Math.Round(ConstrainedA * 255); public string Hex => $"{A255:X2}"; public override string ToString() => $"{A}"; diff --git a/Unicolour/ColourRepresentation.cs b/Unicolour/ColourRepresentation.cs index 319567d2..5e2d6645 100644 --- a/Unicolour/ColourRepresentation.cs +++ b/Unicolour/ColourRepresentation.cs @@ -28,7 +28,7 @@ public abstract record ColourRepresentation internal bool UseAsGreyscale => Heritage == ColourHeritage.Greyscale || Heritage == ColourHeritage.GreyscaleAndHued || (!UseAsNaN && IsGreyscale); /* - * a representation is considered "hued" when it has a hue axis (e.g. HSL / LCH) and is not greyscale + * a representation is considered "hued" when it has a hue component (e.g. HSL / LCH) and is not greyscale * enabling differentiation between representations where: * a) a hue value is meaningful ------------------------------ e.g. HSB(0,0,0) = red with no saturation or brightness * b) a hue value is used as a fallback when there is no hue - e.g. RGB(0,0,0) -> HSB(0,0,0) = black with no red @@ -36,8 +36,8 @@ public abstract record ColourRepresentation * [RGB(0,0,0) black -> RGB(0,0,255) blue] via HSB is [HSB(0,0,0) red with no colour -> HSB(240,1,1) blue with full colour] * but the mixing should only start at the red hue if the value 0 was provided by the user (FromHsb instead of FromRgb) */ - internal bool HasHueAxis => HueIndex != null; - internal bool UseAsHued => (Heritage == ColourHeritage.None || Heritage == ColourHeritage.Hued || Heritage == ColourHeritage.GreyscaleAndHued) && !UseAsNaN && HasHueAxis; + internal bool HasHueComponent => HueIndex != null; + internal bool UseAsHued => (Heritage == ColourHeritage.None || Heritage == ColourHeritage.Hued || Heritage == ColourHeritage.GreyscaleAndHued) && !UseAsNaN && HasHueComponent; internal ColourRepresentation(double first, double second, double third, ColourHeritage heritage) { diff --git a/Unicolour/ColourTriplet.cs b/Unicolour/ColourTriplet.cs index 8c7352b1..2f9c44b5 100644 --- a/Unicolour/ColourTriplet.cs +++ b/Unicolour/ColourTriplet.cs @@ -7,6 +7,8 @@ public record ColourTriplet(double First, double Second, double Third, int? HueI public double Third { get; } = Third; public (double, double, double) Tuple => (First, Second, Third); public int? HueIndex { get; } = HueIndex; + + public double[] AsArray() => new[] { First, Second, Third }; internal double HueValue() { @@ -14,7 +16,7 @@ internal double HueValue() { 0 => First, 2 => Third, - _ => throw new ArgumentException() + _ => throw new ArgumentOutOfRangeException() }; } @@ -24,7 +26,7 @@ internal ColourTriplet WithHueOverride(double hue) { 0 => new(hue, Second, Third, HueIndex), 2 => new(First, Second, hue, HueIndex), - _ => throw new ArgumentException() + _ => throw new ArgumentOutOfRangeException() }; } @@ -34,17 +36,41 @@ internal ColourTriplet WithHueModulo() { 0 => new(First.Modulo(360), Second, Third, HueIndex), 2 => new(First, Second, Third.Modulo(360), HueIndex), - _ => new(First, Second, Third, HueIndex) + null => new(First, Second, Third, HueIndex), + _ => throw new ArgumentOutOfRangeException() }; } - public override string ToString() => Tuple.ToString(); - - // need a custom deconstruct to ignore the nullable hue + internal ColourTriplet WithPremultipliedAlpha(double alpha) + { + return HueIndex switch + { + 0 => new(First, Second * alpha, Third * alpha, HueIndex), + 2 => new(First * alpha, Second * alpha, Third, HueIndex), + null => new(First * alpha, Second * alpha, Third * alpha, HueIndex), + _ => throw new ArgumentOutOfRangeException() + }; + } + + internal ColourTriplet WithUnpremultipliedAlpha(double alpha) + { + if (alpha == 0) return new(First, Second, Third, HueIndex); + return HueIndex switch + { + 0 => new(First, Second / alpha, Third / alpha, HueIndex), + 2 => new(First / alpha, Second / alpha, Third, HueIndex), + null => new(First / alpha, Second / alpha, Third / alpha, HueIndex), + _ => throw new ArgumentOutOfRangeException() + }; + } + + // need a custom deconstruct to ignore the nullable hue index public void Deconstruct(out double first, out double second, out double third) { first = First; second = Second; third = Third; } + + public override string ToString() => Tuple.ToString(); } \ No newline at end of file diff --git a/Unicolour/Interpolation.cs b/Unicolour/Interpolation.cs index 5cbc82f2..63295a84 100644 --- a/Unicolour/Interpolation.cs +++ b/Unicolour/Interpolation.cs @@ -2,29 +2,63 @@ internal static class Interpolation { - internal static Unicolour Mix(ColourSpace colourSpace, Unicolour startColour, Unicolour endColour, double distance) + internal static Unicolour Mix(ColourSpace colourSpace, Unicolour startColour, Unicolour endColour, double distance, bool premultiplyAlpha) { GuardConfiguration(startColour, endColour); var startRepresentation = startColour.GetRepresentation(colourSpace); + var startAlpha = startColour.Alpha; var endRepresentation = endColour.GetRepresentation(colourSpace); + var endAlpha = endColour.Alpha; + + (ColourTriplet start, ColourTriplet end) = GetTripletsToInterpolate( + (startRepresentation, startAlpha), + (endRepresentation, endAlpha), + premultiplyAlpha); - var (start, end) = GetTriplets(startRepresentation, endRepresentation); var triplet = InterpolateTriplet(start, end, distance).WithHueModulo(); - var (first, second, third) = triplet; - var alpha = Interpolate(startColour.Alpha.A, endColour.Alpha.A, distance); - var heritage = ColourHeritage.From(startRepresentation, endRepresentation); + var alpha = Interpolate(startColour.Alpha.ConstrainedA, endColour.Alpha.ConstrainedA, distance); + + if (premultiplyAlpha) + { + triplet = triplet.WithUnpremultipliedAlpha(alpha); + } + var heritage = ColourHeritage.From(startRepresentation, endRepresentation); + var (first, second, third) = triplet; return GetConstructor(colourSpace).Invoke(startColour.Config, heritage, first, second, third, alpha); } - - private static (ColourTriplet start, ColourTriplet end) GetTriplets(ColourRepresentation startRepresentation, ColourRepresentation endRepresentation) + + private static (ColourTriplet start, ColourTriplet end) GetTripletsToInterpolate( + (ColourRepresentation representation, Alpha alpha) start, + (ColourRepresentation representation, Alpha alpha) end, + bool premultiplyAlpha) { - var startTriplet = startRepresentation.Triplet; - var endTriplet = endRepresentation.Triplet; - return startRepresentation.HasHueAxis ? GetTripletsWithHue(startRepresentation, endRepresentation) : (startTriplet, endTriplet); - } + ColourTriplet startTriplet; + ColourTriplet endTriplet; + + // these can't give different answers since they use the same colour space + // (except by reflection, in which case an error would be thrown when later trying to read the hue component) + var hasHueComponent = start.representation.HasHueComponent || end.representation.HasHueComponent; + if (hasHueComponent) + { + (startTriplet, endTriplet) = GetTripletsWithHue(start.representation, end.representation); + } + else + { + startTriplet = start.representation.Triplet; + endTriplet = end.representation.Triplet; + } + + if (premultiplyAlpha) + { + startTriplet = startTriplet.WithPremultipliedAlpha(start.alpha.ConstrainedA); + endTriplet = endTriplet.WithPremultipliedAlpha(end.alpha.ConstrainedA); + } + return (startTriplet, endTriplet); + } + private static (ColourTriplet start, ColourTriplet end) GetTripletsWithHue(ColourRepresentation startRepresentation, ColourRepresentation endRepresentation) { var startTriplet = startRepresentation.Triplet; diff --git a/Unicolour/Unicolour.cs b/Unicolour/Unicolour.cs index e8013b3e..db820bfc 100644 --- a/Unicolour/Unicolour.cs +++ b/Unicolour/Unicolour.cs @@ -76,27 +76,27 @@ private Unicolour(Configuration config, ColourRepresentation initialRepresentati public double DeltaECam02(Unicolour sample) => Comparison.DeltaECam02(this, sample); public double DeltaECam16(Unicolour sample) => Comparison.DeltaECam16(this, sample); - public Unicolour MixRgb(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Rgb, this, other, amount); - public Unicolour MixRgbLinear(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.RgbLinear, this, other, amount); - public Unicolour MixHsb(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hsb, this, other, amount); - public Unicolour MixHsl(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hsl, this, other, amount); - public Unicolour MixHwb(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hwb, this, other, amount); - public Unicolour MixXyz(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Xyz, this, other, amount); - public Unicolour MixXyy(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Xyy, this, other, amount); - public Unicolour MixLab(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Lab, this, other, amount); - public Unicolour MixLchab(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Lchab, this, other, amount); - public Unicolour MixLuv(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Luv, this, other, amount); - public Unicolour MixLchuv(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Lchuv, this, other, amount); - public Unicolour MixHsluv(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hsluv, this, other, amount); - public Unicolour MixHpluv(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hpluv, this, other, amount); - public Unicolour MixIctcp(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Ictcp, this, other, amount); - public Unicolour MixJzazbz(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Jzazbz, this, other, amount); - public Unicolour MixJzczhz(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Jzczhz, this, other, amount); - public Unicolour MixOklab(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Oklab, this, other, amount); - public Unicolour MixOklch(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Oklch, this, other, amount); - public Unicolour MixCam02(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Cam02, this, other, amount); - public Unicolour MixCam16(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Cam16, this, other, amount); - public Unicolour MixHct(Unicolour other, double amount = 0.5) => Interpolation.Mix(ColourSpace.Hct, this, other, amount); + public Unicolour MixRgb(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Rgb, this, other, amount, premultiplyAlpha); + public Unicolour MixRgbLinear(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.RgbLinear, this, other, amount, premultiplyAlpha); + public Unicolour MixHsb(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hsb, this, other, amount, premultiplyAlpha); + public Unicolour MixHsl(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hsl, this, other, amount, premultiplyAlpha); + public Unicolour MixHwb(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hwb, this, other, amount, premultiplyAlpha); + public Unicolour MixXyz(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Xyz, this, other, amount, premultiplyAlpha); + public Unicolour MixXyy(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Xyy, this, other, amount, premultiplyAlpha); + public Unicolour MixLab(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Lab, this, other, amount, premultiplyAlpha); + public Unicolour MixLchab(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Lchab, this, other, amount, premultiplyAlpha); + public Unicolour MixLuv(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Luv, this, other, amount, premultiplyAlpha); + public Unicolour MixLchuv(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Lchuv, this, other, amount, premultiplyAlpha); + public Unicolour MixHsluv(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hsluv, this, other, amount, premultiplyAlpha); + public Unicolour MixHpluv(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hpluv, this, other, amount, premultiplyAlpha); + public Unicolour MixIctcp(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Ictcp, this, other, amount, premultiplyAlpha); + public Unicolour MixJzazbz(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Jzazbz, this, other, amount, premultiplyAlpha); + public Unicolour MixJzczhz(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Jzczhz, this, other, amount, premultiplyAlpha); + public Unicolour MixOklab(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Oklab, this, other, amount, premultiplyAlpha); + public Unicolour MixOklch(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Oklch, this, other, amount, premultiplyAlpha); + public Unicolour MixCam02(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Cam02, this, other, amount, premultiplyAlpha); + public Unicolour MixCam16(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Cam16, this, other, amount, premultiplyAlpha); + public Unicolour MixHct(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) => Interpolation.Mix(ColourSpace.Hct, this, other, amount, premultiplyAlpha); public Unicolour SimulateProtanopia() => VisionDeficiency.SimulateProtanopia(this); public Unicolour SimulateDeuteranopia() => VisionDeficiency.SimulateDeuteranopia(this);