Skip to content

Commit

Permalink
Merge branch 'dominant-wavelength' into 'main'
Browse files Browse the repository at this point in the history
Add dominant wavelength support

See merge request Wacton/Unicolour!51
  • Loading branch information
waacton committed Feb 13, 2024
2 parents 79c9e56 + ba67663 commit bb075f3
Show file tree
Hide file tree
Showing 78 changed files with 2,264 additions and 567 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@
<PublishSingleFile>true</PublishSingleFile>
<SelfContained>true</SelfContained>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<AssemblyName>Wacton.Unicolour.Console</AssemblyName>
<RootNamespace>Wacton.Unicolour.Console</RootNamespace>
<AssemblyName>Wacton.Unicolour.Example.Console</AssemblyName>
<RootNamespace>Wacton.Unicolour.Example.Console</RootNamespace>
<Company>William Acton</Company>
<Product>Wacton.Unicolour.Example.Console</Product>
</PropertyGroup>

<ItemGroup>
Expand Down
File renamed without changes.
24 changes: 24 additions & 0 deletions Example.Diagrams/Example.Diagrams.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<AssemblyName>Wacton.Unicolour.Example.Diagrams</AssemblyName>
<RootNamespace>Wacton.Unicolour.Example.Diagrams</RootNamespace>
<Authors>William Acton</Authors>
<Product>Wacton.Unicolour.Example.Diagrams</Product>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ScottPlot" Version="5.0.21" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\Unicolour.Datasets\Unicolour.Datasets.csproj" />
<ProjectReference Include="..\Unicolour\Unicolour.csproj" />
</ItemGroup>

</Project>
190 changes: 190 additions & 0 deletions Example.Diagrams/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
using ScottPlot;
using ScottPlot.Plottables;
using Wacton.Unicolour.Example.Diagrams;

const string outputDirectory = "../../../../Unicolour.Readme/docs/";
const int height = 640;

/*
* note: chromaticity diagrams that are filled with colour take a couple of minutes to generate each
* because they process 100,000s of colours, checking if imaginary and converting to RGB
* could easily be much faster if not trying to make a solid block of colour
*/

var spectralLocus = Utils.GetSpectralLocus();
var rgbGamut = Utils.GetRgbGamut();
var blackbodyLocus = Utils.GetBlackbodyLocus();
var isotherms = Utils.GetIsotherms();

SpectralLocus();
XyChromaticityWithRgb();
XyChromaticityWithBlackbody();
UvChromaticity();
UvChromaticityWithBlackbody();
return;

void SpectralLocus()
{
var rangeX = (0.0, 0.8);
var rangeY = (0.0, 0.9);
var plot = Utils.GetEmptyPlot(rangeX, rangeY, majorTickInterval: 0.1);
plot.DataBackground = Color.Gray(96);
plot.GetDefaultGrid().MajorLineStyle = new LineStyle { Color = Color.Gray(128) };

var spectralCoordinates = spectralLocus.Select(colour => new Coordinates(colour.Chromaticity.X, colour.Chromaticity.Y)).ToList();
var spectralLocusScatter = plot.Add.Scatter(spectralCoordinates);
spectralLocusScatter.Color = Color.Gray(64);
spectralLocusScatter.LineWidth = 1;
spectralLocusScatter.MarkerStyle = MarkerStyle.None;

var firstCoordinate = new Coordinates(spectralLocus.First().Chromaticity.X, spectralLocus.First().Chromaticity.Y);
var lastCoordinate = new Coordinates(spectralLocus.Last().Chromaticity.X, spectralLocus.Last().Chromaticity.Y);
var lineOfPurples = plot.Add.Line(firstCoordinate, lastCoordinate);
lineOfPurples.Color = new Color(255, 0, 255);
lineOfPurples.LineWidth = 1;
lineOfPurples.MarkerStyle = MarkerStyle.None;

foreach (var colour in spectralLocus)
{
var chromaticity = colour.Chromaticity;
var marker = new Marker
{
X = colour.Chromaticity.X,
Y = colour.Chromaticity.Y,
Color = Utils.GetPlotColour(chromaticity)!.Value,
Size = 5f
};

plot.Add.Plottable(marker);
}

var width = Utils.GetWidth(rangeX, rangeY, height);
plot.SavePng(Path.Combine(outputDirectory, "diagram-spectral-locus.png"), width, height);
}

