diff --git a/Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs b/Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs deleted file mode 100644 index c2cc4d12e..000000000 --- a/Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace Terminal.Gui; - -/// -/// 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. -/// -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)); - } -} diff --git a/Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs b/Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs deleted file mode 100644 index b5103638f..000000000 --- a/Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace Terminal.Gui; - -/// -/// 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 . -/// -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) - ); - } -} diff --git a/Terminal.Gui/Drawing/Quant/LabColorDistance.cs b/Terminal.Gui/Drawing/Quant/LabColorDistance.cs deleted file mode 100644 index f1d97b590..000000000 --- a/Terminal.Gui/Drawing/Quant/LabColorDistance.cs +++ /dev/null @@ -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; - } - } - - /// - public abstract double CalculateDistance (Color c1, Color c2); -} diff --git a/UICatalog/Scenarios/Images.cs b/UICatalog/Scenarios/Images.cs index 8e50d8914..f929cabab 100644 --- a/UICatalog/Scenarios/Images.cs +++ b/UICatalog/Scenarios/Images.cs @@ -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 _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 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 _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; + } + } + + /// + public abstract double CalculateDistance (Color c1, Color c2); +} + +/// +/// 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. +/// +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)); + } +}