Move lab colors to UICatalog

This commit is contained in:
tznind
2024-09-23 20:00:53 +01:00
parent ef56998f5a
commit a7c65bf8b4
4 changed files with 94 additions and 140 deletions

View File

@@ -1,17 +0,0 @@
namespace Terminal.Gui;
/// <summary>
/// This is the simplest method to measure color difference in the CIE Lab color space. The Euclidean distance in Lab
/// space is more aligned with human perception than RGB space, as Lab attempts to model how humans perceive color differences.
/// </summary>
public class CIE76ColorDistance : LabColorDistance
{
public override double CalculateDistance (Color c1, Color c2)
{
var lab1 = RgbToLab (c1);
var lab2 = RgbToLab (c2);
// Euclidean distance in Lab color space
return Math.Sqrt (Math.Pow (lab1.L - lab2.L, 2) + Math.Pow (lab1.A - lab2.A, 2) + Math.Pow (lab1.B - lab2.B, 2));
}
}

View File

@@ -1,45 +0,0 @@
namespace Terminal.Gui;
/// <summary>
/// CIE94 improves on CIE76 by introducing adjustments for chroma (color intensity) and lightness.
/// This algorithm considers human visual perception more accurately by scaling differences in lightness and chroma.
/// It is better but slower than <see cref="CIE76ColorDistance"/>.
/// </summary>
public class CIE94ColorDistance : LabColorDistance
{
// Constants for CIE94 formula (can be modified for different use cases like textiles or graphics)
private const double kL = 1.0;
private const double kC = 1.0;
private const double kH = 1.0;
public override double CalculateDistance (Color first, Color second)
{
var lab1 = RgbToLab (first);
var lab2 = RgbToLab (second);
// Delta L, A, B
double deltaL = lab1.L - lab2.L;
double deltaA = lab1.A - lab2.A;
double deltaB = lab1.B - lab2.B;
// Chroma values for both colors
double c1 = Math.Sqrt (lab1.A * lab1.A + lab1.B * lab1.B);
double c2 = Math.Sqrt (lab2.A * lab2.A + lab2.B * lab2.B);
double deltaC = c1 - c2;
// Delta H (calculated indirectly)
double deltaH = Math.Sqrt (Math.Pow (deltaA, 2) + Math.Pow (deltaB, 2) - Math.Pow (deltaC, 2));
// Scaling factors
double sL = 1.0;
double sC = 1.0 + 0.045 * c1;
double sH = 1.0 + 0.015 * c1;
// CIE94 color difference formula
return Math.Sqrt (
Math.Pow (deltaL / (kL * sL), 2) +
Math.Pow (deltaC / (kC * sC), 2) +
Math.Pow (deltaH / (kH * sH), 2)
);
}
}

View File

@@ -1,52 +0,0 @@
using ColorHelper;
namespace Terminal.Gui;
public abstract class LabColorDistance : IColorDistance
{
// Reference white point for D65 illuminant (can be moved to constants)
private const double RefX = 95.047;
private const double RefY = 100.000;
private const double RefZ = 108.883;
// Conversion from RGB to Lab
protected LabColor RgbToLab (Color c)
{
var xyz = ColorHelper.ColorConverter.RgbToXyz (new RGB (c.R, c.G, c.B));
// Normalize XYZ values by reference white point
double x = xyz.X / RefX;
double y = xyz.Y / RefY;
double z = xyz.Z / RefZ;
// Apply the nonlinear transformation for Lab
x = x > 0.008856 ? Math.Pow (x, 1.0 / 3.0) : 7.787 * x + 16.0 / 116.0;
y = y > 0.008856 ? Math.Pow (y, 1.0 / 3.0) : 7.787 * y + 16.0 / 116.0;
z = z > 0.008856 ? Math.Pow (z, 1.0 / 3.0) : 7.787 * z + 16.0 / 116.0;
// Calculate Lab values
double l = 116.0 * y - 16.0;
double a = 500.0 * (x - y);
double b = 200.0 * (y - z);
return new LabColor (l, a, b);
}
// LabColor class encapsulating L, A, and B values
protected class LabColor
{
public double L { get; }
public double A { get; }
public double B { get; }
public LabColor (double l, double a, double b)
{
L = l;
A = a;
B = b;
}
}
/// <inheritdoc />
public abstract double CalculateDistance (Color c1, Color c2);
}

