Skip to content

Commit

Permalink
Added "nearest match" color mapping
Browse files Browse the repository at this point in the history
Added "nearest match" color mapping for approximate cross-platform
support.  On a non-Windows machine, this will replace the requested RGB
color with the nearest System.ConsoleColor approximation.  This is
(hopefully) temporary, and will (hopefully) be replaced with actual
cross-platform support once System.Drawing.Color is added to .NET Core.
  • Loading branch information
tomakita committed Jul 2, 2016
1 parent 31abcfe commit d20b38c
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 18 deletions.
42 changes: 42 additions & 0 deletions src/Colorful.Console.Tests/ColorMapperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,54 @@ namespace Colorful.Console.Tests

public sealed class ColorMapperTests
{
// TODO: This test doesn't run, because it requires CoreCompat.System.Drawing, which can't coexist with Xunit, yet.
[Fact]
public void MapColor_ThrowsException_WhenCalledAndConsoleWindowIsntOpen()
{
ColorMapper mapper = new ColorMapper();

Assert.Throws<ColorMappingException>(() => mapper.MapColor(ColorStoreTests.TEST_CONSOLE_COLOR, ColorStoreTests.TEST_COLOR));
}

[Fact]
public void GetClosestConsoleColor_FindsClosestMatchToInputColor()
{
Dictionary<ConsoleColor, Color> expectedColorMap = new Dictionary<ConsoleColor, Color>()
{
{ ConsoleColor.Black, Color.Black },
{ ConsoleColor.DarkBlue, Color.DarkBlue },
{ ConsoleColor.DarkGreen, Color.DarkGreen },
{ ConsoleColor.DarkCyan, Color.DarkCyan },
{ ConsoleColor.DarkRed, Color.DarkRed },
{ ConsoleColor.DarkMagenta, Color.DarkMagenta },
{ ConsoleColor.DarkYellow, Color.DarkGoldenrod },
{ ConsoleColor.Gray, Color.Gray },
{ ConsoleColor.DarkGray, Color.DarkGray },
{ ConsoleColor.Blue, Color.Blue },
{ ConsoleColor.Green, Color.Green },
{ ConsoleColor.Cyan, Color.Cyan },
{ ConsoleColor.Red, Color.Red },
{ ConsoleColor.Magenta, Color.Magenta },
{ ConsoleColor.Yellow, Color.Yellow },
{ ConsoleColor.White, Color.White }
};

ColorMapper mapper = new ColorMapper();
bool allColorsMatch = true;
foreach (KeyValuePair<ConsoleColor, Color> expectedColorCorrespondence in expectedColorMap)
{
ConsoleColor expectedConsoleColor = expectedColorCorrespondence.Key;
Color rgbColor = expectedColorCorrespondence.Value;
ConsoleColor closestConsoleColor = mapper.GetClosestConsoleColor(rgbColor.R, rgbColor.G, rgbColor.B);

if (closestConsoleColor != expectedConsoleColor)
{
allColorsMatch = false;
break;
}
}

Assert.True(allColorsMatch);
}
}
}
3 changes: 2 additions & 1 deletion src/Colorful.Console.Tests/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"dependencies": {
"xunit": "2.1.0",
"dotnet-test-xunit": "1.0.0-rc2-build10025",
"Colorful.Console": "*"
"Colorful.Console": "*",
"System.Console": "4.0.0"
},

"frameworks": {
Expand Down
31 changes: 31 additions & 0 deletions src/Colorful.Console/ColorManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using System.Text;
using System.Threading.Tasks;
using System.Drawing;
using System.Runtime.InteropServices;

namespace Colorful
{
Expand Down Expand Up @@ -52,6 +53,31 @@ public Color GetColor(ConsoleColor color)
/// <param name="color">The System.Drawing.Color whose ConsoleColor alias should be retrieved.</param>
/// <returns>The corresponding ConsoleColor.</returns>
public ConsoleColor GetConsoleColor(Color color)
{
try
{
#if NETSTANDARD1_3
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
#endif
return GetConsoleColorNative(color);

#if NETSTANDARD1_3
}
else
{
return GetConsoleColorCrossPlatform(color);
}
#endif
}
// If no NETSTANDARD1_3, but still not running on Windows, catch the exception and approximate the requested color.
catch (Exception ex)
{
return GetConsoleColorCrossPlatform(color);
}
}

private ConsoleColor GetConsoleColorNative(Color color)
{
if (!CanChangeColor())
{
Expand All @@ -73,6 +99,11 @@ public ConsoleColor GetConsoleColor(Color color)
}
}

private ConsoleColor GetConsoleColorCrossPlatform(Color color)
{
return colorMapper.GetClosestConsoleColor(color.R, color.G, color.B);
}

private bool CanChangeColor()
{
return colorChangeCount < maxColorChanges;
Expand Down
45 changes: 28 additions & 17 deletions src/Colorful.Console/ColorMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,30 +83,41 @@ private struct CONSOLE_SCREEN_BUFFER_INFO_EX
[DllImport("kernel32.dll", SetLastError = true)]
private static extern bool SetConsoleScreenBufferInfoEx(IntPtr hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO_EX csbe);

// Adapted from code that was originally written by Glenn Slayden.
public ConsoleColor GetClosestConsoleColor(byte r, byte g, byte b)
{
ConsoleColor closestConsoleColor = 0;
double delta = double.MaxValue;

foreach (ConsoleColor consoleColor in Enum.GetValues(typeof(ConsoleColor)))
{
string consoleColorName = Enum.GetName(typeof(ConsoleColor), consoleColor);
Color rgbColor = System.Drawing.Color.FromName(consoleColorName == "DarkYellow" ? "Orange" : consoleColorName);
double sum = Math.Pow(rgbColor.R - r, 2.0) + Math.Pow(rgbColor.G - g, 2.0) + Math.Pow(rgbColor.B - b, 2.0);

if (sum == 0.0)
{
return consoleColor;
}
else if (sum < delta)
{
delta = sum;
closestConsoleColor = consoleColor;
}
}

return closestConsoleColor;
}

/// <summary>
/// Maps a System.Drawing.Color to a System.ConsoleColor.
/// </summary>
/// <param name="oldColor">The color to be replaced.</param>
/// <param name="newColor">The color to be mapped.</param>
public void MapColor(ConsoleColor oldColor, Color newColor)
{
#if NETSTANDARD1_3
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
#endif
// NOTE: The default console colors used are gray (foreground) and black (background).
MapColor(oldColor, newColor.R, newColor.G, newColor.B);
#if NETSTANDARD1_3
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
// TODO
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
// TODO
}
#endif
// NOTE: The default console colors used are gray (foreground) and black (background).
MapColor(oldColor, newColor.R, newColor.G, newColor.B);
}

private void MapColor(ConsoleColor color, uint r, uint g, uint b)
Expand Down

0 comments on commit d20b38c

Please sign in to comment.