void XyChromaticityWithRgb()
{
var rangeX = (0.0, 0.8);
var rangeY = (0.0, 0.9);
var plot = Utils.GetEmptyPlot(rangeX, rangeY, majorTickInterval: 0.1);

var fillMarkers = Utils.GetXyFillMarkers(rangeX, rangeY, increment: 0.001);
foreach (var marker in fillMarkers)
{
plot.Add.Plottable(marker);
}

var spectralCoordinates = spectralLocus.Select(colour => new Coordinates(colour.Chromaticity.X, colour.Chromaticity.Y)).ToList();
spectralCoordinates.Add(spectralCoordinates.First());
var spectralLocusScatter = plot.Add.Scatter(spectralCoordinates);
spectralLocusScatter.Color = Colors.Black;
spectralLocusScatter.LineWidth = 2.5f;
spectralLocusScatter.MarkerStyle = MarkerStyle.None;

var rgbCoordinates = rgbGamut.Select(colour => new Coordinates(colour.Chromaticity.X, colour.Chromaticity.Y));
var rgbPolygon = plot.Add.Polygon(rgbCoordinates.ToArray());
rgbPolygon.LineStyle = new LineStyle { Color = new Color(0f, 0f, 0f, 0.25f), Width = 2.5f };
rgbPolygon.FillStyle = new FillStyle { Color = Colors.Transparent };

var width = Utils.GetWidth(rangeX, rangeY, height);
plot.SavePng(Path.Combine(outputDirectory, "diagram-xy-chromaticity-rgb.png"), width, height);
}

void XyChromaticityWithBlackbody()
{
var rangeX = (0.0, 0.8);
var rangeY = (0.0, 0.9);
var plot = Utils.GetEmptyPlot(rangeX, rangeY, majorTickInterval: 0.1);

var fillMarkers = Utils.GetXyFillMarkers(rangeX, rangeY, increment: 0.001);
foreach (var marker in fillMarkers)
{
plot.Add.Plottable(marker);
}

var spectralCoordinates = spectralLocus.Select(colour => new Coordinates(colour.Chromaticity.X, colour.Chromaticity.Y)).ToList();
spectralCoordinates.Add(spectralCoordinates.First());
var spectralLocusScatter = plot.Add.Scatter(spectralCoordinates);
spectralLocusScatter.Color = Colors.Black;
spectralLocusScatter.LineWidth = 2.5f;
spectralLocusScatter.MarkerStyle = MarkerStyle.None;

var blackbodyCoordinates = blackbodyLocus.Select(colour => new Coordinates(colour.Chromaticity.X, colour.Chromaticity.Y)).ToList();
var blackbodyScatter = plot.Add.Scatter(blackbodyCoordinates);
blackbodyScatter.Color = Colors.Black;
blackbodyScatter.LineWidth = 2.5f;
blackbodyScatter.MarkerStyle = MarkerStyle.None;

foreach (var (startColour, endColour) in isotherms)
{
var startCoordinates = new Coordinates(startColour.Chromaticity.X, startColour.Chromaticity.Y);
var endCoordinates = new Coordinates(endColour.Chromaticity.X, endColour.Chromaticity.Y);
var duvLine = plot.Add.Line(startCoordinates, endCoordinates);
duvLine.Color = Colors.Black;
duvLine.LineWidth = 2.5f;
}

var width = Utils.GetWidth(rangeX, rangeY, height);
plot.SavePng(Path.Combine(outputDirectory, "diagram-xy-chromaticity-blackbody.png"), width, height);
}

