Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #1265 - Adds Sixel rendering support #3734

Merged
merged 74 commits into from
Oct 28, 2024
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
4d1d740
Initial exploration of how to sixel encode
tznind Sep 9, 2024
f096062
Add ColorQuantizer
tznind Sep 9, 2024
943fa11
Work on SixelEncoder
tznind Sep 9, 2024
c6281dd
Build color palette using median cut instead of naive method
tznind Sep 11, 2024
b482306
Fix build errors
tznind Sep 11, 2024
3081765
Refactoring and comments
tznind Sep 11, 2024
e334bfd
Refactor and split into seperate files WIP
tznind Sep 11, 2024
891adec
Switch to a new WriteSixel algorithm
tznind Sep 13, 2024
484b75a
Output palette in Images scenario
tznind Sep 14, 2024
68d5e99
Fix ConvertToColorArray and namespaces
tznind Sep 14, 2024
d747867
Fix comment
tznind Sep 14, 2024
f103b04
Attribution for the WriteSixel method
tznind Sep 15, 2024
cbef6c5
Add comments
tznind Sep 15, 2024
eaa5c0e
Simplify and speed up palette building
tznind Sep 15, 2024
f8bb2f0
Fix infinite loop building palette
tznind Sep 15, 2024
f40b7b4
Fix test and make comments clearer
tznind Sep 22, 2024
93ce9a8
Add sixel test for grid 3x3 to make 12x12 checkerboard
tznind Sep 22, 2024
ef56998
Tidy up test file and comments in NetDriver
tznind Sep 23, 2024
a7c65bf
Move lab colors to UICatalog
tznind Sep 23, 2024
f07ab92
Switch to simpler and faster palette builder
tznind Sep 23, 2024
2378570
Fix early exit bug in palette builder and change to far more conserva…
tznind Sep 23, 2024
b9bb2ba
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Sep 23, 2024
4571978
Fix fill area - y is not in sixels its in pixels
tznind Sep 25, 2024
3c6804a
Move license to top of page and credit both source repos
tznind Sep 25, 2024
6149c5f
Add 2 tabs to Image scenario - one for sixel one for basic
tznind Sep 26, 2024
050db7a
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Sep 26, 2024
9322b9a
Output at specific position
tznind Sep 26, 2024
519d8c0
Investigate changing sixel to output as part of view render
tznind Sep 28, 2024
94cbc1c
Restore the static approach to rendering for now and fix dispose
tznind Sep 28, 2024
d16f1b6
Fix tabbing into sixel tab view
tznind Sep 28, 2024
18f185d
Fix scenario dispose
tznind Sep 28, 2024
db0fc41
Determine whether sixel is supported by issuing a Device Attributes R…
tznind Sep 29, 2024
08aa992
Switch to existing consts
tznind Sep 30, 2024
b67c662
WIP: trying to get fully transparent alpha to not render
tznind Sep 30, 2024
c240cb1
Fix for when we want alpha pixels
tznind Oct 1, 2024
0122442
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Oct 2, 2024
5e356c2
WindowsDriver prototype sixel support
tznind Oct 3, 2024
f22ef30
Merge branch 'sixel-encoder-tinkering' of https://github.com/tznind/g…
tznind Oct 3, 2024
d60b1fa
Remove double paint!
tznind Oct 3, 2024
23cd888
Do not output sixel if driver does not support it
tznind Oct 3, 2024
14a5fa7
Improve usability of Images scenario
tznind Oct 4, 2024
3566ac2
Make sixel output stop on dispose/close Images scenario
tznind Oct 4, 2024
9058554
Adjust default quantizer
tznind Oct 4, 2024
c4c7754
Add UI components for sixel algorithm
tznind Oct 4, 2024
9d5d853
Actually call relevant builders
tznind Oct 4, 2024
36a8cba
Make updates to the sixel image easier (e.g. changing algorithm)
tznind Oct 4, 2024
a6b1221
Tidy up scenario quantizer options
tznind Oct 5, 2024
97da4cd
Fix unit test expectations
tznind Oct 5, 2024
fd7f994
Fix GetPaletteBuilder
tznind Oct 5, 2024
a3eb19f
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Oct 6, 2024
eee0ded
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Oct 10, 2024
ccb974b
Change to using ansi escape sequences in new standalone class
tznind Oct 5, 2024
78d49fc
Fix algorithm to look for exactly 4 not things like 42 etc.
tznind Oct 5, 2024
ffe8969
Fix new palette order
tznind Oct 5, 2024
fe7e10a
Get sixel resolution using CSI 16 t
tznind Oct 6, 2024
54308ff
Sixel resolution measuring
tznind Oct 6, 2024
64baeca
Update sixel status class name and move to new file
tznind Oct 6, 2024
a8e3a0e
Update xmldoc
tznind Oct 6, 2024
4073ef3
Use round to nearest whole number when calculating sixel resolution f…
tznind Oct 7, 2024
61667fb
Refactor SixelSupportDetector for cleaner reading
tznind Oct 7, 2024
54b737f
If not supporting transparency warn user
tznind Oct 7, 2024
1c3b0d7
Merge pull request #170 from tznind/add-sixel-detector-interface
tznind Oct 10, 2024
9990a55
Fix for accepting
tznind Oct 10, 2024
64d286c
Remove ConsoleDriver.SupportsSixel
tznind Oct 10, 2024
5a6aae6
Fix warnings and tidup code
tznind Oct 10, 2024
bcdb11e
Run TidyCode on all new classes
tznind Oct 10, 2024
57de1ff
Merge branch 'v2_develop' into sixel-encoder-tinkering
tig Oct 11, 2024
06a2f71
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Oct 11, 2024
c46cf78
Merge branch 'v2_develop' into sixel-encoder-tinkering
tznind Oct 13, 2024
04b336a
Try adding sixel to curses driver
tznind Oct 13, 2024
8ddfd71
Merge branch 'v2_develop' into sixel-encoder-tinkering
tig Oct 15, 2024
a6f03e4
Merge branch 'v2_develop' into sixel-encoder-tinkering
tig Oct 23, 2024
3156641
Make sixel 'opt in' in images scenario and apply a hack to fix TabVie…
tznind Oct 23, 2024
ce41afd
xml comments
tznind Oct 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Terminal.Gui/Application/Application.Driver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@
/// </remarks>
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static string ForceDriver { get; set; } = string.Empty;

