-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'dominant-wavelength' into 'main'
Add dominant wavelength support See merge request Wacton/Unicolour!51
- Loading branch information
Showing
78 changed files
with
2,264 additions
and
567 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
Oops, something went wrong.