void UvChromaticity()
{
var rangeU = (0.0, 0.7);
var rangeV = (0.0, 0.4);
var plot = Utils.GetEmptyPlot(rangeU, rangeV, majorTickInterval: 0.05);

var fillMarkers = Utils.GetUvFillMarkers(rangeU, rangeV, increment: 0.001);
foreach (var marker in fillMarkers)
{
plot.Add.Plottable(marker);
}

var spectralCoordinates = spectralLocus.Select(colour => new Coordinates(colour.Chromaticity.U, colour.Chromaticity.V)).ToList();
spectralCoordinates.Add(spectralCoordinates.First());
var spectralLocusScatter = plot.Add.Scatter(spectralCoordinates);
spectralLocusScatter.Color = Colors.Black;
spectralLocusScatter.LineWidth = 2.5f;
spectralLocusScatter.MarkerStyle = MarkerStyle.None;

var width = Utils.GetWidth(rangeU, rangeV, height);
plot.SavePng(Path.Combine(outputDirectory, "diagram-uv-chromaticity.png"), width, height);
}

void UvChromaticityWithBlackbody()
{
var rangeU = (0.1, 0.45);
var rangeV = (0.25, 0.4);
var plot = Utils.GetEmptyPlot(rangeU, rangeV, majorTickInterval: 0.05);

var fillMarkers = Utils.GetUvFillMarkers(rangeU, rangeV, increment: 0.00025);
foreach (var marker in fillMarkers)
{
plot.Add.Plottable(marker);
}

var spectralCoordinates = spectralLocus.Select(colour => new Coordinates(colour.Chromaticity.U, colour.Chromaticity.V)).ToList();
spectralCoordinates.Add(spectralCoordinates.First());
var spectralLocusScatter = plot.Add.Scatter(spectralCoordinates);
spectralLocusScatter.Color = Colors.Black;
spectralLocusScatter.LineWidth = 2.5f;
spectralLocusScatter.MarkerStyle = MarkerStyle.None;

var blackbodyCoordinates = blackbodyLocus.Select(colour => new Coordinates(colour.Chromaticity.U, colour.Chromaticity.V)).ToList();
var blackbodyScatter = plot.Add.Scatter(blackbodyCoordinates);
blackbodyScatter.Color = Colors.Black;
blackbodyScatter.LineWidth = 2.5f;
blackbodyScatter.MarkerStyle = MarkerStyle.None;

foreach (var (startColour, endColour) in isotherms)
{
var startCoordinates = new Coordinates(startColour.Chromaticity.U, startColour.Chromaticity.V);
var endCoordinates = new Coordinates(endColour.Chromaticity.U, endColour.Chromaticity.V);
var duvLine = plot.Add.Line(startCoordinates, endCoordinates);
duvLine.Color = Colors.Black;
duvLine.LineWidth = 2.5f;
}

var width = Utils.GetWidth(rangeU, rangeV, height);
plot.SavePng(Path.Combine(outputDirectory, "diagram-uv-chromaticity-blackbody.png"), width, height);
}
152 changes: 152 additions & 0 deletions Example.Diagrams/Utils.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
namespace Wacton.Unicolour.Example.Diagrams;

using ScottPlot;
using ScottPlot.Plottables;
using ScottPlot.TickGenerators;