public static string Sixel;

Check warning on line 30 in Terminal.Gui/Application/Application.Driver.cs

View workflow job for this annotation

GitHub Actions / build_release

Missing XML comment for publicly visible type or member 'Application.Sixel'

Check warning on line 30 in Terminal.Gui/Application/Application.Driver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Missing XML comment for publicly visible type or member 'Application.Sixel'

Check warning on line 30 in Terminal.Gui/Application/Application.Driver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Missing XML comment for publicly visible type or member 'Application.Sixel'

Check warning on line 30 in Terminal.Gui/Application/Application.Driver.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Missing XML comment for publicly visible type or member 'Application.Sixel'
}
13 changes: 13 additions & 0 deletions Terminal.Gui/ConsoleDrivers/NetDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,19 @@ public override void UpdateScreen ()
SetCursorPosition (lastCol, row);
Console.Write (output);
}

/*
// Hard-coded sixel content from the file
string knownGood = "\u001bP0;0;0q\"1;1;60;23#0;2;0;0;0#1;2;99;36;0#2;2;96;40;0#3;2;98;39;0#4;2;95;42;0#5;2;93;46;0#6;2;91;50;0#7;2;92;49;0#8;2;94;44;0#9;2;0;100;7#10;2;87;56;0#11;2;86;58;0#12;2;84;61;0#13;2;85;60;0#14;2;90;52;0#15;2;91;50;0#16;2;88;54;0#17;2;81;65;0#18;2;80;67;0#19;2;82;64;0#20;2;78;71;0#21;2;79;70;0#22;2;76;74;0#23;2;75;76;0#24;2;75;76;0#25;2;75;76;0#26;2;73;78;0#27;2;73;78;0#28;2;74;77;0#29;2;74;77;0#30;2;74;77;0#31;2;75;77;0#32;2;74;78;0#33;2;74;78;0#34;2;74;77;0#35;2;72;80;0#36;2;71;81;0#37;2;72;81;0#38;2;71;81;0#39;2;72;81;0#40;2;73;78;0#41;2;73;78;0#42;2;73;79;0#43;2;73;79;0#44;2;73;80;0#45;2;73;79;0#46;2;72;80;0#47;2;73;80;0#48;2;72;80;0#49;2;73;79;0#50;2;69;85;0#51;2;68;86;0#52;2;69;86;0#53;2;68;86;0#54;2;69;86;0#55;2;68;87;0#56;2;68;87;0#57;2;68;87;0#58;2;68;87;0#59;2;68;87;0#60;2;71;82;0#61;2;71;82;0#62;2;71;82;0#63;2;71;82;0#64;2;71;82;0#65;2;71;83;0#66;2;70;83;0#67;2;70;84;0#68;2;69;84;0#69;2;70;84;0#70;2;70;84;0#71;2;70;84;0#72;2;71;83;0#73;2;71;83;0#74;2;69;85;0#75;2;69;85;0#76;2;69;85;0#77;2;70;85;0#78;2;69;85;0#79;2;69;86;0#80;2;69;85;0#81;2;62;96;0#82;2;62;96;0#83;2;62;96;0#84;2;62;97;0#85;2;62;97;0#86;2;62;96;0#87;2;61;98;0#88;2;62;97;0#89;2;61;98;0#90;2;62;98;0#91;2;62;97;0#92;2;61;98;0#93;2;62;98;0#94;2;61;98;0#95;2;60;99;0#96;2;61;99;0#97;2;60;99;0#98;2;61;99;0#99;2;60;100;0#100;2;60;100;0#101;2;60;100;0#102;2;62;98;0#103;2;65;90;0#104;2;67;88;0#105;2;67;89;0#106;2;67;89;0#107;2;67;89;0#108;2;67;89;0#109;2;67;88;0#110;2;68;88;0#111;2;67;88;0#112;2;66;89;0#113;2;66;90;0#114;2;66;90;0#115;2;67;89;0#116;2;66;90;0#117;2;66;90;0#118;2;66;91;0#119;2;67;89;0#120;2;65;91;0#121;2;65;91;0#122;2;65;92;0#123;2;65;92;0#124;2;64;93;0#125;2;64;94;0#126;2;64;94;0#127;2;64;93;0#128;2;65;93;0#129;2;64;93;0#130;2;64;93;0#131;2;65;92;0#132;2;66;91;0#133;2;63;95;0#134;2;64;94;0#135;2;64;94;0#136;2;64;95;0#137;2;63;95;0#138;2;63;95;0#139;2;63;95;0#140;2;64;95;0#141;2;63;96;0#142;2;63;96;0#143;2;64;95;0#144;2;75;75;0#4~{w#7??B^}o#14F^_#10BFo_#13F~o#19?BN{#0{Ez|llJ}KsSsKw#37@FW#70BEw_#107?@Mo#121B]w#135@Mw#88BMo_$#8?BCo#6???@#15Nw_#16^{w#11N^w#12?N~{o#17BB#9wCAQQs?oGGGo#45F{o#65F{_#55?@Fw_#132?C#129@Co#141@Eo#87@#98N^~~$#5??BN~{_#18!17?@#22!5?@BB_#32B#26B#48?AG_#74?@F[o#110EO#120??_#124BMo#83@Ko$#23!33?B#72!7?W#52?AG#112?@Nw-#4@F^~w#5FN{o#15?F]o#16N~[_#13?D~w_#19@?{wo#18?Gw_#20Gw#22www_#32Fo_#37DMo#72C_#52@W_#110G_#121@Nw_#83?@^w_$#2EW_#8?Fwo#7BN~w_#10???b]o#12??F^}{#9@@AAAB?@AAA@#23[w#45F]o#65@Mo#74B]_#107@FWo#129?FW_#88??F[_$#3w_#14!9?@No#11??@Ny#0!4?BAEDDDCFEDDDEB#26?G#48@Io#70@J[_#55F]o#112FMo#135?BN}_#87?AO$#17!26?Gwo#21?Wo#124!21?CO#98???@N-#0MIyAyYmggWowGggwGgggGggG}I}wGgggWgggGw}A}??wKuyYYU{wGw?wG}I}$#1o_#4?@@@#8@A#7?@FFE#14?BE#10?BF#11F#13FFC#19?@@@C#18?@F#21FC#22?BFFE#23@#32@?o#37AA#70?@#74@@#55@#110@#107BE#121@F}#124@#135F#141@#83@#88@$#3@O#5!5?DFEG#15?@FC#16@FC#12!4?BF#17???BFE#20??BFC#144??@#45??@Nw#65@B#112!6?@C#129?@E$#9?CC{CcOOO_??oOO?oOOOoOOo?s??oOOO_OOOo??{!4?oGCccg??o???o?s$#2?@@#48!39?DC#120!8?A-#1^^OO#3OO#2O#4OOO#8O#5OO#7O^O#15O#14OO#10??O#11OO#13OO#12OOO#19OWO#18?OO#21O#20OO#22OOO#23O#32O#26O#45WO#48O#65OO#70O#74OO#55O#110O#112@O#121OO#129O#135O$#0??NGNK!4INNGN?NGN?NGNNGNGNNGN?NGGIIGNNGNNHNCLJJJGNFKJIJGNGN$#9???F?B!4D??F???F???F??F?F??F???FFDDF??F??E?BACCCF??BCCCF?F$#16!18?NOO#17!9?F?O#52!18?G#107??O-\u001b\\";

tznind marked this conversation as resolved.
Show resolved Hide resolved
Application.Sixel = knownGood;
*/

