mirror of
https://github.com/gui-cs/Terminal.Gui.git
synced 2025-12-26 15:57:56 +01:00
Move lab colors to UICatalog
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user