diff --git a/Example.Console/Program.cs b/Example.Console/Program.cs index 51e4637..7c0c953 100644 --- a/Example.Console/Program.cs +++ b/Example.Console/Program.cs @@ -17,10 +17,10 @@ var inputHex = AnsiConsole.Ask("[dim]Colour hex:[/]"); try { - var unicolour = new Unicolour(config, inputHex); - var useWhiteText = unicolour.Difference(white, DeltaE.Cie76) > unicolour.Difference(black, DeltaE.Cie76); - AnsiConsole.MarkupLine(GetBar(unicolour, useWhiteText)); - AnsiConsole.Write(GetTable(unicolour)); + var colour = new Unicolour(config, inputHex); + var useWhiteText = colour.Difference(white, DeltaE.Cie76) > colour.Difference(black, DeltaE.Cie76); + AnsiConsole.MarkupLine(GetBar(colour, useWhiteText)); + AnsiConsole.Write(GetTable(colour)); } catch (Exception e) { @@ -30,20 +30,20 @@ Console.WriteLine(); } -string GetBar(Unicolour unicolour, bool useWhiteText) +string GetBar(Unicolour colour, bool useWhiteText) { var textHex = useWhiteText ? white.Hex : black.Hex; - var leftSpace = (barLength - unicolour.Description.Length) / 2; - var rightSpace = barLength - unicolour.Description.Length - leftSpace; + var leftSpace = (barLength - colour.Description.Length) / 2; + var rightSpace = barLength - colour.Description.Length - leftSpace; var leftSpaces = new string(' ', leftSpace); var rightSpaces = new string(' ', rightSpace); - var text = $"{leftSpaces}{unicolour.Description}{rightSpaces}"; - return $"[{textHex} on {unicolour.Hex}]{text}[/]"; + var text = $"{leftSpaces}{colour.Description}{rightSpaces}"; + return $"[{textHex} on {colour.Hex}]{text}[/]"; } -static Table GetTable(Unicolour unicolour) +static Table GetTable(Unicolour colour) { - var rgb255 = unicolour.Rgb.Byte255; + var rgb255 = colour.Rgb.Byte255; var table = new Table { Border = TableBorder.Rounded, @@ -53,44 +53,44 @@ static Table GetTable(Unicolour unicolour) table.AddColumn(new TableColumn("Space").Width(col1Width)); table.AddColumn(new TableColumn("Value").Width(col2Width)); - table.AddRow("Hex", $"{unicolour.Hex}"); - table.AddRow("Rgb 255", $"{unicolour.Rgb.Byte255}"); - table.AddRow("Rgb", $"{unicolour.Rgb}"); - table.AddRow("Rgb Lin.", $"{unicolour.RgbLinear}"); - table.AddRow("Hsl", $"{unicolour.Hsl}"); - table.AddRow("Hsb", $"{unicolour.Hsb}"); - table.AddRow("Hwb", $"{unicolour.Hwb}"); - table.AddRow("Hsi", $"{unicolour.Hsi}"); - table.AddRow("Xyz", $"{unicolour.Xyz}"); - table.AddRow("Xyy", $"{unicolour.Xyy}"); - table.AddRow("Wxy", $"{unicolour.Wxy}"); - table.AddRow("Lab", $"{unicolour.Lab}"); - table.AddRow("Lchab", $"{unicolour.Lchab}"); - table.AddRow("Luv", $"{unicolour.Luv}"); - table.AddRow("Lchuv", $"{unicolour.Lchuv}"); - table.AddRow("Hsluv", $"{unicolour.Hsluv}"); - table.AddRow("Hpluv", $"{unicolour.Hpluv}"); - table.AddRow("Ypbpr", $"{unicolour.Ypbpr}"); - table.AddRow("Ycbcr", $"{unicolour.Ycbcr}"); - table.AddRow("Ycgco", $"{unicolour.Ycgco}"); - table.AddRow("Yuv", $"{unicolour.Yuv}"); - table.AddRow("Yiq", $"{unicolour.Yiq}"); - table.AddRow("Ydbdr", $"{unicolour.Ydbdr}"); - table.AddRow("Tsl", $"{unicolour.Tsl}"); - table.AddRow("Xyb", $"{unicolour.Xyb}"); - table.AddRow("Ipt", $"{unicolour.Ipt}"); - table.AddRow("Ictcp", $"{unicolour.Ictcp}"); - table.AddRow("Jzazbz", $"{unicolour.Jzazbz}"); - table.AddRow("Jzczhz", $"{unicolour.Jzczhz}"); - table.AddRow("Oklab", $"{unicolour.Oklab}"); - table.AddRow("Oklch", $"{unicolour.Oklch}"); - table.AddRow("Okhsv", $"{unicolour.Okhsv}"); - table.AddRow("Okhsl", $"{unicolour.Okhsl}"); - table.AddRow("Okhwb", $"{unicolour.Okhwb}"); - table.AddRow("Cam02", $"{unicolour.Cam02}"); - table.AddRow("Cam16", $"{unicolour.Cam16}"); - table.AddRow("Hct", $"{unicolour.Hct}"); - table.AddRow("Icc", $"{unicolour.Icc}"); + table.AddRow("Hex", $"{colour.Hex}"); + table.AddRow("Rgb 255", $"{colour.Rgb.Byte255}"); + table.AddRow("Rgb", $"{colour.Rgb}"); + table.AddRow("Rgb Lin.", $"{colour.RgbLinear}"); + table.AddRow("Hsl", $"{colour.Hsl}"); + table.AddRow("Hsb", $"{colour.Hsb}"); + table.AddRow("Hwb", $"{colour.Hwb}"); + table.AddRow("Hsi", $"{colour.Hsi}"); + table.AddRow("Xyz", $"{colour.Xyz}"); + table.AddRow("Xyy", $"{colour.Xyy}"); + table.AddRow("Wxy", $"{colour.Wxy}"); + table.AddRow("Lab", $"{colour.Lab}"); + table.AddRow("Lchab", $"{colour.Lchab}"); + table.AddRow("Luv", $"{colour.Luv}"); + table.AddRow("Lchuv", $"{colour.Lchuv}"); + table.AddRow("Hsluv", $"{colour.Hsluv}"); + table.AddRow("Hpluv", $"{colour.Hpluv}"); + table.AddRow("Ypbpr", $"{colour.Ypbpr}"); + table.AddRow("Ycbcr", $"{colour.Ycbcr}"); + table.AddRow("Ycgco", $"{colour.Ycgco}"); + table.AddRow("Yuv", $"{colour.Yuv}"); + table.AddRow("Yiq", $"{colour.Yiq}"); + table.AddRow("Ydbdr", $"{colour.Ydbdr}"); + table.AddRow("Tsl", $"{colour.Tsl}"); + table.AddRow("Xyb", $"{colour.Xyb}"); + table.AddRow("Ipt", $"{colour.Ipt}"); + table.AddRow("Ictcp", $"{colour.Ictcp}"); + table.AddRow("Jzazbz", $"{colour.Jzazbz}"); + table.AddRow("Jzczhz", $"{colour.Jzczhz}"); + table.AddRow("Oklab", $"{colour.Oklab}"); + table.AddRow("Oklch", $"{colour.Oklch}"); + table.AddRow("Okhsv", $"{colour.Okhsv}"); + table.AddRow("Okhsl", $"{colour.Okhsl}"); + table.AddRow("Okhwb", $"{colour.Okhwb}"); + table.AddRow("Cam02", $"{colour.Cam02}"); + table.AddRow("Cam16", $"{colour.Cam16}"); + table.AddRow("Hct", $"{colour.Hct}"); + table.AddRow("Icc", $"{colour.Icc}"); return table; } diff --git a/Example.Diagrams/Utils.cs b/Example.Diagrams/Utils.cs index 4c7ed44..28f022b 100644 --- a/Example.Diagrams/Utils.cs +++ b/Example.Diagrams/Utils.cs @@ -124,8 +124,8 @@ internal static List GetUvFillMarkers((double min, double max) rangeU, ( } else { - var unicolour = new Unicolour(chromaticity); - color = unicolour.IsImaginary ? null : GetScaledColour(unicolour.Rgb); + var colour = new Unicolour(chromaticity); + color = colour.IsImaginary ? null : GetScaledColour(colour.Rgb); ChromaticityCache.Add(chromaticity, color); } diff --git a/Example.Gradients/Example.Gradients.csproj b/Example.Gradients/Example.Gradients.csproj index 23ece6c..7fd6c09 100644 --- a/Example.Gradients/Example.Gradients.csproj +++ b/Example.Gradients/Example.Gradients.csproj @@ -19,6 +19,7 @@ + diff --git a/Example.Gradients/Program.cs b/Example.Gradients/Program.cs index 400fef5..4f02140 100644 --- a/Example.Gradients/Program.cs +++ b/Example.Gradients/Program.cs @@ -1,93 +1,94 @@ using SixLabors.ImageSharp; using SixLabors.ImageSharp.PixelFormats; -using SixLabors.ImageSharp.Processing; using Wacton.Unicolour; using Wacton.Unicolour.Datasets; using Wacton.Unicolour.Example.Gradients; +using Experimental = Wacton.Unicolour.Experimental; const string outputDirectory = "../../../../Unicolour.Readme/docs/"; -var darkText = Css.Black; -var lightText = Css.White; +Unicolour black = new("#000000"); +Unicolour white = new("#FFFFFF"); +Simple(); ColourSpaces(); Temperature(); VisionDeficiency(); AlphaInterpolation(); ColourMaps(); Pigments(); +SpectralJs(); return; +void Simple() +{ + const int width = 800; + const int rowHeight = 100; + const int blocks = 8; + const int columnsPerBlock = width / blocks; + + // "deep pink" to "aquamarine" (CSS colours) + Unicolour start = new("FF1493"); + Unicolour end = new("7FFFD4"); + + // calculate each column by mixing on demand at every distance between start and end + // NOTE: can achieve same result by doing the palette approach below with 1024 colours + Unicolour GetMixedColour(int column) => start.Mix(end, ColourSpace.Oklch, column / (double)width, HueSpan.Decreasing); + var mixingRow = Utils.Draw(width, rowHeight, GetMixedColour); + + // calculate each column according to a pre-generated palette between start and end + var palette = start.Palette(end, ColourSpace.Oklch, blocks, HueSpan.Decreasing).ToArray(); + Unicolour GetPaletteColour(int column) => palette[column / columnsPerBlock]; + var paletteRow = Utils.Draw(width, rowHeight, GetPaletteColour); + + var imageMixing = Utils.DrawRows([mixingRow], width, rowHeight); + imageMixing.Save(Path.Combine(outputDirectory, "gradient-simple-mixing.png")); + + var imagePalette = Utils.DrawRows([paletteRow], width, rowHeight); + imagePalette.Save(Path.Combine(outputDirectory, "gradient-simple-palette.png")); +} + void ColourSpaces() { - const int columnWidth = 800; + const int width = 1000; const int rowHeight = 100; - var purple = new Unicolour(ColourSpace.Hsb, 260, 1.0, 0.33); - var orange = new Unicolour(ColourSpace.Hsb, 30, 0.66, 1.0); - var pink = new Unicolour("#FF1493"); - var cyan = new Unicolour(ColourSpace.Rgb255, 0, 255, 255); - var black = new Unicolour(ColourSpace.Rgb, 0, 0, 0); - var green = new Unicolour(ColourSpace.Rgb, 0, 1, 0); - var columns = new List> - { - DrawColumn([purple, orange]), - DrawColumn([pink, cyan]), - DrawColumn([black, green]) - }; + Unicolour purple = new(ColourSpace.Hsb, 260, 1.0, 0.333); + Unicolour orange = new(ColourSpace.Hsl, 30, 1.0, 0.666); + Unicolour green = new(ColourSpace.Rgb, 0, 1, 0); + + List colourSpaces = + [ + ColourSpace.Rgb, ColourSpace.Rgb255, ColourSpace.RgbLinear, + ColourSpace.Hsb, ColourSpace.Hsl, ColourSpace.Hwb, ColourSpace.Hsi, + ColourSpace.Xyz, ColourSpace.Xyy, ColourSpace.Wxy, + ColourSpace.Lab, ColourSpace.Lchab, ColourSpace.Luv, ColourSpace.Lchuv, + ColourSpace.Hsluv, ColourSpace.Hpluv, + ColourSpace.Ypbpr, ColourSpace.Ycbcr, ColourSpace.Ycgco, ColourSpace.Yuv, ColourSpace.Yiq, ColourSpace.Ydbdr, + ColourSpace.Tsl, ColourSpace.Xyb, + ColourSpace.Ipt, ColourSpace.Ictcp, ColourSpace.Jzazbz, ColourSpace.Jzczhz, + ColourSpace.Oklab, ColourSpace.Oklch, ColourSpace.Okhsv, ColourSpace.Okhsl, ColourSpace.Okhwb, + ColourSpace.Cam02, ColourSpace.Cam16, + ColourSpace.Hct + ]; - var columnHeight = columns.First().Height; - var image = Utils.DrawColumns(columns, columnWidth, columnHeight); - image.Save(Path.Combine(outputDirectory, "gradient-spaces.png")); + DrawGradients(purple, orange, "gradient-spaces-purple-orange.png"); + DrawGradients(black, green, "gradient-spaces-black-green.png"); return; - - Image DrawColumn(Unicolour[] colourPoints) + + void DrawGradients(Unicolour start, Unicolour end, string filename) { - var gradients = new List> - { - Utils.Draw(("RGB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Rgb)), - Utils.Draw(("RGB Linear", lightText), columnWidth, rowHeight, GetColour(ColourSpace.RgbLinear)), - Utils.Draw(("HSB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hsb)), - Utils.Draw(("HSL", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hsl)), - Utils.Draw(("HWB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hwb)), - Utils.Draw(("HSI", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hsi)), - Utils.Draw(("XYZ", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Xyz)), - Utils.Draw(("xyY", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Xyy)), - Utils.Draw(("WXY", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Wxy)), - Utils.Draw(("LAB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Lab)), - Utils.Draw(("LCHab", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Lchab)), - Utils.Draw(("LUV", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Luv)), - Utils.Draw(("LCHuv", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Lchuv)), - Utils.Draw(("HSLuv", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hsluv)), - Utils.Draw(("HPLuv", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hpluv)), - Utils.Draw(("YPbPr", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ypbpr)), - Utils.Draw(("YCbCr", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ycbcr)), - Utils.Draw(("YCgCo", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ycgco)), - Utils.Draw(("YUV", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Yuv)), - Utils.Draw(("YIQ", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Yiq)), - Utils.Draw(("YDbDr", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ydbdr)), - Utils.Draw(("TSL", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Tsl)), - Utils.Draw(("XYB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Xyb)), - Utils.Draw(("IPT", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ipt)), - Utils.Draw(("ICtCp", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Ictcp)), - Utils.Draw(("JzAzBz", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Jzazbz)), - Utils.Draw(("JzCzHz", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Jzczhz)), - Utils.Draw(("OKLAB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Oklab)), - Utils.Draw(("OKLCH", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Oklch)), - Utils.Draw(("OKHSV", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Okhsv)), - Utils.Draw(("OKHSL", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Okhsl)), - Utils.Draw(("OKHWB", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Okhwb)), - Utils.Draw(("CAM02", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Cam02)), - Utils.Draw(("CAM16", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Cam16)), - Utils.Draw(("HCT", lightText), columnWidth, rowHeight, GetColour(ColourSpace.Hct)) - }; + var rows = colourSpaces + .Select(colourSpace => Utils.Draw((colourSpace.ToString(), white), width, rowHeight, GetColour(colourSpace))) + .ToList(); + + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, filename)); + return; - return Utils.DrawRows(gradients, columnWidth, rowHeight); - Utils.GetColour GetColour(ColourSpace colourSpace) { - Utils.Mix mix = (start, end, amount) => start.Mix(end, colourSpace, amount); - return column => Utils.GetMixedColour(colourPoints, column, columnWidth, mix); + return column => start.Mix(end, colourSpace, column / (double)width); } } } @@ -110,7 +111,7 @@ void Temperature() var rows = new List> { - Utils.Draw(("CCT (1,000 K - 13,000 K)", darkText), width, rowHeight, GetColour()) + Utils.Draw(("CCT (1,000 K - 13,000 K)", black), width, rowHeight, GetColour()) }; var image = Utils.DrawRows(rows, width, rowHeight); @@ -126,44 +127,29 @@ Utils.GetColour GetColour() void VisionDeficiency() { - const int width = 1200; + const int width = 1000; const int rowHeight = 100; - var colourPoints = new Unicolour[] - { - // not using OKLCH for the spectrum because the uniform luminance results in flat gradient for Achromatopsia - new(ColourSpace.Hsb, 0, 0.666, 1), - new(ColourSpace.Hsb, 360, 0.666, 1) - }; - - var rows = new List> - { - Utils.Draw(("No deficiency", darkText), width, rowHeight, GetColour(Cvd.None)), - Utils.Draw(("Protanopia", darkText), width, rowHeight, GetColour(Cvd.Protanopia)), - Utils.Draw(("Deuteranopia", darkText), width, rowHeight, GetColour(Cvd.Deuteranopia)), - Utils.Draw(("Tritanopia", darkText), width, rowHeight, GetColour(Cvd.Tritanopia)), - Utils.Draw(("Achromatopsia", darkText), width, rowHeight, GetColour(Cvd.Achromatopsia)) - }; + Unicolour start = new(ColourSpace.Hsb, 0, 0.666, 1); + Unicolour end = new(ColourSpace.Hsb, 360, 0.666, 1); + var cvds = new List { null, Cvd.Protanopia, Cvd.Deuteranopia, Cvd.Tritanopia, Cvd.Achromatopsia }; + var rows = cvds + .Select(cvd => Utils.Draw((cvd?.ToString() ?? "No deficiency", black), width, rowHeight, GetColour(cvd))) + .ToList(); + var image = Utils.DrawRows(rows, width, rowHeight); image.Save(Path.Combine(outputDirectory, "gradient-vision-deficiency.png")); return; - - Utils.GetColour GetColour(Cvd cvd) + + Utils.GetColour GetColour(Cvd? cvd) { - Utils.Mix mix = (start, end, amount) => start.Mix(end, ColourSpace.Hsb, amount, HueSpan.Increasing); return column => { - var unicolour = Utils.GetMixedColour(colourPoints, column, width, mix); - return cvd switch - { - Cvd.None => unicolour, - Cvd.Protanopia => unicolour.SimulateProtanopia(), - Cvd.Deuteranopia => unicolour.SimulateDeuteranopia(), - Cvd.Tritanopia => unicolour.SimulateTritanopia(), - Cvd.Achromatopsia => unicolour.SimulateAchromatopsia(), - _ => throw new ArgumentOutOfRangeException(nameof(cvd), cvd, null) - }; + // not interpolating through OKLCH for the spectrum + // because the uniform luminance results in flat gradient for achromatopsia + var mixed = start.Mix(end, ColourSpace.Hsb, column / (double)width, HueSpan.Increasing); + return cvd.HasValue ? mixed.Simulate(cvd.Value) : mixed; }; } } @@ -171,13 +157,13 @@ Utils.GetColour GetColour(Cvd cvd) void AlphaInterpolation() { const int width = 1000; - const int rowHeight = 120; + const int rowHeight = 100; var colourPoints = new[] { Css.Red, Css.Transparent, Css.Blue }; var rows = new List> { - Utils.Draw(("With premultiplied alpha", darkText), width, rowHeight, GetColour(true)), - Utils.Draw(("Without premultiplied alpha", darkText), width, rowHeight, GetColour(false)) + Utils.Draw(("With premultiplied alpha", black), width, rowHeight, GetColour(true)), + Utils.Draw(("Without premultiplied alpha", black), width, rowHeight, GetColour(false)) }; var image = Utils.DrawRows(rows, width, rowHeight); @@ -193,95 +179,213 @@ Utils.GetColour GetColour(bool premultiplyAlpha) void ColourMaps() { - const int width = 1024; - const int rowHeight = 80; + const int width = 1000; + const int rowHeight = 100; + + List colourmaps = + [ + Colourmaps.Viridis, Colourmaps.Plasma, Colourmaps.Inferno, Colourmaps.Magma, Colourmaps.Cividis, + Colourmaps.Mako, Colourmaps.Rocket, Colourmaps.Crest, Colourmaps.Flare, + Colourmaps.Vlag, Colourmaps.Icefire, Colourmaps.Twilight, Colourmaps.TwilightShifted, + Colourmaps.Turbo, Colourmaps.Cubehelix + ]; + + DrawMaps(); + DrawPalettes(); + return; - var white = Colourmap.White; - var black = Colourmap.Black; - var rows = new List> + void DrawMaps() { - Utils.Draw(("Viridis", white), width, rowHeight, column => Colourmaps.Viridis.Map(Distance(column))), - Utils.Draw(("Plasma", white), width, rowHeight, column => Colourmaps.Plasma.Map(Distance(column))), - Utils.Draw(("Inferno", white), width, rowHeight, column => Colourmaps.Inferno.Map(Distance(column))), - Utils.Draw(("Magma", white), width, rowHeight, column => Colourmaps.Magma.Map(Distance(column))), - Utils.Draw(("Cividis", white), width, rowHeight, column => Colourmaps.Cividis.Map(Distance(column))), - Utils.Draw(("Mako", white), width, rowHeight, column => Colourmaps.Mako.Map(Distance(column))), - Utils.Draw(("Rocket", white), width, rowHeight, column => Colourmaps.Rocket.Map(Distance(column))), - Utils.Draw(("Crest", black), width, rowHeight, column => Colourmaps.Crest.Map(Distance(column))), - Utils.Draw(("Flare", black), width, rowHeight, column => Colourmaps.Flare.Map(Distance(column))), - Utils.Draw(("Vlag", black), width, rowHeight, column => Colourmaps.Vlag.Map(Distance(column))), - Utils.Draw(("Icefire", black), width, rowHeight, column => Colourmaps.Icefire.Map(Distance(column))), - Utils.Draw(("Twilight", black), width, rowHeight, column => Colourmaps.Twilight.Map(Distance(column))), - Utils.Draw(("Twilight Shifted", white), width, rowHeight, column => Colourmaps.TwilightShifted.Map(Distance(column))), - Utils.Draw(("Turbo", white), width, rowHeight, column => Colourmaps.Turbo.Map(Distance(column))), - Utils.Draw(("Cubehelix", white), width, rowHeight, column => Colourmaps.Cubehelix.Map(Distance(column))) - }; + var rows = colourmaps + .Select(map => Utils.Draw((map.ToString()!, GetLabelColour(map)), width, rowHeight, GetColour(map))) + .ToList(); - var image = Utils.DrawRows(rows, width, rowHeight); - image.Save(Path.Combine(outputDirectory, "gradient-maps.png")); - return; + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, "gradient-maps.png")); + return; + + Utils.GetColour GetColour(Colourmap colourmap) + { + return column => colourmap.Map(column / (double)(width - 1)); + } + } + + void DrawPalettes() + { + const int blocks = 10; + const int columnsPerBlock = width / blocks; + + var colourmapToPalette = colourmaps.ToDictionary( + map => map, + map => map.Palette(blocks).ToArray()); + + var rows = colourmapToPalette + .Select(kvp => + { + var (map, palette) = kvp; + return Utils.Draw((map.ToString()!, GetLabelColour(map)), width, rowHeight, GetColour(palette)); + }) + .ToList(); + + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, "gradient-maps-palette.png")); + return; - double Distance(int columnIndex) => columnIndex / (double) (width - 1); + Utils.GetColour GetColour(Unicolour[] palette) + { + return column => palette[column / columnsPerBlock]; + } + } + + Unicolour GetLabelColour(Colourmap colourmap) + { + Colourmap[] light = [Colourmaps.Crest, Colourmaps.Flare, Colourmaps.Vlag, Colourmaps.Icefire, Colourmaps.Twilight]; + return light.Contains(colourmap) ? black : white; + } } void Pigments() { - const int width = 791; - const int rowHeight = 240; - const int blocks = 7; - const int columnsPerBlock = width / blocks; + const int width = 800; + const int rowHeight = 100; + + List pigments = + [ + ArtistPaint.BoneBlack, + ArtistPaint.BismuthVanadateYellow, ArtistPaint.HansaYellowOpaque, ArtistPaint.DiarylideYellow, + ArtistPaint.CadmiumOrange, ArtistPaint.PyrroleOrange, + ArtistPaint.CadmiumRedLight, ArtistPaint.PyrroleRed, ArtistPaint.QuinacridoneRed, + ArtistPaint.QuinacridoneMagenta, ArtistPaint.DioxazinePurple, + ArtistPaint.PhthaloBlueRedShade, ArtistPaint.PhthaloBlueGreenShade, + ArtistPaint.UltramarineBlue, ArtistPaint.CobaltBlue, ArtistPaint.CeruleanBlueChromium, + ArtistPaint.PhthaloGreenBlueShade, ArtistPaint.PhthaloGreenYellowShade + ]; - DrawGradientBlocks(ArtistPaint.QuinacridoneMagenta, ArtistPaint.TitaniumWhite, "magenta-white"); - DrawGradientBlocks(ArtistPaint.PhthaloBlueRedShade, ArtistPaint.TitaniumWhite, "blue-white"); - DrawGradientBlocks(ArtistPaint.CobaltBlue, ArtistPaint.HansaYellowOpaque, "blue-yellow"); - return; + var endPigment = ArtistPaint.TitaniumWhite; - void DrawGradientBlocks(Pigment startPigment, Pigment endPigment, string name) + DrawMixes(); + DrawPalettes(); + return; + + // NOTE: slow for smooth gradient, each column calculates a reflectance curve based on concentrations + // which is then used for integration alongside SPD and CMF values for every wavelength to calculate XYZ + void DrawMixes() { - double minRgb = 0; - double maxRgb = 0; - List colours = []; - for (var i = 0; i < blocks; i++) + var rows = pigments + .Select(pigment => Utils.Draw((pigment.Name, GetLabelColour(pigment)), width, rowHeight, GetColour(pigment, endPigment))) + .ToList(); + + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, "gradient-pigments-mix.png")); + return; + + Utils.GetColour GetColour(Pigment start, Pigment end) { - var endAmount = i / (double)(blocks - 1); + return column => + { + var distance = column / (double)(width - 1); + return new Unicolour([start, end], [1 - distance, distance]); + }; + } + } - var pigments = new[] { startPigment, endPigment }; - var weights = new[] { 1 - endAmount, endAmount }; + void DrawPalettes() + { + const int blocks = 8; + const int columnsPerBlock = width / blocks; + + var weights = Enumerable.Range(0, blocks).Select(i => new[] { 1 - Distance(i), Distance(i) }).ToArray(); + var pigmentToPalette = pigments.ToDictionary( + pigment => pigment, + pigment => weights.Select(weight => new Unicolour([pigment, endPigment], weight)).ToArray()); + + var rows = pigmentToPalette + .Select(kvp => + { + var (pigment, palette) = kvp; + return Utils.Draw((pigment.Name, GetLabelColour(pigment)), width, rowHeight, GetColour(palette)); + }) + .ToList(); + + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, "gradient-pigments-palette.png")); + return; + + double Distance(int i) => i / (double)(blocks - 1); - var colour = new Unicolour(pigments, weights); - var rgb = colour.Rgb; - var rgbComponents = new[] { rgb.R, rgb.G, rgb.B }; - minRgb = Math.Min(minRgb, rgbComponents.Min()); - maxRgb = Math.Max(maxRgb, rgbComponents.Max()); - colours.Add(colour); + Utils.GetColour GetColour(Unicolour[] palette) + { + return column => palette[column / columnsPerBlock]; } + } + + Unicolour GetLabelColour(Pigment pigment) + { + Pigment[] light = [ArtistPaint.BismuthVanadateYellow, ArtistPaint.HansaYellowOpaque, ArtistPaint.DiarylideYellow]; + return light.Contains(pigment) ? black : white; + } +} + +void SpectralJs() +{ + const int width = 540; + const int rowHeight = 60; + + var colours = new List<(Unicolour start, Unicolour end)> + { + // default colours on https://onedayofcrypto.art/ + (new Unicolour("#002185"), new Unicolour("#FCD200")), - var rows = new List> - { - Utils.Draw((string.Empty, Css.Transparent), width, rowHeight, GetColour()) - }; + // colours taken from github example https://github.com/rvanwijnen/spectral.js#usage + (new Unicolour("#005E72"), new Unicolour("#EAD9A7")), + (new Unicolour("#FF8A3E"), new Unicolour("#FF006D")), + (new Unicolour("#002185"), new Unicolour("#F0F0F0")), + (new Unicolour("#DFE800"), new Unicolour("#CC3536")) + }; + + DrawMixes(); + DrawPalettes(); + return; + + // NOTE: very slow for smooth gradient, generates a reflectance curve by solving a system of linear equations per column, for best accuracy + // (Spectral.js itself uses 7 hardcoded "target" curves and weights approximates a curve using those, so less accurate but faster) + void DrawMixes() + { + var rows = colours + .Select(pair => Utils.Draw(width, rowHeight, GetColour(pair.start, pair.end))) + .ToList(); var image = Utils.DrawRows(rows, width, rowHeight); - image.Mutate(x => x.Rotate(RotateMode.Rotate90)); - image.Save(Path.Combine(outputDirectory, $"pigments-{name}.png")); + image.Save(Path.Combine(outputDirectory, "gradient-spectraljs-mix.png")); return; - - Utils.GetColour GetColour() + + Utils.GetColour GetColour(Unicolour start, Unicolour end) { return column => { - var blockIndex = column / columnsPerBlock; - return colours[blockIndex]; + var distance = column / (double)(width - 1); + return Experimental.SpectralJs.Mix([start, end], [1 - distance, distance]); }; } } -} -internal enum Cvd -{ - None, - Protanopia, - Deuteranopia, - Tritanopia, - Achromatopsia + void DrawPalettes() + { + const int blocks = 9; + const int columnsPerBlock = width / blocks; + + var palettes = colours.Select(colours => Experimental.SpectralJs.Palette(colours.start, colours.end, blocks).ToArray()).ToList(); + var rows = palettes + .Select(palette => Utils.Draw(width, rowHeight, GetColour(palette))) + .ToList(); + + var image = Utils.DrawRows(rows, width, rowHeight); + image.Save(Path.Combine(outputDirectory, "gradient-spectraljs-palette.png")); + return; + + Utils.GetColour GetColour(Unicolour[] palette) + { + return column => palette[column / columnsPerBlock]; + } + } } \ No newline at end of file diff --git a/Example.Gradients/Utils.cs b/Example.Gradients/Utils.cs index 9d380a5..9e35306 100644 --- a/Example.Gradients/Utils.cs +++ b/Example.Gradients/Utils.cs @@ -3,6 +3,7 @@ using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.PixelFormats; using SixLabors.ImageSharp.Processing; +using Wacton.Unicolour.Datasets; namespace Wacton.Unicolour.Example.Gradients; @@ -29,7 +30,7 @@ internal static Unicolour GetMixedColour(Unicolour[] colourPoints, int column, i return mix(segmentStartColour, segmentEndColour, segmentDistance); } - internal static Image Draw((string text, Unicolour colour) label, int width, int height, GetColour getColour) + internal static Image Draw(int width, int height, GetColour getColour) { var image = new Image(width, height); @@ -39,6 +40,12 @@ internal static Image Draw((string text, Unicolour colour) label, int wi SetColumnPixels(image, column, height, colour); } + return image; + } + + internal static Image Draw((string text, Unicolour colour) label, int width, int height, GetColour getColour) + { + var image = Draw(width, height, getColour); SetLabel(image, label.text, label.colour); return image; } @@ -75,29 +82,12 @@ internal static Image DrawRows(List> rows, int rowWidth, i Point Location(int index) => new(0, rowHeight * index); } - - internal static Image DrawColumns(List> columns, int columnWidth, int columnHeight) - { - var columnIndex = 0; - var image = new Image(columnWidth * columns.Count, columnHeight); - image.Mutate(context => - { - foreach (var gradient in columns) - { - context.DrawImage(gradient, Location(columnIndex++), 1f); - } - }); - - return image; - - Point Location(int index) => new(columnWidth * index, 0); - } - private static Rgba32 AsRgba32(Unicolour unicolour) + private static Rgba32 AsRgba32(Unicolour colour) { - var (r, g, b) = unicolour.Rgb.Byte255.ConstrainedTriplet; - var alpha = unicolour.Alpha.A255; - var a = unicolour.IsInDisplayGamut || !RenderOutOfGamutAsTransparent ? alpha : 0; + var (r, g, b) = colour.Rgb.Byte255.ConstrainedTriplet; + var alpha = colour.Alpha.A255; + var a = colour.IsInRgbGamut || !RenderOutOfGamutAsTransparent ? alpha : 0; return new Rgba32((byte) r, (byte) g, (byte) b, (byte) a); } } \ No newline at end of file diff --git a/Example.Heatmaps/Program.cs b/Example.Heatmaps/Program.cs index e7e832e..589cf46 100644 --- a/Example.Heatmaps/Program.cs +++ b/Example.Heatmaps/Program.cs @@ -28,8 +28,8 @@ void Generate(string filename) var pixels = new Rgba32[originalImage.Width * originalImage.Height]; originalImage.CopyPixelDataTo(pixels); - var unicolours = pixels.Select(pixel => new Unicolour(ColourSpace.Rgb255, pixel.R, pixel.G, pixel.B, pixel.A)); - var luminances = unicolours.Select(colour => colour.RelativeLuminance).ToArray(); + var colours = pixels.Select(pixel => new Unicolour(ColourSpace.Rgb255, pixel.R, pixel.G, pixel.B, pixel.A)); + var luminances = colours.Select(colour => colour.RelativeLuminance).ToArray(); var maxLuminance = luminances.Max(); var minLuminance = luminances.Min(); var normalisedLuminances = luminances.Select(luminance => Normalise(luminance, minLuminance, maxLuminance)).ToArray(); @@ -86,11 +86,11 @@ void AddLabel(Image image, string text, Unicolour colour) image.Mutate(context => context.DrawText(textOptions, text, AsColor(colour))); } -Rgba32 AsRgba32(Unicolour unicolour) +Rgba32 AsRgba32(Unicolour colour) { - var (r255, g255, b255) = unicolour.Rgb.Byte255.Triplet; - var a255 = unicolour.Alpha.A255; + var (r255, g255, b255) = colour.Rgb.Byte255.Triplet; + var a255 = colour.Alpha.A255; return new((byte)r255, (byte)g255, (byte)b255, (byte)a255); } -Color AsColor(Unicolour unicolour) => new(AsRgba32(unicolour)); +Color AsColor(Unicolour colour) => new(AsRgba32(colour)); diff --git a/Example.Web/App.razor b/Example.Web/App.razor index 07035f1..3a856a6 100644 --- a/Example.Web/App.razor +++ b/Example.Web/App.razor @@ -314,7 +314,7 @@ { currentColour = new Unicolour(currentSpace, slider1.Value, slider2.Value, slider3.Value); conversionError = Utils.HasConversionError(currentColour); - outOfGamut = !currentColour.IsInDisplayGamut; + outOfGamut = !currentColour.IsInRgbGamut; inGamutCss = Utils.ToCss(currentColour, 100); outGamutCss = Utils.ToCss(currentColour, outOfGamut ? 50 : 100); diff --git a/Example.Web/Slider.cs b/Example.Web/Slider.cs index 069db69..4c61f53 100644 --- a/Example.Web/Slider.cs +++ b/Example.Web/Slider.cs @@ -16,7 +16,7 @@ internal class Slider internal List Stops { get; set; } = []; internal string CssGradient => string.Join(",", Stops.Select(x => Utils.ToCss(x, 100))); - internal string CssAlphaGradient => string.Join(",", Stops.Select(x => Utils.ToCss(x, x.IsInDisplayGamut ? 100 : 50))); + internal string CssAlphaGradient => string.Join(",", Stops.Select(x => Utils.ToCss(x, x.IsInRgbGamut ? 100 : 50))); internal Slider(int index, double value) { diff --git a/Example.Web/Utils.cs b/Example.Web/Utils.cs index 5cd713d..0322be3 100644 --- a/Example.Web/Utils.cs +++ b/Example.Web/Utils.cs @@ -2,20 +2,20 @@ namespace Wacton.Unicolour.Example.Web; internal static class Utils { - internal static string ToCss(Unicolour unicolour, double alpha) + internal static string ToCss(Unicolour colour, double alpha) { - if (HasConversionError(unicolour)) + if (HasConversionError(colour)) { return "transparent"; } - var (r, g, b) = unicolour.Rgb.ConstrainedTriplet; + var (r, g, b) = colour.Rgb.ConstrainedTriplet; return $"rgb({r * 100}% {g * 100}% {b * 100}% / {alpha}%)"; } - internal static bool HasConversionError(Unicolour unicolour) + internal static bool HasConversionError(Unicolour colour) { // if the constrained hex has no value, RGB is invalid (likely a NaN during conversion) - return unicolour.Rgb.Byte255.ConstrainedHex == "-"; + return colour.Rgb.Byte255.ConstrainedHex == "-"; } } \ No newline at end of file diff --git a/README.md b/README.md index 2830131..cd2ccac 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ [![GitLab](https://badgen.net/static/gitlab/source/ff1493?icon=gitlab)](https://gitlab.com/Wacton/Unicolour) [![NuGet](https://badgen.net/nuget/v/Wacton.Unicolour?icon)](https://www.nuget.org/packages/Wacton.Unicolour/) [![pipeline status](https://gitlab.com/Wacton/Unicolour/badges/main/pipeline.svg)](https://gitlab.com/Wacton/Unicolour/-/commits/main) -[![tests passed](https://badgen.net/static/tests/216,919/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) +[![tests passed](https://badgen.net/static/tests/217,398/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) [![coverage report](https://gitlab.com/Wacton/Unicolour/badges/main/coverage.svg)](https://gitlab.com/Wacton/Unicolour/-/pipelines) Unicolour is the most comprehensive .NET library for working with colour: @@ -11,7 +11,8 @@ Unicolour is the most comprehensive .NET library for working with colour: - Colour mixing / colour interpolation - Colour difference / colour distance - Colour gamut mapping -- Colour chromaticity & colour temperature +- Colour chromaticity +- Colour temperature - Wavelength attributes - ICC profiles for CMYK conversion @@ -319,7 +320,7 @@ var difference = red.Difference(blue, DeltaE.Cie76); | ΔECAM02 | `DeltaE.Cam02` | | ΔECAM16 | `DeltaE.Cam16` | -### Map colour into display gamut +### Map colour into RGB gamut Colours that cannot be displayed with the [configured RGB model](#rgbconfiguration) can be mapped to the closest in-gamut colour. The gamut mapping algorithm conforms to CSS specifications. ```c# diff --git a/Unicolour.Datasets/Colourmap.cs b/Unicolour.Datasets/Colourmap.cs index 588962b..5c70b32 100644 --- a/Unicolour.Datasets/Colourmap.cs +++ b/Unicolour.Datasets/Colourmap.cs @@ -36,6 +36,20 @@ public Unicolour MapWithClipping(double x, Unicolour? lowerClipColour = null, Un }; } + public IEnumerable Palette(int count) + { + count = Math.Max(count, 0); + + var palette = new List(); + for (var i = 0; i < count; i++) + { + var x = count == 1 ? 0.5 : i / (double)(count - 1); + palette.Add(Map(x)); + } + + return palette; + } + protected static Unicolour InterpolateColourTable(Unicolour[] colourTable, double x) { var (lowerColour, upperColour, mixAmount) = Lut.Lookup(colourTable, x); diff --git a/Unicolour.Datasets/Unicolour.Datasets.csproj b/Unicolour.Datasets/Unicolour.Datasets.csproj index db7bfa5..fe78e5b 100644 --- a/Unicolour.Datasets/Unicolour.Datasets.csproj +++ b/Unicolour.Datasets/Unicolour.Datasets.csproj @@ -15,7 +15,7 @@ https://github.com/waacton/Unicolour Resources\Unicolour.Datasets.png https://github.com/waacton/Unicolour - unicolour colour color colour-data color-data colour-dataset color-dataset colormap colormaps colourmap colourmaps + unicolour colour color colour-data color-data colour-dataset color-dataset colormap colormaps colourmap colourmaps color-palette colour-palette pigments paints Add Nord dataset diff --git a/Unicolour.Experimental/MatrixUtils.cs b/Unicolour.Experimental/MatrixUtils.cs new file mode 100644 index 0000000..f06ee77 --- /dev/null +++ b/Unicolour.Experimental/MatrixUtils.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wacton.Unicolour.Experimental; + +/* + * extensions to make the lagrangian maths of reflectance generator more readable + * makes big assumptions about types and dimensions, not for general purpose + */ +internal static class MatrixUtils +{ + private const int Count = PigmentGenerator.Wavelengths; + + // assumes both left and right are 1D + internal static double[] Concat(Matrix left, Matrix right) + { + return left.GetCol(0).Concat(right.GetCol(0)).ToArray(); + } + + // assumes upperLeft = 36x36, upperRight = 3x36, lowerLeft = 36x3, lowerRight = 3x3 + internal static Matrix Concat(Matrix upperLeft, Matrix upperRight, Matrix lowerLeft, Matrix lowerRight) + { + var data = new double[Count + 3, Count + 3]; + for (var row = 0; row < Count + 3; row++) + { + for (var col = 0; col < Count + 3; col++) + { + data[row, col] = row switch + { + < Count when col < Count => upperLeft[row, col], + < Count when col >= Count => upperRight[row, col - Count], + >= Count when col < Count => lowerLeft[row - Count, col], + _ => lowerRight[row - Count, col - Count] // lower right + }; + } + } + + return new Matrix(data); + } + + internal static Matrix Diag(IEnumerable values) + { + var array = values.ToArray(); + var dimension = array.Length; + var data = new double[dimension, dimension]; + for (var i = 0; i < dimension; i++) + { + data[i, i] = array[i]; + } + + return new Matrix(data); + } + + // assumes 1D array + internal static Matrix Multiply(this Matrix matrix, IEnumerable values) + { + return matrix.Multiply(values.ToArray().ToMatrix()); + } + + internal static double[] Negate(this double[] values) => values.Select(x => -x).ToArray(); + + internal static Matrix Zip(this Matrix matrix, Matrix other, Func operation) + { + var rows = matrix.Rows; + var cols = matrix.Cols; + var result = new Matrix(new double[rows, cols]); + for (var row = 0; row < rows; row++) + { + for (var col = 0; col < cols; col++) + { + result[row, col] = operation(matrix[row, col], other[row, col]); + } + } + + return result; + } + + // returns a Nx1 matrix from a 1D array + private static Matrix ToMatrix(this IEnumerable values) + { + var array = values.ToArray(); + var data = new double[array.Length, 1]; + for (var i = 0; i < array.Length; i++) + { + data[i, 0] = array[i]; + } + + return new Matrix(data); + } + + internal static double[] GetCol(this Matrix matrix, int col) + { + var array = new double[matrix.Rows]; + for (var row = 0; row < matrix.Rows; row++) + { + array[row] = matrix.Data[row, col]; + } + + return array; + } + + internal static double[] GetRow(this Matrix matrix, int row) + { + var array = new double[matrix.Cols]; + for (var col = 0; col < matrix.Cols; col++) + { + array[col] = matrix.Data[row, col]; + } + + return array; + } + + internal static void SetRow(this Matrix matrix, int rowIndex, double[] values) + { + for (var col = 0; col < matrix.Cols; col++) + { + matrix.Data[rowIndex, col] = values[col]; + } + } +} \ No newline at end of file diff --git a/Unicolour.Experimental/PigmentGenerator.cs b/Unicolour.Experimental/PigmentGenerator.cs new file mode 100644 index 0000000..1508980 --- /dev/null +++ b/Unicolour.Experimental/PigmentGenerator.cs @@ -0,0 +1,158 @@ +using System; +using System.Linq; +using static Wacton.Unicolour.Experimental.MatrixUtils; + +namespace Wacton.Unicolour.Experimental; + +public static class PigmentGenerator +{ + private static readonly Illuminant illuminant = Illuminant.D65; + private static readonly Observer observer = Observer.Degree2; + private static readonly WhitePoint d65WhitePoint = illuminant.GetWhitePoint(observer); + + internal const int Start = 380; + internal const int Interval = 10; + internal const int Wavelengths = 36; + private static double[] NotNumber => new double[Wavelengths].Select(_ => double.NaN).ToArray(); + + internal const double Tolerance = 1e-8; + + public static Pigment From(Unicolour colour) + { + var rgb = colour.Rgb.Triplet; + var xyz = colour.Xyz.Triplet; + var xyzConfig = colour.Configuration.Xyz; + + // handle black and white as special conditions + // unlikely to encounter exact RGB of 0s or 1s unless intentionally specified + // for black reflectance, want close to zero reflectance + // (zero and double.Epsilon are too small and results in NaN when converted to XYZ) + var reflectance = rgb switch + { + (0.0, 0.0, 0.0) => new double[Wavelengths].Select(_ => Tolerance).ToArray(), + (1.0, 1.0, 1.0) => new double[Wavelengths].Select(_ => 1.0).ToArray(), + _ => GenerateReflectance(xyz, xyzConfig) + }; + + return new Pigment(Start, Interval, reflectance); + } + + /* + * https://doi.org/10.1002/col.22437 using LHTSS (see also http://scottburns.us/reflectance-curves-from-srgb-2/) + * generating a curve for "object colours" (reflectance distribution between 0 - 1) + * ---------- + * NOTE: also implemented LLSS to generate a curve for any non-imaginary colour (lies within spectral locus) + * to attempt to better support wide-gamut (http://scottburns.us/rec-2020-rgb-to-spectrum-conversion-for-reflectances/) + * but was inaccurate in roundtrip tests; perhaps one to revisit in future + */ + private static double[] GenerateReflectance(ColourTriplet xyz, XyzConfiguration xyzConfig) + { + // custom illuminant from users might not have SPD, or SPD might not have the required wavelengths + // so for simplicity and consistency, calculations are performed in default D65/2° (also assumed by matrix A) + var d65Xyz = Adaptation.WhitePoint(xyz, xyzConfig.WhitePoint, d65WhitePoint); + var d65XyzMatrix = Matrix.FromTriplet(d65Xyz); + + var z = new double[Wavelengths]; + var lambda = new double[3]; + var iteration = 0; + + while (iteration < 20) + { + var f = Lhtss.F(z, lambda, d65XyzMatrix); + var j = Lhtss.J(z, lambda); + + var delta = SystemOfLinearEquations.Solve(j, f.Negate()); + var zDelta = delta.Take(Wavelengths).ToArray(); + var lambdaDelta = delta.Skip(Wavelengths).ToArray(); + + z = z.Zip(zDelta, (x, xDelta) => x + xDelta).ToArray(); + lambda = lambda.Zip(lambdaDelta, (x, xDelta) => x + xDelta).ToArray(); + + if (f.Any(double.IsNaN)) return NotNumber; + if (f.All(value => Math.Abs(value) < Tolerance)) return z.Select(Lhtss.Rho).ToArray(); + + iteration++; + } + + return NotNumber; + } + + // http://scottburns.us/reflectance-curves-from-srgb-10/ + private static class Lhtss + { + internal static double Rho(double z) => (Math.Tanh(z) + 1) / 2.0; // ρ + + internal static double[] F(double[] z, double[] lambda, Matrix xyz) + { + var sechZ = z.Select(value => Math.Pow(Sech(value), 2) / 2.0); + var rhoZ = z.Select(Rho); + + var fUpper = D.Value.Multiply(z).Zip(Diag(sechZ).Multiply(A.Value).Multiply(lambda), (a, b) => a + b); + var fLower = A.Value.Transpose().Multiply(rhoZ).Zip(xyz, (a, b) => a - b); + return Concat(fUpper, fLower); + } + + internal static Matrix J(double[] z, double[] lambda) + { + var sechTanhZ = z.Select(value => Math.Pow(Sech(value), 2) * Math.Tanh(value)); + var sechZ = z.Select(value => Math.Pow(Sech(value), 2) / 2.0); + var diagSechZ = Diag(sechZ); + + var upperLeft = D.Value.Zip(Diag(Diag(sechTanhZ).Multiply(A.Value).Multiply(lambda).GetCol(0)), (a, b) => a - b); + var upperRight = diagSechZ.Multiply(A.Value); + var lowerLeft = A.Value.Transpose().Multiply(diagSechZ); + var lowerRight = new Matrix(new double[3, 3]); // all zeroes + return Concat(upperLeft, upperRight, lowerLeft, lowerRight); + } + + private static double Sech(double value) => 1 / Math.Cosh(value); + } + + private static readonly Lazy A = new(GetA); + private static Matrix GetA() + { + var a = new Matrix(new double[Wavelengths, 3]); + var w = new double[Wavelengths]; + for (var i = 0; i < Wavelengths; i++) + { + var wavelength = Start + i * Interval; + a[i, 0] = observer.ColourMatchX(wavelength); + a[i, 1] = observer.ColourMatchY(wavelength); + a[i, 2] = observer.ColourMatchZ(wavelength); + w[i] = illuminant.Spd![wavelength]; + } + + // normalise w by second CMF column (Y - luminance) + var scale = a.GetCol(1).Zip(w, (an, wn) => an * wn).Sum(); + w = w.Select(wn => wn / scale).ToArray(); + return Diag(w).Multiply(a); + } + + // tridiagonal + private static readonly Lazy D = new(GetD); + private static Matrix GetD() + { + var data = new double[Wavelengths, Wavelengths]; + for (var i = 0; i < Wavelengths; i++) + { + data[i, i] = 4; + + if (i + 1 < Wavelengths) + { + data[i, i + 1] = -2; + data[i + 1, i] = -2; + } + + if (i - 1 > 0) + { + data[i, i - 1] = -2; + data[i - 1, i] = -2; + } + } + + data[0, 0] = 2; + data[Wavelengths - 1, Wavelengths - 1] = 2; + + return new Matrix(data); + } +} \ No newline at end of file diff --git a/Unicolour.Experimental/Resources/Unicolour.Experimental.ico b/Unicolour.Experimental/Resources/Unicolour.Experimental.ico new file mode 100644 index 0000000..d417816 Binary files /dev/null and b/Unicolour.Experimental/Resources/Unicolour.Experimental.ico differ diff --git a/Unicolour.Experimental/Resources/Unicolour.Experimental.png b/Unicolour.Experimental/Resources/Unicolour.Experimental.png new file mode 100644 index 0000000..586216f Binary files /dev/null and b/Unicolour.Experimental/Resources/Unicolour.Experimental.png differ diff --git a/Unicolour.Experimental/SpectralJs.cs b/Unicolour.Experimental/SpectralJs.cs new file mode 100644 index 0000000..915a136 --- /dev/null +++ b/Unicolour.Experimental/SpectralJs.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Wacton.Unicolour.Experimental; + +public static class SpectralJs +{ + /* + * Spectral.js approximates a colour's reflectance (i.e. pigment composition) + * by using 7 predefined reflectances as references, and calculating an average reflectance at each wavelength + * (reflectance curves for RGB colours are generated using http://scottburns.us/reflectance-curves-from-srgb-2/) + * ---------- + * Unicolour instead directly generates reflectance for each input colour using Scott Burns' approach + * which sacrifices the speed of using predefined reference reflectances for greater accuracy + * ---------- + * Unicolour also extends Spectral.js functionality + * so that it can mix more than 2 colours together + */ + public static Unicolour Mix(Unicolour[] colours, double[] weights) + { + // weight -> concentration calculation is the same as Pigment.GetReflectance() + weights = weights.Select(x => Math.Max(x, 0.0)).ToArray(); + var totalWeight = weights.Sum(); + if (totalWeight == 0.0) + { + return new Unicolour(ColourSpace.Xyz, double.NaN, double.NaN, double.NaN); + } + + var concentrations = weights.Select(x => x / totalWeight).ToArray(); + var pigments = colours.Select(PigmentGenerator.From).ToArray(); + return Mix(pigments, concentrations); + } + + // Spectral.js is fundamentally just an adjustment of requested concentrations + // based on luminance and a curve of unknown origin (hence experimental) + private static Unicolour Mix(Pigment[] pigments, double[] concentrations) + { + var ls = new double[pigments.Length]; + for (var wavelengthIndex = 0; wavelengthIndex < PigmentGenerator.Wavelengths; wavelengthIndex++) + { + var wavelength = PigmentGenerator.Start + wavelengthIndex * PigmentGenerator.Interval; + var yBar = Observer.Degree2.ColourMatchY(wavelength); + + // must be single-constant pigments + // which is enforced by the pigments passed to this method being generated from RGB + for (var pigmentIndex = 0; pigmentIndex < ls.Length; pigmentIndex++) + { + ls[pigmentIndex] += pigments[pigmentIndex].R![wavelength] * yBar; + } + } + + var t = new double[pigments.Length]; + for (var pigmentIndex = 0; pigmentIndex < pigments.Length; pigmentIndex++) + { + t[pigmentIndex] = ls[pigmentIndex] * Math.Pow(concentrations[pigmentIndex], 2); + } + + var modifiedConcentrations = t.Select(x => x / t.Max()).ToArray(); + return new Unicolour(pigments, modifiedConcentrations); + } + + public static IEnumerable Palette(Unicolour start, Unicolour end, int count) + { + var colours = new[] { start, end }; + count = Math.Max(count, 0); + + var palette = new List(); + for (var i = 0; i < count; i++) + { + var distance = count == 1 ? 0.5 : i / (double)(count - 1); + var weights = new[] { 1 - distance, distance }; + palette.Add(Mix(colours, weights)); + } + + return palette; + } +} \ No newline at end of file diff --git a/Unicolour.Experimental/SystemOfLinearEquations.cs b/Unicolour.Experimental/SystemOfLinearEquations.cs new file mode 100644 index 0000000..052bfd3 --- /dev/null +++ b/Unicolour.Experimental/SystemOfLinearEquations.cs @@ -0,0 +1,163 @@ +using System; +using System.Linq; + +namespace Wacton.Unicolour.Experimental; + +// NOTE: obviously slower than MathNet, sacrificing speed for understanding and readability +// even less concerned when it's part of the experimental package +// however, easy to replace with copied MathNet code in future if performance is problematic +internal static class SystemOfLinearEquations +{ + // solve Ax = B + internal static double[] Solve(Matrix a, double[] b) + { + var (lu, pivots) = LuDecomposition(a); // PA = LU + var pb = ApplyPivots(b, pivots); + var y = ForwardSubstitution(lu, pb); // LY = PB + var x = BackwardSubstitution(lu, y); // UX = Y + return x; + } + + /* + * i increments both column and row, traversing the diagonal of the matrix top-left to bottom-right + * (the lower triangular area becoming L, the upper triangular area becoming U) + * j increments down through every row below the diagonal + */ + internal static (Matrix factors, int[] pivots) LuDecomposition(Matrix matrix) + { + var order = matrix.Cols; // assuming square matrix, same as row count + + // the factorised matrix will be modified in situ + var factors = new Matrix(new double[order, order]); + Array.Copy(matrix.Data, factors.Data, order * order); + + var pivots = new int[order]; + + for (var i = 0; i < order; i++) + { + // once we get to the final column, row permutation is complete + // so the pivot is always the final row + if (i == order - 1) + { + pivots[i] = i; + continue; + } + + // 1) pivot - swap the current row with the absolute max row, and keep a record + var column = factors.GetCol(i); + var pivot = GetPivot(i, order, column); + pivots[i] = pivot; + + var pivotRow = factors.GetRow(pivot).ToArray(); + if (pivot != i) + { + var currentRow = factors.GetRow(i); + factors.SetRow(i, pivotRow); + factors.SetRow(pivot, currentRow); + } + + // 2) eliminate - scale and subtract the pivot to calculate LU factors + for (var j = i + 1; j < order; j++) + { + // would prefer to divide by zero so that multiplier = NaN and let NaNs propagate + // but this matches MathNet behaviour (and so presumably LAPACK) + if (pivotRow[i] == 0.0) continue; + var resultRow = GetEliminatedRow(factors, j, i, pivotRow); + factors.SetRow(j, resultRow); + } + } + + return (factors, pivots); + } + + // iterates over a column from the diagonal (i) to the last element in the column + // (e.g. column 3 of a 5x5 matrix will inspect indexes 3, 4, 5) + // and finds the index of the largest absolute value + private static int GetPivot(int i, int order, double[] column) + { + var pivot = i; + for (var j = i + 1; j < order; j++) + { + // prefer !(>) over <= for NaN behaviour (comparison is always false) + if (!(Math.Abs(column[j]) > Math.Abs(column[pivot]))) + { + continue; + } + + pivot = j; + } + + return pivot; + } + + // eliminates a row from the diagonal to the last element in the row + // by subtracting the scaled pivot value, so that the ith element becomes zero (effectively the U part) + // and store the scale where the value was eliminated (effectively the U part) + private static double[] GetEliminatedRow(Matrix factors, int j, int i, double[] pivotRow) + { + // triangular U matrix part + var eliminationRow = factors.GetRow(j).ToArray(); + var scale = eliminationRow[i] / pivotRow[i]; + var unchangedPart = eliminationRow.Take(i).ToArray(); + var eliminatedPart = pivotRow.Skip(i).Zip(eliminationRow.Skip(i), (p, e) => e - scale * p).ToArray(); + var resultRow = unchangedPart.Concat(eliminatedPart).ToArray(); + + // triangular L matrix part + resultRow[i] = scale; + return resultRow; + } + + private static double[] ApplyPivots(double[] b, int[] pivots) + { + var length = b.Length; + + // PB will be modified in situ + var pb = new double[length]; + Array.Copy(b, pb, length); + + for (var i = 0; i < length; i++) + { + var pivot = pivots[i]; + if (i == pivot) continue; + (pb[i], pb[pivot]) = (pb[pivot], pb[i]); + } + + return pb; + } + + // solves LY = PB to get Y + // by iterating over the triangular L matrix from the top row (with a single Y term) and substituting previously solved Y values + // first row has 1 Y term and is solvable, second row has 2 Y terms but 1st Y term is now known, etc. + private static double[] ForwardSubstitution(Matrix lu, double[] pb) + { + var order = lu.Cols; // assuming square matrix, same as row count + + var y = new double[order]; + for (var i = 0; i < order; i++) + { + var l = lu.GetRow(i); + var substitutedSolved = l.Take(i).Zip(y.Take(i), (ln, yn) => ln * yn); + y[i] = pb[i] - substitutedSolved.Sum(); // no need to divide by diagonal, diagonal of L is 1 + } + + return y; + } + + // solves UX = Y to get X + // by iterating over the triangular U matrix from the bottom row (with a single X term) and substituting previously solved X values + // first row has 1 X term and is solvable, second row has 2 X terms but 1st X term is now known, etc. + private static double[] BackwardSubstitution(Matrix lu, double[] y) + { + var order = lu.Cols; // assuming square matrix, same as row count + + var x = new double[order]; + for (var i = order - 1; i >= 0; i--) + { + var u = lu.GetRow(i); + var substitutedSolved = u.Skip(i).Zip(x.Skip(i), (un, xn) => un * xn); + x[i] = (y[i] - substitutedSolved.Sum()) / u[i]; + } + + return x; + } +} \ No newline at end of file diff --git a/Unicolour.Experimental/Unicolour.Experimental.csproj b/Unicolour.Experimental/Unicolour.Experimental.csproj new file mode 100644 index 0000000..eedf8fe --- /dev/null +++ b/Unicolour.Experimental/Unicolour.Experimental.csproj @@ -0,0 +1,39 @@ + + + + netstandard2.0 + Wacton.Unicolour.Experimental + Wacton.Unicolour.Experimental + 10 + enable + true + Wacton.Unicolour.Experimental + William Acton + Experimental additions to 🌈 Wacton.Unicolour + William Acton + https://github.com/waacton/Unicolour + https://github.com/waacton/Unicolour + unicolour colour color pigments pigment-mixing kubelka-munk reflectance reflectance-curves spectral.js paints paint-mixing + Add reflectance generation and Spectral.js implementation + Resources\Unicolour.Experimental.png + + + + + True + \ + + + + + + + + + + + <_Parameter1>Wacton.Unicolour.Tests + + + + diff --git a/Unicolour.Readme/Program.cs b/Unicolour.Readme/Program.cs index 594775d..0d9c647 100644 --- a/Unicolour.Readme/Program.cs +++ b/Unicolour.Readme/Program.cs @@ -166,7 +166,7 @@ void FeatureCompare() void FeatureGamutMap() { var outOfGamut = new Unicolour(ColourSpace.Rgb, -0.51, 1.02, -0.31); - var inGamut = outOfGamut.MapToGamut(); + var inGamut = outOfGamut.MapToRgbGamut(); } void FeatureTemperature() @@ -205,7 +205,7 @@ void FeatureImaginary() void FeatureCvd() { var colour = new Unicolour(ColourSpace.Rgb255, 192, 255, 238); - var noRed = colour.SimulateProtanopia(); + var noRed = colour.Simulate(Cvd.Protanopia); } void FeatureIcc() diff --git a/Unicolour.Readme/README.md b/Unicolour.Readme/README.md index 17770fb..53faa39 100644 --- a/Unicolour.Readme/README.md +++ b/Unicolour.Readme/README.md @@ -3,7 +3,7 @@ [![GitLab](https://badgen.net/static/gitlab/source/ff1493?icon=gitlab)](https://gitlab.com/Wacton/Unicolour) [![NuGet](https://badgen.net/nuget/v/Wacton.Unicolour?icon)](https://www.nuget.org/packages/Wacton.Unicolour/) [![pipeline status](https://gitlab.com/Wacton/Unicolour/badges/main/pipeline.svg)](https://gitlab.com/Wacton/Unicolour/-/commits/main) -[![tests passed](https://badgen.net/static/tests/216,919/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) +[![tests passed](https://badgen.net/static/tests/217,398/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) [![coverage report](https://gitlab.com/Wacton/Unicolour/badges/main/coverage.svg)](https://gitlab.com/Wacton/Unicolour/-/pipelines) Unicolour is the most comprehensive .NET library for working with colour: @@ -11,7 +11,8 @@ Unicolour is the most comprehensive .NET library for working with colour: - Colour mixing / colour interpolation - Colour difference / colour distance - Colour gamut mapping -- Colour chromaticity & colour temperature +- Colour chromaticity +- Colour temperature - Wavelength attributes - ICC profiles for CMYK conversion @@ -319,7 +320,7 @@ var difference = red.Difference(blue, DeltaE.Cie76); | ΔECAM02 | `DeltaE.Cam02` | | ΔECAM16 | `DeltaE.Cam16` | -### Map colour into display gamut +### Map colour into RGB gamut Colours that cannot be displayed with the [configured RGB model](#rgbconfiguration) can be mapped to the closest in-gamut colour. The gamut mapping algorithm conforms to CSS specifications. ```c# diff --git a/Unicolour.Readme/README_nuget.md b/Unicolour.Readme/README_nuget.md index 4721306..2a32bd9 100644 --- a/Unicolour.Readme/README_nuget.md +++ b/Unicolour.Readme/README_nuget.md @@ -3,7 +3,7 @@ [![GitLab](https://badgen.net/static/gitlab/source/ff1493?icon=gitlab)](https://gitlab.com/Wacton/Unicolour) [![NuGet](https://badgen.net/nuget/v/Wacton.Unicolour?icon)](https://www.nuget.org/packages/Wacton.Unicolour/) [![pipeline status](https://gitlab.com/Wacton/Unicolour/badges/main/pipeline.svg)](https://gitlab.com/Wacton/Unicolour/-/commits/main) -[![tests passed](https://badgen.net/static/tests/216,552/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) +[![tests passed](https://badgen.net/static/tests/217,393/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) [![coverage report](https://gitlab.com/Wacton/Unicolour/badges/main/coverage.svg)](https://gitlab.com/Wacton/Unicolour/-/pipelines) Unicolour is the most comprehensive .NET library for working with colour: @@ -211,7 +211,7 @@ var difference = red.Difference(blue, DeltaE.Cie76); | ΔECAM02 | `DeltaE.Cam02` | | ΔECAM16 | `DeltaE.Cam16` | -### Map colour into display gamut +### Map colour into RGB gamut Colours that cannot be displayed with the [configured RGB model](https://github.com/waacton/Unicolour#rgbconfiguration) can be mapped to the closest in-gamut colour. The gamut mapping algorithm conforms to CSS specifications. ```cs diff --git a/Unicolour.Readme/Unicolour.Readme.csproj b/Unicolour.Readme/Unicolour.Readme.csproj index eb54f88..8afca2b 100644 --- a/Unicolour.Readme/Unicolour.Readme.csproj +++ b/Unicolour.Readme/Unicolour.Readme.csproj @@ -22,7 +22,16 @@ Always - + + Always + + + Always + + + Always + + Always @@ -37,6 +46,21 @@ Always + + Always + + + Always + + + Always + + + Always + + + Always + Always diff --git a/Unicolour.Readme/docs/gradient-alpha-interpolation.png b/Unicolour.Readme/docs/gradient-alpha-interpolation.png index 874fb16..36c466d 100644 Binary files a/Unicolour.Readme/docs/gradient-alpha-interpolation.png and b/Unicolour.Readme/docs/gradient-alpha-interpolation.png differ diff --git a/Unicolour.Readme/docs/gradient-maps-palette.png b/Unicolour.Readme/docs/gradient-maps-palette.png new file mode 100644 index 0000000..90c7cdf Binary files /dev/null and b/Unicolour.Readme/docs/gradient-maps-palette.png differ diff --git a/Unicolour.Readme/docs/gradient-maps.png b/Unicolour.Readme/docs/gradient-maps.png index d876360..8489714 100644 Binary files a/Unicolour.Readme/docs/gradient-maps.png and b/Unicolour.Readme/docs/gradient-maps.png differ diff --git a/Unicolour.Readme/docs/gradient-pigments-mix.png b/Unicolour.Readme/docs/gradient-pigments-mix.png new file mode 100644 index 0000000..cc10e3a Binary files /dev/null and b/Unicolour.Readme/docs/gradient-pigments-mix.png differ diff --git a/Unicolour.Readme/docs/gradient-pigments-palette.png b/Unicolour.Readme/docs/gradient-pigments-palette.png new file mode 100644 index 0000000..69deedf Binary files /dev/null and b/Unicolour.Readme/docs/gradient-pigments-palette.png differ diff --git a/Unicolour.Readme/docs/gradient-simple-mixing.png b/Unicolour.Readme/docs/gradient-simple-mixing.png new file mode 100644 index 0000000..6588a79 Binary files /dev/null and b/Unicolour.Readme/docs/gradient-simple-mixing.png differ diff --git a/Unicolour.Readme/docs/gradient-simple-palette.png b/Unicolour.Readme/docs/gradient-simple-palette.png new file mode 100644 index 0000000..5e07743 Binary files /dev/null and b/Unicolour.Readme/docs/gradient-simple-palette.png differ diff --git a/Unicolour.Readme/docs/gradient-spaces-black-green.png b/Unicolour.Readme/docs/gradient-spaces-black-green.png new file mode 100644 index 0000000..c2fe30e Binary files /dev/null and b/Unicolour.Readme/docs/gradient-spaces-black-green.png differ diff --git a/Unicolour.Readme/docs/gradient-spaces-purple-orange.png b/Unicolour.Readme/docs/gradient-spaces-purple-orange.png new file mode 100644 index 0000000..43424bb Binary files /dev/null and b/Unicolour.Readme/docs/gradient-spaces-purple-orange.png differ diff --git a/Unicolour.Readme/docs/gradient-spaces.png b/Unicolour.Readme/docs/gradient-spaces.png deleted file mode 100644 index e36f264..0000000 Binary files a/Unicolour.Readme/docs/gradient-spaces.png and /dev/null differ diff --git a/Unicolour.Readme/docs/gradient-spectraljs-mix.png b/Unicolour.Readme/docs/gradient-spectraljs-mix.png new file mode 100644 index 0000000..8689472 Binary files /dev/null and b/Unicolour.Readme/docs/gradient-spectraljs-mix.png differ diff --git a/Unicolour.Readme/docs/gradient-spectraljs-palette.png b/Unicolour.Readme/docs/gradient-spectraljs-palette.png new file mode 100644 index 0000000..68b4a05 Binary files /dev/null and b/Unicolour.Readme/docs/gradient-spectraljs-palette.png differ diff --git a/Unicolour.Readme/docs/gradient-vision-deficiency.png b/Unicolour.Readme/docs/gradient-vision-deficiency.png index 4b19119..43c0397 100644 Binary files a/Unicolour.Readme/docs/gradient-vision-deficiency.png and b/Unicolour.Readme/docs/gradient-vision-deficiency.png differ diff --git a/Unicolour.Readme/docs/wxy-colour-space.md b/Unicolour.Readme/docs/wxy-colour-space.md index 465dcab..fbad9aa 100644 --- a/Unicolour.Readme/docs/wxy-colour-space.md +++ b/Unicolour.Readme/docs/wxy-colour-space.md @@ -165,7 +165,7 @@ const double decrement = 0.05; // reduce for greater accuracy var (w, x, y) = (530, 0.5, 0.7); var colour = new Unicolour(ColourSpace.Wxy, w, x, y); -while (!colour.IsInDisplayGamut) +while (!colour.IsInRgbGamut) { x -= decrement; colour = new Unicolour(ColourSpace.Wxy, w, x, y); diff --git a/Unicolour.Tests/ConfigureIccTests.cs b/Unicolour.Tests/ConfigureIccTests.cs index a0c7b03..e8fc4dc 100644 --- a/Unicolour.Tests/ConfigureIccTests.cs +++ b/Unicolour.Tests/ConfigureIccTests.cs @@ -34,7 +34,7 @@ public void ConvertBetweenIccProfiles(string sourceFileName, string destinationF var convertedUnicolour = sourceUnicolour.ConvertToConfiguration(destinationConfig); var convertedCmyk = convertedUnicolour.Icc; - Assert.That(convertedCmyk.Values, Is.EqualTo(destinationCmyk.Values).Within(1e-15)); + Assert.That(convertedCmyk.Values, Is.EqualTo(destinationCmyk.Values).Within(1.25e-15)); } [Test] diff --git a/Unicolour.Tests/ConfigureIctcpTests.cs b/Unicolour.Tests/ConfigureIctcpTests.cs index eccc913..0f21618 100644 --- a/Unicolour.Tests/ConfigureIctcpTests.cs +++ b/Unicolour.Tests/ConfigureIctcpTests.cs @@ -41,8 +41,8 @@ public void Rec2020RgbToIctcp100() [Test] // matches the behaviour of python-based "colour-science/colour" (https://github.com/colour-science/colour#31224ictcp-colour-encoding) public void Rec2020RgbToIctcp1() { - var unicolour = new Unicolour(Config1, ColourSpace.RgbLinear, TestLinearRgb.Tuple); - TestUtils.AssertTriplet(unicolour.Ictcp.Triplet, new(0.07351364, 0.00475253, 0.09351596), 0.00001); + var colour = new Unicolour(Config1, ColourSpace.RgbLinear, TestLinearRgb.Tuple); + TestUtils.AssertTriplet(colour.Ictcp.Triplet, new(0.07351364, 0.00475253, 0.09351596), 0.00001); var white = new Unicolour(Config1, ColourSpace.Xyz, XyzWhite.Tuple); var black = new Unicolour(Config1, ColourSpace.Xyz, 0, 0, 0); @@ -53,8 +53,8 @@ public void Rec2020RgbToIctcp1() [Test] // matches the behaviour of javascript-based "color.js" (https://github.com/LeaVerou/color.js / https://colorjs.io/apps/picker) public void Rec2020RgbToIctcp203() { - var unicolour = new Unicolour(Config203, ColourSpace.RgbLinear, TestLinearRgb.Tuple); - TestUtils.AssertTriplet(unicolour.Ictcp.Triplet, new(0.39224991, -0.0001166, 0.28389029), 0.0001); + var colour = new Unicolour(Config203, ColourSpace.RgbLinear, TestLinearRgb.Tuple); + TestUtils.AssertTriplet(colour.Ictcp.Triplet, new(0.39224991, -0.0001166, 0.28389029), 0.0001); var white = new Unicolour(Config203, ColourSpace.Xyz, XyzWhite.Tuple); var black = new Unicolour(Config203, ColourSpace.Xyz, 0, 0, 0); diff --git a/Unicolour.Tests/ConfigureJzazbzTests.cs b/Unicolour.Tests/ConfigureJzazbzTests.cs index d132b24..d132e46 100644 --- a/Unicolour.Tests/ConfigureJzazbzTests.cs +++ b/Unicolour.Tests/ConfigureJzazbzTests.cs @@ -41,8 +41,8 @@ public void XyzD65ToJzazbz100() [Test] // matches the behaviour of python-based "colour-science/colour" (https://github.com/colour-science/colour#31212jzazbz-colourspace) public void XyzD65ToJzazbz1() { - var unicolour = new Unicolour(Config1, ColourSpace.Xyz, TestXyz.Tuple); - TestUtils.AssertTriplet(unicolour, new(0.00535048, 0.00924302, 0.00526007), 0.00001); + var colour = new Unicolour(Config1, ColourSpace.Xyz, TestXyz.Tuple); + TestUtils.AssertTriplet(colour, new(0.00535048, 0.00924302, 0.00526007), 0.00001); var white = new Unicolour(Config1, ColourSpace.Xyz, XyzWhite.Tuple); var black = new Unicolour(Config1, ColourSpace.Xyz, 0, 0, 0); @@ -53,8 +53,8 @@ public void XyzD65ToJzazbz1() [Test] // matches the behaviour of javascript-based "color.js" (https://github.com/LeaVerou/color.js / https://colorjs.io/apps/picker) public void XyzD65ToJzazbz203() { - var unicolour = new Unicolour(Config203, ColourSpace.Xyz, TestXyz.Tuple); - TestUtils.AssertTriplet(unicolour, new(0.10287841, 0.08613415, 0.05873694), 0.0001); + var colour = new Unicolour(Config203, ColourSpace.Xyz, TestXyz.Tuple); + TestUtils.AssertTriplet(colour, new(0.10287841, 0.08613415, 0.05873694), 0.0001); var white = new Unicolour(Config203, ColourSpace.Xyz, XyzWhite.Tuple); var black = new Unicolour(Config203, ColourSpace.Xyz, 0, 0, 0); diff --git a/Unicolour.Tests/ConfigureRgbTests.cs b/Unicolour.Tests/ConfigureRgbTests.cs index 8fbcf9b..b9fe8db 100644 --- a/Unicolour.Tests/ConfigureRgbTests.cs +++ b/Unicolour.Tests/ConfigureRgbTests.cs @@ -175,15 +175,15 @@ public void XyzD65ToStandardRgbD65() Assert.That(xyzToRgbMatrix.Data, Is.EqualTo(expectedMatrixA).Within(0.0005)); Assert.That(xyzToRgbMatrix.Data, Is.EqualTo(expectedMatrixB).Within(0.0000001)); - var unicolourXyz = new Unicolour(Configuration.Default, ColourSpace.Xyz, 0.200757, 0.119618, 0.506757); - var unicolourXyzNoConfig = new Unicolour(ColourSpace.Xyz, 0.200757, 0.119618, 0.506757); - var unicolourLab = new Unicolour(Configuration.Default, ColourSpace.Lab, 41.1553, 51.4108, -56.4485); - var unicolourLabNoConfig = new Unicolour(ColourSpace.Lab, 41.1553, 51.4108, -56.4485); + var colourXyz = new Unicolour(Configuration.Default, ColourSpace.Xyz, 0.200757, 0.119618, 0.506757); + var colourXyzNoConfig = new Unicolour(ColourSpace.Xyz, 0.200757, 0.119618, 0.506757); + var colourLab = new Unicolour(Configuration.Default, ColourSpace.Lab, 41.1553, 51.4108, -56.4485); + var colourLabNoConfig = new Unicolour(ColourSpace.Lab, 41.1553, 51.4108, -56.4485); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourXyzNoConfig, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLabNoConfig, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourXyzNoConfig, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLabNoConfig, expectedRgb, Tolerance); } [Test] @@ -212,11 +212,11 @@ public void XyzD50ToStandardRgbD65() var xyzToRgbMatrix = rgbToXyzMatrix.Inverse(); Assert.That(xyzToRgbMatrix.Data, Is.EqualTo(expectedMatrix).Within(0.0000001)); - var unicolourXyz = new Unicolour(config, ColourSpace.Xyz, 0.187691, 0.115771, 0.381093); - var unicolourLab = new Unicolour(config, ColourSpace.Lab, 40.5359, 46.0847, -57.1158); + var colourXyz = new Unicolour(config, ColourSpace.Xyz, 0.187691, 0.115771, 0.381093); + var colourLab = new Unicolour(config, ColourSpace.Lab, 40.5359, 46.0847, -57.1158); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); } [Test] @@ -225,10 +225,10 @@ public void XyzD65ToAcesRgbD60() var d65XyzConfig = new XyzConfiguration(Illuminant.D65, Observer.Degree2); var config = new Configuration(RgbConfiguration.Acescg, d65XyzConfig); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - var unicolourXyz = new Unicolour(config, ColourSpace.Xyz, 0.485665, 0.345912, 0.817454); - var unicolourLab = new Unicolour(config, ColourSpace.Lab, 65.4291, 48.7467, -41.3660); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); + var colourXyz = new Unicolour(config, ColourSpace.Xyz, 0.485665, 0.345912, 0.817454); + var colourLab = new Unicolour(config, ColourSpace.Lab, 65.4291, 48.7467, -41.3660); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); } [Test] @@ -237,10 +237,10 @@ public void XyzD50ToAcesRgbD60() var d50XyzConfig = new XyzConfiguration(Illuminant.D50, Observer.Degree2); var config = new Configuration(RgbConfiguration.Acescg, d50XyzConfig); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - var unicolourXyz = new Unicolour(config, ColourSpace.Xyz, 0.475850, 0.343035, 0.615342); - var unicolourLab = new Unicolour(config, ColourSpace.Lab, 65.2028, 45.1028, -41.3650); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); + var colourXyz = new Unicolour(config, ColourSpace.Xyz, 0.475850, 0.343035, 0.615342); + var colourLab = new Unicolour(config, ColourSpace.Lab, 65.2028, 45.1028, -41.3650); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); } [Test] @@ -249,10 +249,10 @@ public void XyzD65ToWideGamutRgbD50() var d65XyzConfig = new XyzConfiguration(Illuminant.D65, Observer.Degree2); var config = new Configuration(WideGamutRgbConfig, d65XyzConfig); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - var unicolourXyz = new Unicolour(config, ColourSpace.Xyz, 0.251993, 0.102404, 0.550393); - var unicolourLab = new Unicolour(config, ColourSpace.Lab, 38.2704, 87.2838, -65.7493); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); + var colourXyz = new Unicolour(config, ColourSpace.Xyz, 0.251993, 0.102404, 0.550393); + var colourLab = new Unicolour(config, ColourSpace.Lab, 38.2704, 87.2838, -65.7493); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); } [Test] @@ -261,10 +261,10 @@ public void XyzD50ToWideGamutRgbD50() var d50XyzConfig = new XyzConfiguration(Illuminant.D50, Observer.Degree2); var config = new Configuration(WideGamutRgbConfig, d50XyzConfig); var expectedRgb = new ColourTriplet(0.5, 0.25, 0.75); - var unicolourXyz = new Unicolour(config, ColourSpace.Xyz, 0.238795, 0.099490, 0.413181); - var unicolourLab = new Unicolour(config, ColourSpace.Lab, 37.7508, 82.3084, -66.1402); - TestUtils.AssertTriplet(unicolourXyz, expectedRgb, Tolerance); - TestUtils.AssertTriplet(unicolourLab, expectedRgb, Tolerance); + var colourXyz = new Unicolour(config, ColourSpace.Xyz, 0.238795, 0.099490, 0.413181); + var colourLab = new Unicolour(config, ColourSpace.Lab, 37.7508, 82.3084, -66.1402); + TestUtils.AssertTriplet(colourXyz, expectedRgb, Tolerance); + TestUtils.AssertTriplet(colourLab, expectedRgb, Tolerance); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllIlluminantsTestCases))] diff --git a/Unicolour.Tests/ConfigureXyzTests.cs b/Unicolour.Tests/ConfigureXyzTests.cs index 29b768a..9a3f3c7 100644 --- a/Unicolour.Tests/ConfigureXyzTests.cs +++ b/Unicolour.Tests/ConfigureXyzTests.cs @@ -50,17 +50,17 @@ public void StandardRgbD65ToXyzD65() Assert.That(rgbToXyzMatrix.Data, Is.EqualTo(expectedMatrixA).Within(0.0005)); Assert.That(rgbToXyzMatrix.Data, Is.EqualTo(expectedMatrixB).Within(0.0000001)); - var unicolour = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75); - var unicolourNoConfig = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colourNoConfig = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.200757, 0.119618, 0.506757); var expectedLab = new ColourTriplet(41.1553, 51.4108, -56.4485); var expectedLuv = new ColourTriplet(41.1553, 16.3709, -86.7190); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); - TestUtils.AssertTriplet(unicolourNoConfig, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolourNoConfig, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolourNoConfig, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colourNoConfig, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colourNoConfig, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colourNoConfig, expectedLuv, LuvTolerance); } [Test] @@ -88,13 +88,13 @@ public void StandardRgbD65ToXyzD50() rgbToXyzMatrix = Adaptation.WhitePoint(rgbToXyzMatrix, standardRgbConfig.WhitePoint, d50XyzConfig.WhitePoint); Assert.That(rgbToXyzMatrix.Data, Is.EqualTo(expectedMatrix).Within(0.0000001)); - var unicolour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.187691, 0.115771, 0.381093); var expectedLab = new ColourTriplet(40.5359, 46.0847, -57.1158); var expectedLuv = new ColourTriplet(40.5359, 18.7523, -78.2057); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); } [Test] @@ -110,13 +110,13 @@ public void AdobeRgbD65ToXyzD65() var d65XyzConfig = new XyzConfiguration(Illuminant.D65, Observer.Degree2); var config = new Configuration(adobeRgbConfig, d65XyzConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.234243, 0.134410, 0.535559); var expectedLab = new ColourTriplet(43.4203, 57.3600, -55.4259); var expectedLuv = new ColourTriplet(43.4203, 25.4480, -87.3268); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); } [Test] @@ -132,13 +132,13 @@ public void AdobeRgbD65ToXyzD50() var d50XyzConfig = new XyzConfiguration(Illuminant.D50, Observer.Degree2); var config = new Configuration(adobeRgbConfig, d50XyzConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.221673, 0.130920, 0.402670); var expectedLab = new ColourTriplet(42.9015, 52.4152, -55.9013); var expectedLuv = new ColourTriplet(42.9015, 29.0751, -78.5576); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); } [Test] @@ -154,13 +154,13 @@ public void WideGamutRgbD50ToXyzD65() var d65XyzConfig = new XyzConfiguration(Illuminant.D65, Observer.Degree2); var config = new Configuration(wideGamutRgbConfig, d65XyzConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.251993, 0.102404, 0.550393); var expectedLab = new ColourTriplet(38.2704, 87.2838, -65.7493); var expectedLuv = new ColourTriplet(38.2704, 47.3837, -99.6819); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); } [Test] @@ -176,13 +176,13 @@ public void WideGamutRgbD50ToXyzD50() var d50XyzConfig = new XyzConfiguration(Illuminant.D50, Observer.Degree2); var config = new Configuration(wideGamutRgbConfig, d50XyzConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); + var colour = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75); var expectedXyz = new ColourTriplet(0.238795, 0.099490, 0.413181); var expectedLab = new ColourTriplet(37.7508, 82.3084, -66.1402); var expectedLuv = new ColourTriplet(37.7508, 55.1488, -91.6044); - TestUtils.AssertTriplet(unicolour, expectedXyz, XyzTolerance); - TestUtils.AssertTriplet(unicolour, expectedLab, LabTolerance); - TestUtils.AssertTriplet(unicolour, expectedLuv, LuvTolerance); + TestUtils.AssertTriplet(colour, expectedXyz, XyzTolerance); + TestUtils.AssertTriplet(colour, expectedLab, LabTolerance); + TestUtils.AssertTriplet(colour, expectedLuv, LuvTolerance); } [TestCase(nameof(Illuminant.D65), 0.312727, 0.329023)] diff --git a/Unicolour.Tests/ContrastTests.cs b/Unicolour.Tests/ContrastTests.cs index d1f5836..aa7059c 100644 --- a/Unicolour.Tests/ContrastTests.cs +++ b/Unicolour.Tests/ContrastTests.cs @@ -87,18 +87,18 @@ private static void AssertKnownContrast(Unicolour colour1, Unicolour colour2, do Assert.That(delta1, Is.EqualTo(delta2)); } - private static void AssertRelativeLuminance(Unicolour unicolour) + private static void AssertRelativeLuminance(Unicolour colour) { // WCAG relative luminance is defined according to sRGB https://www.w3.org/TR/WCAG21/#dfn-relative-luminance // which should match XYZ.Y under D65 // so need to ensure colour is using the correct configuration for the formula to work - if (unicolour.Configuration != Configuration.Default) + if (colour.Configuration != Configuration.Default) { - unicolour = unicolour.ConvertToConfiguration(Configuration.Default); + colour = colour.ConvertToConfiguration(Configuration.Default); } - var (r, g, b) = unicolour.RgbLinear.Triplet; + var (r, g, b) = colour.RgbLinear.Triplet; var expected = 0.2126 * r + 0.7152 * g + 0.0722 * b; - Assert.That(unicolour.RelativeLuminance, Is.EqualTo(expected).Within(0.0005)); + Assert.That(colour.RelativeLuminance, Is.EqualTo(expected).Within(0.0005)); } } \ No newline at end of file diff --git a/Unicolour.Tests/DatasetColourmapTests.cs b/Unicolour.Tests/DatasetColourmapTests.cs index 47db9c7..59e6527 100644 --- a/Unicolour.Tests/DatasetColourmapTests.cs +++ b/Unicolour.Tests/DatasetColourmapTests.cs @@ -280,71 +280,117 @@ public void MapToIndex(Colourmap colourmap, int index, double expectedR, double var lookup = Lookups[colourmap]; var maxIndex = lookup.Length - 1; var distance = index / (double)maxIndex; - var unicolour = colourmap.Map(distance); - Assert.That(unicolour, Is.EqualTo(lookup[index])); - TestUtils.AssertTriplet(unicolour, new ColourTriplet(expectedR, expectedG, expectedB), 0.0); + var colour = colourmap.Map(distance); + Assert.That(colour, Is.EqualTo(lookup[index])); + TestUtils.AssertTriplet(colour, new ColourTriplet(expectedR, expectedG, expectedB), 0.0); } [TestCaseSource(nameof(InterpolatedTestData))] public void MapToInterpolated(Colourmap colourmap, double index, double expectedR, double expectedG, double expectedB) { var maxIndex = Lookups[colourmap].Length - 1; - var unicolour = colourmap.Map(index / maxIndex); - TestUtils.AssertTriplet(unicolour, new ColourTriplet(expectedR, expectedG, expectedB), 0.0000000000001); + var colour = colourmap.Map(index / maxIndex); + TestUtils.AssertTriplet(colour, new ColourTriplet(expectedR, expectedG, expectedB), 0.0000000000001); } [Test] public void MapBelowMin([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { var expected = colourmap.Map(0); - var unicolour = colourmap.Map(-0.000000000000001); - Assert.That(unicolour, Is.EqualTo(expected)); + var colour = colourmap.Map(-0.000000000000001); + Assert.That(colour, Is.EqualTo(expected)); } [Test] public void MapAboveMax([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { var expected = colourmap.Map(1); - var unicolour = colourmap.Map(1.000000000000001); - Assert.That(unicolour, Is.EqualTo(expected)); + var colour = colourmap.Map(1.000000000000001); + Assert.That(colour, Is.EqualTo(expected)); } [Test] public void MapToClippedLower([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { - var unicolour = colourmap.MapWithClipping(-0.000000000000001); - Assert.That(unicolour, Is.EqualTo(Colourmap.Black)); + var colour = colourmap.MapWithClipping(-0.000000000000001); + Assert.That(colour, Is.EqualTo(Colourmap.Black)); } [Test] public void MapToClippedLowerCustom([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { var clipColour = new Unicolour(ColourSpace.Rgb, 2, 0, 0); - var unicolour = colourmap.MapWithClipping(-0.000000000000001, lowerClipColour: clipColour); - Assert.That(unicolour, Is.EqualTo(clipColour)); + var colour = colourmap.MapWithClipping(-0.000000000000001, lowerClipColour: clipColour); + Assert.That(colour, Is.EqualTo(clipColour)); } [Test] public void MapToClippedUpper([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { - var unicolour = colourmap.MapWithClipping(1.000000000000001); - Assert.That(unicolour, Is.EqualTo(Colourmap.White)); + var colour = colourmap.MapWithClipping(1.000000000000001); + Assert.That(colour, Is.EqualTo(Colourmap.White)); } [Test] public void MapToClippedUpperCustom([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { var clipColour = new Unicolour(ColourSpace.Rgb, 0, 0, 2); - var unicolour = colourmap.MapWithClipping(1.000000000000001, upperClipColour: clipColour); - Assert.That(unicolour, Is.EqualTo(clipColour)); + var colour = colourmap.MapWithClipping(1.000000000000001, upperClipColour: clipColour); + Assert.That(colour, Is.EqualTo(clipColour)); } [Test] public void MapToClippedInRange([ValueSource(nameof(allColourmaps))] Colourmap colourmap) { var clipColour = new Unicolour(ColourSpace.Rgb, 0, 2, 0); - var unicolour = colourmap.MapWithClipping(0.5, lowerClipColour: clipColour, upperClipColour: clipColour); - Assert.That(unicolour, Is.Not.EqualTo(clipColour)); + var colour = colourmap.MapWithClipping(0.5, lowerClipColour: clipColour, upperClipColour: clipColour); + Assert.That(colour, Is.Not.EqualTo(clipColour)); + } + + [Test] + public void PaletteMultiple([ValueSource(nameof(allColourmaps))] Colourmap colourmap) + { + const int count = 9; + var palette = colourmap.Palette(count).ToArray(); + Assert.That(palette.Length, Is.EqualTo(count)); + + double[] distances = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; + for (var i = 0; i < count; i++) + { + var mapped = colourmap.Map(distances[i]); + Assert.That(palette[i], Is.EqualTo(mapped)); + } + } + + [Test] + public void PaletteTwo([ValueSource(nameof(allColourmaps))] Colourmap colourmap) + { + var palette = colourmap.Palette(2).ToArray(); + Assert.That(palette.Length, Is.EqualTo(2)); + Assert.That(palette[0], Is.EqualTo(colourmap.Map(0))); + Assert.That(palette[1], Is.EqualTo(colourmap.Map(1))); + } + + [Test] + public void PaletteOne([ValueSource(nameof(allColourmaps))] Colourmap colourmap) + { + var palette = colourmap.Palette(1).ToArray(); + Assert.That(palette.Length, Is.EqualTo(1)); + Assert.That(palette[0], Is.EqualTo(colourmap.Map(0.5))); + } + + [Test] + public void PaletteZero([ValueSource(nameof(allColourmaps))] Colourmap colourmap) + { + var palette = colourmap.Palette(0).ToArray(); + Assert.That(palette, Is.Empty); + } + + [Test] + public void PaletteNegative([ValueSource(nameof(allColourmaps))] Colourmap colourmap) + { + var palette = colourmap.Palette(-1).ToArray(); + Assert.That(palette, Is.Empty); } [TestCaseSource(nameof(LookupCountTestData))] @@ -366,5 +412,5 @@ public void CubehelixCustom([Range(0, 999)] int lookupIndex) var fraction = lookupIndex / 999.0; var actual = Cubehelix.Map(fraction, -6.6, 0.6, 1.75, 0.75); TestUtils.AssertTriplet(actual, expected, 0.0005); - } + } } \ No newline at end of file diff --git a/Unicolour.Tests/DatasetCssTests.cs b/Unicolour.Tests/DatasetCssTests.cs index 0e3b169..711f767 100644 --- a/Unicolour.Tests/DatasetCssTests.cs +++ b/Unicolour.Tests/DatasetCssTests.cs @@ -317,10 +317,10 @@ public class DatasetCssTests ]; [TestCaseSource(nameof(Rgb255TestData))] - public void Rgb255(string name, Unicolour unicolour) + public void Rgb255(string name, Unicolour colour) { var expected = Rgb255Lookup[name]; - TestUtils.AssertTriplet(unicolour, expected, 0); + TestUtils.AssertTriplet(colour, expected, 0); } [Test] @@ -344,10 +344,10 @@ public void Transparent() ]; [TestCaseSource(nameof(DuplicateTestData))] - public void Duplicates(Unicolour unicolour1, Unicolour unicolour2) + public void Duplicates(Unicolour colour1, Unicolour colour2) { - Assert.That(ReferenceEquals(unicolour1, unicolour2), Is.False); - Assert.That(unicolour1.Hex, Is.EqualTo(unicolour2.Hex)); + Assert.That(ReferenceEquals(colour1, colour2), Is.False); + Assert.That(colour1.Hex, Is.EqualTo(colour2.Hex)); } [Test] @@ -370,11 +370,11 @@ public void All() [Test] public void Name([ValueSource(nameof(Names))] string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Not.Null); + var colour = FromName(name); + Assert.That(colour, Is.Not.Null); var expected = Rgb255Lookup[name]; - TestUtils.AssertTriplet(unicolour!, expected, 0); + TestUtils.AssertTriplet(colour!, expected, 0); } private static readonly List TransformedNames = Names.Select(AddWhitespaceAndUppercase).ToList(); @@ -387,8 +387,8 @@ private static string AddWhitespaceAndUppercase(string text) [Test] public void NameWithWhitespaceAndCasing([ValueSource(nameof(TransformedNames))] string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Not.Null); + var colour = FromName(name); + Assert.That(colour, Is.Not.Null); } // names taken from https://en.wikipedia.org/wiki/X11_color_names but are not defined in the CSS specification @@ -427,7 +427,7 @@ public void NameWithWhitespaceAndCasing([ValueSource(nameof(TransformedNames))] [TestCase(null)] public void NameNotFound(string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Null); + var colour = FromName(name); + Assert.That(colour, Is.Null); } } \ No newline at end of file diff --git a/Unicolour.Tests/DatasetEbnerFairchildTests.cs b/Unicolour.Tests/DatasetEbnerFairchildTests.cs index 46ad507..94ab60a 100644 --- a/Unicolour.Tests/DatasetEbnerFairchildTests.cs +++ b/Unicolour.Tests/DatasetEbnerFairchildTests.cs @@ -47,20 +47,20 @@ public class DatasetEbnerFairchildTests ]; [TestCaseSource(nameof(ReferenceHues))] - public void ReferenceHue(Unicolour unicolour, int expectedHue) + public void ReferenceHue(Unicolour colour, int expectedHue) { const double tolerance = 0.05; - Assert.That(unicolour.Lchab.H, Is.EqualTo(expectedHue).Within(tolerance).Or.EqualTo(expectedHue + 360).Within(tolerance)); + Assert.That(colour.Lchab.H, Is.EqualTo(expectedHue).Within(tolerance).Or.EqualTo(expectedHue + 360).Within(tolerance)); } [TestCaseSource(nameof(GroupedByHue))] - public void GroupedHue(List unicolours, int expectedHue, int expectedCount) + public void GroupedHue(List colours, int expectedHue, int expectedCount) { - Assert.That(unicolours.Count, Is.EqualTo(expectedCount)); + Assert.That(colours.Count, Is.EqualTo(expectedCount)); // questionable, but would be surprised if a Lab's hue for a group was beyond a neighbouring group's hue const int tolerance = 24; - var hues = unicolours.Select(x => x.Lchab.H); + var hues = colours.Select(x => x.Lchab.H); Assert.That(hues, Has.All.EqualTo(expectedHue).Within(tolerance).Or.EqualTo(expectedHue + 360).Within(tolerance)); } diff --git a/Unicolour.Tests/DatasetHungBernsTests.cs b/Unicolour.Tests/DatasetHungBernsTests.cs index 2dc5f8e..acd6863 100644 --- a/Unicolour.Tests/DatasetHungBernsTests.cs +++ b/Unicolour.Tests/DatasetHungBernsTests.cs @@ -43,34 +43,34 @@ public class DatasetHungBernsTests ]; [TestCaseSource(nameof(ReferenceHues))] - public void ReferenceXyy(Unicolour unicolour, Xyy xyy, Luv luv) + public void ReferenceXyy(Unicolour colour, Xyy xyy, Luv luv) { - TestUtils.AssertTriplet(unicolour, xyy.Triplet, 0.00005); + TestUtils.AssertTriplet(colour, xyy.Triplet, 0.00005); } [TestCaseSource(nameof(ReferenceHues))] - public void ReferenceLuv(Unicolour unicolour, Xyy xyy, Luv luv) + public void ReferenceLuv(Unicolour colour, Xyy xyy, Luv luv) { // for some reason, cyan Luv.V from the data table doesn't quite match Unicolour calculations // though no reason to distrust Unicolour since the conversions have been heavily tested - var tolerance = unicolour.Equals(CyanRef) ? 0.4 : 0.15; - TestUtils.AssertTriplet(unicolour, luv.Triplet, tolerance); + var tolerance = colour.Equals(CyanRef) ? 0.4 : 0.15; + TestUtils.AssertTriplet(colour, luv.Triplet, tolerance); } [TestCaseSource(nameof(GroupedByHue))] - public void GroupedHue(List unicolours) + public void GroupedHue(List colours) { // questionable, but would expect hue group to be no more than 30 degrees different (360 / 12 groups) - var hues = unicolours.Select(x => x.Lchab.H).ToList(); + var hues = colours.Select(x => x.Lchab.H).ToList(); Assert.That(hues.Max() - hues.Min(), Is.LessThan(30)); } [TestCaseSource(nameof(GroupedByHue))] - public void OrderedChroma(List unicolours) + public void OrderedChroma(List colours) { // assumes the hue list is returned in the order they were defined, not ideal - var chromas = unicolours.Select(x => x.Lchab.C).ToList(); - Assert.That(unicolours.Count, Is.EqualTo(4)); + var chromas = colours.Select(x => x.Lchab.C).ToList(); + Assert.That(colours.Count, Is.EqualTo(4)); Assert.That(chromas[0], Is.LessThan(chromas[1])); Assert.That(chromas[1], Is.LessThan(chromas[2])); Assert.That(chromas[2], Is.LessThan(chromas[3])); diff --git a/Unicolour.Tests/DatasetMacbethTests.cs b/Unicolour.Tests/DatasetMacbethTests.cs index 490953c..cc3017b 100644 --- a/Unicolour.Tests/DatasetMacbethTests.cs +++ b/Unicolour.Tests/DatasetMacbethTests.cs @@ -41,11 +41,11 @@ public class DatasetMacbethTests ]; [TestCaseSource(nameof(XyyTestData))] - public void Xyy(Unicolour unicolour, double expectedX, double expectedY, double expectedLuminance) + public void Xyy(Unicolour colour, double expectedX, double expectedY, double expectedLuminance) { - var unicolourIlluminantC = unicolour.ConvertToConfiguration(ConfigIlluminantC); + var colourIlluminantC = colour.ConvertToConfiguration(ConfigIlluminantC); var expectedXyy = new Xyy(expectedX, expectedY, expectedLuminance / 100.0); - TestUtils.AssertTriplet(unicolourIlluminantC, expectedXyy.Triplet, 0.02); + TestUtils.AssertTriplet(colourIlluminantC, expectedXyy.Triplet, 0.02); } [Test] diff --git a/Unicolour.Tests/DatasetXkcdTests.cs b/Unicolour.Tests/DatasetXkcdTests.cs index 8cef32d..13a6ecb 100644 --- a/Unicolour.Tests/DatasetXkcdTests.cs +++ b/Unicolour.Tests/DatasetXkcdTests.cs @@ -1916,10 +1916,10 @@ public class DatasetXkcdTests ]; [TestCaseSource(nameof(HexTestData))] - public void Hex(string name, Unicolour unicolour) + public void Hex(string name, Unicolour colour) { var expected = HexLookup[name].ToUpper(); - Assert.That(unicolour.Hex, Is.EqualTo(expected)); + Assert.That(colour.Hex, Is.EqualTo(expected)); } [Test] // not 954! @@ -1934,11 +1934,11 @@ public void All() [Test] public void Name([ValueSource(nameof(Names))] string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Not.Null); + var colour = FromName(name); + Assert.That(colour, Is.Not.Null); var expected = HexLookup[name].ToUpper(); - Assert.That(unicolour!.Hex, Is.EqualTo(expected)); + Assert.That(colour!.Hex, Is.EqualTo(expected)); } private static readonly List TransformedNames = Names.Select(AddWhitespaceAndUppercase).ToList(); @@ -1951,8 +1951,8 @@ private static string AddWhitespaceAndUppercase(string text) [Test] public void NameWithWhitespaceAndCasing([ValueSource(nameof(TransformedNames))] string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Not.Null); + var colour = FromName(name); + Assert.That(colour, Is.Not.Null); } [TestCase("bubblegumpink")] // could be either "bubblegum pink" or "bubble gum pink" @@ -1960,8 +1960,8 @@ public void NameWithWhitespaceAndCasing([ValueSource(nameof(TransformedNames))] [TestCase("yellow\tgreen")] // could be either "yellow green", "yellowgreen", or "yellow/green" public void NameWithMultipleMatches(string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Not.Null); + var colour = FromName(name); + Assert.That(colour, Is.Not.Null); } // names that look like they should exist based on other names @@ -1992,7 +1992,7 @@ public void NameWithMultipleMatches(string name) [TestCase(null)] public void NameNotFound(string name) { - var unicolour = FromName(name); - Assert.That(unicolour, Is.Null); + var colour = FromName(name); + Assert.That(colour, Is.Null); } } \ No newline at end of file diff --git a/Unicolour.Tests/DescriptionTests.cs b/Unicolour.Tests/DescriptionTests.cs index 443c758..2b3ec9e 100644 --- a/Unicolour.Tests/DescriptionTests.cs +++ b/Unicolour.Tests/DescriptionTests.cs @@ -385,13 +385,13 @@ private static void AssertDescription(double h, double s, double l, ColourDescri var hues = new List { h, h + 360, h - 360 }; foreach (var hue in hues) { - var unicolour = new Unicolour(ColourSpace.Hsl, hue, s, l); - Assert.That(unicolour.Description.Split(" ").Length, Is.LessThanOrEqualTo(3)); - Assert.That(unicolour.Description, Contains.Substring(included.ToString())); + var colour = new Unicolour(ColourSpace.Hsl, hue, s, l); + Assert.That(colour.Description.Split(" ").Length, Is.LessThanOrEqualTo(3)); + Assert.That(colour.Description, Contains.Substring(included.ToString())); foreach (var excludedItem in excluded) { - Assert.That(unicolour.Description, !Contains.Substring(excludedItem.ToString())); + Assert.That(colour.Description, !Contains.Substring(excludedItem.ToString())); } } } diff --git a/Unicolour.Tests/DifferenceTests.cs b/Unicolour.Tests/DifferenceTests.cs index 46b0cd7..756ba2b 100644 --- a/Unicolour.Tests/DifferenceTests.cs +++ b/Unicolour.Tests/DifferenceTests.cs @@ -255,8 +255,8 @@ public void DifferentConfigs() [TestCase(DeltaE.Cam16, ColourSpace.Cam16)] public void AssertNotNumberDeltas(DeltaE deltaE, ColourSpace colourSpace) { - var unicolour = new Unicolour(colourSpace, double.NaN, double.NaN, double.NaN); - var delta = unicolour.Difference(unicolour, deltaE); + var colour = new Unicolour(colourSpace, double.NaN, double.NaN, double.NaN); + var delta = colour.Difference(colour, deltaE); Assert.That(delta, Is.NaN); } } \ No newline at end of file diff --git a/Unicolour.Tests/DominantWavelengthTests.cs b/Unicolour.Tests/DominantWavelengthTests.cs index b60868f..c6193e2 100644 --- a/Unicolour.Tests/DominantWavelengthTests.cs +++ b/Unicolour.Tests/DominantWavelengthTests.cs @@ -87,9 +87,9 @@ public class DominantWavelengthTests [TestCaseSource(nameof(RgbTestData))] public void RgbGamut(Configuration configuration, double r, double g, double b, double expectedWavelength) { - var unicolour = new Unicolour(configuration, ColourSpace.Rgb, r, g, b); - var hasLuminance = unicolour.Xyy.Luminance > 0; - Assert.That(unicolour.DominantWavelength, Is.EqualTo(hasLuminance ? expectedWavelength : double.NaN).Within(0.25)); + var colour = new Unicolour(configuration, ColourSpace.Rgb, r, g, b); + var hasLuminance = colour.Xyy.Luminance > 0; + Assert.That(colour.DominantWavelength, Is.EqualTo(hasLuminance ? expectedWavelength : double.NaN).Within(0.25)); } private static readonly Dictionary<(Illuminant illuminant, Observer observer), Configuration> Configurations = new() @@ -110,8 +110,8 @@ public void Monochromatic( var observer = TestUtils.Observers[observerName]; var config = Configurations[(illuminant, observer)]; - var unicolour = new Unicolour(config, Spd.Monochromatic(wavelength)); - Assert.That(unicolour.DominantWavelength, Is.EqualTo(wavelength).Within(0.000000005)); + var colour = new Unicolour(config, Spd.Monochromatic(wavelength)); + Assert.That(colour.DominantWavelength, Is.EqualTo(wavelength).Within(0.000000005)); } /* @@ -159,8 +159,8 @@ public void Monochromatic( [TestCaseSource(nameof(ImaginaryTestData))] public void Imaginary(Configuration config, double x, double y, double expectedWavelength) { - var unicolour = new Unicolour(config, new Chromaticity(x, y)); - Assert.That(unicolour.DominantWavelength, Is.EqualTo(expectedWavelength).Within(0.25)); + var colour = new Unicolour(config, new Chromaticity(x, y)); + Assert.That(colour.DominantWavelength, Is.EqualTo(expectedWavelength).Within(0.25)); } [Test] @@ -168,9 +168,9 @@ public void SampleBetweenWhiteAndNear() { // white point (0.333, 0.333), sample right & down (0.34, 0.32), near = line of purples, far = green boundary // sample between: white point -> near (line of purples) = negative wavelength - var unicolour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.34, 0.32)); - Assert.That(unicolour.DominantWavelength, Is.Negative); - Assert.That(unicolour.IsImaginary, Is.False); + var colour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.34, 0.32)); + Assert.That(colour.DominantWavelength, Is.Negative); + Assert.That(colour.IsImaginary, Is.False); } [Test] @@ -178,9 +178,9 @@ public void SampleBetweenWhiteAndFar() { // white point (0.333, 0.333), sample left & up (0.34, 0.32), near = line of purples, far = green boundary // sample between: white point -> far (green boundary) = positive wavelength - var unicolour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.32, 0.34)); - Assert.That(unicolour.DominantWavelength, Is.Positive); - Assert.That(unicolour.IsImaginary, Is.False); + var colour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.32, 0.34)); + Assert.That(colour.DominantWavelength, Is.Positive); + Assert.That(colour.IsImaginary, Is.False); } [Test] @@ -188,9 +188,9 @@ public void SampleImaginaryNearNegative() { // white point (0.333, 0.333), sample right & down (0.5, 0.1), near = line of purples, far = green boundary // sample outside boundary: -> near (line of purples) = negative wavelength - var unicolour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.5, 0.1)); - Assert.That(unicolour.DominantWavelength, Is.Negative); - Assert.That(unicolour.IsImaginary, Is.True); + var colour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.5, 0.1)); + Assert.That(colour.DominantWavelength, Is.Negative); + Assert.That(colour.IsImaginary, Is.True); } [Test] @@ -198,8 +198,8 @@ public void SampleImaginaryNearPositive() { // white point (0.333, 0.333), sample left & up (0.0, 0.9), near = green boundary, far = line of purples // sample outside boundary: -> near (green boundary) = positive wavelength - var unicolour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.0, 0.9)); - Assert.That(unicolour.DominantWavelength, Is.Positive); - Assert.That(unicolour.IsImaginary, Is.True); + var colour = new Unicolour(RgbStandardXyzE, new Chromaticity(0.0, 0.9)); + Assert.That(colour.DominantWavelength, Is.Positive); + Assert.That(colour.IsImaginary, Is.True); } } \ No newline at end of file diff --git a/Unicolour.Tests/EqualityTests.cs b/Unicolour.Tests/EqualityTests.cs index 73b71be..ae2debf 100644 --- a/Unicolour.Tests/EqualityTests.cs +++ b/Unicolour.Tests/EqualityTests.cs @@ -12,56 +12,56 @@ public class EqualityTests [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void SameReference(ColourSpace colourSpace) { - var unicolour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - var unicolour2 = unicolour1; - AssertUnicoloursEqual(unicolour1, unicolour2); + var colour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + var colour2 = colour1; + AssertUnicoloursEqual(colour1, colour2); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void SameReferenceNotNumber(ColourSpace colourSpace) { - var unicolour1 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, double.NaN, double.NaN, double.NaN); - var unicolour2 = unicolour1; - AssertUnicoloursEqual(unicolour1, unicolour2); + var colour1 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, double.NaN, double.NaN, double.NaN); + var colour2 = colour1; + AssertUnicoloursEqual(colour1, colour2); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void DifferentType(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); + var colour = RandomColours.UnicolourFrom(colourSpace); var notUnicolour = new object(); - AssertNotEqual(unicolour, notUnicolour); + AssertNotEqual(colour, notUnicolour); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void NullUnicolour(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - Assert.That(unicolour.Equals(null), Is.False); + var colour = RandomColours.UnicolourFrom(colourSpace); + Assert.That(colour.Equals(null), Is.False); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void NullObject(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - Assert.That(unicolour.Equals(null as object), Is.False); + var colour = RandomColours.UnicolourFrom(colourSpace); + Assert.That(colour.Equals(null as object), Is.False); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void EqualObjects(ColourSpace colourSpace) { - var unicolour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - var unicolour2 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, unicolour1.GetRepresentation(colourSpace).Triplet.Tuple, unicolour1.Alpha.A); - AssertUnicoloursEqual(unicolour1, unicolour2); + var colour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + var colour2 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, colour1.GetRepresentation(colourSpace).Triplet.Tuple, colour1.Alpha.A); + AssertUnicoloursEqual(colour1, colour2); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void NotEqualObjects(ColourSpace colourSpace) { - var unicolour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - var differentTuple = GetDifferent(unicolour1.GetRepresentation(colourSpace).Triplet).Tuple; - var unicolour2 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, differentTuple, unicolour1.Alpha.A + 0.1); - AssertUnicoloursNotEqual(unicolour1, unicolour2, unicolour => unicolour.GetRepresentation(colourSpace).Triplet); + var colour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + var differentTuple = GetDifferent(colour1.GetRepresentation(colourSpace).Triplet).Tuple; + var colour2 = new Unicolour(TestUtils.DefaultFogra39Config, colourSpace, differentTuple, colour1.Alpha.A + 0.1); + AssertUnicoloursNotEqual(colour1, colour2, unicolour => unicolour.GetRepresentation(colourSpace).Triplet); } [Test] @@ -140,63 +140,63 @@ public void DifferentColourHeritageObjects() private static ColourTriplet GetDifferent(ColourTriplet triplet, double diff = 0.1) => new(triplet.First + diff, triplet.Second + diff, triplet.Third + diff); - private static void AssertUnicoloursEqual(Unicolour unicolour1, Unicolour unicolour2) + private static void AssertUnicoloursEqual(Unicolour colour1, Unicolour colour2) { - AssertEqual(unicolour1.Alpha, unicolour2.Alpha); - AssertEqual(unicolour1.Cam02, unicolour2.Cam02); - AssertEqual(unicolour1.Cam16, unicolour2.Cam16); - AssertEqual(unicolour1.Chromaticity, unicolour2.Chromaticity); - AssertEqual(unicolour1.Description, unicolour2.Description); - AssertEqual(unicolour1.DominantWavelength, unicolour2.DominantWavelength); - AssertEqual(unicolour1.ExcitationPurity, unicolour2.ExcitationPurity); - AssertEqual(unicolour1.Hct, unicolour2.Hct); - AssertEqual(unicolour1.Hex, unicolour2.Hex); - AssertEqual(unicolour1.Hpluv, unicolour2.Hpluv); - AssertEqual(unicolour1.Hsb, unicolour2.Hsb); - AssertEqual(unicolour1.Hsi, unicolour2.Hsi); - AssertEqual(unicolour1.Hsl, unicolour2.Hsl); - AssertEqual(unicolour1.Hsluv, unicolour2.Hsluv); - AssertEqual(unicolour1.Hwb, unicolour2.Hwb); - AssertEqual(unicolour1.Icc, unicolour2.Icc); - AssertEqual(unicolour1.Ictcp, unicolour2.Ictcp); - AssertEqual(unicolour1.Ipt, unicolour2.Ipt); - AssertEqual(unicolour1.IsImaginary, unicolour2.IsImaginary); - AssertEqual(unicolour1.IsInDisplayGamut, unicolour2.IsInDisplayGamut); - AssertEqual(unicolour1.Jzazbz, unicolour2.Jzazbz); - AssertEqual(unicolour1.Jzczhz, unicolour2.Jzczhz); - AssertEqual(unicolour1.Lab, unicolour2.Lab); - AssertEqual(unicolour1.Lchab, unicolour2.Lchab); - AssertEqual(unicolour1.Luv, unicolour2.Luv); - AssertEqual(unicolour1.Lchuv, unicolour2.Lchuv); - AssertEqual(unicolour1.Oklab, unicolour2.Oklab); - AssertEqual(unicolour1.Oklch, unicolour2.Oklch); - AssertEqual(unicolour1.Okhsl, unicolour2.Okhsl); - AssertEqual(unicolour1.Okhsv, unicolour2.Okhsv); - AssertEqual(unicolour1.Okhwb, unicolour2.Okhwb); - AssertEqual(unicolour1.RelativeLuminance, unicolour2.RelativeLuminance); - AssertEqual(unicolour1.Rgb, unicolour2.Rgb); - AssertEqual(unicolour1.Rgb.Byte255, unicolour2.Rgb.Byte255); - AssertEqual(unicolour1.RgbLinear, unicolour2.RgbLinear); - AssertEqual(unicolour1.Temperature, unicolour2.Temperature); - AssertEqual(unicolour1.Tsl, unicolour2.Tsl); - AssertEqual(unicolour1.Wxy, unicolour2.Wxy); - AssertEqual(unicolour1.Xyb, unicolour2.Xyb); - AssertEqual(unicolour1.Xyy, unicolour2.Xyy); - AssertEqual(unicolour1.Xyz, unicolour2.Xyz); - AssertEqual(unicolour1.Ycbcr, unicolour2.Ycbcr); - AssertEqual(unicolour1.Ycbcr, unicolour2.Ycbcr); - AssertEqual(unicolour1.Ycgco, unicolour2.Ycgco); - AssertEqual(unicolour1.Yiq, unicolour2.Yiq); - AssertEqual(unicolour1.Ypbpr, unicolour2.Ypbpr); - AssertEqual(unicolour1.Yuv, unicolour2.Yuv); + AssertEqual(colour1.Alpha, colour2.Alpha); + AssertEqual(colour1.Cam02, colour2.Cam02); + AssertEqual(colour1.Cam16, colour2.Cam16); + AssertEqual(colour1.Chromaticity, colour2.Chromaticity); + AssertEqual(colour1.Description, colour2.Description); + AssertEqual(colour1.DominantWavelength, colour2.DominantWavelength); + AssertEqual(colour1.ExcitationPurity, colour2.ExcitationPurity); + AssertEqual(colour1.Hct, colour2.Hct); + AssertEqual(colour1.Hex, colour2.Hex); + AssertEqual(colour1.Hpluv, colour2.Hpluv); + AssertEqual(colour1.Hsb, colour2.Hsb); + AssertEqual(colour1.Hsi, colour2.Hsi); + AssertEqual(colour1.Hsl, colour2.Hsl); + AssertEqual(colour1.Hsluv, colour2.Hsluv); + AssertEqual(colour1.Hwb, colour2.Hwb); + AssertEqual(colour1.Icc, colour2.Icc); + AssertEqual(colour1.Ictcp, colour2.Ictcp); + AssertEqual(colour1.Ipt, colour2.Ipt); + AssertEqual(colour1.IsImaginary, colour2.IsImaginary); + AssertEqual(colour1.IsInRgbGamut, colour2.IsInRgbGamut); + AssertEqual(colour1.Jzazbz, colour2.Jzazbz); + AssertEqual(colour1.Jzczhz, colour2.Jzczhz); + AssertEqual(colour1.Lab, colour2.Lab); + AssertEqual(colour1.Lchab, colour2.Lchab); + AssertEqual(colour1.Luv, colour2.Luv); + AssertEqual(colour1.Lchuv, colour2.Lchuv); + AssertEqual(colour1.Oklab, colour2.Oklab); + AssertEqual(colour1.Oklch, colour2.Oklch); + AssertEqual(colour1.Okhsl, colour2.Okhsl); + AssertEqual(colour1.Okhsv, colour2.Okhsv); + AssertEqual(colour1.Okhwb, colour2.Okhwb); + AssertEqual(colour1.RelativeLuminance, colour2.RelativeLuminance); + AssertEqual(colour1.Rgb, colour2.Rgb); + AssertEqual(colour1.Rgb.Byte255, colour2.Rgb.Byte255); + AssertEqual(colour1.RgbLinear, colour2.RgbLinear); + AssertEqual(colour1.Temperature, colour2.Temperature); + AssertEqual(colour1.Tsl, colour2.Tsl); + AssertEqual(colour1.Wxy, colour2.Wxy); + AssertEqual(colour1.Xyb, colour2.Xyb); + AssertEqual(colour1.Xyy, colour2.Xyy); + AssertEqual(colour1.Xyz, colour2.Xyz); + AssertEqual(colour1.Ycbcr, colour2.Ycbcr); + AssertEqual(colour1.Ycbcr, colour2.Ycbcr); + AssertEqual(colour1.Ycgco, colour2.Ycgco); + AssertEqual(colour1.Yiq, colour2.Yiq); + AssertEqual(colour1.Ypbpr, colour2.Ypbpr); + AssertEqual(colour1.Yuv, colour2.Yuv); - if (unicolour1.Xyz.HctToXyzSearchResult != null) + if (colour1.Xyz.HctToXyzSearchResult != null) { - AssertEqual(unicolour1.Xyz.HctToXyzSearchResult, unicolour2.Xyz.HctToXyzSearchResult); + AssertEqual(colour1.Xyz.HctToXyzSearchResult, colour2.Xyz.HctToXyzSearchResult); } - AssertConfigurationEqual(unicolour1.Configuration, unicolour2.Configuration); - AssertEqual(unicolour1, unicolour2); + AssertConfigurationEqual(colour1.Configuration, colour2.Configuration); + AssertEqual(colour1, colour2); } private static void AssertConfigurationEqual(Configuration config1, Configuration config2) @@ -211,7 +211,7 @@ private static void AssertConfigurationEqual(Configuration config1, Configuratio AssertEqual(config1.Rgb.InverseCompandToLinear, config2.Rgb.InverseCompandToLinear); AssertEqual(config1.Xyz.WhitePoint, config2.Xyz.WhitePoint); AssertEqual(config1.Xyz.Observer, config2.Xyz.Observer); - AssertEqual(config1.Xyz.Spectral, config2.Xyz.Spectral); + AssertEqual(config1.Xyz.SpectralBoundary, config2.Xyz.SpectralBoundary); AssertEqual(config1.Xyz.Planckian, config2.Xyz.Planckian); AssertEqual(config1.Ybr.Kr, config2.Ybr.Kr); AssertEqual(config1.Ybr.Kg, config2.Ybr.Kg); @@ -229,11 +229,11 @@ private static void AssertConfigurationEqual(Configuration config1, Configuratio AssertEqual(config1.JzazbzScalar, config2.JzazbzScalar); } - private static void AssertUnicoloursNotEqual(Unicolour unicolour1, Unicolour unicolour2, Func getTriplet) + private static void AssertUnicoloursNotEqual(Unicolour colour1, Unicolour colour2, Func getTriplet) { - AssertNotEqual(getTriplet(unicolour1), getTriplet(unicolour2)); - AssertNotEqual(unicolour1.Alpha, unicolour2.Alpha); - AssertNotEqual(unicolour1, unicolour2); + AssertNotEqual(getTriplet(colour1), getTriplet(colour2)); + AssertNotEqual(colour1.Alpha, colour2.Alpha); + AssertNotEqual(colour1, colour2); } private static void AssertEqual(T object1, T object2) => TestUtils.AssertEqual(object1, object2); diff --git a/Unicolour.Tests/ExcitationPurityTests.cs b/Unicolour.Tests/ExcitationPurityTests.cs index d2b632b..034f434 100644 --- a/Unicolour.Tests/ExcitationPurityTests.cs +++ b/Unicolour.Tests/ExcitationPurityTests.cs @@ -9,16 +9,16 @@ public class ExcitationPurityTests [Test] public void RgbGamut([Values(0, 255)] int r, [Values(0, 255)] int g, [Values(0, 255)] int b) { - var unicolour = new Unicolour(ColourSpace.Rgb255, r, g, b); + var colour = new Unicolour(ColourSpace.Rgb255, r, g, b); var greyscale = r == g && g == b; - Assert.That(unicolour.ExcitationPurity, greyscale ? Is.NaN : Is.LessThan(1.0)); + Assert.That(colour.ExcitationPurity, greyscale ? Is.NaN : Is.LessThan(1.0)); } [Test] public void Greyscale([Range(0, 1, 0.1)] double value) { - var unicolour = new Unicolour(ColourSpace.Rgb, value, value, value); - Assert.That(unicolour.ExcitationPurity, Is.NaN); + var colour = new Unicolour(ColourSpace.Rgb, value, value, value); + Assert.That(colour.ExcitationPurity, Is.NaN); } private static readonly Dictionary<(Illuminant illuminant, Observer observer), Configuration> Configurations = new() @@ -39,8 +39,8 @@ public void Monochromatic( var observer = TestUtils.Observers[observerName]; var config = Configurations[(illuminant, observer)]; - var unicolour = new Unicolour(config, Spd.Monochromatic(wavelength)); - Assert.That(unicolour.ExcitationPurity, Is.EqualTo(1.0).Within(0.00000000000005)); + var colour = new Unicolour(config, Spd.Monochromatic(wavelength)); + Assert.That(colour.ExcitationPurity, Is.EqualTo(1.0).Within(0.00000000000005)); } [TestCase(0, 0)] @@ -49,7 +49,7 @@ public void Monochromatic( [TestCase(1, 1)] public void Imaginary(double x, double y) { - var unicolour = new Unicolour(new Chromaticity(x, y)); - Assert.That(unicolour.ExcitationPurity, Is.GreaterThan(1.0)); + var colour = new Unicolour(new Chromaticity(x, y)); + Assert.That(colour.ExcitationPurity, Is.GreaterThan(1.0)); } } \ No newline at end of file diff --git a/Unicolour.Tests/ExtremeValuesTests.cs b/Unicolour.Tests/ExtremeValuesTests.cs index 3c8fab2..2cf580c 100644 --- a/Unicolour.Tests/ExtremeValuesTests.cs +++ b/Unicolour.Tests/ExtremeValuesTests.cs @@ -115,12 +115,12 @@ public void Mix( [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, [ValueSource(typeof(TestUtils), nameof(TestUtils.ExtremeDoubles))] double amount) { - var unicolour1 = RandomColours.UnicolourFrom(colourSpace); - var unicolour2 = RandomColours.UnicolourFrom(colourSpace); - TestUtils.AssertNoPropertyError(unicolour1.Mix(unicolour2, colourSpace, amount)); + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + TestUtils.AssertNoPropertyError(colour1.Mix(colour2, colourSpace, amount)); - unicolour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - unicolour2 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - TestUtils.AssertNoPropertyError(unicolour1.Mix(unicolour2, colourSpace, amount)); + colour1 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + colour2 = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + TestUtils.AssertNoPropertyError(colour1.Mix(colour2, colourSpace, amount)); } } \ No newline at end of file diff --git a/Unicolour.Tests/GamutMappingTests.cs b/Unicolour.Tests/GamutMapChromaReductionTests.cs similarity index 56% rename from Unicolour.Tests/GamutMappingTests.cs rename to Unicolour.Tests/GamutMapChromaReductionTests.cs index 9c5c1c1..547d9c5 100644 --- a/Unicolour.Tests/GamutMappingTests.cs +++ b/Unicolour.Tests/GamutMapChromaReductionTests.cs @@ -3,39 +3,34 @@ namespace Wacton.Unicolour.Tests; -public class GamutMappingTests +public class GamutMapChromaReductionTests { private const double Tolerance = 0.00000000001; - [TestCase(0, 0, 0)] - [TestCase(0.00000000001, 0, 0)] - [TestCase(0, 0.00000000001, 0)] - [TestCase(0, 0, 0.00000000001)] - [TestCase(1, 1, 0.99999999999)] - [TestCase(1, 0.99999999999, 1)] - [TestCase(0.99999999999, 1, 1)] - [TestCase(1, 1, 1)] - public void RgbInGamut(double r, double g, double b) - { - var original = new Unicolour(ColourSpace.Rgb, r, g, b); - var gamutMapped = original.MapToGamut(); - Assert.That(original.IsInDisplayGamut, Is.True); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); - TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.Triplet, Tolerance); - } - - [TestCase(-0.00000000001, 0, 0)] - [TestCase(0, -0.00000000001, 0)] - [TestCase(0, 0, -0.00000000001)] - [TestCase(1, 1, 1.00000000001)] - [TestCase(1, 1.00000000001, 1)] - [TestCase(1.00000000001, 1, 1)] - public void RgbOutOfGamut(double r, double g, double b) + [Test] // https://www.w3.org/TR/css-color-4/#GM-chroma + public void YellowOutOfGamut() { - var original = new Unicolour(ColourSpace.Rgb, r, g, b); - var gamutMapped = original.MapToGamut(); - Assert.That(original.IsInDisplayGamut, Is.False); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); + const double tripletTolerance = 0.00005; + var yellowDisplayP3 = new Unicolour(new Configuration(RgbConfiguration.DisplayP3), ColourSpace.Rgb, 1, 1, 0); + var yellowStandardRgb = yellowDisplayP3.ConvertToConfiguration(new Configuration(RgbConfiguration.StandardRgb)); + TestUtils.AssertTriplet(yellowDisplayP3, new(1.00000, 1.00000, 0.00000), tripletTolerance); + TestUtils.AssertTriplet(yellowStandardRgb, new(1.00000, 1.00000, -0.34630), tripletTolerance); + + // different because Unicolour doesn't limit Oklab / Oklch to sRGB + TestUtils.AssertTriplet(yellowStandardRgb, new(0.96476, 0.24503, 110.23), tripletTolerance); + TestUtils.AssertTriplet(yellowDisplayP3, new(0.96798, 0.21101, 109.77), tripletTolerance); + + var gamutMappedDisplayP3 = yellowDisplayP3.MapToRgbGamut(); + Assert.That(gamutMappedDisplayP3, Is.EqualTo(yellowDisplayP3)); + Assert.That(yellowDisplayP3.IsInRgbGamut, Is.True); + Assert.That(gamutMappedDisplayP3.IsInRgbGamut, Is.True); + + const double gamutMapTolerance = 0.0025; + var gamutMappedStandardRgb = yellowStandardRgb.MapToRgbGamut(); + Assert.That(yellowStandardRgb.Oklch.C, Is.EqualTo(0.245).Within(gamutMapTolerance)); + Assert.That(yellowStandardRgb.IsInRgbGamut, Is.False); + Assert.That(gamutMappedStandardRgb.Oklch.C, Is.EqualTo(0.210).Within(gamutMapTolerance)); + Assert.That(gamutMappedStandardRgb.IsInRgbGamut, Is.True); } [TestCase(1.0, 0, 0)] @@ -43,7 +38,7 @@ public void RgbOutOfGamut(double r, double g, double b) public void MaxLightness(double l, double c, double h) { var white = new Unicolour(ColourSpace.Oklch, l, c, h); - var gamutMapped = white.MapToGamut(); + var gamutMapped = white.MapToRgbGamut(); TestUtils.AssertTriplet(gamutMapped, new(1, 1, 1), Tolerance); } @@ -52,19 +47,19 @@ public void MaxLightness(double l, double c, double h) public void MinLightness(double l, double c, double h) { var white = new Unicolour(ColourSpace.Oklch, l, c, h); - var gamutMapped = white.MapToGamut(); + var gamutMapped = white.MapToRgbGamut(); TestUtils.AssertTriplet(gamutMapped, new(0, 0, 0), Tolerance); } - + [TestCase] public void NoChromaInGamut() { // OKLCH without chroma isn't processed by the gamut mapping algorithm (chroma is already considered to be converged, can't go lower) // OKLCH (0.5, 0, 0) corresponds to an in-gamut sRGB (0.39, 0.39, 0.39), so make sure that original RGB is returned var original = new Unicolour(ColourSpace.Oklch, 0.5, 0, 0); - var gamutMapped = original.MapToGamut(); - Assert.That(original.IsInDisplayGamut, Is.True); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); + var gamutMapped = original.MapToRgbGamut(); + Assert.That(original.IsInRgbGamut, Is.True); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.Triplet, Tolerance); } @@ -75,16 +70,16 @@ public void NoChromaOutOfGamut() // OKLCH (0.99999999, 0, 0) corresponds to an out-of-gamut RGB (0.999999956, 0.999999995, 1.000000102) // so make sure RGB is brought into gamut anyway var original = new Unicolour(ColourSpace.Oklch, 0.99999999, 0, 0); - var gamutMapped = original.MapToGamut(); - Assert.That(original.IsInDisplayGamut, Is.False); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); + var gamutMapped = original.MapToRgbGamut(); + Assert.That(original.IsInRgbGamut, Is.False); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); } [Test] public void NegativeChroma() { var original = new Unicolour(ColourSpace.Oklch, 0.5, -0.5, 180); - var gamutMapped = original.MapToGamut(); + var gamutMapped = original.MapToRgbGamut(); Assert.That(gamutMapped.Rgb.IsInGamut, Is.True); TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.ConstrainedTriplet, Tolerance); } @@ -94,70 +89,25 @@ public void NegativeChroma() public void ChromaCannotConverge(double chroma) { var original = new Unicolour(ColourSpace.Oklch, 0.5, chroma, 180); - Assert.DoesNotThrow(() => original.MapToGamut()); + Assert.DoesNotThrow(() => original.MapToRgbGamut()); } [Test] public void NegativeHue() { var original = new Unicolour(ColourSpace.Oklch, 0.5, 0.1, -180); - var gamutMapped = original.MapToGamut(); + var gamutMapped = original.MapToRgbGamut(); Assert.That(gamutMapped.Rgb.IsInGamut, Is.True); TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.ConstrainedTriplet, 0.0001); } - [TestCaseSource(typeof(RandomColours), nameof(RandomColours.RgbTriplets))] - public void RandomRgb(ColourTriplet triplet) - { - var original = new Unicolour(ColourSpace.Rgb, triplet.Tuple); - var gamutMapped = original.MapToGamut(); - Assert.That(original.IsInDisplayGamut, Is.True); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); - TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.Triplet, Tolerance); - } - - [TestCaseSource(typeof(RandomColours), nameof(RandomColours.OklchTriplets))] - public void RandomOklch(ColourTriplet triplet) - { - var original = new Unicolour(ColourSpace.Oklch, triplet.Tuple); - var gamutMapped = original.MapToGamut(); - Assert.That(gamutMapped.Rgb.IsInGamut, Is.True); - Assert.That(gamutMapped.Rgb.Triplet, Is.EqualTo(gamutMapped.Rgb.ConstrainedTriplet)); - } - - [Test] // https://www.w3.org/TR/css-color-4/#GM-chroma - public void YellowOutOfGamut() - { - const double tripletTolerance = 0.00005; - var yellowDisplayP3 = new Unicolour(new Configuration(RgbConfiguration.DisplayP3), ColourSpace.Rgb, 1, 1, 0); - var yellowStandardRgb = yellowDisplayP3.ConvertToConfiguration(new Configuration(RgbConfiguration.StandardRgb)); - TestUtils.AssertTriplet(yellowDisplayP3, new(1.00000, 1.00000, 0.00000), tripletTolerance); - TestUtils.AssertTriplet(yellowStandardRgb, new(1.00000, 1.00000, -0.34630), tripletTolerance); - - // different because Unicolour doesn't limit Oklab / Oklch to sRGB - TestUtils.AssertTriplet(yellowStandardRgb, new(0.96476, 0.24503, 110.23), tripletTolerance); - TestUtils.AssertTriplet(yellowDisplayP3, new(0.96798, 0.21101, 109.77), tripletTolerance); - - var gamutMappedDisplayP3 = yellowDisplayP3.MapToGamut(); - Assert.That(gamutMappedDisplayP3, Is.EqualTo(yellowDisplayP3)); - Assert.That(yellowDisplayP3.IsInDisplayGamut, Is.True); - Assert.That(gamutMappedDisplayP3.IsInDisplayGamut, Is.True); - - const double gamutMapTolerance = 0.0025; - var gamutMappedStandardRgb = yellowStandardRgb.MapToGamut(); - Assert.That(yellowStandardRgb.Oklch.C, Is.EqualTo(0.245).Within(gamutMapTolerance)); - Assert.That(yellowStandardRgb.IsInDisplayGamut, Is.False); - Assert.That(gamutMappedStandardRgb.Oklch.C, Is.EqualTo(0.210).Within(gamutMapTolerance)); - Assert.That(gamutMappedStandardRgb.IsInDisplayGamut, Is.True); - } - /* search result that is in gamut before being clipped is hard to come by */ [TestCase(ColourSpace.Oklch, 0.495275659305237, 0.23728554321306156, 320.367400932029)] [TestCase(ColourSpace.Rgb, 0.5981, -0.0003, 0.6961)] public void ResultInGamutNotClipped(ColourSpace colourSpace, double first, double second, double third) { var original = new Unicolour(colourSpace, first, second, third); - var gamutMapped = original.MapToGamut(); - Assert.That(gamutMapped.IsInDisplayGamut, Is.True); + var gamutMapped = original.MapToRgbGamut(); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); } } \ No newline at end of file diff --git a/Unicolour.Tests/GamutMapPurityReductionTests.cs b/Unicolour.Tests/GamutMapPurityReductionTests.cs new file mode 100644 index 0000000..c08b586 --- /dev/null +++ b/Unicolour.Tests/GamutMapPurityReductionTests.cs @@ -0,0 +1,41 @@ +using NUnit.Framework; + +namespace Wacton.Unicolour.Tests; + +public class GamutMapPurityReductionTests +{ + [TestCase(360, 1, 0.5)] + [TestCase(530, 1, 0.5)] + [TestCase(700, 1, 0.5)] + [TestCase(-490, 1, 0.5)] + [TestCase(-560, 1, 0.5)] + public void ReducePurity(double w, double x, double y) + { + var colour = new Unicolour(ColourSpace.Wxy, w, x, y); + var gamutMapped = colour.MapToRgbGamut(GamutMap.WxyPurityReduction); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + + // only X (excitation purity) should change + Assert.That(gamutMapped.Wxy.W, Is.EqualTo(w)); + Assert.That(gamutMapped.Wxy.Y, Is.EqualTo(y)); + + var increasedPurity = new Unicolour(ColourSpace.Wxy, w, gamutMapped.Wxy.X + 0.05, y); + Assert.That(increasedPurity.IsInRgbGamut, Is.False); + } + + [Test] + public void BeyondMaxPurity() + { + var colour = new Unicolour(ColourSpace.Wxy, 530, 1.00000000001, 0.5); + var gamutMapped = colour.MapToRgbGamut(GamutMap.WxyPurityReduction); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + } + + [Test] + public void BeyondMinPurity() + { + var colour = new Unicolour(ColourSpace.Wxy, 530, -0.00000000001, 0.5); + var gamutMapped = colour.MapToRgbGamut(GamutMap.WxyPurityReduction); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/GamutMapRgbClippingTests.cs b/Unicolour.Tests/GamutMapRgbClippingTests.cs new file mode 100644 index 0000000..2f53c0c --- /dev/null +++ b/Unicolour.Tests/GamutMapRgbClippingTests.cs @@ -0,0 +1,39 @@ +using System.Collections.Generic; +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class GamutMapRgbClippingTests +{ + private static readonly List TestData = + [ + new(new ColourTriplet(-0.00001, 0.0, 0.0), new ColourTriplet(0.0, 0.0, 0.0)), + new(new ColourTriplet(0.0, -0.00001, 0.0), new ColourTriplet(0.0, 0.0, 0.0)), + new(new ColourTriplet(0.0, 0.0, -0.00001), new ColourTriplet(0.0, 0.0, 0.0)), + new(new ColourTriplet(1.00001, 1.0, 1.0), new ColourTriplet(1.0, 1.0, 1.0)), + new(new ColourTriplet(1.0, 1.00001, 1.0), new ColourTriplet(1.0, 1.0, 1.0)), + new(new ColourTriplet(1.0, 1.0, 1.00001), new ColourTriplet(1.0, 1.0, 1.0)), + new(new ColourTriplet(double.MaxValue, 0.5, 0.5), new ColourTriplet(1.0, 0.5, 0.5)), + new(new ColourTriplet(0.5, double.MaxValue, 0.5), new ColourTriplet(0.5, 1.0, 0.5)), + new(new ColourTriplet(0.5, 0.5, double.MaxValue), new ColourTriplet(0.5, 0.5, 1.0)), + new(new ColourTriplet(double.MinValue, 0.5, 0.5), new ColourTriplet(0.0, 0.5, 0.5)), + new(new ColourTriplet(0.5, double.MinValue, 0.5), new ColourTriplet(0.5, 0.0, 0.5)), + new(new ColourTriplet(0.5, 0.5, double.MinValue), new ColourTriplet(0.5, 0.5, 0.0)), + new(new ColourTriplet(double.PositiveInfinity, 0.5, 0.5), new ColourTriplet(1.0, 0.5, 0.5)), + new(new ColourTriplet(0.5, double.PositiveInfinity, 0.5), new ColourTriplet(0.5, 1.0, 0.5)), + new(new ColourTriplet(0.5, 0.5, double.PositiveInfinity), new ColourTriplet(0.5, 0.5, 1.0)), + new(new ColourTriplet(double.NegativeInfinity, 0.5, 0.5), new ColourTriplet(0.0, 0.5, 0.5)), + new(new ColourTriplet(0.5, double.NegativeInfinity, 0.5), new ColourTriplet(0.5, 0.0, 0.5)), + new(new ColourTriplet(0.5, 0.5, double.NegativeInfinity), new ColourTriplet(0.5, 0.5, 0.0)) + ]; + + [TestCaseSource(nameof(TestData))] + public void Clip(ColourTriplet outOfGamut, ColourTriplet expected) + { + var colour = new Unicolour(ColourSpace.Rgb, outOfGamut.Tuple); + var gamutMapped = colour.MapToRgbGamut(GamutMap.RgbClipping); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + TestUtils.AssertTriplet(gamutMapped, expected, 0); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/GamutMapTests.cs b/Unicolour.Tests/GamutMapTests.cs new file mode 100644 index 0000000..484cd7b --- /dev/null +++ b/Unicolour.Tests/GamutMapTests.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class GamutMapTests +{ + private static GamutMap[] GamutMaps = [GamutMap.RgbClipping, GamutMap.OklchChromaReduction, GamutMap.WxyPurityReduction]; + + private static List<(double, double, double)> GamutInsideValues = + [ + (0, 0, 0), + (0.00000000001, 0, 0), + (0, 0.00000000001, 0), + (0, 0, 0.00000000001), + (1, 1, 0.99999999999), + (1, 0.99999999999, 1), + (0.99999999999, 1, 1), + (1, 1, 1) + ]; + + [Test, Combinatorial] + public void GamutInside( + [ValueSource(nameof(GamutInsideValues))] (double, double, double) rgb, + [ValueSource(nameof(GamutMaps))] GamutMap gamutMap) + { + var original = new Unicolour(ColourSpace.Rgb, rgb); + var gamutMapped = original.MapToRgbGamut(gamutMap); + Assert.That(original.IsInRgbGamut, Is.True); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.Triplet, 1e-16); + } + + private static List<(double, double, double)> GamutOutsideValues = + [ + (-0.00000000001, 0, 0), + (0, -0.00000000001, 0), + (0, 0, -0.00000000001), + (1, 1, 1.00000000001), + (1, 1.00000000001, 1), + (1.00000000001, 1, 1) + ]; + + [Test, Combinatorial] + public void GamutOutside( + [ValueSource(nameof(GamutOutsideValues))] (double, double, double) rgb, + [ValueSource(nameof(GamutMaps))] GamutMap gamutMap) + { + var original = new Unicolour(ColourSpace.Rgb, rgb); + var gamutMapped = original.MapToRgbGamut(gamutMap); + Assert.That(original.IsInRgbGamut, Is.False); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + } + + private static ColourTriplet[] RandomInGamutValues = RandomColours.RgbTriplets.Take(100).ToArray(); + private static ColourTriplet[] RandomOutGamutValues = RandomInGamutValues.Select(MakeOutOfGamut).ToArray(); + + [Test, Combinatorial] + public void RandomGamutInside( + [ValueSource(nameof(RandomInGamutValues))] ColourTriplet triplet, + [ValueSource(nameof(GamutMaps))] GamutMap gamutMap) + { + var original = new Unicolour(ColourSpace.Rgb, triplet.Tuple); + var gamutMapped = original.MapToRgbGamut(gamutMap); + Assert.That(gamutMapped.Rgb.IsInGamut, Is.True); + Assert.That(gamutMapped.Rgb.Triplet, Is.EqualTo(gamutMapped.Rgb.ConstrainedTriplet)); + TestUtils.AssertTriplet(gamutMapped.Rgb.Triplet, original.Rgb.Triplet, 0); + } + + [Test, Combinatorial] + public void RandomGamutOutside( + [ValueSource(nameof(RandomOutGamutValues))] ColourTriplet triplet, + [ValueSource(nameof(GamutMaps))] GamutMap gamutMap) + { + var original = new Unicolour(ColourSpace.Rgb, triplet.Tuple); + var gamutMapped = original.MapToRgbGamut(gamutMap); + Assert.That(original.IsInRgbGamut, Is.False); + Assert.That(gamutMapped.IsInRgbGamut, Is.True); + } + + // regression test of values calculated for https://unicolour.wacton.xyz/wxy-colour-space#%EF%B8%8F-gamut-mapping + [TestCase(GamutMap.RgbClipping, "#00ED00")] + [TestCase(GamutMap.OklchChromaReduction, "#00D367")] + [TestCase(GamutMap.WxyPurityReduction, "#04D77D")] + public void Green(GamutMap gamutMap, string expected) + { + var colour = new Unicolour(ColourSpace.Wxy, 530, 1, 0.5); + var gamutMapped = colour.MapToRgbGamut(gamutMap); + Assert.That(gamutMapped.Hex, Is.EqualTo(expected)); + } + + // regression test of values calculated for https://unicolour.wacton.xyz/wxy-colour-space#%EF%B8%8F-gamut-mapping + [TestCase(GamutMap.RgbClipping, "#FF00FF")] + [TestCase(GamutMap.OklchChromaReduction, "#FFC6F8")] + [TestCase(GamutMap.WxyPurityReduction, "#FF9AE6")] + public void Magenta(GamutMap gamutMap, string expected) + { + var colour = new Unicolour(ColourSpace.Wxy, -530, 1, 0.5); + var gamutMapped = colour.MapToRgbGamut(gamutMap); + Assert.That(gamutMapped.Hex, Is.EqualTo(expected)); + } + + [Test, Combinatorial] + public void ExtremeValues( + [ValueSource(typeof(TestUtils), nameof(TestUtils.ExtremeDoubles))] double value, + [ValueSource(nameof(GamutMaps))] GamutMap gamutMap) + { + var colourSpace = gamutMap switch + { + GamutMap.RgbClipping => ColourSpace.Rgb, + GamutMap.OklchChromaReduction => ColourSpace.Oklch, + GamutMap.WxyPurityReduction => ColourSpace.Wxy, + _ => throw new ArgumentOutOfRangeException(nameof(gamutMap), gamutMap, null) + }; + + // if extreme values are being used for the colour space in which mapping takes place + // mapping should still return an in-gamut colour, with the exception of NaNs + var original = new Unicolour(colourSpace, value, value, value); + var gamutMapped = original.MapToRgbGamut(gamutMap); + Assert.That(gamutMapped.IsInRgbGamut, gamutMapped.Rgb.UseAsNaN ? Is.False : Is.True); + } + + private static ColourTriplet MakeOutOfGamut(ColourTriplet triplet) => new(MakeOutOfGamut(triplet.First), MakeOutOfGamut(triplet.Second), MakeOutOfGamut(triplet.Third)); + private static double MakeOutOfGamut(double x) => Math.Sign(x) * (Math.Abs(x) + 1); +} \ No newline at end of file diff --git a/Unicolour.Tests/GreyscaleTests.cs b/Unicolour.Tests/GreyscaleTests.cs index 4209d99..e736983 100644 --- a/Unicolour.Tests/GreyscaleTests.cs +++ b/Unicolour.Tests/GreyscaleTests.cs @@ -434,11 +434,11 @@ public class GreyscaleTests [TestCase(180.0, 50, 99.99999999999, false)] public void Hct(double h, double c, double t, bool expected) => AssertUnicolour(new(ColourSpace.Hct, h, c, t), expected); - private static void AssertUnicolour(Unicolour unicolour, bool shouldBeGreyscale) + private static void AssertUnicolour(Unicolour colour, bool shouldBeGreyscale) { - var data = new ColourHeritageData(unicolour); - var initialRepresentation = unicolour.InitialRepresentation; - var initialColourSpace = unicolour.InitialColourSpace; + var data = new ColourHeritageData(colour); + var initialRepresentation = colour.InitialRepresentation; + var initialColourSpace = colour.InitialColourSpace; AssertInitialRepresentation(initialRepresentation, shouldBeGreyscale); if (!initialRepresentation.IsGreyscale) diff --git a/Unicolour.Tests/HuedTests.cs b/Unicolour.Tests/HuedTests.cs index 696c366..ed61179 100644 --- a/Unicolour.Tests/HuedTests.cs +++ b/Unicolour.Tests/HuedTests.cs @@ -55,10 +55,10 @@ public class HuedTests [Test] public void Hct() => AssertUnicolour(new(ColourSpace.Hct, 180, 0, 0), []); - private static void AssertUnicolour(Unicolour unicolour, List adjacentHuedSpaces) + private static void AssertUnicolour(Unicolour colour, List adjacentHuedSpaces) { - var data = new ColourHeritageData(unicolour); - var initial = unicolour.InitialRepresentation; + var data = new ColourHeritageData(colour); + var initial = colour.InitialRepresentation; Assert.That(initial.Heritage, Is.EqualTo(ColourHeritage.None)); Assert.That(initial.UseAsHued, Is.True); Assert.That(initial.UseAsGreyscale, Is.True); @@ -67,7 +67,7 @@ private static void AssertUnicolour(Unicolour unicolour, List adjac Assert.That(data.UseAsGreyscale(adjacentHuedSpaces), Has.All.True); // the first non-hued space to be converted to (e.g. RGB from HSB) will have hued heritage (since from HSB) - var otherSpaces = TestUtils.AllColourSpaces.Except(adjacentHuedSpaces.Concat(new[] { unicolour.InitialColourSpace })).ToList(); + var otherSpaces = TestUtils.AllColourSpaces.Except(adjacentHuedSpaces.Concat(new[] { colour.InitialColourSpace })).ToList(); Assert.That(data.Heritages(otherSpaces), Has.One.EqualTo(ColourHeritage.GreyscaleAndHued)); Assert.That(data.Heritages(otherSpaces), Has.Exactly(otherSpaces.Count - 1).EqualTo(ColourHeritage.Greyscale)); Assert.That(data.UseAsHued(otherSpaces), Has.All.False); diff --git a/Unicolour.Tests/IccConversionTests.cs b/Unicolour.Tests/IccConversionTests.cs index 8484e9a..7a6f7d9 100644 --- a/Unicolour.Tests/IccConversionTests.cs +++ b/Unicolour.Tests/IccConversionTests.cs @@ -111,48 +111,48 @@ public void NoReverseTransform() var iccConfig = new IccConfiguration(profile, Intent.Unspecified, "no reverse transform"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error, Is.Null); - Assert.That(unicolour.Icc.Values, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(profile.Header.DataColourSpace)); - Assert.That(unicolour.Icc.Error!.Contains("transform is not defined")); + Assert.That(colour.Icc.Values, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(profile.Header.DataColourSpace)); + Assert.That(colour.Icc.Error!.Contains("transform is not defined")); } [TestCaseSource(nameof(DeviceToUnicolourD65TestData))] - public void DeviceToUnicolourXyzD65(IccFile iccFile, Intent intent, double[] deviceValues) + public void DeviceTocolourXyzD65(IccFile iccFile, Intent intent, double[] deviceValues) { var profile = iccFile.GetProfile(); // device channels values are used to create D65 unicolour var iccD65Config = GetConfig(XyzConfiguration.D65, iccFile, intent); - var unicolourD65 = new Unicolour(iccD65Config, new Channels(deviceValues)); + var colourD65 = new Unicolour(iccD65Config, new Channels(deviceValues)); // unicolour converted to the ICC D50 white point var iccD50Config = GetConfig(Transform.XyzD50, iccFile, intent); - var unicolourD50 = unicolourD65.ConvertToConfiguration(iccD50Config); + var colourD50 = colourD65.ConvertToConfiguration(iccD50Config); // the XYZ values should be the same as calling the core ICC profile function var expectedXyzD50 = profile.Transform.ToXyz(deviceValues, intent); - Assert.That(unicolourD50.Xyz.Triplet.ToArray(), Is.EqualTo(expectedXyzD50).Within(1e-15)); + Assert.That(colourD50.Xyz.Triplet.ToArray(), Is.EqualTo(expectedXyzD50).Within(1e-15)); } [TestCaseSource(nameof(UnicolourD65ToDeviceTestData))] - public void UnicolourXyzD65ToDevice(IccFile iccFile, Intent intent, double[] xyzValues) + public void colourXyzD65ToDevice(IccFile iccFile, Intent intent, double[] xyzValues) { var profile = iccFile.GetProfile(); // XYZ values are used to create D50 unicolour var iccD50Config = GetConfig(Transform.XyzD50, iccFile, intent); - var unicolourD50 = new Unicolour(iccD50Config, ColourSpace.Xyz, xyzValues[0], xyzValues[1], xyzValues[2]); + var colourD50 = new Unicolour(iccD50Config, ColourSpace.Xyz, xyzValues[0], xyzValues[1], xyzValues[2]); // unicolour converted to the ICC D65 white point var iccD65Config = GetConfig(XyzConfiguration.D65, iccFile, intent); - var unicolourD65 = unicolourD50.ConvertToConfiguration(iccD65Config); + var colourD65 = colourD50.ConvertToConfiguration(iccD65Config); // the device channel values should be the same as calling the core ICC profile function var expectedDevice = profile.Transform.FromXyz(xyzValues, intent); - Assert.That(unicolourD65.Icc.Values, Is.EqualTo(expectedDevice).Within(1e-15)); + Assert.That(colourD65.Icc.Values, Is.EqualTo(expectedDevice).Within(1e-15)); } private static void AddTestData(IccFile iccFile) diff --git a/Unicolour.Tests/IccUnsupportedTests.cs b/Unicolour.Tests/IccUnsupportedTests.cs index 620c40a..0cc9d92 100644 --- a/Unicolour.Tests/IccUnsupportedTests.cs +++ b/Unicolour.Tests/IccUnsupportedTests.cs @@ -17,11 +17,11 @@ public void FileNullFromChannels() var iccConfig = new IccConfiguration(null, "not provided"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -32,11 +32,11 @@ public void FileNullFromRgb() var iccConfig = new IccConfiguration(null, "not provided"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -50,12 +50,12 @@ public void FileNotFoundFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not found"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.StartsWith("could not find file", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -69,12 +69,12 @@ public void FileNotFoundFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not found"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.StartsWith("could not find file", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -89,12 +89,12 @@ public void FileNotEnoughBytesFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not enough bytes"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.Contains("does not contain enough bytes", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -110,12 +110,12 @@ public void FileNotEnoughBytesFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not enough bytes"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.Contains("does not contain enough bytes", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -131,12 +131,12 @@ public void FileNotParseableFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not parseable"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.Contains("could not be parsed", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -152,12 +152,12 @@ public void FileNotParseableFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not parseable"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(Intent.Unspecified)); Assert.That(iccConfig.Error!.Contains("could not be parsed", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -172,12 +172,12 @@ public void ProfileNotSupportedHeaderFromChannels() var iccConfig = new IccConfiguration(profile, Intent.Unspecified, "not supported header"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -191,12 +191,12 @@ public void ProfileNotSupportedHeaderFromRgb() var iccConfig = new IccConfiguration(profile, Intent.Unspecified, "not supported header"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -215,12 +215,12 @@ public void ProfileNotSupportedTransformDToBFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not supported transform"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -239,12 +239,12 @@ public void ProfileNotSupportedTransformDToBFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not supported transform"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -263,12 +263,12 @@ public void ProfileNotSupportedTransformNoneFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not supported transform"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -287,12 +287,12 @@ public void ProfileNotSupportedTransformNoneFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "not supported transform"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("not supported", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); } [Test] @@ -309,12 +309,12 @@ public void ProfileBadSignatureFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad profile signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("signature is incorrect", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -332,12 +332,12 @@ public void ProfileBadSignatureFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad profile signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error!.Contains("signature is incorrect", StringComparison.CurrentCultureIgnoreCase)); - Assert.That(unicolour.Icc, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); - Assert.That(unicolour.Icc.Error, Is.Null); + Assert.That(colour.Icc, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Channels.UncalibratedCmyk)); + Assert.That(colour.Icc.Error, Is.Null); File.Delete(path); } @@ -355,12 +355,12 @@ public void LutBadSignatureFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad lut signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error, Is.Null); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); - Assert.That(unicolour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); + Assert.That(colour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); File.Delete(path); } @@ -378,12 +378,12 @@ public void LutBadSignatureFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad lut signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error, Is.Null); - Assert.That(unicolour.Icc.Values, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); - Assert.That(unicolour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); + Assert.That(colour.Icc.Values, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); + Assert.That(colour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); File.Delete(path); } @@ -401,12 +401,12 @@ public void CurveBadSignatureFromChannels() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad curve signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, channels); - TestUtils.AssertTriplet(unicolour, expected.Triplet, 0); + var colour = new Unicolour(config, channels); + TestUtils.AssertTriplet(colour, expected.Triplet, 0); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error, Is.Null); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); - Assert.That(unicolour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); + Assert.That(colour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); File.Delete(path); } @@ -424,12 +424,12 @@ public void CurveBadSignatureFromRgb() var iccConfig = new IccConfiguration(path, Intent.Unspecified, "bad curve signature"); var config = new Configuration(iccConfig: iccConfig); - var unicolour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); + var colour = new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple); Assert.That(iccConfig.Intent, Is.EqualTo(profile.Header.Intent)); Assert.That(iccConfig.Error, Is.Null); - Assert.That(unicolour.Icc.Values, Is.EqualTo(expected)); - Assert.That(unicolour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); - Assert.That(unicolour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); + Assert.That(colour.Icc.Values, Is.EqualTo(expected)); + Assert.That(colour.Icc.ColourSpace, Is.EqualTo(Signatures.Cmyk)); + Assert.That(colour.Icc.Error!.Contains(nameof(ArgumentOutOfRangeException))); File.Delete(path); } diff --git a/Unicolour.Tests/IlluminantTests.cs b/Unicolour.Tests/IlluminantTests.cs index c7a3ee0..6329893 100644 --- a/Unicolour.Tests/IlluminantTests.cs +++ b/Unicolour.Tests/IlluminantTests.cs @@ -76,7 +76,7 @@ public void CustomWhitePoint(Observer observer) var xyzConfig = new XyzConfiguration(illuminant, observer); var config = new Configuration(xyzConfig: xyzConfig); - var unicolour = new Unicolour(config, "#FFFFFF"); - TestUtils.AssertTriplet(unicolour, new(0.5, 0.5, 0.5), 0.00000000001); + var colour = new Unicolour(config, "#FFFFFF"); + TestUtils.AssertTriplet(colour, new(0.5, 0.5, 0.5), 0.00000000001); } } \ No newline at end of file diff --git a/Unicolour.Tests/ImaginaryTests.cs b/Unicolour.Tests/ImaginaryTests.cs index 656ad1f..7e701cb 100644 --- a/Unicolour.Tests/ImaginaryTests.cs +++ b/Unicolour.Tests/ImaginaryTests.cs @@ -10,15 +10,15 @@ public class ImaginaryTests [Test] public void RgbGamut([Values(0, 255)] int r, [Values(0, 255)] int g, [Values(0, 255)] int b) { - var unicolour = new Unicolour(ColourSpace.Rgb255, r, g, b); - Assert.That(unicolour.IsImaginary, Is.False); + var colour = new Unicolour(ColourSpace.Rgb255, r, g, b); + Assert.That(colour.IsImaginary, Is.False); } [Test] public void Greyscale([Range(0, 1, 0.1)] double value) { - var unicolour = new Unicolour(ColourSpace.Rgb, value, value, value); - Assert.That(unicolour.IsImaginary, Is.False); + var colour = new Unicolour(ColourSpace.Rgb, value, value, value); + Assert.That(colour.IsImaginary, Is.False); } private static readonly Dictionary<(Illuminant illuminant, Observer observer), Configuration> Configurations = new() @@ -39,8 +39,8 @@ public void Monochromatic( var observer = TestUtils.Observers[observerName]; var config = Configurations[(illuminant, observer)]; - var unicolour = new Unicolour(config, Spd.Monochromatic(wavelength)); - Assert.That(unicolour.IsImaginary, Is.False); + var colour = new Unicolour(config, Spd.Monochromatic(wavelength)); + Assert.That(colour.IsImaginary, Is.False); } private const double Offset = 0.0000001; @@ -75,8 +75,8 @@ public void BoundaryEdge(Edge edge, double xOffset, double yOffset, bool expecte var monochromatic = new Unicolour(Spd.Monochromatic(wavelength)); var chromaticity = monochromatic.Chromaticity; var offsetChromaticity = new Chromaticity(chromaticity.X + xOffset, chromaticity.Y + yOffset); - var unicolour = new Unicolour(offsetChromaticity); - Assert.That(unicolour.IsImaginary, Is.EqualTo(expectedImaginary)); + var colour = new Unicolour(offsetChromaticity); + Assert.That(colour.IsImaginary, Is.EqualTo(expectedImaginary)); } public enum Edge { Bottom, Left, Top, Right } diff --git a/Unicolour.Tests/InvalidColourSpaceTests.cs b/Unicolour.Tests/InvalidColourSpaceTests.cs index a2cda5c..dd4861e 100644 --- a/Unicolour.Tests/InvalidColourSpaceTests.cs +++ b/Unicolour.Tests/InvalidColourSpaceTests.cs @@ -9,12 +9,12 @@ public class InvalidColourSpaceTests { private const ColourSpace BadColourSpace = (ColourSpace)int.MaxValue; - private Unicolour? unicolour; + private Unicolour? colour; [SetUp] public void Init() { - unicolour = new Unicolour(ColourSpace.Xyz, 0.1, 0.2, 0.3); + colour = new Unicolour(ColourSpace.Xyz, 0.1, 0.2, 0.3); } [Test] @@ -36,18 +36,18 @@ public void InvalidUnicolourProperty() [Test] public void InvalidInterpolationParameter() { - var unicolour1 = new Unicolour(ColourSpace.Rgb, 0.1, 0.2, 0.3); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0.7, 0.8, 0.9); - Assert.Throws(() => Interpolation.Mix(unicolour1, unicolour2, BadColourSpace, 0.5, HueSpan.Shorter, true)); + var colour1 = new Unicolour(ColourSpace.Rgb, 0.1, 0.2, 0.3); + var colour2 = new Unicolour(ColourSpace.Rgb, 0.7, 0.8, 0.9); + Assert.Throws(() => Interpolation.Mix(colour1, colour2, BadColourSpace, 0.5, HueSpan.Shorter, true)); } private static void AssertDoesNotThrow(Action action) => Assert.DoesNotThrow(action.Invoke); private static void AssertThrows(Action action) => Assert.Throws(ExceptionConstraint(), action.Invoke); private static ExactTypeConstraint ExceptionConstraint() => Is.TypeOf().And.InnerException.TypeOf(); - private void InvokePrivateMethod(string name, params object[] args) => GetPrivateMethod(name).Invoke(unicolour, args); - private void InvokePrivateGenericMethod(string name, Type genericType, params object[] args) => GetPrivateMethod(name, genericType).Invoke(unicolour, args); - private void SetPrivateField(string name, object value) => GetPrivateField(name).SetValue(unicolour, value); + private void InvokePrivateMethod(string name, params object[] args) => GetPrivateMethod(name).Invoke(colour, args); + private void InvokePrivateGenericMethod(string name, Type genericType, params object[] args) => GetPrivateMethod(name, genericType).Invoke(colour, args); + private void SetPrivateField(string name, object value) => GetPrivateField(name).SetValue(colour, value); private static MethodInfo GetPrivateMethod(string name) => typeof(Unicolour).GetMethod(name, BindingFlags.NonPublic | BindingFlags.Instance)!; private static MethodInfo GetPrivateMethod(string name, Type genericType) => GetPrivateMethod(name).MakeGenericMethod(genericType); diff --git a/Unicolour.Tests/InvalidCvdTests.cs b/Unicolour.Tests/InvalidCvdTests.cs new file mode 100644 index 0000000..5a1d816 --- /dev/null +++ b/Unicolour.Tests/InvalidCvdTests.cs @@ -0,0 +1,16 @@ +using System; +using NUnit.Framework; + +namespace Wacton.Unicolour.Tests; + +public class InvalidCvdTests +{ + private const Cvd BadCvd = (Cvd)int.MaxValue; + + [Test] + public void InvalidParameter() + { + var colour = new Unicolour(ColourSpace.Rgb, 0.1, 0.2, 0.3); + Assert.Throws(() => VisionDeficiency.Simulate(BadCvd, colour)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/InvalidDeltaETests.cs b/Unicolour.Tests/InvalidDeltaETests.cs index c7372a4..d0c9902 100644 --- a/Unicolour.Tests/InvalidDeltaETests.cs +++ b/Unicolour.Tests/InvalidDeltaETests.cs @@ -11,8 +11,8 @@ public class InvalidDeltaETests [Test] public void InvalidConstructor() { - var unicolour1 = RandomColours.UnicolourFrom(ColourSpace.Rgb); - var unicolour2 = RandomColours.UnicolourFrom(ColourSpace.Rgb); - Assert.Throws(() => unicolour1.Difference(unicolour2, BadDeltaE)); + var colour1 = RandomColours.UnicolourFrom(ColourSpace.Rgb); + var colour2 = RandomColours.UnicolourFrom(ColourSpace.Rgb); + Assert.Throws(() => colour1.Difference(colour2, BadDeltaE)); } } \ No newline at end of file diff --git a/Unicolour.Tests/InvalidGamutMapTests.cs b/Unicolour.Tests/InvalidGamutMapTests.cs new file mode 100644 index 0000000..560e01f --- /dev/null +++ b/Unicolour.Tests/InvalidGamutMapTests.cs @@ -0,0 +1,16 @@ +using System; +using NUnit.Framework; + +namespace Wacton.Unicolour.Tests; + +public class InvalidGamutMapTests +{ + private const GamutMap BadGamutMap = (GamutMap)int.MaxValue; + + [Test] + public void InvalidParameter() + { + var colour = new Unicolour(ColourSpace.Rgb, 1.1, 2.2, 3.3); + Assert.Throws(() => GamutMapping.ToRgbGamut(colour, BadGamutMap)); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/InvalidHueSpanTests.cs b/Unicolour.Tests/InvalidHueSpanTests.cs index 9ac64db..0e2445d 100644 --- a/Unicolour.Tests/InvalidHueSpanTests.cs +++ b/Unicolour.Tests/InvalidHueSpanTests.cs @@ -10,8 +10,8 @@ public class InvalidHueSpanTests [Test] public void InvalidParameter() { - var unicolour1 = new Unicolour(ColourSpace.Rgb, 0.1, 0.2, 0.3); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0.7, 0.8, 0.9); - Assert.Throws(() => Interpolation.Mix(unicolour1, unicolour2, ColourSpace.Hsb, 0.5, BadHueSpan, true)); + var colour1 = new Unicolour(ColourSpace.Rgb, 0.1, 0.2, 0.3); + var colour2 = new Unicolour(ColourSpace.Rgb, 0.7, 0.8, 0.9); + Assert.Throws(() => Interpolation.Mix(colour1, colour2, ColourSpace.Hsb, 0.5, BadHueSpan, true)); } } \ No newline at end of file diff --git a/Unicolour.Tests/KnownCmykTests.cs b/Unicolour.Tests/KnownCmykTests.cs index 7d6a8b7..e3da585 100644 --- a/Unicolour.Tests/KnownCmykTests.cs +++ b/Unicolour.Tests/KnownCmykTests.cs @@ -100,13 +100,13 @@ private static void AssertCmy(Unicolour original, double[] expected, double tole Assert.That(actual[1], Is.EqualTo(expected[1]).Within(tolerance)); Assert.That(actual[2], Is.EqualTo(expected[2]).Within(tolerance)); - var unicolour = Cmy.ToUnicolour(actual); - TestUtils.AssertTriplet(unicolour, original.Rgb.Triplet, tolerance); + var colour = Cmy.ToUnicolour(actual); + TestUtils.AssertTriplet(colour, original.Rgb.Triplet, tolerance); } - private static void AssertCmyk(Unicolour unicolour, double[] expected, double tolerance = Tolerance) + private static void AssertCmyk(Unicolour colour, double[] expected, double tolerance = Tolerance) { - var actual = Channels.UncalibratedFromRgb(unicolour.Rgb).Values; + var actual = Channels.UncalibratedFromRgb(colour.Rgb).Values; Assert.That(actual[0], Is.EqualTo(expected[0]).Within(tolerance)); Assert.That(actual[1], Is.EqualTo(expected[1]).Within(tolerance)); Assert.That(actual[2], Is.EqualTo(expected[2]).Within(tolerance)); diff --git a/Unicolour.Tests/KnownHsluvTests.cs b/Unicolour.Tests/KnownHsluvTests.cs index 11b770f..3f6fa98 100644 --- a/Unicolour.Tests/KnownHsluvTests.cs +++ b/Unicolour.Tests/KnownHsluvTests.cs @@ -52,18 +52,18 @@ public void White() public void SnapshotTestColour(TestColour testColour) { var hex = testColour.Hex!; - var unicolour = new Unicolour(hex); - var info = $"{hex} \n-> RGB: {unicolour.Rgb} \n-> XYZ: {unicolour.Xyz} \n-> LUV: {unicolour.Luv} \n-> LCH: {unicolour.Lchuv}"; + var colour = new Unicolour(hex); + var info = $"{hex} \n-> RGB: {colour.Rgb} \n-> XYZ: {colour.Xyz} \n-> LUV: {colour.Luv} \n-> LCH: {colour.Lchuv}"; ColourTriplet HandleNoHue(ColourTriplet triplet, bool hasHue) => hasHue ? triplet : triplet.WithHueOverride(0); - var lchuv = HandleNoHue(unicolour.Lchuv.Triplet, unicolour.Lchuv.UseAsHued); - var hsluv = HandleNoHue(unicolour.Hsluv.Triplet, unicolour.Hsluv.UseAsHued); - var hpluv = HandleNoHue(unicolour.Hpluv.Triplet, unicolour.Hpluv.UseAsHued); + var lchuv = HandleNoHue(colour.Lchuv.Triplet, colour.Lchuv.UseAsHued); + var hsluv = HandleNoHue(colour.Hsluv.Triplet, colour.Hsluv.UseAsHued); + var hpluv = HandleNoHue(colour.Hpluv.Triplet, colour.Hpluv.UseAsHued); // accuracy drops off when saturation goes beyond 100% (mostly at 1,500%+), so be slightly more tolerant for larger values - 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(colour.Rgb.Triplet, testColour.Rgb!, 0.00000000001, info); + AssertSnapshot(colour.Xyz.Triplet, testColour.Xyz!, 0.0005, info); + AssertSnapshot(colour.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); diff --git a/Unicolour.Tests/KnownIccTests.cs b/Unicolour.Tests/KnownIccTests.cs index c2bf8ec..436aad0 100644 --- a/Unicolour.Tests/KnownIccTests.cs +++ b/Unicolour.Tests/KnownIccTests.cs @@ -17,32 +17,32 @@ public class KnownIccTests public void Fogra39Rose() { var cmyk = new Channels(0.0, 0.7, 0.2, 0.0); - var unicolour = new Unicolour(GetConfig(IccFile.Fogra39), cmyk); - TestUtils.AssertTriplet(unicolour, new(63.673303, 51.576902, 5.811058), Tolerance); + var colour = new Unicolour(GetConfig(IccFile.Fogra39), cmyk); + TestUtils.AssertTriplet(colour, new(63.673303, 51.576902, 5.811058), Tolerance); } [Test] // https://www.w3.org/TR/css-color-5/#ex-swop5v2 public void Swop2006Rose() { var cmyk = new Channels(0.0, 0.7, 0.2, 0.0); - var unicolour = new Unicolour(GetConfig(IccFile.Swop2006), cmyk); - TestUtils.AssertTriplet(unicolour, new(64.965217, 52.119710, 5.406966), Tolerance); + var colour = new Unicolour(GetConfig(IccFile.Swop2006), cmyk); + TestUtils.AssertTriplet(colour, new(64.965217, 52.119710, 5.406966), Tolerance); } [Test] // https://www.w3.org/TR/css-color-5/#ex-device-cmyk-naive public void NoProfileFirebrick() { var cmyk = new Channels(0.0, 0.81, 0.81, 0.3); - var unicolour = new Unicolour(cmyk); - TestUtils.AssertTriplet(unicolour, new(178, 34, 34), 0); + var colour = new Unicolour(cmyk); + TestUtils.AssertTriplet(colour, new(178, 34, 34), 0); } [Test] // https://www.w3.org/TR/css-color-5/#ex-device-cmyk-colprof public void Fogra39Firebrick() { var cmyk = new Channels(0.0, 0.81, 0.81, 0.3); - var unicolour = new Unicolour(GetConfig(IccFile.Fogra39), cmyk); - TestUtils.AssertTriplet(unicolour, new(45.060, 45.477, 35.459), Tolerance); + var colour = new Unicolour(GetConfig(IccFile.Fogra39), cmyk); + TestUtils.AssertTriplet(colour, new(45.060, 45.477, 35.459), Tolerance); } // TODO: find out why these values don't match - is example wrong? @@ -56,8 +56,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.9, 0.0, 9.0, 0.0); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(56.596645, -58.995875, 28.072154), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(56.596645, -58.995875, 28.072154), Tolerance); // } // TODO: these values don't match well enough to be good tests - has the profile itself changed since `2020_13.003_FOGRA55beta_CL_Profile.icc`? @@ -71,8 +71,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.183596, 0.464444, 0.461729, 0.612490, 0.156903, 0.000000, 0.000000); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.458702, 0.320071, 0.263813), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.458702, 0.320071, 0.263813), Tolerance); // } // // [Test] // https://www.w3.org/TR/css-color-5/#ex-fogra55beta-7color @@ -84,8 +84,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.070804, 0.334971, 0.321802, 0.215606, 0.103107, 0.000000, 0.000000); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.780170, 0.581957, 0.507737), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.780170, 0.581957, 0.507737), Tolerance); // } // // [Test] // https://www.w3.org/TR/css-color-5/#ex-fogra55beta-7color @@ -97,8 +97,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.572088, 0.229346, 0.081708, 0.282044, 0.000000, 0.000000, 0.168260); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.358614, 0.480665, 0.616556), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.358614, 0.480665, 0.616556), Tolerance); // } // // [Test] // https://www.w3.org/TR/css-color-5/#ex-fogra55beta-7color @@ -110,8 +110,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.314566, 0.145687, 0.661941, 0.582879, 0.000000, 0.234362, 0.000000); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.349582, 0.423446, 0.254209), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.349582, 0.423446, 0.254209), Tolerance); // } // // [Test] // https://www.w3.org/TR/css-color-5/#ex-fogra55beta-7color @@ -123,8 +123,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.375515, 0.259934, 0.034849, 0.107161, 0.000000, 0.000000, 0.308200); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.512952, 0.504131, 0.689186), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.512952, 0.504131, 0.689186), Tolerance); // } // // [Test] // https://www.w3.org/TR/css-color-5/#ex-fogra55beta-7color @@ -136,8 +136,8 @@ public void Fogra39Firebrick() // ); // // var cmyk = new Channels(0.397575, 0.010047, 0.223682, 0.031140, 0.000000, 0.317066, 0.000000); - // var unicolour = new Unicolour(config, cmyk); - // TestUtils.AssertTriplet(unicolour, new(0.368792, 0.743685, 0.674749), Tolerance); + // var colour = new Unicolour(config, cmyk); + // TestUtils.AssertTriplet(colour, new(0.368792, 0.743685, 0.674749), Tolerance); // } private static Configuration GetConfig(IccFile iccFile) diff --git a/Unicolour.Tests/KnownSpdTests.cs b/Unicolour.Tests/KnownSpdTests.cs index fc5063d..a673f26 100644 --- a/Unicolour.Tests/KnownSpdTests.cs +++ b/Unicolour.Tests/KnownSpdTests.cs @@ -17,8 +17,8 @@ public class KnownSpdTests public void Constant() { var spd = new Spd(StartWavelength, interval: 1, Wavelengths.Select(_ => 8.0).ToArray()); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.3333, 0.3333, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.3333, 0.3333, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -26,8 +26,8 @@ public void Constant() public void LinearTowardsRed() { var spd = new Spd(StartWavelength, interval: 1, Wavelengths.Select(wavelength => (double)(wavelength - StartWavelength)).ToArray()); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.4299, 0.4040, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.4299, 0.4040, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -35,8 +35,8 @@ public void LinearTowardsRed() public void LinearTowardsBlue() { var spd = new Spd(StartWavelength, interval: 1, Wavelengths.Select(wavelength => (double)(EndWavelength - wavelength)).ToArray()); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.2762, 0.2916, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.2762, 0.2916, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -44,8 +44,8 @@ public void LinearTowardsBlue() public void ExponentialTowardsRed() { var spd = new Spd(StartWavelength, interval: 1, Wavelengths.Select(wavelength => Math.Pow(wavelength - StartWavelength, 3)).ToArray()); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.5542, 0.4128, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.5542, 0.4128, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -53,8 +53,8 @@ public void ExponentialTowardsRed() public void ExponentialTowardsBlue() { var spd = new Spd(StartWavelength, interval: 1, Wavelengths.Select(wavelength => Math.Pow(EndWavelength - wavelength, 3)).ToArray()); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.2013, 0.2004, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.2013, 0.2004, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -62,8 +62,8 @@ public void ExponentialTowardsBlue() public void YellowSpike() { var spd = Spd.Monochromatic(580); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(0.5125, 0.4866, 1.0000), 0.00005); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(0.5125, 0.4866, 1.0000), 0.00005); AssertWhitePoint(spd); } @@ -73,9 +73,9 @@ public void NoPower() // an empty SPD is equivalent to all wavelengths having 0 power, and treated as not processable // as opposed to some kind of "black" illumination (illuminating with the absence of any light...) var spd = new Spd(start: 580, interval: 1); - var unicolour = new Unicolour(spd); - TestUtils.AssertTriplet(unicolour, new(double.NaN, double.NaN, double.NaN), 0); - TestUtils.AssertTriplet(unicolour, new(double.NaN, double.NaN, double.NaN), 0); + var colour = new Unicolour(spd); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); } private static void AssertWhitePoint(Spd spd) @@ -84,7 +84,7 @@ private static void AssertWhitePoint(Spd spd) // if the SPD is set as the white point, RGB will be white var xyzConfig = new XyzConfiguration(new Illuminant(spd), Observer.Degree2); var config = new Configuration(xyzConfig: xyzConfig); - var unicolour = new Unicolour(config, spd); - TestUtils.AssertTriplet(unicolour, new(1.0, 1.0, 1.0), 0.00000000001); + var colour = new Unicolour(config, spd); + TestUtils.AssertTriplet(colour, new(1.0, 1.0, 1.0), 0.00000000001); } } \ No newline at end of file diff --git a/Unicolour.Tests/KnownTemperatureTests.cs b/Unicolour.Tests/KnownTemperatureTests.cs index 476a551..564d9dd 100644 --- a/Unicolour.Tests/KnownTemperatureTests.cs +++ b/Unicolour.Tests/KnownTemperatureTests.cs @@ -33,8 +33,8 @@ public void Yellow() [Test] // matches the behaviour of python-based "coloraide" (https://facelessuser.github.io/coloraide/temperature/#duv) public void DisplayP3() { - var unicolour = new Unicolour(new Configuration(rgbConfig: RgbConfiguration.DisplayP3), 1200); - TestUtils.AssertTriplet(unicolour, new(1.6804, 0.62798, 0.05495), 0.005); + var colour = new Unicolour(new Configuration(rgbConfig: RgbConfiguration.DisplayP3), 1200); + TestUtils.AssertTriplet(colour, new(1.6804, 0.62798, 0.05495), 0.005); } [Test] // matches the behaviour of python-based "colour-science/colour" (https://github.com/colour-science/colour/blob/d7d79c745b15b97e7e5b8ccf50e3f676c762c770/colour/temperature/ohno2013.py#L144) @@ -64,8 +64,8 @@ public void IlluminantCct(string illuminantName, double kelvins) { var illuminant = TestUtils.Illuminants[illuminantName]; var whitePoint = illuminant.GetWhitePoint(Observer.Degree2); - var unicolour = new Unicolour(ColourSpace.Xyz, whitePoint.AsXyzMatrix().ToTriplet().Tuple); - var temperature = unicolour.Temperature; + var colour = new Unicolour(ColourSpace.Xyz, whitePoint.AsXyzMatrix().ToTriplet().Tuple); + var temperature = colour.Temperature; Assert.That(temperature.Cct, Is.EqualTo(kelvins).Within(1.5), temperature.ToString); } @@ -109,10 +109,10 @@ public void Luminance(double luminance) { var temperatureD65 = new Temperature(6500 * 1.4388 / 1.4380, 0.0032); - var unicolour = new Unicolour(Configuration.Default, temperatureD65, luminance); - TestUtils.AssertTriplet(unicolour, new(0.3127, 0.3290, luminance), 0.00005); - TestUtils.AssertTriplet(unicolour, new(luminance, luminance, luminance), 0.0005); - Assert.That(unicolour.Xyy.Luminance, Is.EqualTo(luminance)); - Assert.That(unicolour.Temperature, Is.EqualTo(temperatureD65)); + var colour = new Unicolour(Configuration.Default, temperatureD65, luminance); + TestUtils.AssertTriplet(colour, new(0.3127, 0.3290, luminance), 0.00005); + TestUtils.AssertTriplet(colour, new(luminance, luminance, luminance), 0.0005); + Assert.That(colour.Xyy.Luminance, Is.EqualTo(luminance)); + Assert.That(colour.Temperature, Is.EqualTo(temperatureD65)); } } \ No newline at end of file diff --git a/Unicolour.Tests/KnownWxyTests.cs b/Unicolour.Tests/KnownWxyTests.cs index 5867ea8..182e8c4 100644 --- a/Unicolour.Tests/KnownWxyTests.cs +++ b/Unicolour.Tests/KnownWxyTests.cs @@ -73,7 +73,7 @@ public void Grey() [Test] public void UltravioletPositive() { - var violet = new Unicolour(ColourSpace.Wxy, Spectral.MinWavelength, 0.5, 0.5); + var violet = new Unicolour(ColourSpace.Wxy, SpectralBoundary.MinWavelength, 0.5, 0.5); var ultraviolet = new Unicolour(ColourSpace.Wxy, 300, 0.5, 0.5); TestUtils.AssertTriplet(ultraviolet.Xyy.Triplet, violet.Xyy.Triplet, Tolerance); } @@ -81,7 +81,7 @@ public void UltravioletPositive() [Test] public void UltravioletNegative() { - var violet = new Unicolour(ColourSpace.Wxy, Spectral.MinWavelength, 0.5, 0.5); + var violet = new Unicolour(ColourSpace.Wxy, SpectralBoundary.MinWavelength, 0.5, 0.5); var ultraviolet = new Unicolour(ColourSpace.Wxy, -600, 0.5, 0.5); TestUtils.AssertTriplet(ultraviolet.Xyy.Triplet, violet.Xyy.Triplet, Tolerance); } @@ -89,7 +89,7 @@ public void UltravioletNegative() [Test] public void UltravioletPositiveComplementary() { - var complementary = XyzConfiguration.D65.Spectral.MinNegativeWavelength; + var complementary = XyzConfiguration.D65.SpectralBoundary.MinNegativeWavelength; var violet = new Unicolour(ColourSpace.Wxy, complementary, 0.5, 0.5); var ultraviolet = new Unicolour(ColourSpace.Wxy, 300, 0.5, 0.5); TestUtils.AssertTriplet(ultraviolet.Xyy.Triplet, violet.Xyy.Triplet, Tolerance); @@ -98,7 +98,7 @@ public void UltravioletPositiveComplementary() [Test] public void UltravioletNegativeComplementary() { - var complementary = XyzConfiguration.D65.Spectral.MinNegativeWavelength; + var complementary = XyzConfiguration.D65.SpectralBoundary.MinNegativeWavelength; var violet = new Unicolour(ColourSpace.Wxy, complementary, 0.5, 0.5); var ultraviolet = new Unicolour(ColourSpace.Wxy, -600, 0.5, 0.5); TestUtils.AssertTriplet(ultraviolet.Xyy.Triplet, violet.Xyy.Triplet, Tolerance); @@ -107,7 +107,7 @@ public void UltravioletNegativeComplementary() [Test] public void InfraredPositive() { - var red = new Unicolour(ColourSpace.Wxy, Spectral.MaxWavelength, 0.5, 0.5); + var red = new Unicolour(ColourSpace.Wxy, SpectralBoundary.MaxWavelength, 0.5, 0.5); var infrared = new Unicolour(ColourSpace.Wxy, 750, 0.5, 0.5); TestUtils.AssertTriplet(infrared.Xyy.Triplet, red.Xyy.Triplet, Tolerance); } @@ -115,7 +115,7 @@ public void InfraredPositive() [Test] public void InfraredNegative() { - var red = new Unicolour(ColourSpace.Wxy, Spectral.MaxWavelength, 0.5, 0.5); + var red = new Unicolour(ColourSpace.Wxy, SpectralBoundary.MaxWavelength, 0.5, 0.5); var infrared = new Unicolour(ColourSpace.Wxy, -450, 0.5, 0.5); TestUtils.AssertTriplet(infrared.Xyy.Triplet, red.Xyy.Triplet, Tolerance); } @@ -123,7 +123,7 @@ public void InfraredNegative() [Test] public void InfraredPositiveComplementary() { - var complementary = XyzConfiguration.D65.Spectral.MaxNegativeWavelength; + var complementary = XyzConfiguration.D65.SpectralBoundary.MaxNegativeWavelength; var red = new Unicolour(ColourSpace.Wxy, complementary, 0.5, 0.5); var infrared = new Unicolour(ColourSpace.Wxy, 750, 0.5, 0.5); TestUtils.AssertTriplet(infrared.Xyy.Triplet, red.Xyy.Triplet, Tolerance); @@ -132,7 +132,7 @@ public void InfraredPositiveComplementary() [Test] public void InfraredNegativeComplementary() { - var complementary = XyzConfiguration.D65.Spectral.MaxNegativeWavelength; + var complementary = XyzConfiguration.D65.SpectralBoundary.MaxNegativeWavelength; var red = new Unicolour(ColourSpace.Wxy, complementary, 0.5, 0.5); var infrared = new Unicolour(ColourSpace.Wxy, -450, 0.5, 0.5); TestUtils.AssertTriplet(infrared.Xyy.Triplet, red.Xyy.Triplet, Tolerance); diff --git a/Unicolour.Tests/KnownXyyTests.cs b/Unicolour.Tests/KnownXyyTests.cs index c7a01e8..cb0dc30 100644 --- a/Unicolour.Tests/KnownXyyTests.cs +++ b/Unicolour.Tests/KnownXyyTests.cs @@ -36,8 +36,8 @@ public void Blue(string illuminantName, double x, double y, double expectedZ) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333, 0.000000)] public void Black(string illuminantName, double x, double y, double luminance) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0, 0, 0); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0, 0, 0); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, luminance), Tolerance); } [TestCase(nameof(Illuminant.D65), 0.312727, 0.329023, 0.000001)] @@ -45,8 +45,8 @@ public void Black(string illuminantName, double x, double y, double luminance) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333, 0.000001)] public void NearBlack(string illuminantName, double x, double y, double luminance) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0.00001, 0.00001, 0.00001); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0.00001, 0.00001, 0.00001); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, luminance), Tolerance); } [TestCase(nameof(Illuminant.D65), 0.312727, 0.329023, 0.214041)] @@ -54,8 +54,8 @@ public void NearBlack(string illuminantName, double x, double y, double luminanc [TestCase(nameof(Illuminant.E), 0.333333, 0.333333, 0.214041)] public void Grey(string illuminantName, double x, double y, double luminance) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0.5, 0.5, 0.5); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 0.5, 0.5, 0.5); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, luminance), Tolerance); } [TestCase(nameof(Illuminant.D65), 0.312727, 0.329023, 1.000000)] @@ -63,8 +63,8 @@ public void Grey(string illuminantName, double x, double y, double luminance) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333, 1.000000)] public void White(string illuminantName, double x, double y, double luminance) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 1, 1, 1); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, luminance), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Rgb, 1, 1, 1); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, luminance), Tolerance); } [TestCase(-0.00000000001)] @@ -72,8 +72,8 @@ public void White(string illuminantName, double x, double y, double luminance) [TestCase(0.00000000001)] public void ChromaticityY(double chromaticityY) { - var unicolour = new Unicolour(ColourSpace.Xyy, 0.5, chromaticityY, 1); - var xyz = unicolour.Xyz; + var colour = new Unicolour(ColourSpace.Xyy, 0.5, chromaticityY, 1); + var xyz = colour.Xyz; var useZero = chromaticityY <= 0; Assert.That(xyz.X, useZero ? Is.EqualTo(0) : Is.GreaterThan(0)); diff --git a/Unicolour.Tests/KnownXyzTests.cs b/Unicolour.Tests/KnownXyzTests.cs index 9d198ad..54e350c 100644 --- a/Unicolour.Tests/KnownXyzTests.cs +++ b/Unicolour.Tests/KnownXyzTests.cs @@ -52,8 +52,8 @@ public void White(string illuminantName, double x, double y, double z) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333)] public void BlackChromaticity(string illuminantName, double x, double y) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 0.0, 0.0, 0.0); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 0.0), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 0.0, 0.0, 0.0); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, 0.0), Tolerance); } [TestCase(nameof(Illuminant.D65), 0.333333, 0.333333)] @@ -61,8 +61,8 @@ public void BlackChromaticity(string illuminantName, double x, double y) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333)] public void GreyChromaticity(string illuminantName, double x, double y) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 0.5, 0.5, 0.5); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 0.5), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 0.5, 0.5, 0.5); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, 0.5), Tolerance); } [TestCase(nameof(Illuminant.D65), 0.333333, 0.333333)] @@ -70,7 +70,7 @@ public void GreyChromaticity(string illuminantName, double x, double y) [TestCase(nameof(Illuminant.E), 0.333333, 0.333333)] public void WhiteChromaticity(string illuminantName, double x, double y) { - var unicolour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 1, 1, 1); - TestUtils.AssertTriplet(unicolour.Xyy.Triplet, new(x, y, 1.0), Tolerance); + var colour = new Unicolour(ConfigUtils.GetConfigWithStandardRgb(illuminantName), ColourSpace.Xyz, 1, 1, 1); + TestUtils.AssertTriplet(colour.Xyy.Triplet, new(x, y, 1.0), Tolerance); } } \ No newline at end of file diff --git a/Unicolour.Tests/LazyBackingFieldsTests.cs b/Unicolour.Tests/LazyBackingFieldsTests.cs index efc64f0..37af4ab 100644 --- a/Unicolour.Tests/LazyBackingFieldsTests.cs +++ b/Unicolour.Tests/LazyBackingFieldsTests.cs @@ -18,8 +18,8 @@ public void InitialUnicolour(ColourSpace colourSpace) { // no backing fields are evaluated when a unicolour is created // not even the backing field for the initial colour space - var unicolour = RandomColours.UnicolourFrom(colourSpace); - AssertBackingFieldsNotEvaluated(unicolour, ColourSpacesWithBackingFields); + var colour = RandomColours.UnicolourFrom(colourSpace); + AssertBackingFieldsNotEvaluated(colour, ColourSpacesWithBackingFields); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] @@ -27,133 +27,133 @@ public void AfterEquality(ColourSpace colourSpace) { // the initial colour space backing field is not required for equality // which uses the `InitialColourRepresentation` object - var unicolour = RandomColours.UnicolourFrom(colourSpace); + var colour = RandomColours.UnicolourFrom(colourSpace); var other = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Equals(other); - AssertBackingFieldsNotEvaluated(unicolour, ColourSpacesWithBackingFields); + _ = colour.Equals(other); + AssertBackingFieldsNotEvaluated(colour, ColourSpacesWithBackingFields); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterIcc(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); - _ = unicolour.Icc; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyz); + var colour = RandomColours.UnicolourFrom(colourSpace, TestUtils.DefaultFogra39Config); + _ = colour.Icc; + AssertBackingFieldEvaluated(colour, ColourSpace.Xyz); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterIccUncalibrated(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Icc; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Rgb); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.Icc; + AssertBackingFieldEvaluated(colour, ColourSpace.Rgb); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterHex(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Hex; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Rgb); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.Hex; + AssertBackingFieldEvaluated(colour, ColourSpace.Rgb); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] - public void AfterIsInDisplayGamut(ColourSpace colourSpace) + public void AfterIsInRgbGamut(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.IsInDisplayGamut; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Rgb); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.IsInRgbGamut; + AssertBackingFieldEvaluated(colour, ColourSpace.Rgb); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterDescription(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Description; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Hsl); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.Description; + AssertBackingFieldEvaluated(colour, ColourSpace.Hsl); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterChromaticity(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Chromaticity; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyy); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.Chromaticity; + AssertBackingFieldEvaluated(colour, ColourSpace.Xyy); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterIsImaginary(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.IsImaginary; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyy); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.IsImaginary; + AssertBackingFieldEvaluated(colour, ColourSpace.Xyy); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterRelativeLuminance(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.RelativeLuminance; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyz); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.RelativeLuminance; + AssertBackingFieldEvaluated(colour, ColourSpace.Xyz); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterTemperature(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.Temperature; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyy); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.Temperature; + AssertBackingFieldEvaluated(colour, ColourSpace.Xyy); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterDominantWavelength(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.DominantWavelength; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Wxy); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.DominantWavelength; + AssertBackingFieldEvaluated(colour, ColourSpace.Wxy); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterExcitationPurity(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.ExcitationPurity; - AssertBackingFieldEvaluated(unicolour, ColourSpace.Wxy); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.ExcitationPurity; + AssertBackingFieldEvaluated(colour, ColourSpace.Wxy); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterConfigurationConversion(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); - _ = unicolour.ConvertToConfiguration(Configuration.Default); - AssertBackingFieldEvaluated(unicolour, ColourSpace.Xyz); + var colour = RandomColours.UnicolourFrom(colourSpace); + _ = colour.ConvertToConfiguration(Configuration.Default); + AssertBackingFieldEvaluated(colour, ColourSpace.Xyz); } [TestCaseSource(typeof(TestUtils), nameof(TestUtils.AllColourSpacesTestCases))] public void AfterMix(ColourSpace colourSpace) { - var unicolour = RandomColours.UnicolourFrom(colourSpace); + var colour = RandomColours.UnicolourFrom(colourSpace); var other = RandomColours.UnicolourFrom(colourSpace); - var initialColourSpace = unicolour.InitialColourSpace; - _ = Interpolation.Mix(unicolour, other, initialColourSpace, 0.5, HueSpan.Shorter, true); - AssertBackingFieldsNotEvaluated(unicolour, ColourSpacesWithBackingFields.Except(new []{ colourSpace }).ToList()); - AssertBackingFieldEvaluated(unicolour, colourSpace); + var initialColourSpace = colour.InitialColourSpace; + _ = Interpolation.Mix(colour, other, initialColourSpace, 0.5, HueSpan.Shorter, true); + AssertBackingFieldsNotEvaluated(colour, ColourSpacesWithBackingFields.Except(new []{ colourSpace }).ToList()); + AssertBackingFieldEvaluated(colour, colourSpace); } - private static void AssertBackingFieldsNotEvaluated(Unicolour unicolour, List colourSpaces) + private static void AssertBackingFieldsNotEvaluated(Unicolour colour, List colourSpaces) { foreach (var colourSpace in colourSpaces) { var backingFieldName = GetBackingFieldName(colourSpace); - var isEvaluated = IsBackingFieldEvaluated(unicolour, backingFieldName); + var isEvaluated = IsBackingFieldEvaluated(colour, backingFieldName); Assert.That(isEvaluated, Is.False); } } - private static void AssertBackingFieldEvaluated(Unicolour unicolour, ColourSpace colourSpace) + private static void AssertBackingFieldEvaluated(Unicolour colour, ColourSpace colourSpace) { var backingFieldName = GetBackingFieldName(colourSpace); - var isEvaluated = IsBackingFieldEvaluated(unicolour, backingFieldName); + var isEvaluated = IsBackingFieldEvaluated(colour, backingFieldName); Assert.That(isEvaluated, Is.True); } @@ -163,9 +163,9 @@ private static string GetBackingFieldName(ColourSpace colourSpace) return char.ToLower(colourSpaceName[0]) + colourSpaceName[1..]; } - private static bool IsBackingFieldEvaluated(Unicolour unicolour, string backingFieldName) + private static bool IsBackingFieldEvaluated(Unicolour colour, string backingFieldName) { - var lazyBackingField = GetPrivateField(backingFieldName).GetValue(unicolour)!; + var lazyBackingField = GetPrivateField(backingFieldName).GetValue(colour)!; var isValueCreatedProperty = lazyBackingField.GetType().GetProperty("IsValueCreated")!; var isValueCreated = isValueCreatedProperty.GetValue(lazyBackingField, null); return (bool)isValueCreated!; diff --git a/Unicolour.Tests/MatrixTests.cs b/Unicolour.Tests/MatrixTests.cs index ea502f5..46db6bb 100644 --- a/Unicolour.Tests/MatrixTests.cs +++ b/Unicolour.Tests/MatrixTests.cs @@ -259,7 +259,7 @@ private static void AssertMatrixMultiply(double[,] dataA, double[,] dataB, doubl var mathNetMatrixB = Matrix.Build.DenseOfArray(dataB); var mathNetMultipliedMatrix = mathNetMatrixA.Multiply(mathNetMatrixB); - AssertMatrixEquals(multipliedMatrix, mathNetMultipliedMatrix, expected); + AssertMatrix(multipliedMatrix, mathNetMultipliedMatrix, expected); } private static void AssertMatrixInverse(double[,] data, double[,] expected) @@ -270,21 +270,21 @@ private static void AssertMatrixInverse(double[,] data, double[,] expected) var mathNetMatrix = Matrix.Build.DenseOfArray(data); var mathNetInverseMatrix = mathNetMatrix.Inverse(); - AssertMatrixEquals(inverseMatrix, mathNetInverseMatrix, expected); + AssertMatrix(inverseMatrix, mathNetInverseMatrix, expected); } private static void AssertMatrixScale(double[,] data, double scalar, double[,] expected) { var matrix = new Matrix(data).Select(x => x * scalar); var mathNetMatrix = Matrix.Build.DenseOfArray(data).Multiply(scalar); - AssertMatrixEquals(matrix, mathNetMatrix, expected); + AssertMatrix(matrix, mathNetMatrix, expected); } private static void AssertMatrixSelect(double[,] data, Func operation, double[,] expected) { var matrix = new Matrix(data).Select(operation); var mathNetMatrix = Matrix.Build.DenseOfArray(data).Map(operation); - AssertMatrixEquals(matrix, mathNetMatrix, expected); + AssertMatrix(matrix, mathNetMatrix, expected); } private static void AssertMatrixTranspose(double[,] data, double[,] expected) @@ -295,10 +295,10 @@ private static void AssertMatrixTranspose(double[,] data, double[,] expected) var mathNetMatrix = Matrix.Build.DenseOfArray(data); var mathNetInverseMatrix = mathNetMatrix.Transpose(); - AssertMatrixEquals(transposeMatrix, mathNetInverseMatrix, expected); + AssertMatrix(transposeMatrix, mathNetInverseMatrix, expected); } - private static void AssertMatrixEquals(Matrix actual, Matrix actualMathNet, double[,] expected) + private static void AssertMatrix(Matrix actual, Matrix actualMathNet, double[,] expected) { Assert.That(actual.Data, Is.EqualTo(expected)); diff --git a/Unicolour.Tests/MixConfigurationTests.cs b/Unicolour.Tests/MixConfigurationTests.cs index 921b433..5146900 100644 --- a/Unicolour.Tests/MixConfigurationTests.cs +++ b/Unicolour.Tests/MixConfigurationTests.cs @@ -8,26 +8,26 @@ public class MixConfigurationTests [Test] public void UndefinedConfig() { - var unicolour1 = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - AssertConfig(unicolour1, unicolour2, expectSameId: true); + var colour1 = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + var colour2 = new Unicolour(ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + AssertConfig(colour1, colour2, expectSameId: true); } [Test] public void DefaultConfig() { - var unicolour1 = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - var unicolour2 = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - AssertConfig(unicolour1, unicolour2, expectSameId: true); + var colour1 = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + var colour2 = new Unicolour(Configuration.Default, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + AssertConfig(colour1, colour2, expectSameId: true); } [Test] public void SameConfig() { var config = GetConfig(); - var unicolour1 = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - var unicolour2 = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - AssertConfig(unicolour1, unicolour2, expectSameId: true); + var colour1 = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + var colour2 = new Unicolour(config, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + AssertConfig(colour1, colour2, expectSameId: true); } [Test] @@ -35,9 +35,9 @@ public void DifferentConfigSameValues() { var config1 = GetConfig(); var config2 = GetConfig(); - var unicolour1 = new Unicolour(config1, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - var unicolour2 = new Unicolour(config2, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); - AssertConfig(unicolour1, unicolour2, expectSameId: false); + var colour1 = new Unicolour(config1, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + var colour2 = new Unicolour(config2, ColourSpace.Rgb, 0.5, 0.25, 0.75, 0.5); + AssertConfig(colour1, colour2, expectSameId: false); } [Test] @@ -45,15 +45,15 @@ public void DifferentConfigDifferentValues() { var config1 = GetConfig(); var config2 = GetConfig(defaultConfig: false); - var unicolour1 = new Unicolour(config1, ColourSpace.Rgb, 0.0, 0.25, 0.75, 1.0); - var unicolour2 = new Unicolour(config1, ColourSpace.Rgb, 1.0, 0.75, 0.25, 0.0); - var defaultConfigHex = unicolour2.Hex; - unicolour2 = unicolour2.ConvertToConfiguration(config2); - Assert.That(unicolour2.Hex, Is.Not.EqualTo(defaultConfigHex)); - AssertConfig(unicolour1, unicolour2, expectSameId: false); + var colour1 = new Unicolour(config1, ColourSpace.Rgb, 0.0, 0.25, 0.75, 1.0); + var colour2 = new Unicolour(config1, ColourSpace.Rgb, 1.0, 0.75, 0.25, 0.0); + var defaultConfigHex = colour2.Hex; + colour2 = colour2.ConvertToConfiguration(config2); + Assert.That(colour2.Hex, Is.Not.EqualTo(defaultConfigHex)); + AssertConfig(colour1, colour2, expectSameId: false); // unicolour 2 should be converted back to config 1, therefore interpolating halfway between original values - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); TestUtils.AssertTriplet(mixed, new(0.5, 0.5, 0.5), 0.00000000005); Assert.That(mixed.Alpha.A, Is.EqualTo(0.5)); } @@ -65,18 +65,18 @@ private static Configuration GetConfig(bool defaultConfig = true) : new Configuration(RgbConfiguration.DisplayP3, XyzConfiguration.D50, YbrConfiguration.Rec709, CamConfiguration.Hct); } - private static void AssertConfig(Unicolour unicolour1, Unicolour unicolour2, bool expectSameId) + private static void AssertConfig(Unicolour colour1, Unicolour colour2, bool expectSameId) { - Assert.That(unicolour1.Configuration.Id, expectSameId ? Is.EqualTo(unicolour2.Configuration.Id) : Is.Not.EqualTo(unicolour2.Configuration.Id)); + Assert.That(colour1.Configuration.Id, expectSameId ? Is.EqualTo(colour2.Configuration.Id) : Is.Not.EqualTo(colour2.Configuration.Id)); - var mix1 = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); - var mix2 = unicolour2.Mix(unicolour1, ColourSpace.Rgb, premultiplyAlpha: false); - var mix3 = unicolour1.Mix(unicolour2, ColourSpace.Hsb, premultiplyAlpha: false); - var mix4 = unicolour2.Mix(unicolour1, ColourSpace.Hsb, premultiplyAlpha: false); + var mix1 = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mix2 = colour2.Mix(colour1, ColourSpace.Rgb, premultiplyAlpha: false); + var mix3 = colour1.Mix(colour2, ColourSpace.Hsb, premultiplyAlpha: false); + var mix4 = colour2.Mix(colour1, ColourSpace.Hsb, premultiplyAlpha: false); - Assert.That(mix1.Configuration, Is.EqualTo(unicolour1.Configuration)); - Assert.That(mix2.Configuration, Is.EqualTo(unicolour2.Configuration)); - Assert.That(mix3.Configuration, Is.EqualTo(unicolour1.Configuration)); - Assert.That(mix4.Configuration, Is.EqualTo(unicolour2.Configuration)); + Assert.That(mix1.Configuration, Is.EqualTo(colour1.Configuration)); + Assert.That(mix2.Configuration, Is.EqualTo(colour2.Configuration)); + Assert.That(mix3.Configuration, Is.EqualTo(colour1.Configuration)); + Assert.That(mix4.Configuration, Is.EqualTo(colour2.Configuration)); } } \ No newline at end of file diff --git a/Unicolour.Tests/MixHeritageTests.cs b/Unicolour.Tests/MixHeritageTests.cs index ad104da..f4a0fe8 100644 --- a/Unicolour.Tests/MixHeritageTests.cs +++ b/Unicolour.Tests/MixHeritageTests.cs @@ -24,14 +24,14 @@ public class MixHeritageTests public void HuedToHued() { // hued + hued --> hued - var unicolour1 = new Unicolour(ColourSpace.Rgb, 1, 0, 1); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 1, 1, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Rgb, 1, 0, 1); + var colour2 = new Unicolour(ColourSpace.Rgb, 1, 1, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + AssertInitialHeritage(colour2, 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 component is present - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -39,7 +39,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.Mix(unicolour2, ColourSpace.Hsb, premultiplyAlpha: false); + mixed = colour1.Mix(colour2, ColourSpace.Hsb, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.Hued, isHued: true, isGreyscale: false, isNotNumber: false); data = new ColourHeritageData(mixed); @@ -51,14 +51,14 @@ public void HuedToHued() public void HuedToGreyscale() { // hued + hued --> grey - var unicolour1 = new Unicolour(ColourSpace.Rgb, 1, 0, 1); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Rgb, 1, 0, 1); + var colour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + AssertInitialHeritage(colour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); // initial mixed representation: no heritage, greyscale values; // all representations are used as greyscale, none are used as hued - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -70,14 +70,14 @@ public void HuedToGreyscale() public void GreyscaleToHued() { // grey + hued --> hued (no other possibility) - var unicolour1 = new Unicolour(ColourSpace.Rgb, 1, 1, 1); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Rgb, 1, 1, 1); + var colour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); + AssertInitialHeritage(colour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); // initial mixed representation: no heritage, non-greyscale values; // all representations are used as hued where hue component is present - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -89,14 +89,14 @@ public void GreyscaleToHued() public void GreyscaleToGreyscale() { // grey + grey --> grey (no other possibility) - var unicolour1 = new Unicolour(ColourSpace.Rgb, 1, 1, 1); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0, 0, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Rgb, 1, 1, 1); + var colour2 = new Unicolour(ColourSpace.Rgb, 0, 0, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); + AssertInitialHeritage(colour2, ColourHeritage.None, isHued: false, isGreyscale: true, isNotNumber: false); // initial mixed representation: greyscale heritage, greyscale values; // all representations are used as greyscale, none are used as hued - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Lab, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Lab, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.Greyscale, isHued: false, isGreyscale: true, isNotNumber: false); var data = new ColourHeritageData(mixed); @@ -108,14 +108,14 @@ public void GreyscaleToGreyscale() public void NotNumberToNotNumber() { // NaN + X --> NaN (no other possibility) - var unicolour1 = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); - var unicolour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: true); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); + var colour2 = new Unicolour(ColourSpace.Rgb, 0, 1, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: true); + AssertInitialHeritage(colour2, ColourHeritage.None, isHued: false, isGreyscale: false, isNotNumber: false); // initial mixed representation: NaN heritage, NaN values; // all representations are used as NaN, none are used as hued or greyscale - var mixed = unicolour1.Mix(unicolour2, ColourSpace.Rgb, premultiplyAlpha: false); + var mixed = colour1.Mix(colour2, ColourSpace.Rgb, premultiplyAlpha: false); AssertInitialHeritage(mixed, ColourHeritage.NaN, isHued: false, isGreyscale: false, isNotNumber: true); var data = new ColourHeritageData(mixed); @@ -132,34 +132,34 @@ public void NotNumberToNotNumber() [Test] public void GreyscaleAndHued() { - var unicolour1 = new Unicolour(ColourSpace.Hsb, 0, 0, 0); - var unicolour2 = new Unicolour(ColourSpace.Hsb, 120, 0, 0); - AssertInitialHeritage(unicolour1, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); - AssertInitialHeritage(unicolour2, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); + var colour1 = new Unicolour(ColourSpace.Hsb, 0, 0, 0); + var colour2 = new Unicolour(ColourSpace.Hsb, 120, 0, 0); + AssertInitialHeritage(colour1, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); + AssertInitialHeritage(colour2, ColourHeritage.None, isHued: true, isGreyscale: true, isNotNumber: false); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace.Hsb, 0.75, premultiplyAlpha: false); // 90, 0, 0 - var mixed2 = unicolour1.Mix(unicolour2, ColourSpace.Hsb, 0.25, premultiplyAlpha: false); // 30, 0, 0 + var mixed1 = colour1.Mix(colour2, ColourSpace.Hsb, 0.75, premultiplyAlpha: false); // 90, 0, 0 + var mixed2 = colour1.Mix(colour2, ColourSpace.Hsb, 0.25, premultiplyAlpha: 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.Mix(mixed2, ColourSpace.Hsb, premultiplyAlpha: false); // 60, 0, 0 - var unicolour3 = new Unicolour(ColourSpace.Hsb, 240, 1, 1); + var colour3 = new Unicolour(ColourSpace.Hsb, 240, 1, 1); AssertInitialHeritage(mixed3, ColourHeritage.GreyscaleAndHued, isHued: true, isGreyscale: true, isNotNumber: false); - AssertInitialHeritage(unicolour3, ColourHeritage.None, isHued: true, isGreyscale: false, isNotNumber: false); + AssertInitialHeritage(colour3, ColourHeritage.None, isHued: true, isGreyscale: false, isNotNumber: false); - var mixed4 = mixed3.Mix(unicolour3, ColourSpace.Hsb, premultiplyAlpha: false); // 180, 0.5, 0.5 + var mixed4 = mixed3.Mix(colour3, ColourSpace.Hsb, premultiplyAlpha: 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.Mix(unicolour3, ColourSpace.Hsl, premultiplyAlpha: false); + var mixedHsl = mixed3.Mix(colour3, ColourSpace.Hsl, premultiplyAlpha: false); AssertInitialHeritage(mixed4, ColourHeritage.Hued, isHued: true, isGreyscale: false, isNotNumber: false); Assert.That(mixedHsl.Hsb.H, Is.EqualTo(150)); } - private static void AssertInitialHeritage(Unicolour unicolour, ColourHeritage colourHeritage, bool isHued, bool isGreyscale, bool isNotNumber) + private static void AssertInitialHeritage(Unicolour colour, ColourHeritage colourHeritage, bool isHued, bool isGreyscale, bool isNotNumber) { - var initialRepresentation = unicolour.InitialRepresentation; + var initialRepresentation = colour.InitialRepresentation; Assert.That(initialRepresentation.Heritage, Is.EqualTo(colourHeritage)); Assert.That(initialRepresentation.UseAsHued, Is.EqualTo(isHued)); Assert.That(initialRepresentation.UseAsGreyscale, Is.EqualTo(isGreyscale)); diff --git a/Unicolour.Tests/MixHueAgnosticTests.cs b/Unicolour.Tests/MixHueAgnosticTests.cs index 839bbfb..fd2272b 100644 --- a/Unicolour.Tests/MixHueAgnosticTests.cs +++ b/Unicolour.Tests/MixHueAgnosticTests.cs @@ -26,12 +26,12 @@ protected MixHueAgnosticTests(ColourSpace colourSpace, Range first, Range second [Test] public void SameColour() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.25, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.75, premultiplyAlpha: false); - var mixed3 = unicolour1.Mix(unicolour2, ColourSpace, 0.75, premultiplyAlpha: false); - var mixed4 = unicolour2.Mix(unicolour1, ColourSpace, 0.25, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.25, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.75, premultiplyAlpha: false); + var mixed3 = colour1.Mix(colour2, ColourSpace, 0.75, premultiplyAlpha: false); + var mixed4 = colour2.Mix(colour1, ColourSpace, 0.25, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5)); AssertMix(mixed2, (First.At(0.5), Second.At(0.25), Third.At(0.75), 0.5)); @@ -42,10 +42,10 @@ public void SameColour() [Test] public void Equidistant() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), Third.At(0.0), 0.0); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), Third.At(0.5), 0.2); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), Third.At(0.0), 0.0); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), Third.At(0.5), 0.2); + var mixed1 = colour1.Mix(colour2, ColourSpace, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.25), Second.At(0.5), Third.At(0.25), 0.1)); AssertMix(mixed2, (First.At(0.25), Second.At(0.5), Third.At(0.25), 0.1)); @@ -54,10 +54,10 @@ public void Equidistant() [Test] public void CloserToEndColour() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(1.0), Third.At(0.5)); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.0), Third.At(0.0), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.75, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.75, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(1.0), Third.At(0.5)); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.0), Third.At(0.0), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.75, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.75, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.375), Second.At(0.25), Third.At(0.125), 0.625)); AssertMix(mixed2, (First.At(0.125), Second.At(0.75), Third.At(0.375), 0.875)); @@ -66,10 +66,10 @@ public void CloserToEndColour() [Test] public void CloserToStartColour() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(1.0), Third.At(0.5)); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.0), Third.At(0.0), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.25, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.25, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(1.0), Third.At(0.5)); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.0), Third.At(0.0), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.25, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.25, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.125), Second.At(0.75), Third.At(0.375), 0.875)); AssertMix(mixed2, (First.At(0.375), Second.At(0.25), Third.At(0.125), 0.625)); @@ -78,10 +78,10 @@ public void CloserToStartColour() [Test] public void BeyondEndColour() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.2), Second.At(0.4), Third.At(0.6), 0.8); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.3), Second.At(0.6), Third.At(0.4), 0.9); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 1.5, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 1.5, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.2), Second.At(0.4), Third.At(0.6), 0.8); + var colour2 = new Unicolour(ColourSpace, First.At(0.3), Second.At(0.6), Third.At(0.4), 0.9); + var mixed1 = colour1.Mix(colour2, ColourSpace, 1.5, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 1.5, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.35), Second.At(0.7), Third.At(0.3), 0.95)); AssertMix(mixed2, (First.At(0.15), Second.At(0.3), Third.At(0.7), 0.75)); @@ -90,10 +90,10 @@ public void BeyondEndColour() [Test] public void BeyondStartColour() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.2), Second.At(0.4), Third.At(0.6), 0.8); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.3), Second.At(0.6), Third.At(0.4), 0.9); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, -0.5, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, -0.5, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.2), Second.At(0.4), Third.At(0.6), 0.8); + var colour2 = new Unicolour(ColourSpace, First.At(0.3), Second.At(0.6), Third.At(0.4), 0.9); + var mixed1 = colour1.Mix(colour2, ColourSpace, -0.5, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, -0.5, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.15), Second.At(0.3), Third.At(0.7), 0.75)); AssertMix(mixed2, (First.At(0.35), Second.At(0.7), Third.At(0.3), 0.95)); @@ -102,9 +102,9 @@ public void BeyondStartColour() [Test] public void BeyondMaxAlpha() { - var unicolour1 = new Unicolour(ColourSpace, 0, 0, 0, 0.5); - var unicolour2 = new Unicolour(ColourSpace, 0, 0, 0, 1.5); - var mixed = unicolour1.Mix(unicolour2, ColourSpace, 3); + var colour1 = new Unicolour(ColourSpace, 0, 0, 0, 0.5); + var colour2 = new Unicolour(ColourSpace, 0, 0, 0, 1.5); + var mixed = colour1.Mix(colour2, ColourSpace, 3); Assert.That(mixed.Alpha.A, Is.EqualTo(2.0)); Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(1.0)); } @@ -112,16 +112,16 @@ public void BeyondMaxAlpha() [Test] public void BeyondMinAlpha() { - var unicolour1 = new Unicolour(ColourSpace, 0, 0, 0, 0.5); - var unicolour2 = new Unicolour(ColourSpace, 0, 0, 0, -0.5); - var mixed = unicolour1.Mix(unicolour2, ColourSpace, 3); + var colour1 = new Unicolour(ColourSpace, 0, 0, 0, 0.5); + var colour2 = new Unicolour(ColourSpace, 0, 0, 0, -0.5); + var mixed = colour1.Mix(colour2, ColourSpace, 3); Assert.That(mixed.Alpha.A, Is.EqualTo(-1.0)); Assert.That(mixed.Alpha.ConstrainedA, Is.EqualTo(0.0)); } - protected void AssertMix(Unicolour unicolour, (double first, double second, double third, double alpha) expected) + protected void AssertMix(Unicolour colour, (double first, double second, double third, double alpha) expected) { - TestUtils.AssertMixed(unicolour.GetRepresentation(ColourSpace).Triplet, unicolour.Alpha.A, expected); + TestUtils.AssertMixed(colour.GetRepresentation(ColourSpace).Triplet, colour.Alpha.A, expected); } } diff --git a/Unicolour.Tests/MixHueFirstComponentTests.cs b/Unicolour.Tests/MixHueFirstComponentTests.cs index bc72e1a..f71f341 100644 --- a/Unicolour.Tests/MixHueFirstComponentTests.cs +++ b/Unicolour.Tests/MixHueFirstComponentTests.cs @@ -55,10 +55,10 @@ public MixHueFirstComponentTests(ColourSpace colourSpace, Range first, Range sec [Test] public void EquidistantViaZero() { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.0), Third.At(0.0), 0.0); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(340), Second.At(1.0), Third.At(0.5), 0.2); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.0), Third.At(0.0), 0.0); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(340), Second.At(1.0), Third.At(0.5), 0.2); + var mixed1 = colour1.Mix(colour2, ColourSpace, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, premultiplyAlpha: false); AssertMix(mixed1, (350, Second.At(0.5), Third.At(0.25), 0.1)); AssertMix(mixed2, (350, Second.At(0.5), Third.At(0.25), 0.1)); @@ -67,10 +67,10 @@ public void EquidistantViaZero() [Test] public void CloserToEndColourViaZero() { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(300), Second.At(1.0), Third.At(0.5)); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(60), Second.At(0.0), Third.At(0.0), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.75, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.75, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(300), Second.At(1.0), Third.At(0.5)); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(60), Second.At(0.0), Third.At(0.0), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.75, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.75, premultiplyAlpha: false); AssertMix(mixed1, (30, Second.At(0.25), Third.At(0.125), 0.625)); AssertMix(mixed2, (330, Second.At(0.75), Third.At(0.375), 0.875)); @@ -79,10 +79,10 @@ public void CloserToEndColourViaZero() [Test] public void CloserToStartColourViaZero() { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(300), Second.At(1.0), Third.At(0.5)); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(60), Second.At(0.0), Third.At(0.0), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.25, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.25, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(300), Second.At(1.0), Third.At(0.5)); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(60), Second.At(0.0), Third.At(0.0), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.25, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.25, premultiplyAlpha: false); AssertMix(mixed1, (330, Second.At(0.75), Third.At(0.375), 0.875)); AssertMix(mixed2, (30, Second.At(0.25), Third.At(0.125), 0.625)); @@ -94,10 +94,10 @@ public void CloserToStartColourViaZero() [TestCase(HueSpan.Decreasing, 0)] public void Span0(HueSpan hueSpan, double expected) { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (expected, Second.At(0.5), Third.At(0.5), 0.5)); AssertMix(mixed2, (expected, Second.At(0.5), Third.At(0.5), 0.5)); @@ -109,10 +109,10 @@ public void Span0(HueSpan hueSpan, double expected) [TestCase(HueSpan.Decreasing, 0, 180)] public void Span120(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(120), Second.At(0.5), Third.At(0.5), 0.5); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(240), Second.At(0.5), Third.At(0.5), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(120), Second.At(0.5), Third.At(0.5), 0.5); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(240), Second.At(0.5), Third.At(0.5), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (expectedForward, Second.At(0.5), Third.At(0.5), 0.5)); AssertMix(mixed2, (expectedBackward, Second.At(0.5), Third.At(0.5), 0.5)); @@ -124,10 +124,10 @@ public void Span120(HueSpan hueSpan, double expectedForward, double expectedBack [TestCase(HueSpan.Decreasing, 0, 180)] public void Span360(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(360), Second.At(0.5), Third.At(0.5), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(0), Second.At(0.5), Third.At(0.5), 0.5); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(360), Second.At(0.5), Third.At(0.5), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (expectedForward, Second.At(0.5), Third.At(0.5), 0.5)); AssertMix(mixed2, (expectedBackward, Second.At(0.5), Third.At(0.5), 0.5)); @@ -139,10 +139,10 @@ public void Span360(HueSpan hueSpan, double expectedForward, double expectedBack [TestCase(HueSpan.Decreasing, 315, 135)] public void SpanUneven(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, mapFromDegree(90), Second.At(0.5), Third.At(0.5), 0.5); - var unicolour2 = new Unicolour(ColourSpace, mapFromDegree(180), Second.At(0.5), Third.At(0.5), 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, mapFromDegree(90), Second.At(0.5), Third.At(0.5), 0.5); + var colour2 = new Unicolour(ColourSpace, mapFromDegree(180), Second.At(0.5), Third.At(0.5), 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (expectedForward, Second.At(0.5), Third.At(0.5), 0.5)); AssertMix(mixed2, (expectedBackward, Second.At(0.5), Third.At(0.5), 0.5)); @@ -163,16 +163,16 @@ public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount start = start with { Triplet = start.Triplet.WithDegreeMap(mapFromDegree) }; end = end with { Triplet = end.Triplet.WithDegreeMap(mapFromDegree) }; - var unicolour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); - var unicolour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); - var mixed = unicolour1.Mix(unicolour2, ColourSpace, amount, HueSpan.Increasing, premultiplyAlpha: true); + var colour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); + var colour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); + var mixed = colour1.Mix(colour2, ColourSpace, amount, HueSpan.Increasing, premultiplyAlpha: true); AssertMix(mixed, expected.Tuple); } // this extra step is needed to handle the unusual WXY "hue" - private new void AssertMix(Unicolour unicolour, (double first, double second, double third, double alpha) expected) + private new void AssertMix(Unicolour colour, (double first, double second, double third, double alpha) expected) { - var tripletWithDegree = unicolour.GetRepresentation(ColourSpace).Triplet.WithDegreeMap(mapToDegree).WithHueModulo(); - TestUtils.AssertMixed(tripletWithDegree, unicolour.Alpha.A, expected); + var tripletWithDegree = colour.GetRepresentation(ColourSpace).Triplet.WithDegreeMap(mapToDegree).WithHueModulo(); + TestUtils.AssertMixed(tripletWithDegree, colour.Alpha.A, expected); } } \ No newline at end of file diff --git a/Unicolour.Tests/MixHueNoComponentTests.cs b/Unicolour.Tests/MixHueNoComponentTests.cs index 6ef9ef3..129d041 100644 --- a/Unicolour.Tests/MixHueNoComponentTests.cs +++ b/Unicolour.Tests/MixHueNoComponentTests.cs @@ -52,9 +52,9 @@ public MixHueNoComponentTests(ColourSpace colourSpace, Range first, Range second [TestCaseSource(nameof(PremultipliedAlphaTestData))] public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) { - var unicolour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); - var unicolour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); - var mixed = unicolour1.Mix(unicolour2, ColourSpace, amount, premultiplyAlpha: true); + var colour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); + var colour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); + var mixed = colour1.Mix(colour2, ColourSpace, amount, premultiplyAlpha: true); AssertMix(mixed, expected.Tuple); } } \ No newline at end of file diff --git a/Unicolour.Tests/MixHueThirdComponentTests.cs b/Unicolour.Tests/MixHueThirdComponentTests.cs index 964d6be..7fedd62 100644 --- a/Unicolour.Tests/MixHueThirdComponentTests.cs +++ b/Unicolour.Tests/MixHueThirdComponentTests.cs @@ -28,10 +28,10 @@ public MixHueThirdComponentTests(ColourSpace colourSpace, Range first, Range sec [Test] public void EquidistantViaZero() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 0, 0.0); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 340, 0.2); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 0, 0.0); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 340, 0.2); + var mixed1 = colour1.Mix(colour2, ColourSpace, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.25), Second.At(0.5), 350, 0.1)); AssertMix(mixed2, (First.At(0.25), Second.At(0.5), 350, 0.1)); @@ -40,10 +40,10 @@ public void EquidistantViaZero() [Test] public void CloserToEndColourViaZero() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 300); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 60, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.75, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.75, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 300); + var colour2 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 60, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.75, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.75, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.125), Second.At(0.25), 30, 0.625)); AssertMix(mixed2, (First.At(0.375), Second.At(0.75), 330, 0.875)); @@ -52,10 +52,10 @@ public void CloserToEndColourViaZero() [Test] public void CloserToStartColourViaZero() { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 300); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 60, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.25, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.25, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(1.0), 300); + var colour2 = new Unicolour(ColourSpace, First.At(0.0), Second.At(0.0), 60, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.25, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.25, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.375), Second.At(0.75), 330, 0.875)); AssertMix(mixed2, (First.At(0.125), Second.At(0.25), 30, 0.625)); @@ -67,10 +67,10 @@ public void CloserToStartColourViaZero() [TestCase(HueSpan.Decreasing, 0)] public void Span0(HueSpan hueSpan, double expected) { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.5), Second.At(0.5), expected, 0.5)); AssertMix(mixed2, (First.At(0.5), Second.At(0.5), expected, 0.5)); @@ -82,10 +82,10 @@ public void Span0(HueSpan hueSpan, double expected) [TestCase(HueSpan.Decreasing, 0, 180)] public void Span120(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 120, 0.5); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 240, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 120, 0.5); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 240, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.5), Second.At(0.5), expectedForward, 0.5)); AssertMix(mixed2, (First.At(0.5), Second.At(0.5), expectedBackward, 0.5)); @@ -97,10 +97,10 @@ public void Span120(HueSpan hueSpan, double expectedForward, double expectedBack [TestCase(HueSpan.Decreasing, 0, 180)] public void Span360(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 360, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 0, 0.5); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 360, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.5), Second.At(0.5), expectedForward, 0.5)); AssertMix(mixed2, (First.At(0.5), Second.At(0.5), expectedBackward, 0.5)); @@ -112,10 +112,10 @@ public void Span360(HueSpan hueSpan, double expectedForward, double expectedBack [TestCase(HueSpan.Decreasing, 315, 135)] public void SpanUneven(HueSpan hueSpan, double expectedForward, double expectedBackward) { - var unicolour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 90, 0.5); - var unicolour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 180, 0.5); - var mixed1 = unicolour1.Mix(unicolour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); - var mixed2 = unicolour2.Mix(unicolour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var colour1 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 90, 0.5); + var colour2 = new Unicolour(ColourSpace, First.At(0.5), Second.At(0.5), 180, 0.5); + var mixed1 = colour1.Mix(colour2, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); + var mixed2 = colour2.Mix(colour1, ColourSpace, 0.5, hueSpan, premultiplyAlpha: false); AssertMix(mixed1, (First.At(0.5), Second.At(0.5), expectedForward, 0.5)); AssertMix(mixed2, (First.At(0.5), Second.At(0.5), expectedBackward, 0.5)); @@ -133,9 +133,9 @@ public void SpanUneven(HueSpan hueSpan, double expectedForward, double expectedB [TestCaseSource(nameof(PremultipliedAlphaTestData))] public void PremultiplyAlpha(AlphaTriplet start, AlphaTriplet end, double amount, AlphaTriplet expected) { - var unicolour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); - var unicolour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); - var mixed = unicolour1.Mix(unicolour2, ColourSpace, amount, premultiplyAlpha: true); + var colour1 = new Unicolour(ColourSpace, start.Triplet.Tuple, start.Alpha); + var colour2 = new Unicolour(ColourSpace, end.Triplet.Tuple, end.Alpha); + var mixed = colour1.Mix(colour2, ColourSpace, amount, premultiplyAlpha: true); AssertMix(mixed, expected.Tuple); } } \ No newline at end of file diff --git a/Unicolour.Tests/NotNumberTests.cs b/Unicolour.Tests/NotNumberTests.cs index 4304b74..a34be06 100644 --- a/Unicolour.Tests/NotNumberTests.cs +++ b/Unicolour.Tests/NotNumberTests.cs @@ -143,24 +143,24 @@ public class NotNumberTests [TestCaseSource(nameof(TestData))] public void IsNumberButUseAsNotNumber(Configuration config, double l, double u, double v) { - var unicolour = new Unicolour(ColourSpace.Luv, l, u, v); - Assert.That(unicolour.Luv.IsNaN, Is.True); - Assert.That(unicolour.Xyz.IsNaN, Is.False); - Assert.That(unicolour.Rgb.IsNaN, Is.False); - Assert.That(unicolour.Hsb.IsNaN, Is.False); - Assert.That(unicolour.Hsl.IsNaN, Is.False); + var colour = new Unicolour(ColourSpace.Luv, l, u, v); + Assert.That(colour.Luv.IsNaN, Is.True); + Assert.That(colour.Xyz.IsNaN, Is.False); + Assert.That(colour.Rgb.IsNaN, Is.False); + Assert.That(colour.Hsb.IsNaN, Is.False); + Assert.That(colour.Hsl.IsNaN, Is.False); - Assert.That(unicolour.Luv.UseAsNaN, Is.True); - Assert.That(unicolour.Xyz.UseAsNaN, Is.True); - Assert.That(unicolour.Rgb.UseAsNaN, Is.True); - Assert.That(unicolour.Hsb.UseAsNaN, Is.True); - Assert.That(unicolour.Hsl.UseAsNaN, Is.True); + Assert.That(colour.Luv.UseAsNaN, Is.True); + Assert.That(colour.Xyz.UseAsNaN, Is.True); + Assert.That(colour.Rgb.UseAsNaN, Is.True); + Assert.That(colour.Hsb.UseAsNaN, Is.True); + Assert.That(colour.Hsl.UseAsNaN, Is.True); } - private static void AssertUnicolour(Unicolour unicolour) + private static void AssertUnicolour(Unicolour colour) { - var data = new ColourHeritageData(unicolour); - var initial = unicolour.InitialRepresentation; + var data = new ColourHeritageData(colour); + var initial = colour.InitialRepresentation; Assert.That(initial.Heritage, Is.EqualTo(ColourHeritage.None)); Assert.That(initial.IsNaN, Is.True); @@ -168,18 +168,18 @@ private static void AssertUnicolour(Unicolour unicolour) Assert.That(initial.UseAsGreyscale, Is.False); Assert.That(initial.UseAsHued, Is.False); Assert.That(initial.ToString().StartsWith("NaN")); - Assert.That(unicolour.Hex, Is.EqualTo("-")); - Assert.That(unicolour.Rgb.Byte255.ConstrainedHex, Is.EqualTo("-")); - Assert.That(unicolour.Chromaticity.Xy, Is.EqualTo((double.NaN, double.NaN))); - Assert.That(unicolour.Chromaticity.Uv, Is.EqualTo((double.NaN, double.NaN))); - Assert.That(unicolour.IsInDisplayGamut, Is.False); - Assert.That(unicolour.RelativeLuminance, Is.NaN); - Assert.That(unicolour.Description, Is.EqualTo("-")); - Assert.That(unicolour.Temperature.Cct, Is.NaN); - Assert.That(unicolour.Temperature.Duv, Is.NaN); - Assert.That(unicolour.Icc.ToString().StartsWith("NaN")); - - var spaces = TestUtils.AllColourSpaces.Except(new [] { unicolour.InitialColourSpace }).ToList(); + Assert.That(colour.Hex, Is.EqualTo("-")); + Assert.That(colour.Rgb.Byte255.ConstrainedHex, Is.EqualTo("-")); + Assert.That(colour.Chromaticity.Xy, Is.EqualTo((double.NaN, double.NaN))); + Assert.That(colour.Chromaticity.Uv, Is.EqualTo((double.NaN, double.NaN))); + Assert.That(colour.IsInRgbGamut, Is.False); + Assert.That(colour.RelativeLuminance, Is.NaN); + Assert.That(colour.Description, Is.EqualTo("-")); + Assert.That(colour.Temperature.Cct, Is.NaN); + Assert.That(colour.Temperature.Duv, Is.NaN); + Assert.That(colour.Icc.ToString().StartsWith("NaN")); + + var spaces = TestUtils.AllColourSpaces.Except(new [] { colour.InitialColourSpace }).ToList(); Assert.That(data.Heritages(spaces), Has.All.EqualTo(ColourHeritage.NaN)); Assert.That(data.UseAsNaN(spaces), Has.All.True); Assert.That(data.UseAsGreyscale(spaces), Has.All.False); diff --git a/Unicolour.Tests/PaletteTests.cs b/Unicolour.Tests/PaletteTests.cs new file mode 100644 index 0000000..fb9b88c --- /dev/null +++ b/Unicolour.Tests/PaletteTests.cs @@ -0,0 +1,82 @@ +using System.Linq; +using NUnit.Framework; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class PaletteTests +{ + private static readonly HueSpan[] HueSpans = [HueSpan.Shorter, HueSpan.Longer, HueSpan.Increasing, HueSpan.Decreasing]; + + [Test, Combinatorial] + public void PaletteMultiple( + [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, + [ValueSource(nameof(HueSpans))] HueSpan hueSpan, + [Values(true, false)] bool premultiplyAlpha) + { + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + + const int count = 9; + var palette = colour1.Palette(colour2, colourSpace, count, hueSpan, premultiplyAlpha).ToArray(); + Assert.That(palette.Length, Is.EqualTo(count)); + + double[] distances = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; + for (var i = 0; i < count; i++) + { + var mixed = colour1.Mix(colour2, colourSpace, distances[i], hueSpan, premultiplyAlpha); + Assert.That(palette[i], Is.EqualTo(mixed)); + } + } + + [Test, Combinatorial] + public void PaletteTwo( + [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, + [ValueSource(nameof(HueSpans))] HueSpan hueSpan, + [Values(true, false)] bool premultiplyAlpha) + { + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + var palette = colour1.Palette(colour2, colourSpace, count: 2, hueSpan, premultiplyAlpha).ToArray(); + Assert.That(palette.Length, Is.EqualTo(2)); + Assert.That(palette[0], Is.EqualTo(colour1.Mix(colour2, colourSpace, 0, hueSpan, premultiplyAlpha))); + Assert.That(palette[1], Is.EqualTo(colour1.Mix(colour2, colourSpace, 1, hueSpan, premultiplyAlpha))); + } + + [Test, Combinatorial] + public void PaletteOne( + [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, + [ValueSource(nameof(HueSpans))] HueSpan hueSpan, + [Values(true, false)] bool premultiplyAlpha) + { + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + var palette = colour1.Palette(colour2, colourSpace, count: 1, hueSpan, premultiplyAlpha).ToArray(); + Assert.That(palette.Length, Is.EqualTo(1)); + Assert.That(palette[0], Is.EqualTo(colour1.Mix(colour2, colourSpace, 0.5, hueSpan, premultiplyAlpha))); + } + + [Test, Combinatorial] + public void PaletteZero( + [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, + [ValueSource(nameof(HueSpans))] HueSpan hueSpan, + [Values(true, false)] bool premultiplyAlpha) + { + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + var palette = colour1.Palette(colour2, colourSpace, count: 0, hueSpan, premultiplyAlpha).ToArray(); + Assert.That(palette, Is.Empty); + } + + [Test, Combinatorial] + public void PaletteNegative( + [ValueSource(typeof(TestUtils), nameof(TestUtils.AllColourSpaces))] ColourSpace colourSpace, + [ValueSource(nameof(HueSpans))] HueSpan hueSpan, + [Values(true, false)] bool premultiplyAlpha) + { + var colour1 = RandomColours.UnicolourFrom(colourSpace); + var colour2 = RandomColours.UnicolourFrom(colourSpace); + var palette = colour1.Palette(colour2, colourSpace, count: -1, hueSpan, premultiplyAlpha).ToArray(); + Assert.That(palette, Is.Empty); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/PigmentGeneratorTests.cs b/Unicolour.Tests/PigmentGeneratorTests.cs new file mode 100644 index 0000000..cb23853 --- /dev/null +++ b/Unicolour.Tests/PigmentGeneratorTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using Wacton.Unicolour.Experimental; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class PigmentGeneratorTests +{ + // ReSharper disable CollectionNeverQueried.Local - used in test case sources by name + private static readonly List TestData = []; + // ReSharper restore CollectionNeverQueried.Local + + static PigmentGeneratorTests() + { + var randomColours = RandomColours.RgbTriplets.Take(100).Select(x => new Unicolour(ColourSpace.Rgb, x.Tuple)).ToArray(); + TestData.Add(new(StandardRgb.Black)); + TestData.Add(new(StandardRgb.White)); + TestData.Add(new(StandardRgb.Grey)); + TestData.Add(new(StandardRgb.Red)); + TestData.Add(new(StandardRgb.Green)); + TestData.Add(new(StandardRgb.Blue)); + TestData.Add(new(StandardRgb.Cyan)); + TestData.Add(new(StandardRgb.Magenta)); + TestData.Add(new(StandardRgb.Yellow)); + TestData.AddRange(randomColours.Select(x => new TestCaseData(x))); + } + + [TestCaseSource(nameof(TestData))] + public void Roundtrip(Unicolour original) + { + var pigment = PigmentGenerator.From(original); + var roundtrip = new Unicolour([pigment], [1.0]); + TestUtils.AssertTriplet(roundtrip.Rgb.Triplet, original.Rgb.Triplet, 0.05); + } + + [Test] + public void NegativeRgb() + { + var pigment = PigmentGenerator.From(new(ColourSpace.RgbLinear, -0.5, -0.5, -0.5)); + Assert.That(pigment.R!.Coefficients, Is.All.NaN); + + var colour = new Unicolour([pigment], [1.0]); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + } + + [Test] + public void TooManyIterations() + { + // initial values <= algorithm's tolerance seem to take longer to converge + var pigment = PigmentGenerator.From(new(ColourSpace.Xyz, PigmentGenerator.Tolerance, PigmentGenerator.Tolerance, PigmentGenerator.Tolerance)); + Assert.That(pigment.R!.Coefficients, Is.All.NaN); + + var colour = new Unicolour([pigment], [1.0]); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + } +} \ No newline at end of file diff --git a/Unicolour.Tests/PigmentTests.cs b/Unicolour.Tests/PigmentTests.cs index 2e0d247..90c3bb7 100644 --- a/Unicolour.Tests/PigmentTests.cs +++ b/Unicolour.Tests/PigmentTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using Wacton.Unicolour.Tests.Utils; @@ -126,6 +127,53 @@ public void TwoConstantKubelkaMunkWithZero() AssertReflectance(pigments, concentrations, [expectedR, expectedR, double.NaN, expectedR, expectedR], expectedXyzNaN: true); } + [Test] + public void AbsorptionHasMoreElements() + { + // first test case data is single pigment of k[0] and s[0] + var absorption = k[0].Concat([0.5]).ToArray(); + var scattering = s[0]; + var expected = TwoConstantData[0].Arguments[3] as double[]; + + // the additional absorption element is ignored + Pigment pigment = new(400, 10, absorption, scattering); + AssertReflectance([pigment], [1.0], expected, expectedXyzNaN: false); + } + + [Test] + public void ScatteringHasMoreElements() + { + // first test case data is single pigment of k[0] and s[0] + var absorption = k[0]; + var scattering = s[0].Concat([0.5]).ToArray(); + var expected = TwoConstantData[0].Arguments[3] as double[]; + + // the additional scattering element is ignored + Pigment pigment = new(400, 10, absorption, scattering); + AssertReflectance([pigment], [1.0], expected, expectedXyzNaN: false); + } + + [Test] + public void ReflectanceEmpty() + { + Pigment pigment = new(400, 10, r: []); + AssertReflectance([pigment], [1.0], expected: [], expectedXyzNaN: true); + } + + [Test] + public void AbsorptionEmpty() + { + Pigment pigment = new(400, 10, k: [], s[0]); + AssertReflectance([pigment], [1.0], expected: [], expectedXyzNaN: true); + } + + [Test] + public void ScatteringEmpty() + { + Pigment pigment = new(400, 10, k[0], s: []); + AssertReflectance([pigment], [1.0], expected: [], expectedXyzNaN: true); + } + [Test] public void MismatchWavelength() { diff --git a/Unicolour.Tests/DisplayGamutTests.cs b/Unicolour.Tests/RgbGamutTests.cs similarity index 81% rename from Unicolour.Tests/DisplayGamutTests.cs rename to Unicolour.Tests/RgbGamutTests.cs index 841eb0b..5ddfc2c 100644 --- a/Unicolour.Tests/DisplayGamutTests.cs +++ b/Unicolour.Tests/RgbGamutTests.cs @@ -2,7 +2,7 @@ namespace Wacton.Unicolour.Tests; -public class DisplayGamutTests +public class RgbGamutTests { [TestCase(0.0, 0.0, 0.0)] [TestCase(0.5, 0.5, 0.5)] @@ -10,8 +10,8 @@ public class DisplayGamutTests [TestCase(double.Epsilon, double.Epsilon, double.Epsilon)] public void InRgbGamut(double r, double g, double b) { - var unicolour = new Unicolour(ColourSpace.Rgb, r, g, b); - Assert.That(unicolour.IsInDisplayGamut, Is.True); + var colour = new Unicolour(ColourSpace.Rgb, r, g, b); + Assert.That(colour.IsInRgbGamut, Is.True); } [TestCase(-0.00001, 0.0, 0.0)] @@ -37,7 +37,7 @@ public void InRgbGamut(double r, double g, double b) [TestCase(0.5, 0.5, double.NaN)] public void OutRgbGamut(double r, double g, double b) { - var unicolour = new Unicolour(ColourSpace.Rgb, r, g, b); - Assert.That(unicolour.IsInDisplayGamut, Is.False); + var colour = new Unicolour(ColourSpace.Rgb, r, g, b); + Assert.That(colour.IsInRgbGamut, Is.False); } } \ No newline at end of file diff --git a/Unicolour.Tests/SmokeTests.cs b/Unicolour.Tests/SmokeTests.cs index 151b2e0..ad61236 100644 --- a/Unicolour.Tests/SmokeTests.cs +++ b/Unicolour.Tests/SmokeTests.cs @@ -386,10 +386,10 @@ public void IccChannelsUncalibratedWithAlpha( AssertNoError(expected, new Unicolour(Configuration.Default, new Channels(iccValues), alpha)); } - private static void AssertNoError(Unicolour expected, Unicolour unicolour) + private static void AssertNoError(Unicolour expected, Unicolour colour) { - TestUtils.AssertNoPropertyError(unicolour); - Assert.That(unicolour, Is.EqualTo(expected)); + TestUtils.AssertNoPropertyError(colour); + Assert.That(colour, Is.EqualTo(expected)); } } diff --git a/Unicolour.Tests/SpectralLocusTests.cs b/Unicolour.Tests/SpectralBoundaryTests.cs similarity index 79% rename from Unicolour.Tests/SpectralLocusTests.cs rename to Unicolour.Tests/SpectralBoundaryTests.cs index 90008bd..19711e6 100644 --- a/Unicolour.Tests/SpectralLocusTests.cs +++ b/Unicolour.Tests/SpectralBoundaryTests.cs @@ -3,11 +3,11 @@ namespace Wacton.Unicolour.Tests; -public class SpectralLocusTests +public class SpectralBoundaryTests { private static readonly Observer Observer = Observer.Degree2; private static readonly Chromaticity WhiteChromaticity = new(0.3, 0.3); - private static readonly Spectral Spectral = new(Observer, WhiteChromaticity); + private static readonly SpectralBoundary SpectralBoundary = new(Observer, WhiteChromaticity); // when the sample is exactly the same as the white point // there is no line that connects them @@ -15,7 +15,7 @@ public class SpectralLocusTests [Test] public void SampleIsWhitePoint() { - var intersects = Spectral.FindBoundaryIntersects(WhiteChromaticity); + var intersects = SpectralBoundary.FindIntersects(WhiteChromaticity); Assert.That(intersects, Is.Null); } @@ -25,7 +25,7 @@ public void SampleIsWhitePoint() [Test] public void InfiniteDistanceToSampleX([Values(0.1, 0, -0.1)] double offset) { - var intersects = Spectral.FindBoundaryIntersects(new(double.PositiveInfinity, WhiteChromaticity.Y + offset)); + var intersects = SpectralBoundary.FindIntersects(new(double.PositiveInfinity, WhiteChromaticity.Y + offset)); Assert.That(intersects, Is.Null); } @@ -35,7 +35,7 @@ public void InfiniteDistanceToSampleX([Values(0.1, 0, -0.1)] double offset) [Test] public void InfiniteDistanceToSampleY([Values(0.1, 0, -0.1)] double offset) { - var intersects = Spectral.FindBoundaryIntersects(new(WhiteChromaticity.X + offset, double.PositiveInfinity)); + var intersects = SpectralBoundary.FindIntersects(new(WhiteChromaticity.X + offset, double.PositiveInfinity)); Assert.That(intersects, Is.Null); } @@ -46,8 +46,8 @@ public void InfiniteDistanceToSampleY([Values(0.1, 0, -0.1)] double offset) public void HorizontalIntersect(double offset, bool expectedImaginary) { var sample = new Chromaticity(WhiteChromaticity.X + offset, WhiteChromaticity.Y); - var intersects = Spectral.FindBoundaryIntersects(sample)!; - var isImaginary = Spectral.IsImaginary(sample); + var intersects = SpectralBoundary.FindIntersects(sample)!; + var isImaginary = SpectralBoundary.IsOutside(sample); Assert.That(intersects.Sample.Y, Is.EqualTo(sample.Y)); Assert.That(intersects.White.Y, Is.EqualTo(sample.Y)); Assert.That(intersects.Near.Chromaticity.Y, Is.EqualTo(sample.Y)); @@ -62,8 +62,8 @@ public void HorizontalIntersect(double offset, bool expectedImaginary) public void VerticalIntersect(double offset, bool expectedImaginary) { var sample = new Chromaticity(WhiteChromaticity.X, WhiteChromaticity.Y + offset); - var intersects = Spectral.FindBoundaryIntersects(sample)!; - var isImaginary = Spectral.IsImaginary(sample); + var intersects = SpectralBoundary.FindIntersects(sample)!; + var isImaginary = SpectralBoundary.IsOutside(sample); Assert.That(intersects.Sample.X, Is.EqualTo(sample.X)); Assert.That(intersects.White.X, Is.EqualTo(sample.X)); Assert.That(intersects.Near.Chromaticity.X, Is.EqualTo(sample.X)); @@ -76,8 +76,8 @@ public void SameSample() { var sample1 = new Chromaticity(WhiteChromaticity.X + 0.1, WhiteChromaticity.Y + 0.1); var sample2 = new Chromaticity(WhiteChromaticity.X + 0.1, WhiteChromaticity.Y + 0.1); - var intersects1 = Spectral.FindBoundaryIntersects(sample1)!; - var intersects2 = Spectral.FindBoundaryIntersects(sample2)!; + var intersects1 = SpectralBoundary.FindIntersects(sample1)!; + var intersects2 = SpectralBoundary.FindIntersects(sample2)!; TestUtils.AssertEqual(intersects1.Near, intersects2.Near); TestUtils.AssertEqual(intersects1.Far, intersects2.Far); TestUtils.AssertEqual(intersects1.Sample, intersects2.Sample); @@ -90,8 +90,8 @@ public void DifferentSampleSameLine() { var sample1 = new Chromaticity(WhiteChromaticity.X + 0.1, WhiteChromaticity.Y + 0.1); var sample2 = new Chromaticity(WhiteChromaticity.X + 1.1, WhiteChromaticity.Y + 1.1); - var intersects1 = Spectral.FindBoundaryIntersects(sample1)!; - var intersects2 = Spectral.FindBoundaryIntersects(sample2)!; + var intersects1 = SpectralBoundary.FindIntersects(sample1)!; + var intersects2 = SpectralBoundary.FindIntersects(sample2)!; TestUtils.AssertEqual(intersects1.Near.Segment, intersects2.Near.Segment); TestUtils.AssertEqual(intersects1.Near.Wavelength, intersects2.Near.Wavelength); @@ -115,8 +115,8 @@ public void DifferentSampleDifferentLine() { var sample1 = new Chromaticity(WhiteChromaticity.X + 0.1, WhiteChromaticity.Y + 0.1); var sample2 = new Chromaticity(WhiteChromaticity.X - 0.1, WhiteChromaticity.Y + 0.1); - var intersects1 = Spectral.FindBoundaryIntersects(sample1)!; - var intersects2 = Spectral.FindBoundaryIntersects(sample2)!; + var intersects1 = SpectralBoundary.FindIntersects(sample1)!; + var intersects2 = SpectralBoundary.FindIntersects(sample2)!; TestUtils.AssertNotEqual(intersects1.Near.Segment, intersects2.Near.Segment); TestUtils.AssertNotEqual(intersects1.Near.Wavelength, intersects2.Near.Wavelength); @@ -147,16 +147,16 @@ public void SingleIntersect() * - the dominant wavelength is the monochromatic wavelength * - excitation purity cannot be calculated; the intersect is both pure monochromatic light AND the white point where there is no colour */ - var minWavelengthXyz = Xyz.FromSpd(Spd.Monochromatic(Spectral.MinWavelength), Observer.Degree2); + var minWavelengthXyz = Xyz.FromSpd(Spd.Monochromatic(SpectralBoundary.MinWavelength), Observer.Degree2); var minWavelengthWhite = WhitePoint.FromXyz(minWavelengthXyz).ToChromaticity(); - var spectral = new Spectral(Observer.Degree2, minWavelengthWhite); + var boundary = new SpectralBoundary(Observer.Degree2, minWavelengthWhite); var sample = new Chromaticity(0.8, 0.2); - var intersects = spectral.FindBoundaryIntersects(sample)!; - var isImaginarySample = spectral.IsImaginary(sample); - var isImaginaryWhitePoint = spectral.IsImaginary(minWavelengthWhite); + var intersects = boundary.FindIntersects(sample)!; + var isImaginarySample = boundary.IsOutside(sample); + var isImaginaryWhitePoint = boundary.IsOutside(minWavelengthWhite); Assert.That(intersects.Far, Is.EqualTo(intersects.Near)); Assert.That(intersects.Near.DistanceToWhite, Is.Zero); - Assert.That(intersects.DominantWavelength, Is.EqualTo(Spectral.MinWavelength)); + Assert.That(intersects.DominantWavelength, Is.EqualTo(SpectralBoundary.MinWavelength)); Assert.That(intersects.ExcitationPurity, Is.NaN); Assert.That(isImaginarySample, Is.True); Assert.That(isImaginaryWhitePoint, Is.False); @@ -167,26 +167,26 @@ public void NoIntersect() { var white = new Chromaticity(0.8, 0.4); var sample = new Chromaticity(0.4, 0.8); - var spectral = new Spectral(Observer.Degree2, white); - var intersects = spectral.FindBoundaryIntersects(sample)!; - var isImaginarySample = spectral.IsImaginary(sample); - var isImaginaryWhitePoint = spectral.IsImaginary(white); + var boundary = new SpectralBoundary(Observer.Degree2, white); + var intersects = boundary.FindIntersects(sample)!; + var isImaginarySample = boundary.IsOutside(sample); + var isImaginaryWhitePoint = boundary.IsOutside(white); Assert.That(intersects, Is.Null); Assert.That(isImaginarySample, Is.True); Assert.That(isImaginaryWhitePoint, Is.True); } - private const int MidWavelength = Spectral.MinWavelength + (Spectral.MaxWavelength - Spectral.MinWavelength) / 2; + private const int MidWavelength = SpectralBoundary.MinWavelength + (SpectralBoundary.MaxWavelength - SpectralBoundary.MinWavelength) / 2; - [TestCase(Spectral.MinWavelength, double.NaN, -Spectral.MinWavelength)] - [TestCase(Spectral.MaxWavelength, -Spectral.MaxWavelength, double.NaN)] + [TestCase(SpectralBoundary.MinWavelength, double.NaN, -SpectralBoundary.MinWavelength)] + [TestCase(SpectralBoundary.MaxWavelength, -SpectralBoundary.MaxWavelength, double.NaN)] [TestCase(MidWavelength, -MidWavelength, -MidWavelength)] public void WhitePointOnLocus(int wavelength, double expectedMinNegative, double expectedMaxNegative) { var xyz = Xyz.FromSpd(Spd.Monochromatic(wavelength), Observer.Degree2); var white = WhitePoint.FromXyz(xyz).ToChromaticity(); - var spectral = new Spectral(Observer.Degree2, white); - Assert.That(spectral.MinNegativeWavelength, Is.EqualTo(expectedMinNegative).Within(0.000000000001)); - Assert.That(spectral.MaxNegativeWavelength, Is.EqualTo(expectedMaxNegative).Within(0.000000000001)); + var boundary = new SpectralBoundary(Observer.Degree2, white); + Assert.That(boundary.MinNegativeWavelength, Is.EqualTo(expectedMinNegative).Within(0.000000000001)); + Assert.That(boundary.MaxNegativeWavelength, Is.EqualTo(expectedMaxNegative).Within(0.000000000001)); } } \ No newline at end of file diff --git a/Unicolour.Tests/SpectralJsTests.cs b/Unicolour.Tests/SpectralJsTests.cs new file mode 100644 index 0000000..a198ad4 --- /dev/null +++ b/Unicolour.Tests/SpectralJsTests.cs @@ -0,0 +1,145 @@ +using System; +using System.Linq; +using NUnit.Framework; +using Wacton.Unicolour.Experimental; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class SpectralJsTests +{ + private const int Count = 9; + private static readonly double[] Distances = [0, 0.125, 0.25, 0.375, 0.5, 0.625, 0.75, 0.875, 1]; + private static readonly string[] BlueToYellowHex = [ "#002185", "#003857", "#005348", "#007344", "#3C933E", "#6FAF35", "#A2C428", "#D4D015", "#FCD200"]; + private static readonly string[] PhthaloBlueToTitaniumWhiteHex = [ "#1E1439", "#34265A", "#5E4B8D", "#8774B4", "#AB9ACE", "#C7BBE0", "#DED6ED", "#EFECF6", "#F9FAF9"]; // based on Datasets.ArtistPaint at full concentration + private static readonly string[] BlackToWhiteHex = [ "#000000", "#262626", "#555555", "#818181", "#A6A6A6", "#C4C4C4", "#DCDCDC", "#EFEFEF", "#FFFFFF" ]; + private static readonly string[] RedToGreenHex = ["#FF0000", "#C11C15", "#96251C", "#81301E", "#7B411F", "#7D5920", "#807D1F", "#75B31B", "#00FF00"]; + private static readonly string[] PinkToCyanHex = [ "#FF1493", "#B03597", "#8C3BA1", "#7D46B2", "#775AC6", "#7378DA", "#6CA0EA", "#59CFF7", "#00FFFF"]; + + private static readonly Unicolour Blue = new(BlueToYellowHex.First()); + private static readonly Unicolour Yellow = new(BlueToYellowHex.Last()); + private static readonly Unicolour PhthaloBlue = new(PhthaloBlueToTitaniumWhiteHex.First()); + private static readonly Unicolour TitaniumWhite = new(PhthaloBlueToTitaniumWhiteHex.Last()); + private static readonly Unicolour Black = new(BlackToWhiteHex.First()); + private static readonly Unicolour White = new(BlackToWhiteHex.Last()); + private static readonly Unicolour Red = new(RedToGreenHex.First()); + private static readonly Unicolour Green = new(RedToGreenHex.Last()); + private static readonly Unicolour Pink = new(PinkToCyanHex.First()); + private static readonly Unicolour Cyan = new(PinkToCyanHex.Last()); + + // default example on https://onedayofcrypto.art/ + [Test, Sequential] + public void BlueToYellowMix( + [ValueSource(nameof(Distances))] double distance, + [ValueSource(nameof(BlueToYellowHex))] string expected) + { + var colour = SpectralJs.Mix([Blue, Yellow], [1 - distance, distance]); + AssertDifference(colour, expected); + } + + [Test] + public void BlueToYellowPalette() => AssertPalette(Blue, Yellow, Count, BlueToYellowHex); + + // showcases the difference between single-constant (e.g. spectral.js) vs two-constant (e.g. mixbox) + [Test, Sequential] + public void PhthaloBlueToTitaniumWhiteMix( + [ValueSource(nameof(Distances))] double distance, + [ValueSource(nameof(PhthaloBlueToTitaniumWhiteHex))] string expected) + { + var colour = SpectralJs.Mix([PhthaloBlue, TitaniumWhite], [1 - distance, distance]); + AssertDifference(colour, expected); + } + + [Test] + public void PhthaloBlueToTitaniumWhitePalette() => AssertPalette(PhthaloBlue, TitaniumWhite, Count, PhthaloBlueToTitaniumWhiteHex); + + + [Test, Sequential] + public void BlackToWhiteMix( + [ValueSource(nameof(Distances))] double distance, + [ValueSource(nameof(BlackToWhiteHex))] string expected) + { + var colour = SpectralJs.Mix([Black, White], [1 - distance, distance]); + AssertDifference(colour, expected); + } + + [Test] + public void BlackToWhitePalette() => AssertPalette(Black, White, Count, BlackToWhiteHex); + + [Test, Sequential] + public void RedToGreenMix( + [ValueSource(nameof(Distances))] double distance, + [ValueSource(nameof(RedToGreenHex))] string expected) + { + var colour = SpectralJs.Mix([Red, Green], [1 - distance, distance]); + AssertDifference(colour, expected); + } + + [Test] + public void RedToGreenPalette() => AssertPalette(Red, Green, Count, RedToGreenHex); + + [Test, Sequential] + public void PinkToCyanMix( + [ValueSource(nameof(Distances))] double distance, + [ValueSource(nameof(PinkToCyanHex))] string expected) + { + var colour = SpectralJs.Mix([Pink, Cyan], [1 - distance, distance]); + AssertDifference(colour, expected); + } + + [Test] + public void PinkToCyanPalette() => AssertPalette(Pink, Cyan, Count, PinkToCyanHex); + + + [Test] + public void MixNegativeRgb() + { + var negative = new Unicolour(ColourSpace.RgbLinear, -0.5, -0.5, -0.5); + var positive = new Unicolour(ColourSpace.RgbLinear, 0.5, 0.5, 0.5); + var colour = SpectralJs.Mix([negative, positive], [0.5, 0.5]); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + } + + [Test] + public void MixNoConcentration() + { + var colour = SpectralJs.Mix([StandardRgb.Black, StandardRgb.White], [-0.5, 0.0]); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + } + + [Test] + public void PaletteTwo() => AssertPalette(Blue, Yellow, 2, [BlueToYellowHex.First(), BlueToYellowHex.Last()]); + + [Test] + public void PaletteOne() => AssertPalette(Blue, Yellow, 1, [BlueToYellowHex[4]]); + + [Test] + public void PaletteZero() => AssertPalette(Blue, Yellow, 0, []); + + [Test] + public void PaletteNegative() => AssertPalette(Blue, Yellow, -1, []); + + // Unicolour intentionally does not follow Spectral.js implementation exactly + // in order to generate more accurate reflectance curves of colours being mixed + // however it should produce a result relatively close to Spectral.js + private static void AssertDifference(Unicolour colour, string expectedHex) + { + var expected = new Unicolour(expectedHex); + + var expectedRgb = expected.RgbLinear.Triplet.ToArray(); + var actualRgb = colour.RgbLinear.Triplet.ToArray(); + var rgbDifferences = expectedRgb.Zip(actualRgb, (a, b) => Math.Abs(a - b)).ToArray(); + Assert.That(rgbDifferences.Max(), Is.LessThan(0.125)); + Assert.That(rgbDifferences.Sum(), Is.LessThan(0.175)); + } + + private static void AssertPalette(Unicolour start, Unicolour end, int count, string[] expected) + { + var palette = SpectralJs.Palette(start, end, count).ToArray(); + Assert.That(palette.Length, Is.EqualTo(Math.Max(count, 0))); + for (var i = 0; i < palette.Length; i++) + { + AssertDifference(palette[i], expected[i]); + } + } +} \ No newline at end of file diff --git a/Unicolour.Tests/SystemOfLinearEquationsTests.cs b/Unicolour.Tests/SystemOfLinearEquationsTests.cs new file mode 100644 index 0000000..9337b4f --- /dev/null +++ b/Unicolour.Tests/SystemOfLinearEquationsTests.cs @@ -0,0 +1,90 @@ +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using MathNet.Numerics.LinearAlgebra; +using MathNet.Numerics.LinearAlgebra.Factorization; +using NUnit.Framework; +using Wacton.Unicolour.Experimental; +using Wacton.Unicolour.Tests.Utils; + +namespace Wacton.Unicolour.Tests; + +public class SystemOfLinearEquationsTests +{ + // ReSharper disable CollectionNeverQueried.Global - used in test case sources by name + public static readonly List LuDecompositionTestData = []; + public static readonly List SolveTestData = []; + // ReSharper restore CollectionNeverQueried.Global + + static SystemOfLinearEquationsTests() + { + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 1.0, 2.0, 3 }, { 4.0, 5.0, 6 }, { 7.0, 8.0, 9 } }).SetName("every row pivots")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 1.0, 2.0, -3 }, { 4.0, 5.0, -6 }, { 7.0, 8.0, -9 } }).SetName("every row pivots (negative)")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 9.0, 8.0, 7 }, { 6.0, 5.0, 4 }, { 3.0, 2.0, 1 } }).SetName("no row pivots")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { -9.0, 8.0, 7 }, { -6.0, 5.0, 4 }, { -3.0, 2.0, 1 } }).SetName("no row pivots (negative)")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { -5.0, 10.0, 10 }, { -10.0, 5.0, 10 }, { 10.0, -5.0, 10 } }).SetName("multiple pivots")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 4.0, 8.0, 1 }, { 0.0, 0.0, 1 }, { 2.0, 12.0, 1 } }).SetName("pivot value also above diagonal")); // 2nd column after pivot 1 becomes 8.0, 0.0, 8 - only 2nd 8 should be found + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 0.0, 5.0, 5 }, { 5.0, 0.0, 5 }, { 5.0, 5.0, 0 } }).SetName("zero diagonal")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { 0.0, 1.0, 2 }, { 0.0, 0.0, 3 }, { 0.0, 0.0, 0 } }).SetName("zero lower triangle")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { double.PositiveInfinity, double.NegativeInfinity, double.PositiveInfinity }, { double.NegativeInfinity, double.PositiveInfinity, double.NegativeInfinity }, { double.PositiveInfinity, double.NegativeInfinity, double.PositiveInfinity } }).SetName("infinity")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { double.NaN, double.NaN, double.NaN }, { double.NaN, double.NaN, double.NaN }, { double.NaN, double.NaN, double.NaN } }).SetName("not number")); + LuDecompositionTestData.Add(new TestCaseData(new[,] { { TestUtils.RandomDouble(), TestUtils.RandomDouble(), TestUtils.RandomDouble() }, { TestUtils.RandomDouble(), TestUtils.RandomDouble(), TestUtils.RandomDouble() }, { TestUtils.RandomDouble(), TestUtils.RandomDouble(), TestUtils.RandomDouble() } }).SetName("random")); + + int[] matrixOrders = [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]; + foreach (var order in matrixOrders) + { + for (var i = 0; i < 10; i++) + { + var a = RandomMatrix(order); + var b = new double[order].Select(_ => TestUtils.RandomDouble()).ToArray(); + SolveTestData.Add(new TestCaseData(a, b).SetName($"{order}x{order} {i}")); + } + } + + SolveTestData.Add(new TestCaseData(new[,] { { double.PositiveInfinity, double.PositiveInfinity }, { double.NegativeInfinity, double.NegativeInfinity } }, new[] { double.NegativeInfinity, double.PositiveInfinity }).SetName("infinity")); + SolveTestData.Add(new TestCaseData(new[,] { { double.NaN, double.NaN }, { double.NaN, double.NaN } }, new[] { double.NaN, double.NaN }).SetName("not number")); + return; + + double[,] RandomMatrix(int order) + { + var data = new double[order, order]; + for (var row = 0; row < order; row++) + { + for (var col = 0; col < order; col++) + { + data[row, col] = TestUtils.RandomDouble(); + } + } + + return data; + } + } + + [TestCaseSource(nameof(LuDecompositionTestData))] + public void LuDecomposition(double[,] data) + { + var expected = Matrix.Build.DenseOfArray(data).LU(); + var expectedFactors = GetMathNetFactors(expected); + var expectedPivots = GetMathNetPivots(expected); + var actual = SystemOfLinearEquations.LuDecomposition(new Matrix(data)); + Assert.That(actual.factors.Data, Is.EqualTo(expectedFactors.ToArray()).Within(1e-12)); + Assert.That(actual.pivots, Is.EqualTo(expectedPivots).Within(1e-12)); + } + + // implicitly tests results of ApplyPivot, ForwardSubstitution, BackwardSubstitution + // which MathNet does not expose (and has no reason to) + [TestCaseSource(nameof(SolveTestData))] + public void Solve(double[,] a, double[] b) + { + var expected = Matrix.Build.DenseOfArray(a).LU().Solve(Vector.Build.DenseOfArray(b)); + var actual = SystemOfLinearEquations.Solve(new Matrix(a), b); + Assert.That(actual, Is.EqualTo(expected.ToArray()).Within(5e-10)); + } + + private static Matrix GetMathNetFactors(LU lu) => (Matrix)GetField(lu, "Factors"); + private static int[] GetMathNetPivots(LU lu) => (int[])GetField(lu, "Pivots"); + private static object GetField(LU lu, string name) + { + return lu.GetType().GetField(name, BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(lu)!; + } +} \ No newline at end of file diff --git a/Unicolour.Tests/Unicolour.Tests.csproj b/Unicolour.Tests/Unicolour.Tests.csproj index 36ef529..4be7ad5 100644 --- a/Unicolour.Tests/Unicolour.Tests.csproj +++ b/Unicolour.Tests/Unicolour.Tests.csproj @@ -22,6 +22,7 @@ + diff --git a/Unicolour.Tests/UnseenTests.cs b/Unicolour.Tests/UnseenTests.cs index 6e4e5c5..b502bf3 100644 --- a/Unicolour.Tests/UnseenTests.cs +++ b/Unicolour.Tests/UnseenTests.cs @@ -10,30 +10,30 @@ public class UnseenTests public void EighthColour() { var hex = $"{Unicolour.UnseenName.ToUpper()}"; - var unicolour = new Unicolour(hex); - AssertEighthColour(unicolour); + var colour = new Unicolour(hex); + AssertEighthColour(colour); } [Test] public void EighthColourWithAlphaOverride() { var hex = $"{new CultureInfo("en-GB", false).TextInfo.ToTitleCase(Unicolour.UnseenName)}"; - var unicolour = new Unicolour(hex, 1.0); - AssertEighthColour(unicolour); + var colour = new Unicolour(hex, 1.0); + AssertEighthColour(colour); } [Test] public void SeenAlphaUnaffected() { - var unicolour = new Unicolour("#C0FFEE", 1.0); - Assert.That(unicolour.Alpha.A, Is.EqualTo(1.0)); + var colour = new Unicolour("#C0FFEE", 1.0); + Assert.That(colour.Alpha.A, Is.EqualTo(1.0)); } - private static void AssertEighthColour(Unicolour unicolour) + private static void AssertEighthColour(Unicolour colour) { - Assert.That(unicolour.Hex, Is.EqualTo(Unicolour.UnseenName)); - Assert.That(unicolour.Description, Is.EqualTo(Unicolour.UnseenDescription)); - TestUtils.AssertTriplet(unicolour, new(double.NaN, double.NaN, double.NaN), 0); - Assert.That(unicolour.Alpha.A, Is.EqualTo(0.0)); + Assert.That(colour.Hex, Is.EqualTo(Unicolour.UnseenName)); + Assert.That(colour.Description, Is.EqualTo(Unicolour.UnseenDescription)); + TestUtils.AssertTriplet(colour, new(double.NaN, double.NaN, double.NaN), 0); + Assert.That(colour.Alpha.A, Is.EqualTo(0.0)); } } \ No newline at end of file diff --git a/Unicolour.Tests/Utils/ColourHeritageData.cs b/Unicolour.Tests/Utils/ColourHeritageData.cs index fdfa1f5..a15aa04 100644 --- a/Unicolour.Tests/Utils/ColourHeritageData.cs +++ b/Unicolour.Tests/Utils/ColourHeritageData.cs @@ -10,9 +10,9 @@ internal class ColourHeritageData private readonly Dictionary hued; private readonly Dictionary nan; - internal ColourHeritageData(Unicolour unicolour) + internal ColourHeritageData(Unicolour colour) { - var all = TestUtils.AllColourSpaces.ToDictionary(x => x, unicolour.GetRepresentation); + var all = TestUtils.AllColourSpaces.ToDictionary(x => x, colour.GetRepresentation); heritages = all.ToDictionary(x => x.Key, x => x.Value.Heritage); greyscale = all.ToDictionary(x => x.Key, x => x.Value.UseAsGreyscale); hued = all.ToDictionary(x => x.Key, x => x.Value.UseAsHued); diff --git a/Unicolour.Tests/Utils/RandomColours.cs b/Unicolour.Tests/Utils/RandomColours.cs index 6ce9c81..bcf91bc 100644 --- a/Unicolour.Tests/Utils/RandomColours.cs +++ b/Unicolour.Tests/Utils/RandomColours.cs @@ -119,6 +119,7 @@ internal static Unicolour UnicolourFrom(ColourSpace colourSpace, Configuration? { return new Unicolour(configuration ?? Configuration.Default, colourSpace, GetRandomTriplet(colourSpace).Tuple, Alpha()); } + private static ColourTriplet GetRandomTriplet(ColourSpace colourSpace) { return colourSpace switch diff --git a/Unicolour.Tests/Utils/TestUtils.cs b/Unicolour.Tests/Utils/TestUtils.cs index c7a0a37..5676a88 100644 --- a/Unicolour.Tests/Utils/TestUtils.cs +++ b/Unicolour.Tests/Utils/TestUtils.cs @@ -140,10 +140,10 @@ internal static void AssertTriplet(ColourTriplet actual, ColourTriplet expected, AssertTripletValue(actual.Third, expected.Third, tolerance, FailMessage("Channel 3"), actual.HueIndex == 2); } - internal static void AssertTriplet(Unicolour unicolour, ColourTriplet expected, double tolerance) where T : ColourRepresentation + internal static void AssertTriplet(Unicolour colour, ColourTriplet expected, double tolerance) where T : ColourRepresentation { var colourSpace = RepresentationTypeToColourSpace[typeof(T)]; - var colourRepresentation = unicolour.GetRepresentation(colourSpace); + var colourRepresentation = colour.GetRepresentation(colourSpace); AssertTriplet(colourRepresentation.Triplet, expected, tolerance); } @@ -175,62 +175,62 @@ internal static void AssertMixed(ColourTriplet triplet, double alpha, (double fi Assert.That(alpha, Is.EqualTo(expected.alpha).Within(MixTolerance), "Alpha"); } - internal static void AssertNoPropertyError(Unicolour unicolour) + internal static void AssertNoPropertyError(Unicolour colour) { Assert.DoesNotThrow(AccessProperties); return; void AccessProperties() { - AccessProperty(() => unicolour.Alpha); - AccessProperty(() => unicolour.Cam02); - AccessProperty(() => unicolour.Cam16); - AccessProperty(() => unicolour.Chromaticity); - AccessProperty(() => unicolour.Configuration); - AccessProperty(() => unicolour.Description); - AccessProperty(() => unicolour.DominantWavelength); - AccessProperty(() => unicolour.ExcitationPurity); - AccessProperty(() => unicolour.Hct); - AccessProperty(() => unicolour.Hex); - AccessProperty(() => unicolour.Hpluv); - AccessProperty(() => unicolour.Hsb); - AccessProperty(() => unicolour.Hsi); - AccessProperty(() => unicolour.Hsl); - AccessProperty(() => unicolour.Hsluv); - AccessProperty(() => unicolour.Hwb); - AccessProperty(() => unicolour.Icc); - AccessProperty(() => unicolour.Ictcp); - AccessProperty(() => unicolour.Ipt); - AccessProperty(() => unicolour.IsImaginary); - AccessProperty(() => unicolour.IsInDisplayGamut); - AccessProperty(() => unicolour.Jzazbz); - AccessProperty(() => unicolour.Jzczhz); - AccessProperty(() => unicolour.Lab); - AccessProperty(() => unicolour.Lchab); - AccessProperty(() => unicolour.Lchuv); - AccessProperty(() => unicolour.Luv); - AccessProperty(() => unicolour.Oklab); - AccessProperty(() => unicolour.Oklch); - AccessProperty(() => unicolour.Okhsl); - AccessProperty(() => unicolour.Okhsv); - AccessProperty(() => unicolour.Okhwb); - AccessProperty(() => unicolour.RelativeLuminance); - AccessProperty(() => unicolour.Rgb); - AccessProperty(() => unicolour.Rgb.Byte255); - AccessProperty(() => unicolour.RgbLinear); - AccessProperty(() => unicolour.Temperature); - AccessProperty(() => unicolour.Tsl); - AccessProperty(() => unicolour.Wxy); - AccessProperty(() => unicolour.Xyb); - AccessProperty(() => unicolour.Xyy); - AccessProperty(() => unicolour.Xyz); - AccessProperty(() => unicolour.Ypbpr); - AccessProperty(() => unicolour.Ycbcr); - AccessProperty(() => unicolour.Ycgco); - AccessProperty(() => unicolour.Yuv); - AccessProperty(() => unicolour.Yiq); - AccessProperty(() => unicolour.Ydbdr); - AccessProperty(unicolour.ToString); + AccessProperty(() => colour.Alpha); + AccessProperty(() => colour.Cam02); + AccessProperty(() => colour.Cam16); + AccessProperty(() => colour.Chromaticity); + AccessProperty(() => colour.Configuration); + AccessProperty(() => colour.Description); + AccessProperty(() => colour.DominantWavelength); + AccessProperty(() => colour.ExcitationPurity); + AccessProperty(() => colour.Hct); + AccessProperty(() => colour.Hex); + AccessProperty(() => colour.Hpluv); + AccessProperty(() => colour.Hsb); + AccessProperty(() => colour.Hsi); + AccessProperty(() => colour.Hsl); + AccessProperty(() => colour.Hsluv); + AccessProperty(() => colour.Hwb); + AccessProperty(() => colour.Icc); + AccessProperty(() => colour.Ictcp); + AccessProperty(() => colour.Ipt); + AccessProperty(() => colour.IsImaginary); + AccessProperty(() => colour.IsInRgbGamut); + AccessProperty(() => colour.Jzazbz); + AccessProperty(() => colour.Jzczhz); + AccessProperty(() => colour.Lab); + AccessProperty(() => colour.Lchab); + AccessProperty(() => colour.Lchuv); + AccessProperty(() => colour.Luv); + AccessProperty(() => colour.Oklab); + AccessProperty(() => colour.Oklch); + AccessProperty(() => colour.Okhsl); + AccessProperty(() => colour.Okhsv); + AccessProperty(() => colour.Okhwb); + AccessProperty(() => colour.RelativeLuminance); + AccessProperty(() => colour.Rgb); + AccessProperty(() => colour.Rgb.Byte255); + AccessProperty(() => colour.RgbLinear); + AccessProperty(() => colour.Temperature); + AccessProperty(() => colour.Tsl); + AccessProperty(() => colour.Wxy); + AccessProperty(() => colour.Xyb); + AccessProperty(() => colour.Xyy); + AccessProperty(() => colour.Xyz); + AccessProperty(() => colour.Ypbpr); + AccessProperty(() => colour.Ycbcr); + AccessProperty(() => colour.Ycgco); + AccessProperty(() => colour.Yuv); + AccessProperty(() => colour.Yiq); + AccessProperty(() => colour.Ydbdr); + AccessProperty(colour.ToString); } void AccessProperty(Func getProperty) diff --git a/Unicolour.Tests/VisionDeficiencyTests.cs b/Unicolour.Tests/VisionDeficiencyTests.cs index 0cbd413..1d5f512 100644 --- a/Unicolour.Tests/VisionDeficiencyTests.cs +++ b/Unicolour.Tests/VisionDeficiencyTests.cs @@ -21,8 +21,8 @@ public class VisionDeficiencyTests [TestCase(nameof(StandardRgb.White), 255, 255, 255)] public void Protanopia(string colourName, double expectedR, double expectedG, double expectedB) { - var unicolour = StandardRgb.Lookup[colourName]; - var simulatedColour = unicolour.SimulateProtanopia(); + var colour = StandardRgb.Lookup[colourName]; + var simulatedColour = colour.Simulate(Cvd.Protanopia); var simulatedRgb = simulatedColour.Rgb.Byte255.ConstrainedTriplet; TestUtils.AssertTriplet(simulatedRgb, new(expectedR, expectedG, expectedB), 1); } @@ -37,8 +37,8 @@ public void Protanopia(string colourName, double expectedR, double expectedG, do [TestCase(nameof(StandardRgb.White), 255, 255, 255)] public void Deuteranopia(string colourName, double expectedR, double expectedG, double expectedB) { - var unicolour = StandardRgb.Lookup[colourName]; - var simulatedColour = unicolour.SimulateDeuteranopia(); + var colour = StandardRgb.Lookup[colourName]; + var simulatedColour = colour.Simulate(Cvd.Deuteranopia); var simulatedRgb = simulatedColour.Rgb.Byte255.ConstrainedTriplet; TestUtils.AssertTriplet(simulatedRgb, new(expectedR, expectedG, expectedB), 1); } @@ -53,8 +53,8 @@ public void Deuteranopia(string colourName, double expectedR, double expectedG, [TestCase(nameof(StandardRgb.White), 255, 255, 255)] public void Tritanopia(string colourName, double expectedR, double expectedG, double expectedB) { - var unicolour = StandardRgb.Lookup[colourName]; - var simulatedColour = unicolour.SimulateTritanopia(); + var colour = StandardRgb.Lookup[colourName]; + var simulatedColour = colour.Simulate(Cvd.Tritanopia); var simulatedRgb = simulatedColour.Rgb.Byte255.ConstrainedTriplet; TestUtils.AssertTriplet(simulatedRgb, new(expectedR, expectedG, expectedB), 2); } @@ -69,8 +69,8 @@ public void Tritanopia(string colourName, double expectedR, double expectedG, do [TestCase(nameof(StandardRgb.White), 255, 255, 255)] public void Achromatopsia(string colourName, double expectedR, double expectedG, double expectedB) { - var unicolour = StandardRgb.Lookup[colourName]; - var simulatedColour = unicolour.SimulateAchromatopsia(); + var colour = StandardRgb.Lookup[colourName]; + var simulatedColour = colour.Simulate(Cvd.Achromatopsia); var simulatedRgb = simulatedColour.Rgb.Byte255.ConstrainedTriplet; TestUtils.AssertTriplet(simulatedRgb, new(expectedR, expectedG, expectedB), 1); } @@ -78,32 +78,32 @@ public void Achromatopsia(string colourName, double expectedR, double expectedG, [Test] public void ProtanopiaNotNumber() { - var unicolour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); - var simulatedColour = unicolour.SimulateProtanopia(); + var colour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); + var simulatedColour = colour.Simulate(Cvd.Protanopia); Assert.That(simulatedColour.Rgb.IsNaN); } [Test] public void DeuteranopiaNotNumber() { - var unicolour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); - var simulatedColour = unicolour.SimulateDeuteranopia(); + var colour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); + var simulatedColour = colour.Simulate(Cvd.Deuteranopia); Assert.That(simulatedColour.Rgb.IsNaN); } [Test] public void TritanopiaNotNumber() { - var unicolour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); - var simulatedColour = unicolour.SimulateTritanopia(); + var colour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); + var simulatedColour = colour.Simulate(Cvd.Tritanopia); Assert.That(simulatedColour.Rgb.IsNaN); } [Test] public void AchromatopsiaNotNumber() { - var unicolour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); - var simulatedColour = unicolour.SimulateAchromatopsia(); + var colour = new Unicolour(ColourSpace.Rgb, double.NaN, double.NaN, double.NaN); + var simulatedColour = colour.Simulate(Cvd.Achromatopsia); Assert.That(simulatedColour.Rgb.IsNaN); } } \ No newline at end of file diff --git a/Unicolour.sln b/Unicolour.sln index 7324cfd..717801e 100644 --- a/Unicolour.sln +++ b/Unicolour.sln @@ -18,6 +18,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Heatmaps", "Example EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example.Web", "Example.Web\Example.Web.csproj", "{0E69D6F9-3B93-46D7-88C1-486A52CCC93D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Unicolour.Experimental", "Unicolour.Experimental\Unicolour.Experimental.csproj", "{F4481154-8433-44B4-B3A8-72C2C9098554}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -60,5 +62,9 @@ Global {0E69D6F9-3B93-46D7-88C1-486A52CCC93D}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E69D6F9-3B93-46D7-88C1-486A52CCC93D}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E69D6F9-3B93-46D7-88C1-486A52CCC93D}.Release|Any CPU.Build.0 = Release|Any CPU + {F4481154-8433-44B4-B3A8-72C2C9098554}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F4481154-8433-44B4-B3A8-72C2C9098554}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F4481154-8433-44B4-B3A8-72C2C9098554}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F4481154-8433-44B4-B3A8-72C2C9098554}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal diff --git a/Unicolour/Cmy.cs b/Unicolour/Cmy.cs index 0bebbaf..f8c9919 100644 --- a/Unicolour/Cmy.cs +++ b/Unicolour/Cmy.cs @@ -8,7 +8,7 @@ internal static class Cmy { - internal static double[] FromUnicolour(Unicolour unicolour) => FromRgb(unicolour.Rgb); + internal static double[] FromUnicolour(Unicolour colour) => FromRgb(colour.Rgb); internal static double[] FromRgb(Rgb rgb) { var (r, g, b) = rgb.ConstrainedTriplet.Tuple; diff --git a/Unicolour/ColourDescription.cs b/Unicolour/ColourDescription.cs index c7646c4..0159dcd 100644 --- a/Unicolour/ColourDescription.cs +++ b/Unicolour/ColourDescription.cs @@ -1,5 +1,10 @@ namespace Wacton.Unicolour; +/* + * this is not any kind of "official" naming + * just an attempt to provide a vaguely useful description + * based on measurement instead of opinion + */ internal record ColourDescription(string description) { private readonly string description = description; diff --git a/Unicolour/Cvd.cs b/Unicolour/Cvd.cs new file mode 100644 index 0000000..c06292f --- /dev/null +++ b/Unicolour/Cvd.cs @@ -0,0 +1,9 @@ +namespace Wacton.Unicolour; + +public enum Cvd +{ + Protanopia, + Deuteranopia, + Tritanopia, + Achromatopsia +} \ No newline at end of file diff --git a/Unicolour/GamutMap.cs b/Unicolour/GamutMap.cs new file mode 100644 index 0000000..29e84f1 --- /dev/null +++ b/Unicolour/GamutMap.cs @@ -0,0 +1,8 @@ +namespace Wacton.Unicolour; + +public enum GamutMap +{ + RgbClipping, + OklchChromaReduction, + WxyPurityReduction +} \ No newline at end of file diff --git a/Unicolour/GamutMapping.cs b/Unicolour/GamutMapping.cs index 8190118..dbf01fe 100644 --- a/Unicolour/GamutMapping.cs +++ b/Unicolour/GamutMapping.cs @@ -2,6 +2,30 @@ namespace Wacton.Unicolour; internal static class GamutMapping { + internal static Unicolour ToRgbGamut(Unicolour colour, GamutMap gamutMap) + { + if (colour.IsInRgbGamut) + { + return new Unicolour(colour.Configuration, ColourSpace.Rgb, colour.Rgb.Triplet.Tuple, colour.Alpha.A); + } + + // could do some early checks for unusual values (NaN, infinity, etc) + // but don't want to add more edge cases to the algorithms + // which should end before too long anyway + return gamutMap switch + { + GamutMap.RgbClipping => RgbClipping(colour), + GamutMap.OklchChromaReduction => OklchChromaReduction(colour), + GamutMap.WxyPurityReduction => WxyPurityReduction(colour), + _ => throw new ArgumentOutOfRangeException(nameof(gamutMap), gamutMap, null) + }; + } + + private static Unicolour RgbClipping(Unicolour colour) + { + return new Unicolour(colour.Configuration, ColourSpace.Rgb, colour.Rgb.ConstrainedTriplet.Tuple, colour.Alpha.A); + } + /* * adapted from https://www.w3.org/TR/css-color-4/#css-gamut-mapping & https://www.w3.org/TR/css-color-4/#binsearch * the pseudocode doesn't appear to handle the edge case scenario where: @@ -12,14 +36,12 @@ internal static class GamutMapping * - even if the search did execute, would not return clipped variant since ΔE is *too small*, and min never changes from 0 * so need to clip if the mapped colour is somehow out-of-gamut (i.e. not processed) */ - internal static Unicolour ToRgbGamut(Unicolour unicolour) + private static Unicolour OklchChromaReduction(Unicolour colour) { - var config = unicolour.Configuration; - var rgb = unicolour.Rgb; - var alpha = unicolour.Alpha.A; - if (unicolour.IsInDisplayGamut) return new Unicolour(config, ColourSpace.Rgb, rgb.Triplet.Tuple, alpha); + var config = colour.Configuration; + var alpha = colour.Alpha.A; - var oklch = unicolour.Oklch; + var oklch = colour.Oklch; if (oklch.L >= 1.0) return new Unicolour(config, ColourSpace.Rgb, 1, 1, 1, alpha); if (oklch.L <= 0.0) return new Unicolour(config, ColourSpace.Rgb, 0, 0, 0, alpha); @@ -39,7 +61,7 @@ internal static Unicolour ToRgbGamut(Unicolour unicolour) iterations++; var chroma = (minChroma + maxChroma) / 2.0; - current = FromOklchWithChroma(chroma); + current = new Unicolour(config, ColourSpace.Oklch, oklch.L, chroma, oklch.H, alpha); if (minChromaInGamut && current.Rgb.IsInGamut) { @@ -47,7 +69,7 @@ internal static Unicolour ToRgbGamut(Unicolour unicolour) continue; } - var clipped = FromRgbWithClipping(current.Rgb); + var clipped = RgbClipping(current); var deltaE = clipped.Difference(current, DeltaE.Ok); var isNoticeableDifference = deltaE >= jnd; @@ -73,14 +95,31 @@ internal static Unicolour ToRgbGamut(Unicolour unicolour) } // in case while loop never executes (e.g. Oklch.C == 0) - current ??= FromOklchWithChroma(oklch.C); + current ??= new Unicolour(config, ColourSpace.Oklch, oklch.Triplet.Tuple, alpha); // it's possible for the "current" colour to still be out of RGB gamut, either because: // a) the original OKLCH was not processed (chroma too low) and was already out of RGB gamut // b) the algorithm converged on an OKLCH that is out of RGB gamut (happens ~5% of the time for me with using random OKLCH inputs) - return current.IsInDisplayGamut ? current : FromRgbWithClipping(current.Rgb); + return current.IsInRgbGamut ? current : RgbClipping(current); + } + + private static Unicolour WxyPurityReduction(Unicolour colour) + { + var config = colour.Configuration; + var alpha = colour.Alpha.A; + + var (w, x, y) = colour.Wxy.Triplet; + x = x.Clamp(0.0, 1.0); // no point starting with purity outwith 0 - 100% + y = y.Clamp(0.0, 1.0); // luminance also needs to be bound for a sensible result + var current = new Unicolour(config, ColourSpace.Wxy, w, x, y, alpha); + while (!current.IsInRgbGamut && x > 0) + { + x -= 0.001; // at most 1000 iterations from purity of 1 to 0 + current = new Unicolour(config, ColourSpace.Wxy, w, x, y, alpha); + } - Unicolour FromOklchWithChroma(double chroma) => new(config, ColourSpace.Oklch, oklch.L, chroma, oklch.H, alpha); - Unicolour FromRgbWithClipping(Rgb unclippedRgb) => new(config, ColourSpace.Rgb, unclippedRgb.ConstrainedTriplet.Tuple, alpha); + // if purity has been reduced from 100% to 0% and no colours have been found to be in gamut + // then there is no solution that is in gamut + return current.IsInRgbGamut ? current : new Unicolour(config, ColourSpace.Wxy, double.NaN, double.NaN, double.NaN, alpha); } } \ No newline at end of file diff --git a/Unicolour/Interpolation.cs b/Unicolour/Interpolation.cs index 88ff381..350f247 100644 --- a/Unicolour/Interpolation.cs +++ b/Unicolour/Interpolation.cs @@ -38,6 +38,21 @@ internal static Unicolour Mix(Unicolour startColour, Unicolour endColour, Colour return new Unicolour(config, heritage, colourSpace, first, second, third, alpha); } + internal static IEnumerable Palette(Unicolour startColour, Unicolour endColour, ColourSpace colourSpace, int count, HueSpan hueSpan, bool premultiplyAlpha) + { + count = Math.Max(count, 0); + var (start, end, _) = AdjustConfiguration(startColour, endColour); // saves doing this N times via Mix() if configs are different + + var palette = new List(); + for (var i = 0; i < count; i++) + { + var distance = count == 1 ? 0.5 : i / (double)(count - 1); + palette.Add(Mix(start, end, colourSpace, distance, hueSpan, premultiplyAlpha)); + } + + return palette; + } + // TODO: explore if this is worthwhile // internal static Unicolour MixChannels(Unicolour startColour, Unicolour endColour, double distance, bool premultiplyAlpha) // { @@ -53,7 +68,9 @@ internal static Unicolour Mix(Unicolour startColour, Unicolour endColour, Colour private static (Unicolour start, Unicolour end, Configuration config) AdjustConfiguration(Unicolour start, Unicolour end) { var config = start.Configuration; - return end.Configuration == config ? (start, end, config) : (start, end.ConvertToConfiguration(config), config); + return end.Configuration == config + ? (start, end, config) + : (start, end.ConvertToConfiguration(config), config); } private static (ColourTriplet start, ColourTriplet end) GetTripletsToInterpolate( diff --git a/Unicolour/Matrix.cs b/Unicolour/Matrix.cs index a96b009..a0d25ce 100644 --- a/Unicolour/Matrix.cs +++ b/Unicolour/Matrix.cs @@ -8,15 +8,17 @@ internal class Matrix internal double this[int row, int col] { get => Data[row, col]; - // set => Data[row, col] = value; + set => Data[row, col] = value; } - private int Rows => Data.GetLength(0); - private int Cols => Data.GetLength(1); + internal int Rows { get; } + internal int Cols { get; } internal Matrix(double[,] data) { Data = data; + Rows = Data.GetLength(0); + Cols = Data.GetLength(1); } internal Matrix Multiply(Matrix other) diff --git a/Unicolour/Pigment.cs b/Unicolour/Pigment.cs index 39d8977..f7f1959 100644 --- a/Unicolour/Pigment.cs +++ b/Unicolour/Pigment.cs @@ -6,11 +6,12 @@ public class Pigment private int StartWavelength { get; } private int WavelengthInterval { get; } private int[] Wavelengths { get; } - private SpectralCoefficients? R { get; } // reflectance - private SpectralCoefficients? K { get; } // absorption - private SpectralCoefficients? S { get; } // scattering - private double? K1 { get; } - private double? K2 { get; } + + public SpectralCoefficients? R { get; } // reflectance + public SpectralCoefficients? K { get; } // absorption + public SpectralCoefficients? S { get; } // scattering + public double? K1 { get; } + public double? K2 { get; } public string Name { get; } public Pigment(int startWavelength, int wavelengthInterval, double[] r, string name = Utils.Unnamed) @@ -30,7 +31,7 @@ public Pigment(int startWavelength, int wavelengthInterval, double[] k, double[] WavelengthInterval = wavelengthInterval; K = new SpectralCoefficients(startWavelength, wavelengthInterval, k); S = new SpectralCoefficients(startWavelength, wavelengthInterval, s); - Wavelengths = K.Wavelengths; + Wavelengths = K.Wavelengths.Length <= S.Wavelengths.Length ? K.Wavelengths : S.Wavelengths; K1 = k1; K2 = k2; Name = name; diff --git a/Unicolour/Spectral.cs b/Unicolour/SpectralBoundary.cs similarity index 94% rename from Unicolour/Spectral.cs rename to Unicolour/SpectralBoundary.cs index 36edcef..d955406 100644 --- a/Unicolour/Spectral.cs +++ b/Unicolour/SpectralBoundary.cs @@ -1,6 +1,6 @@ namespace Wacton.Unicolour; -internal class Spectral +internal class SpectralBoundary { private readonly Observer observer; private readonly Chromaticity whiteChromaticity; @@ -14,7 +14,7 @@ internal class Spectral internal double MinNegativeWavelength => minNegativeWavelength.Value; internal double MaxNegativeWavelength => maxNegativeWavelength.Value; - internal Spectral(Observer observer, Chromaticity whiteChromaticity) + internal SpectralBoundary(Observer observer, Chromaticity whiteChromaticity) { this.observer = observer; this.whiteChromaticity = whiteChromaticity; @@ -24,20 +24,20 @@ internal Spectral(Observer observer, Chromaticity whiteChromaticity) minNegativeWavelength = new Lazy(() => { // possible to be null if user sets white point as monochromatic light (i.e. on the boundary itself) - var intersects = FindBoundaryIntersects(lineOfPurples.Value.EndChromaticity); + var intersects = FindIntersects(lineOfPurples.Value.EndChromaticity); return intersects == null ? double.NaN : -intersects.Far.Wavelength; }); maxNegativeWavelength = new Lazy(() => { // possible to be null if user sets white point as monochromatic light (i.e. on the boundary itself) - var intersects = FindBoundaryIntersects(lineOfPurples.Value.StartChromaticity); + var intersects = FindIntersects(lineOfPurples.Value.StartChromaticity); return intersects == null ? double.NaN : -intersects.Far.Wavelength; }); } - internal Intersects? FindBoundaryIntersects(Chromaticity sample) => FindBoundaryIntersects(sample, whiteChromaticity); - private Intersects? FindBoundaryIntersects(Chromaticity sample, Chromaticity white) + internal Intersects? FindIntersects(Chromaticity sample) => FindIntersects(sample, whiteChromaticity); + private Intersects? FindIntersects(Chromaticity sample, Chromaticity white) { if (sample == white) return null; var whiteToSampleLine = Line.FromPoints(white.Xy, sample.Xy); @@ -105,7 +105,7 @@ internal Chromaticity GetChromaticity(double dominantWavelength, double purity) ); } - internal bool IsImaginary(Chromaticity chromaticity) + internal bool IsOutside(Chromaticity chromaticity) { /* * although FindBoundaryIntersects takes a "white" chromaticity as an argument @@ -115,7 +115,7 @@ internal bool IsImaginary(Chromaticity chromaticity) */ const double sampleOffset = 0.00001; var offsetSample = new Chromaticity(chromaticity.X, chromaticity.Y + sampleOffset); - var intersects = FindBoundaryIntersects(offsetSample, chromaticity); + var intersects = FindIntersects(offsetSample, chromaticity); // no intersects; point is definitely outside the locus if (intersects == null) return true; diff --git a/Unicolour/Unicolour.cs b/Unicolour/Unicolour.cs index d1b8635..f2bc739 100644 --- a/Unicolour/Unicolour.cs +++ b/Unicolour/Unicolour.cs @@ -85,11 +85,11 @@ public partial class Unicolour : IEquatable public Hct Hct => hct.Value; public Channels Icc => icc.Value; - public string Hex => isUnseen ? UnseenName : !IsInDisplayGamut ? "-" : Rgb.Byte255.ConstrainedHex; - public bool IsInDisplayGamut => Rgb.IsInGamut; + public string Hex => isUnseen ? UnseenName : !IsInRgbGamut ? "-" : Rgb.Byte255.ConstrainedHex; + public bool IsInRgbGamut => Rgb.IsInGamut; public string Description => isUnseen ? UnseenDescription : string.Join(" ", ColourDescription.Get(Hsl)); public Chromaticity Chromaticity => Xyy.UseAsNaN ? new Chromaticity(double.NaN, double.NaN) : Xyy.Chromaticity; - public bool IsImaginary => Configuration.Xyz.Spectral.IsImaginary(Chromaticity); + public bool IsImaginary => Configuration.Xyz.SpectralBoundary.IsOutside(Chromaticity); public double RelativeLuminance => Xyz.UseAsNaN ? double.NaN : Xyz.Y; // will meet https://www.w3.org/TR/WCAG21/#dfn-relative-luminance when sRGB (middle row of RGB -> XYZ matrix) public Temperature Temperature => temperature.Value; public double DominantWavelength => Wxy.UseAsNaN || Wxy.UseAsGreyscale ? double.NaN : Wxy.DominantWavelength; @@ -166,18 +166,20 @@ public Unicolour Mix(Unicolour other, ColourSpace colourSpace, double amount = 0 return Interpolation.Mix(this, other, colourSpace, amount, hueSpan, premultiplyAlpha); } + public IEnumerable Palette(Unicolour other, ColourSpace colourSpace, int count, HueSpan hueSpan = HueSpan.Shorter, bool premultiplyAlpha = true) + { + return Interpolation.Palette(this, other, colourSpace, count, hueSpan, premultiplyAlpha); + } + // TODO: explore if this is worthwhile // public Unicolour MixChannels(Unicolour other, double amount = 0.5, bool premultiplyAlpha = true) // { // return Interpolation.MixChannels(this, other, amount, premultiplyAlpha); // } - - public Unicolour SimulateProtanopia() => VisionDeficiency.SimulateProtanopia(this); - public Unicolour SimulateDeuteranopia() => VisionDeficiency.SimulateDeuteranopia(this); - public Unicolour SimulateTritanopia() => VisionDeficiency.SimulateTritanopia(this); - public Unicolour SimulateAchromatopsia() => VisionDeficiency.SimulateAchromatopsia(this); - public Unicolour MapToGamut() => GamutMapping.ToRgbGamut(this); + public Unicolour Simulate(Cvd cvd) => VisionDeficiency.Simulate(cvd, this); + + public Unicolour MapToRgbGamut(GamutMap gamutMap = GamutMap.OklchChromaReduction) => GamutMapping.ToRgbGamut(this, gamutMap); public Unicolour ConvertToConfiguration(Configuration config) { diff --git a/Unicolour/Unicolour.csproj b/Unicolour/Unicolour.csproj index 539ee13..9d84f82 100644 --- a/Unicolour/Unicolour.csproj +++ b/Unicolour/Unicolour.csproj @@ -16,7 +16,7 @@ True Resources\Unicolour.png 4.8.0 - colour color RGB HSB HSV HSL HWB HSI XYZ xyY WXY LAB LUV LCH LCHab LCHuv HSLuv HPLuv YPbPr YCbCr YCgCo YUV YIQ YDbDr TSL XYB IPT ICtCp JzAzBz JzCzHz Oklab Oklch Okhsv Okhsl Okhwb CAM02 CAM16 HCT ICC icc-profile CMYK CMYKOGV converter colour-converter colour-conversion color-converter color-conversion colour-space colour-spaces color-space color-spaces interpolation colour-interpolation color-interpolation colour-mixing color-mixing comparison colour-comparison color-comparison contrast luminance deltaE chromaticity display-p3 rec-2020 rec-601 rec-709 A98 ProPhoto ACES ACEScg ACEScct ACEScc xvYCC PAL NTSC SECAM gamut-mapping temperature cct duv cvd colour-vision-deficiency color-vision-deficiency colour-blindness color-blindness protanopia deuteranopia tritanopia achromatopsia spd dominant-wavelength excitation-purity imaginary-color imaginary-colour + colour color RGB HSB HSV HSL HWB HSI XYZ xyY WXY LAB LUV LCH LCHab LCHuv HSLuv HPLuv YPbPr YCbCr YCgCo YUV YIQ YDbDr TSL XYB IPT ICtCp JzAzBz JzCzHz Oklab Oklch Okhsv Okhsl Okhwb CAM02 CAM16 HCT ICC icc-profile CMYK CMYKOGV converter colour-converter colour-conversion color-converter color-conversion colour-space colour-spaces color-space color-spaces interpolation colour-interpolation color-interpolation colour-mixing color-mixing color-palette colour-palette comparison colour-comparison color-comparison contrast luminance delta-e deltaE chromaticity display-p3 rec-2020 rec-601 rec-709 A98 ProPhoto ACES ACEScg ACEScct ACEScc xvYCC PAL NTSC SECAM gamut-map gamut-mapping temperature cct duv cvd colour-vision-deficiency color-vision-deficiency colour-blindness color-blindness protanopia deuteranopia tritanopia achromatopsia spd dominant-wavelength excitation-purity imaginary-color imaginary-colour pigments pigment-mixing kubelka-munk paints paint-mixing Add ICC profile support (TRC transforms, Input device, Display device) Resources\Unicolour.ico LICENSE @@ -44,6 +44,10 @@ <_Parameter1>$(AssemblyName).Datasets + + + <_Parameter1>$(AssemblyName).Experimental + diff --git a/Unicolour/VisionDeficiency.cs b/Unicolour/VisionDeficiency.cs index 40655c0..a27d40b 100644 --- a/Unicolour/VisionDeficiency.cs +++ b/Unicolour/VisionDeficiency.cs @@ -24,26 +24,26 @@ internal static class VisionDeficiency { -0.078411, +0.930809, +0.147602 }, { +0.004733, +0.691367, +0.303900 } }); + + internal static Unicolour Simulate(Cvd cvd, Unicolour colour) + { + var simulatedRgbLinear = cvd switch + { + Cvd.Protanopia => ApplySimulationMatrix(colour, Protanomaly), + Cvd.Deuteranopia => ApplySimulationMatrix(colour, Deuteranomaly), + Cvd.Tritanopia => ApplySimulationMatrix(colour, Tritanomaly), + Cvd.Achromatopsia => new(colour.RelativeLuminance, colour.RelativeLuminance, colour.RelativeLuminance), + _ => throw new ArgumentOutOfRangeException(nameof(cvd), cvd, null) + }; + + return new Unicolour(colour.Configuration, ColourSpace.RgbLinear, simulatedRgbLinear.Tuple, colour.Alpha.A); + } - private static Unicolour SimulateCvd(Unicolour unicolour, Matrix cvdMatrix) + private static ColourTriplet ApplySimulationMatrix(Unicolour colour, Matrix cvdMatrix) { - var config = unicolour.Configuration; - // since simulated RGB-Linear often results in values outwith 0 - 1, seems unnecessary to use constrained inputs - var rgbLinearMatrix = Matrix.FromTriplet(unicolour.RgbLinear.Triplet); + var rgbLinearMatrix = Matrix.FromTriplet(colour.RgbLinear.Triplet); var simulatedRgbLinearMatrix = cvdMatrix.Multiply(rgbLinearMatrix); - return new Unicolour(config, ColourSpace.RgbLinear, simulatedRgbLinearMatrix.ToTriplet().Tuple); - } - - internal static Unicolour SimulateProtanopia(Unicolour unicolour) => SimulateCvd(unicolour, Protanomaly); - internal static Unicolour SimulateDeuteranopia(Unicolour unicolour) => SimulateCvd(unicolour, Deuteranomaly); - internal static Unicolour SimulateTritanopia(Unicolour unicolour) => SimulateCvd(unicolour, Tritanomaly); - internal static Unicolour SimulateAchromatopsia(Unicolour unicolour) - { - var config = unicolour.Configuration; - - // luminance is based on Linear RGB, so needs to be companded back into chosen RGB space - var rgbLuminance = config.Rgb.CompandFromLinear(unicolour.RelativeLuminance); - return new Unicolour(config, ColourSpace.Rgb, rgbLuminance, rgbLuminance, rgbLuminance); + return simulatedRgbLinearMatrix.ToTriplet(); } } \ No newline at end of file diff --git a/Unicolour/Wxy.cs b/Unicolour/Wxy.cs index a1f6d82..e25eb1a 100644 --- a/Unicolour/Wxy.cs +++ b/Unicolour/Wxy.cs @@ -34,9 +34,9 @@ internal static Wxy FromXyy(Xyy xyy, XyzConfiguration xyzConfig) var intersects = xyy.UseAsNaN || xyy.UseAsGreyscale ? null - : xyzConfig.Spectral.FindBoundaryIntersects(chromaticity); + : xyzConfig.SpectralBoundary.FindIntersects(chromaticity); - var w = intersects?.DominantWavelength ?? Spectral.MinWavelength; + var w = intersects?.DominantWavelength ?? SpectralBoundary.MinWavelength; var x = intersects?.ExcitationPurity ?? 0; var y = luminance; return new Wxy(w, x, y, ColourHeritage.From(xyy)); @@ -57,7 +57,7 @@ internal static Xyy ToXyy(Wxy wxy, XyzConfiguration xyzConfig) } else { - chromaticity = xyzConfig.Spectral.GetChromaticity(wavelength, purity); + chromaticity = xyzConfig.SpectralBoundary.GetChromaticity(wavelength, purity); } return new Xyy(chromaticity.X, chromaticity.Y, luminance, ColourHeritage.From(wxy)); @@ -70,14 +70,14 @@ internal static double WavelengthToDegree(double wavelength, XyzConfiguration xy double degree; if (wavelength >= 0) { - var (min, max) = (Spectral.MinWavelength, Spectral.MaxWavelength); + var (min, max) = (SpectralBoundary.MinWavelength, SpectralBoundary.MaxWavelength); var clamped = wavelength.Clamp(min, max); var normalised = (clamped - min) / (max - min); degree = normalised * 180; } else { - var (min, max) = (xyzConfig.Spectral.MinNegativeWavelength, xyzConfig.Spectral.MaxNegativeWavelength); + var (min, max) = (xyzConfig.SpectralBoundary.MinNegativeWavelength, xyzConfig.SpectralBoundary.MaxNegativeWavelength); var clamped = wavelength.Clamp(min, max); var normalised = 1 - (clamped - min) / (max - min); degree = 180 + normalised * 180; @@ -93,13 +93,13 @@ internal static double DegreeToWavelength(double degree, XyzConfiguration xyzCon double wavelength; if (degree <= 180) { - var (min, max) = (Spectral.MinWavelength, Spectral.MaxWavelength); + var (min, max) = (SpectralBoundary.MinWavelength, SpectralBoundary.MaxWavelength); var normalised = degree / 180.0; wavelength = normalised * (max - min) + min; } else { - var (min, max) = (xyzConfig.Spectral.MinNegativeWavelength, xyzConfig.Spectral.MaxNegativeWavelength); + var (min, max) = (xyzConfig.SpectralBoundary.MinNegativeWavelength, xyzConfig.SpectralBoundary.MaxNegativeWavelength); var normalised = 1 - (degree - 180) / 180.0; wavelength = normalised * (max - min) + min; } diff --git a/Unicolour/XyzConfiguration.cs b/Unicolour/XyzConfiguration.cs index 6017be2..47be583 100644 --- a/Unicolour/XyzConfiguration.cs +++ b/Unicolour/XyzConfiguration.cs @@ -9,7 +9,7 @@ public class XyzConfiguration public Chromaticity WhiteChromaticity => WhitePoint.ToChromaticity(); public Observer Observer { get; } internal Illuminant? Illuminant { get; } - internal Spectral Spectral { get; } + internal SpectralBoundary SpectralBoundary { get; } internal Planckian Planckian { get; } public string Name { get; } @@ -30,7 +30,7 @@ public XyzConfiguration(WhitePoint whitePoint, Observer observer, string name = { WhitePoint = whitePoint; Observer = observer; - Spectral = new Spectral(observer, WhiteChromaticity); + SpectralBoundary = new SpectralBoundary(observer, WhiteChromaticity); Planckian = new Planckian(observer); Name = name; } diff --git a/docs/README.md b/docs/README.md index 38c963b..f29200c 100644 --- a/docs/README.md +++ b/docs/README.md @@ -3,7 +3,7 @@ [![GitLab](https://badgen.net/static/gitlab/source/ff1493?icon=gitlab)](https://gitlab.com/Wacton/Unicolour) [![NuGet](https://badgen.net/nuget/v/Wacton.Unicolour?icon)](https://www.nuget.org/packages/Wacton.Unicolour/) [![pipeline status](https://gitlab.com/Wacton/Unicolour/badges/main/pipeline.svg)](https://gitlab.com/Wacton/Unicolour/-/commits/main) -[![tests passed](https://badgen.net/static/tests/216,919/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) +[![tests passed](https://badgen.net/static/tests/217,398/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) [![coverage report](https://gitlab.com/Wacton/Unicolour/badges/main/coverage.svg)](https://gitlab.com/Wacton/Unicolour/-/pipelines) Unicolour is the most comprehensive .NET library for working with colour: @@ -11,7 +11,8 @@ Unicolour is the most comprehensive .NET library for working with colour: - Colour mixing / colour interpolation - Colour difference / colour distance - Colour gamut mapping -- Colour chromaticity & colour temperature +- Colour chromaticity +- Colour temperature - Wavelength attributes - ICC profiles for CMYK conversion @@ -213,7 +214,7 @@ var difference = red.Difference(blue, DeltaE.Cie76); | ΔECAM02 | `DeltaE.Cam02` | | ΔECAM16 | `DeltaE.Cam16` | -### Map colour into display gamut +### Map colour into RGB gamut Colours that cannot be displayed with the [configured RGB model](#rgbconfiguration) can be mapped to the closest in-gamut colour. The gamut mapping algorithm conforms to CSS specifications. ```c# diff --git a/docs/README_us.md b/docs/README_us.md index b4738f3..14d0097 100644 --- a/docs/README_us.md +++ b/docs/README_us.md @@ -3,7 +3,7 @@ [![GitLab](https://badgen.net/static/gitlab/source/ff1493?icon=gitlab)](https://gitlab.com/Wacton/Unicolour) [![NuGet](https://badgen.net/nuget/v/Wacton.Unicolour?icon)](https://www.nuget.org/packages/Wacton.Unicolour/) [![pipeline status](https://gitlab.com/Wacton/Unicolour/badges/main/pipeline.svg)](https://gitlab.com/Wacton/Unicolour/-/commits/main) -[![tests passed](https://badgen.net/static/tests/216,919/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) +[![tests passed](https://badgen.net/static/tests/217,398/green/)](https://gitlab.com/Wacton/Unicolour/-/pipelines) [![coverage report](https://gitlab.com/Wacton/Unicolour/badges/main/coverage.svg)](https://gitlab.com/Wacton/Unicolour/-/pipelines) Unicolour is the most comprehensive .NET library for working with color: @@ -11,7 +11,8 @@ Unicolour is the most comprehensive .NET library for working with color: - Color mixing / color interpolation - Color difference / color distance - Color gamut mapping -- Color chromaticity & color temperature +- Color chromaticity +- Color temperature - Wavelength attributes - ICC profiles for CMYK conversion @@ -213,7 +214,7 @@ var difference = red.Difference(blue, DeltaE.Cie76); | ΔECAM02 | `DeltaE.Cam02` | | ΔECAM16 | `DeltaE.Cam16` | -### Map color into display gamut +### Map color into RGB gamut Colors that cannot be displayed with the [configured RGB model](#rgbconfiguration) can be mapped to the closest in-gamut color. The gamut mapping algorithm conforms to CSS specifications. ```c# diff --git a/docs/gradient-alpha-interpolation.png b/docs/gradient-alpha-interpolation.png index 874fb16..36c466d 100644 Binary files a/docs/gradient-alpha-interpolation.png and b/docs/gradient-alpha-interpolation.png differ diff --git a/docs/gradient-maps-palette.png b/docs/gradient-maps-palette.png new file mode 100644 index 0000000..90c7cdf Binary files /dev/null and b/docs/gradient-maps-palette.png differ diff --git a/docs/gradient-maps.png b/docs/gradient-maps.png index d876360..8489714 100644 Binary files a/docs/gradient-maps.png and b/docs/gradient-maps.png differ diff --git a/docs/gradient-pigments-mix.png b/docs/gradient-pigments-mix.png new file mode 100644 index 0000000..cc10e3a Binary files /dev/null and b/docs/gradient-pigments-mix.png differ diff --git a/docs/gradient-pigments-palette.png b/docs/gradient-pigments-palette.png new file mode 100644 index 0000000..69deedf Binary files /dev/null and b/docs/gradient-pigments-palette.png differ diff --git a/docs/gradient-pigments.png b/docs/gradient-pigments.png new file mode 100644 index 0000000..69deedf Binary files /dev/null and b/docs/gradient-pigments.png differ diff --git a/docs/gradient-simple-mixing.png b/docs/gradient-simple-mixing.png new file mode 100644 index 0000000..6588a79 Binary files /dev/null and b/docs/gradient-simple-mixing.png differ diff --git a/docs/gradient-simple-palette.png b/docs/gradient-simple-palette.png new file mode 100644 index 0000000..5e07743 Binary files /dev/null and b/docs/gradient-simple-palette.png differ diff --git a/docs/gradient-spaces-black-green.png b/docs/gradient-spaces-black-green.png new file mode 100644 index 0000000..c2fe30e Binary files /dev/null and b/docs/gradient-spaces-black-green.png differ diff --git a/docs/gradient-spaces-purple-orange.png b/docs/gradient-spaces-purple-orange.png new file mode 100644 index 0000000..43424bb Binary files /dev/null and b/docs/gradient-spaces-purple-orange.png differ diff --git a/docs/gradient-spaces.png b/docs/gradient-spaces.png deleted file mode 100644 index e36f264..0000000 Binary files a/docs/gradient-spaces.png and /dev/null differ diff --git a/docs/gradient-spectraljs-mix.png b/docs/gradient-spectraljs-mix.png new file mode 100644 index 0000000..8689472 Binary files /dev/null and b/docs/gradient-spectraljs-mix.png differ diff --git a/docs/gradient-spectraljs-palette.png b/docs/gradient-spectraljs-palette.png new file mode 100644 index 0000000..68b4a05 Binary files /dev/null and b/docs/gradient-spectraljs-palette.png differ diff --git a/docs/gradient-vision-deficiency.png b/docs/gradient-vision-deficiency.png index 4b19119..43c0397 100644 Binary files a/docs/gradient-vision-deficiency.png and b/docs/gradient-vision-deficiency.png differ