Converting Color to ConsoleColor?
Asked Answered
C

8

22

What is the best way to convert a System.Drawing.Color to a similar System.ConsoleColor?

Coh answered 1/1, 2010 at 15:7 Comment(2)
I suggest to have a look at stackoverflow.com/questions/28211009 instead of following any of the (all wrong) answers presented here >_>Clinical
That's the opposite of what the OP is asking. The answer you've linked to concerns mapping console colour to the system drawiing color, which is a simpler problem than mapping any system drawing colours to the closest console color.Wheelhorse
D
17

Unfortunately, even though the Windows console can support RGB colors, the Console class only exposes the ConsoleColor enumeration which greatly limits the possible colors you can use. If you want a Color structure to be mapped to the "closest" ConsoleColor, that will be tricky.

But if you want the named Color to match a corresponding ConsoleColor you can make a map such as:

var map = new Dictionary<Color, ConsoleColor>();
map[Color.Red] = ConsoleColor.Red;
map[Color.Blue] = ConsoleColor.Blue;
etc...

Or if performance is not that important, you can round trip through String. (Only works for named colors)

var color = Enum.Parse(typeof(ConsoleColor), color.Name);

EDIT: Here's a link to a question about finding color "closeness".

Dragrope answered 1/1, 2010 at 15:17 Comment(4)
Is it possible without the Console class?Coh
Doesn't appear so. The SetConsoleTextAttribute Win32 API only defines 4 x 1 bit flags for R, G, B, plus an intensity bit. That would only support the 16 colors supported by the ConsoleColor enum.Dragrope
It does, on Vista and up. You have to P/Invoke SetConsoleScreenBufferInfoEx(), social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/…Hodge
You have to be aware that the named colors do not correspond; especially gray, darkgray, green and dark yellow will yield very different colors.Idolatrize
D
40

Here are the console color hex values, as converted by .NET 4.5. First the program:

using System;
using System.Drawing;

class Program
{
    static void Main(string[] args)
    {
        foreach (var n in Enum.GetNames(typeof(ConsoleColor)))
            Console.WriteLine("{0,-12} #{1:X6}", n, Color.FromName(n).ToArgb() & 0xFFFFFF);
    }
}

And here's the output. As you can see, there's a problem with the reporting for DarkYellow. The full 32-bits of that one show up as zero. All the others have 0xFF for the alpha channel.

Black        #000000
DarkBlue     #00008B
DarkGreen    #006400
DarkCyan     #008B8B
DarkRed      #8B0000
DarkMagenta  #8B008B
DarkYellow   #000000   <-- see comments
Gray         #808080
DarkGray     #A9A9A9
Blue         #0000FF
Green        #008000
Cyan         #00FFFF
Red          #FF0000
Magenta      #FF00FF
Yellow       #FFFF00
White        #FFFFFF

edit: I got a little more carried away just now, so here's a converter from RGB to the nearest ConsoleColor value. Note that the dependency on System.Windows.Media is only for the demonstration harness; the actual function itself only references System.Drawing.

using System;
using System.Windows.Media;

class NearestConsoleColor
{
    static ConsoleColor ClosestConsoleColor(byte r, byte g, byte b)
    {
        ConsoleColor ret = 0;
        double rr = r, gg = g, bb = b, delta = double.MaxValue;

        foreach (ConsoleColor cc in Enum.GetValues(typeof(ConsoleColor)))
        {
            var n = Enum.GetName(typeof(ConsoleColor), cc);
            var c = System.Drawing.Color.FromName(n == "DarkYellow" ? "Orange" : n); // bug fix
            var t = Math.Pow(c.R - rr, 2.0) + Math.Pow(c.G - gg, 2.0) + Math.Pow(c.B - bb, 2.0);
            if (t == 0.0)
                return cc;
            if (t < delta)
            {
                delta = t;
                ret = cc;
            }
        }
        return ret;
    }

    static void Main()
    {
        foreach (var pi in typeof(Colors).GetProperties())
        {
            var c = (Color)ColorConverter.ConvertFromString(pi.Name);
            var cc = ClosestConsoleColor(c.R, c.G, c.B);

            Console.ForegroundColor = cc;
            Console.WriteLine("{0,-20} {1} {2}", pi.Name, c, Enum.GetName(typeof(ConsoleColor), cc));
        }
    }
}