if (!string.IsNullOrWhiteSpace(Application.Sixel))
{
Console.SetCursorPosition (0,0);
Console.Write (Application.Sixel);
}
}

SetCursorPosition (0, 0);
Expand Down
17 changes: 17 additions & 0 deletions Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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)

Check warning on line 9 in Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_release

Missing XML comment for publicly visible type or member 'CIE76ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 9 in Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Missing XML comment for publicly visible type or member 'CIE76ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 9 in Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Missing XML comment for publicly visible type or member 'CIE76ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 9 in Terminal.Gui/Drawing/Quant/CIE76ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Missing XML comment for publicly visible type or member 'CIE76ColorDistance.CalculateDistance(Color, Color)'
{
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));
}
}
45 changes: 45 additions & 0 deletions Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
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
tznind marked this conversation as resolved.
Show resolved Hide resolved
{
// 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)

Check warning on line 15 in Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_release

Missing XML comment for publicly visible type or member 'CIE94ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 15 in Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Missing XML comment for publicly visible type or member 'CIE94ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 15 in Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Missing XML comment for publicly visible type or member 'CIE94ColorDistance.CalculateDistance(Color, Color)'

Check warning on line 15 in Terminal.Gui/Drawing/Quant/CIE94ColorDistance.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Missing XML comment for publicly visible type or member 'CIE94ColorDistance.CalculateDistance(Color, Color)'
{
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)
);
}
}
70 changes: 70 additions & 0 deletions Terminal.Gui/Drawing/Quant/ColorQuantizer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@