View File

@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using ColorHelper;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -20,7 +21,7 @@ public class Images : Scenario
public override void Main ()
{
Application.Init ();
var win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName()}" };
var win = new Window { Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" };
bool canTrueColor = Application.Driver?.SupportsTrueColor ?? false;
@@ -50,7 +51,7 @@ public class Images : Scenario
var btnOpenImage = new Button { X = Pos.Right (cbUseTrueColor) + 2, Y = 0, Text = "Open Image" };
win.Add (btnOpenImage);
var imageView = new ImageView
{
X = 0, Y = Pos.Bottom (lblDriverName), Width = Dim.Fill (), Height = Dim.Fill ()
@@ -105,10 +106,8 @@ public class Images : Scenario
Application.Refresh ();
};
var btnSixel = new Button () { X = Pos.Right (btnOpenImage) + 2, Y = 0, Text = "Output Sixel" };
btnSixel.Accept += (s, e) => { imageView.OutputSixel ();};
var btnSixel = new Button { X = Pos.Right (btnOpenImage) + 2, Y = 0, Text = "Output Sixel" };
btnSixel.Accept += (s, e) => { imageView.OutputSixel (); };
win.Add (btnSixel);
Application.Run (win);
@@ -116,7 +115,6 @@ public class Images : Scenario
Application.Shutdown ();
}
private class ImageView : View
{
private readonly ConcurrentDictionary<Rgba32, Attribute> _cache = new ();
@@ -147,10 +145,10 @@ public class Images : Scenario
Attribute attr = _cache.GetOrAdd (
rgb,
rgb => new Attribute (
new Color (),
new Color (rgb.R, rgb.G, rgb.B)
)
rgb => new (
new Color (),
new Color (rgb.R, rgb.G, rgb.B)
)
);
Driver.SetAttribute (attr);
@@ -174,18 +172,18 @@ public class Images : Scenario
var encoder = new SixelEncoder ();
var encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));
string encoded = encoder.EncodeSixel (ConvertToColorArray (_fullResImage));
var pv = new PaletteView (encoder.Quantizer.Palette.ToList ());
var dlg = new Dialog ()
var dlg = new Dialog
{
Title = "Palette (Esc to close)",
Width = Dim.Fill (2),
Height = Dim.Fill (1),
Height = Dim.Fill (1)
};
var btn = new Button ()
var btn = new Button
{
Text = "Ok"
};
@@ -197,6 +195,7 @@ public class Images : Scenario
Application.Sixel = encoded;
}
public static Color [,] ConvertToColorArray (Image<Rgba32> image)
{
int width = image.Width;
@@ -204,18 +203,19 @@ public class Images : Scenario
Color [,] colors = new Color [width, height];
// Loop through each pixel and convert Rgba32 to Terminal.Gui color
for (int x = 0; x < width; x++)
for (var x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
for (var y = 0; y < height; y++)
{
var pixel = image [x, y];
colors [x, y] = new Color (pixel.R, pixel.G, pixel.B); // Convert Rgba32 to Terminal.Gui color
Rgba32 pixel = image [x, y];
colors [x, y] = new (pixel.R, pixel.G, pixel.B); // Convert Rgba32 to Terminal.Gui color
}
}
return colors;
}
}
public class PaletteView : View
{
private List<Color> _palette;
@@ -231,20 +231,20 @@ public class Images : Scenario
private (int columns, int rows) CalculateGridSize (Rectangle bounds)
{
// Characters are twice as wide as they are tall, so use 2:1 width-to-height ratio
int availableWidth = bounds.Width / 2; // Each color block is 2 character wide
int availableWidth = bounds.Width / 2; // Each color block is 2 character wide
int availableHeight = bounds.Height;
int numColors = _palette.Count;
// Calculate the number of columns and rows we can fit within the bounds
int columns = Math.Min (availableWidth, numColors);
int rows = (numColors + columns - 1) / columns; // Ceiling division for rows
int rows = (numColors + columns - 1) / columns; // Ceiling division for rows
// Ensure we do not exceed the available height
if (rows > availableHeight)
{
rows = availableHeight;
columns = (numColors + rows - 1) / rows; // Recalculate columns if needed
columns = (numColors + rows - 1) / rows; // Recalculate columns if needed
}
return (columns, rows);
@@ -255,13 +255,15 @@ public class Images : Scenario
base.OnDrawContent (bounds);
if (_palette == null || _palette.Count == 0)
{
return;
}
// Calculate the grid size based on the bounds
var (columns, rows) = CalculateGridSize (bounds);
(int columns, int rows) = CalculateGridSize (bounds);
// Draw the colors in the palette
for (int i = 0; i < _palette.Count && i < columns * rows; i++)
for (var i = 0; i < _palette.Count && i < columns * rows; i++)
{
int row = i / columns;
int col = i % columns;
@@ -271,10 +273,10 @@ public class Images : Scenario
int y = row;
// Set the color attribute for the block
Driver.SetAttribute (new Terminal.Gui.Attribute (_palette [i], _palette [i]));
Driver.SetAttribute (new (_palette [i], _palette [i]));
// Draw the block (2 characters wide per block)
for (int dx = 0; dx < 2; dx++) // Fill the width of the block
for (var dx = 0; dx < 2; dx++) // Fill the width of the block
{
AddRune (x + dx, y, (Rune)' ');
}
@@ -289,3 +291,69 @@ public class Images : Scenario
}
}
}
public abstract class LabColorDistance : IColorDistance
{
// Reference white point for D65 illuminant (can be moved to constants)
private const double RefX = 95.047;
private const double RefY = 100.000;
private const double RefZ = 108.883;
// Conversion from RGB to Lab
protected LabColor RgbToLab (Color c)
{
XYZ xyz = ColorConverter.RgbToXyz (new (c.R, c.G, c.B));
// Normalize XYZ values by reference white point
double x = xyz.X / RefX;
double y = xyz.Y / RefY;
double z = xyz.Z / RefZ;
// Apply the nonlinear transformation for Lab
x = x > 0.008856 ? Math.Pow (x, 1.0 / 3.0) : 7.787 * x + 16.0 / 116.0;
y = y > 0.008856 ? Math.Pow (y, 1.0 / 3.0) : 7.787 * y + 16.0 / 116.0;
z = z > 0.008856 ? Math.Pow (z, 1.0 / 3.0) : 7.787 * z + 16.0 / 116.0;
// Calculate Lab values
double l = 116.0 * y - 16.0;
double a = 500.0 * (x - y);
double b = 200.0 * (y - z);
return new (l, a, b);
}
// LabColor class encapsulating L, A, and B values
protected class LabColor
{
public double L { get; }
public double A { get; }
public double B { get; }
public LabColor (double l, double a, double b)
{
L = l;
A = a;
B = b;
}
}
/// <inheritdoc/>
public abstract double CalculateDistance (Color c1, Color c2);
}
/// <summary>
/// This is the simplest method to measure color difference in the CIE Lab color space. The Euclidean distance in Lab
/// space is more aligned with human perception than RGB space, as Lab attempts to model how humans perceive color
/// differences.
/// </summary>
public class CIE76ColorDistance : LabColorDistance
{
public override double CalculateDistance (Color c1, Color c2)
{
LabColor lab1 = RgbToLab (c1);
LabColor lab2 = RgbToLab (c2);
// Euclidean distance in Lab color space
return Math.Sqrt (Math.Pow (lab1.L - lab2.L, 2) + Math.Pow (lab1.A - lab2.A, 2) + Math.Pow (lab1.B - lab2.B, 2));
}
}