The output (partial)...

test output

Deepsix answered 9/9, 2012 at 14:57 Comment(6)
The DarkYellow hex code above (the beginning of the post) is wrong. It says #000000, but it should be #d7c32a (as stated here => rgb.to/color/6990/dark-yellow)Coom
@Coom Please note my comment in the original post: "As you can see, there's a problem with the reporting for DarkYellow."Deepsix
Oh, OK! I didn't read that it's a program output. I used that hex values for my version of your algorithm and realized of the problem when adding the values to a dictionary :) Sorry!Coom
@Coom No problem, I updated the answer to draw more attention to the fact that System.Drawing produces a misleading result, in order to prevent future misunderstandings.Deepsix
@GlennSlayden ?Reinsure
Thanks for going the extra mile, your ClosestConsoleColor method just saved me a chunk of time.Conservancy
J
25
public static System.ConsoleColor FromColor(System.Drawing.Color c) {
    int index = (c.R > 128 | c.G > 128 | c.B > 128) ? 8 : 0; // Bright bit
    index |= (c.R > 64) ? 4 : 0; // Red bit
    index |= (c.G > 64) ? 2 : 0; // Green bit
    index |= (c.B > 64) ? 1 : 0; // Blue bit
    return (System.ConsoleColor)index;
}

The ConsoleColors enumeration seems to use the EGA style palette ordering, which is:

index Brgb
  0   0000  dark black
  1   0001  dark blue
  2   0010  dark green
  3   0011  dark cyan
  4   0100  dark red
  5   0101  dark purple
  6   0110  dark yellow (brown)
  7   0111  dark white (light grey)
  8   1000  bright black (dark grey)
  9   1001  bright blue
 10   1010  bright green
 11   1011  bright cyan    
 12   1100  bright red
 13   1101  bright purple
 14   1110  bright yellow
 15   1111  bright white

You can roughly map a 24-bit colour (or 32-bit colour, by ignoring the alpha channel) to what is essentially 3-bit colour with a brightness component. In this case, the 'brightness' bit is set if any of the System.Drawing.Color's red, green or blue bytes are greater than 128, and the red, green, blue bits are set if the equivalent source bytes are higher than 64.

Jean answered 22/3, 2015 at 8:39 Comment(0)
D
17

Unfortunately, even though the Windows console can support RGB colors, the Console class only exposes the ConsoleColor enumeration which greatly limits the possible colors you can use. If you want a Color structure to be mapped to the "closest" ConsoleColor, that will be tricky.

But if you want the named Color to match a corresponding ConsoleColor you can make a map such as:

var map = new Dictionary<Color, ConsoleColor>();
map[Color.Red] = ConsoleColor.Red;
map[Color.Blue] = ConsoleColor.Blue;
etc...

Or if performance is not that important, you can round trip through String. (Only works for named colors)

var color = Enum.Parse(typeof(ConsoleColor), color.Name);

EDIT: Here's a link to a question about finding color "closeness".

Dragrope answered 1/1, 2010 at 15:17 Comment(4)
Is it possible without the Console class?Coh
Doesn't appear so. The SetConsoleTextAttribute Win32 API only defines 4 x 1 bit flags for R, G, B, plus an intensity bit. That would only support the 16 colors supported by the ConsoleColor enum.Dragrope
It does, on Vista and up. You have to P/Invoke SetConsoleScreenBufferInfoEx(), social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/…Hodge
You have to be aware that the named colors do not correspond; especially gray, darkgray, green and dark yellow will yield very different colors.Idolatrize
D
2

On Vista and later see the SetConsoleScreenBufferInfoEx API function.

For an example of usage refer to my answer to another very similar StackOverflow question. (Thanks Hans Passant for original answer).

Dogma answered 25/6, 2012 at 11:23 Comment(0)
S
2

You can use reflection.