namespace Terminal.Gui;

/// <summary>
/// Translates colors in an image into a Palette of up to 256 colors.
/// </summary>
public class ColorQuantizer
{
/// <summary>
/// Gets the current colors in the palette based on the last call to
/// <see cref="BuildPalette"/>.
/// </summary>
public IReadOnlyCollection<Color> Palette { get; private set; } = new List<Color> ();

/// <summary>
/// Gets or sets the maximum number of colors to put into the <see cref="Palette"/>.
/// Defaults to 256 (the maximum for sixel images).
/// </summary>
public int MaxColors { get; set; } = 256;

/// <summary>
/// Gets or sets the algorithm used to map novel colors into existing
/// palette colors (closest match). Defaults to <see cref="CIE94ColorDistance"/>
/// </summary>
public IColorDistance DistanceAlgorithm { get; set; } = new CIE94ColorDistance ();

/// <summary>
/// Gets or sets the algorithm used to build the <see cref="Palette"/>.
/// </summary>
public IPaletteBuilder PaletteBuildingAlgorithm { get; set; } = new MedianCutPaletteBuilder (new EuclideanColorDistance ()) ;

public void BuildPalette (Color [,] pixels)

Check warning on line 33 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_release

Missing XML comment for publicly visible type or member 'ColorQuantizer.BuildPalette(Color[*,*])'

Check warning on line 33 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.BuildPalette(Color[*,*])'

Check warning on line 33 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.BuildPalette(Color[*,*])'

Check warning on line 33 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.BuildPalette(Color[*,*])'
{
List<Color> allColors = new List<Color> ();
int width = pixels.GetLength (0);
int height = pixels.GetLength (1);

for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
allColors.Add (pixels [x, y]);
}
}