internal static class Utils
{
internal static Plot GetEmptyPlot((double min, double max) rangeX, (double min, double max) rangeY, double majorTickInterval)
{
var plot = new Plot();
plot.Axes.SetLimits(rangeX.min, rangeX.max, rangeY.min, rangeY.max);
plot.Axes.Bottom.TickGenerator = GetTickGenerator(rangeX.min, rangeX.max, majorTickInterval);
plot.Axes.Left.TickGenerator = GetTickGenerator(rangeY.min, rangeY.max, majorTickInterval);
return plot;
}

internal static List<Unicolour> GetSpectralLocus()
{
var data = new List<Unicolour>();
for (var nm = 360; nm < 700; nm++)
{
data.Add(new Unicolour(new Spd { { nm, 1.0 } }));
}

return data;
}

internal static List<Unicolour> GetRgbGamut()
{
var r = new Unicolour(ColourSpace.Rgb, 1, 0, 0);
var g = new Unicolour(ColourSpace.Rgb, 0, 1, 0);
var b = new Unicolour(ColourSpace.Rgb, 0, 0, 1);
return new List<Unicolour> { r, g, b };
}

internal static List<Unicolour> GetBlackbodyLocus()
{
var data = new List<Unicolour>();
for (var cct = 500; cct < 20000; cct += 100)
{
data.Add(new Unicolour(new Temperature(cct)));
}

return data;
}

internal static List<(Unicolour start, Unicolour end)> GetIsotherms()
{
var data = new List<(Unicolour start, Unicolour end)>();
for (var cct = 2000; cct < 10000; cct += 1000)
{
var upper = new Unicolour(new Temperature(cct, 0.05));
var lower = new Unicolour(new Temperature(cct, -0.05));
data.Add((upper, lower));
}

return data;
}

internal static List<Marker> GetXyFillMarkers((double min, double max) rangeX, (double min, double max) rangeY, double increment)
{
var data = new List<Marker>();
for (var x = rangeX.min; x < rangeX.max; x += increment)
{
for (var y = rangeY.min; y < rangeY.max; y += increment)
{
var chromaticity = new Chromaticity(x, y);
var color = GetPlotColour(chromaticity);
if (color == null) continue;

var marker = new Marker { X = x, Y = y, Color = color.Value, Size = 2.5f };
data.Add(marker);
}
}

return data;
}

internal static List<Marker> GetUvFillMarkers((double min, double max) rangeU, (double min, double max) rangeV, double increment)
{
var data = new List<Marker>();
for (var u = rangeU.min; u < rangeU.max; u += increment)
{
for (var v = rangeV.min; v < rangeV.max; v += increment)
{
var chromaticity = Chromaticity.FromUv(u, v);
var color = GetPlotColour(chromaticity);
if (color == null) continue;

var marker = new Marker { X = u, Y = v, Color = color.Value, Size = 2.5f };
data.Add(marker);
}
}

return data;
}

private static readonly Dictionary<Chromaticity, Color?> ChromaticityCache = new();
internal static Color? GetPlotColour(Chromaticity chromaticity)
{
Color? color;
if (ChromaticityCache.ContainsKey(chromaticity))
{
color = ChromaticityCache[chromaticity];
}
else
{
var unicolour = new Unicolour(chromaticity);
color = unicolour.IsImaginary ? null : GetScaledColour(unicolour.Rgb);
ChromaticityCache.Add(chromaticity, color);
}

return color;
}

private static Color GetScaledColour(Rgb rgb)
{
var components = new[] { rgb.R, rgb.G, rgb.B };
var max = components.Max();
var scaled = components.Select(component => Clamp(component / max, 0, 1)).ToList();
return new Color((float)scaled[0], (float)scaled[1], (float)scaled[2]);
}

internal static int GetWidth((double min, double max) rangeX, (double min, double max) rangeY, int height)
{
return (int)(height * (rangeX.max - rangeX.min) / (rangeY.max - rangeY.min));
}

private static NumericManual GetTickGenerator(double min, double max, double majorTickInterval)
{
const double increment = 0.05;
var ticks = new List<Tick>();
var nextMajorTick = min;
for (var i = min; i < max + increment; i += increment)
{
var isMajor = IsEffectivelyZero(i - nextMajorTick);
if (isMajor)
{
nextMajorTick += majorTickInterval;
}

ticks.Add(new Tick(i, isMajor ? $"{i:F2}" : string.Empty, isMajor));
}

return new NumericManual(ticks.ToArray());
}

private static bool IsEffectivelyZero(double x) => Math.Abs(x) < 5e-14;

private static double Clamp(double value, double min, double max) => value < min ? min : value > max ? max : value;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<AssemblyName>Wacton.Unicolour.Example</AssemblyName>
<RootNamespace>Wacton.Unicolour.Example</RootNamespace>
<AssemblyName>Wacton.Unicolour.Example.Gradients</AssemblyName>
<RootNamespace>Wacton.Unicolour.Example.Gradients</RootNamespace>
<Authors>William Acton</Authors>
<Product>Wacton.Unicolour.Example.Gradients</Product>
</PropertyGroup>

<ItemGroup>
Expand Down
File renamed without changes.
File renamed without changes.
Loading

0 comments on commit bb075f3

Please sign in to comment.