public static class ColorHelpers
{
    public static bool TryGetConsoleColor(Color color, out ConsoleColor consoleColor)
    {
        foreach (PropertyInfo property in typeof (Color).GetProperties())
        {
            Color c = (Color) property.GetValue(null);

            if (color == c)
            {
                int index = Array.IndexOf(Enum.GetNames(typeof (ConsoleColor)), property.Name);
                if (index != -1)
                {
                    consoleColor = (ConsoleColor) Enum.GetValues(typeof (ConsoleColor)).GetValue(index);
                    return true;
                }
            }
        }
        consoleColor = default (ConsoleColor);
        return false;
    }
}

Usage:

private static void Main()
{
    ConsoleColor c;
    if (ColorHelpers.TryGetConsoleColor(Color.Red, out c))
    {
        Console.ForegroundColor = c;
    }
}
Shore answered 10/8, 2014 at 14:50 Comment(0)
I
1

A simple and effective approach can be implemented by using the GetHue, GetBrightness and GetSaturation methods of the Color class.

public static ConsoleColor GetConsoleColor(this Color color) {
    if (color.GetSaturation() < 0.5) {
        // we have a grayish color
        switch ((int)(color.GetBrightness()*3.5)) {
        case 0:  return ConsoleColor.Black;
        case 1:  return ConsoleColor.DarkGray;
        case 2:  return ConsoleColor.Gray;
        default: return ConsoleColor.White;
        }
    }
    int hue = (int)Math.Round(color.GetHue()/60, MidpointRounding.AwayFromZero);
    if (color.GetBrightness() < 0.4) {
        // dark color
        switch (hue) {
        case 1:  return ConsoleColor.DarkYellow;
        case 2:  return ConsoleColor.DarkGreen;
        case 3:  return ConsoleColor.DarkCyan;
        case 4:  return ConsoleColor.DarkBlue;
        case 5:  return ConsoleColor.DarkMagenta;
        default: return ConsoleColor.DarkRed;
        }
    }
    // bright color
    switch (hue) {
    case 1:  return ConsoleColor.Yellow;
    case 2:  return ConsoleColor.Green;
    case 3:  return ConsoleColor.Cyan;
    case 4:  return ConsoleColor.Blue;
    case 5:  return ConsoleColor.Magenta;
    default: return ConsoleColor.Red;
    }
}

Note that the color names of the console do not match the well known colors. So if you test a color mapping scheme, you have to keep in mind that (for instance) in the well known colors, Gray is dark gray, Light Gray is gray, Green is dark green, Lime is pure green, and Olive is dark yellow.

Idolatrize answered 10/8, 2014 at 14:20 Comment(0)
L
1

Easy one...

Public Shared Function ColorToConsoleColor(cColor As Color) As ConsoleColor
        Dim cc As ConsoleColor
        If Not System.Enum.TryParse(Of ConsoleColor)(cColor.Name, cc) Then
            Dim intensity = If(Color.Gray.GetBrightness() < cColor.GetBrightness(), 8, 0)
            Dim r = If(cColor.R >= &H80, 4, 0)
            Dim g = If(cColor.G >= &H80, 2, 0)
            Dim b = If(cColor.B >= &H80, 1, 0)

            cc = CType(intensity + r + g + b, ConsoleColor)
        End If
        Return cc
    End Function
Lukasz answered 3/9, 2018 at 19:58 Comment(1)
Maybe a bit late now but that isn't C#. Looks like VB.NET to me.Indochina
S
1

The distinct default "white-on-blue" color of PowerShell.exe before version 6 (and any ConsoleWindowClass window) is actually DarkYellow on DarkMagenta if you check $Host.UI.RawUI. This is because the ConsoleColor enum values are just indices into the console color table, which is configurable (see this answer about DarkYellow).

Here are the hex RGB values for the default console color table:

Value Hex RGB Name
    0 #000000 Black
    1 #000080 DarkBlue
    2 #008000 DarkGreen
    3 #008080 DarkCyan
    4 #800000 DarkRed
    5 #012456 DarkMagenta
    6 #EEEDF0 DarkYellow
    7 #C0C0C0 Gray
    8 #808080 DarkGray
    9 #0000FF Blue
   10 #00FF00 Green
   11 #00FFFF Cyan
   12 #FF0000 Red
   13 #FF00FF Magenta
   14 #FFFF00 Yellow
   15 #FFFFFF White
Succubus answered 15/11, 2018 at 18:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.