Palette = PaletteBuildingAlgorithm.BuildPalette (allColors, MaxColors);
}

public int GetNearestColor (Color toTranslate)

Check warning on line 50 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_release

Missing XML comment for publicly visible type or member 'ColorQuantizer.GetNearestColor(Color)'

Check warning on line 50 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (ubuntu-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.GetNearestColor(Color)'

Check warning on line 50 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (windows-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.GetNearestColor(Color)'

Check warning on line 50 in Terminal.Gui/Drawing/Quant/ColorQuantizer.cs

View workflow job for this annotation

GitHub Actions / build_and_test_debug (macos-latest)

Missing XML comment for publicly visible type or member 'ColorQuantizer.GetNearestColor(Color)'
{
// Simple nearest color matching based on DistanceAlgorithm
double minDistance = double.MaxValue;
int nearestIndex = 0;

for (var index = 0; index < Palette.Count; index++)
{
Color color = Palette.ElementAt (index);
double distance = DistanceAlgorithm.CalculateDistance (color, toTranslate);

if (distance < minDistance)
{
minDistance = distance;
nearestIndex = index;
}
}

return nearestIndex;
}
}
16 changes: 16 additions & 0 deletions Terminal.Gui/Drawing/Quant/EuclideanColorDistance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace Terminal.Gui;

/// <summary>
/// Calculates the distance between two colors using Euclidean distance in 3D RGB space.
/// This measures the straight-line distance between the two points representing the colors.
/// </summary>
public class EuclideanColorDistance : IColorDistance
{
public double CalculateDistance (Color c1, Color c2)
{
int rDiff = c1.R - c2.R;
int gDiff = c1.G - c2.G;
int bDiff = c1.B - c2.B;
return Math.Sqrt (rDiff * rDiff + gDiff * gDiff + bDiff * bDiff);
}
}
18 changes: 18 additions & 0 deletions Terminal.Gui/Drawing/Quant/IColorDistance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
namespace Terminal.Gui;

/// <summary>
/// Interface for algorithms that compute the relative distance between pairs of colors.
/// This is used for color matching to a limited palette, such as in Sixel rendering.
/// </summary>
public interface IColorDistance
{
/// <summary>
/// Computes a similarity metric between two <see cref="Color"/> instances.
/// A larger value indicates more dissimilar colors, while a smaller value indicates more similar colors.
/// The metric is internally consistent for the given algorithm.
/// </summary>
/// <param name="c1">The first color.</param>
/// <param name="c2">The second color.</param>
/// <returns>A numeric value representing the distance between the two colors.</returns>
double CalculateDistance (Color c1, Color c2);
}
17 changes: 17 additions & 0 deletions Terminal.Gui/Drawing/Quant/IPaletteBuilder.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace Terminal.Gui;

/// <summary>
/// Builds a palette of a given size for a given set of input colors.
/// </summary>
public interface IPaletteBuilder
{
/// <summary>
/// Reduce the number of <paramref name="colors"/> to <paramref name="maxColors"/> (or less)
/// using an appropriate selection algorithm.
/// </summary>
/// <param name="colors">Color of every pixel in the image. Contains duplication in order
/// to support algorithms that weigh how common a color is.</param>
/// <param name="maxColors">The maximum number of colours that should be represented.</param>
/// <returns></returns>
List<Color> BuildPalette (List<Color> colors, int maxColors);
}
52 changes: 52 additions & 0 deletions Terminal.Gui/Drawing/Quant/LabColorDistance.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
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);
}
Loading
Loading