Convert 4-bit color (=ConsoleColor) to RGB and back

2.2k Views Asked by At

I want to write a console-like gui for a game, and I start with a real console. So I have a class that specifies the colors. Now I want to convert consolecolor to rgb (24-bit) and the other way round. I have tried this:

int d = (((int)col & 8) != 0) ? 255 : 128;
r = (((int)col & 4) != 0) ? d : 0;
g = (((int)col & 2) != 0) ? d : 0;
b = (((int)col & 1) != 0) ? d : 0;

4-Bit colors have this bit-scheme: drgb. When d is 0, the color will be dark; if it's 1, the color will be bright and the rgb values will be 255. My problem is: Color 1000 (bright black) and Color 0111 (dark white) are black and gray in my program. They should be darkgray and lightgray. And how to convert the colors back with rounding and not just converting specific colors back?

3

There are 3 best solutions below

1
On BEST ANSWER

Colors are defined in a 3-dimensional parameter space with the three coordinate axes R, G and B. You can calculate the distance between two colors a and b like this:

double dist = Math.Sqrt(Sqr(a.R - b.R) + Sqr(a.G - b.G) + Sqr(a.B - b.B));

This formula uses this method

private static int Sqr(int x)
{
    return x * x;
}

The best matching color is the one with the smallest distance. You can also use the square of the distance for the comparison of distances (for efficiency):

int sqrDist = Sqr(a.R - b.R) + Sqr(a.G - b.G) + Sqr(a.B - b.B);

Now define an array with the 16 Console colors:

Color[] consoleColors = new[] {
    Colors.Black,
    Colors.Red,
    ...
};

and get the best match with

Color bestMatch;
int bestSqrDist = Int32.MaxValue;
foreach (Color consoleColor in consoleColors) {
    int sqrDist = Sqr(theColor.R - consoleColor.R) +
                  Sqr(theColor.G - consoleColor.G) +
                  Sqr(theColor.B - consoleColor.B);
    if (sqrDist < bestSqrDist) {
        bestMatch = consoleColor;
        bestSqrDist = sqrDist;
    }
}

A more sophisticated approach is to use an algorithm like Floyd–Steinberg dithering to not only reduce the color depth pixel by pixel but to spread the conversion error among several pixels to give the illusion of having subtle color nuances.

0
On

I'm not sure if I understood you correctly but try this:

    int d = (((int)col & 8) != 0) ? 64 : 0;
    r = (((int)col & 4) != 0) ? d + 191 : d;
    g = (((int)col & 2) != 0) ? d + 191 : d;
    b = (((int)col & 1) != 0) ? d + 191 : d;
0
On

The lightgray color is a special case, you just have to handle it separately.

You can set up arrays with the RGB values for the colors and use them for the conversion. They will be handy when you are going to convert the colors back.

Converting 24-bit colors to the palette of 16 colors can be done in many different ways. One way is to calculate the differences between the RGB color and the palette colors, and then pick the palette color with the smallest difference:

Example:

byte[] r = { 0, 0, 0, 0, 128, 128, 128, 128, 192, 0, 0, 0, 255, 255, 255, 255 };
byte[] g = { 0, 0, 128, 128, 0, 0, 128, 128, 192, 0, 255, 255, 0, 0, 255, 255 };
byte[] b = { 0, 128, 0, 128, 0, 128, 0, 128, 192, 255, 0, 255, 0, 255, 0, 255 };

Color c = Color.Khaki;
int index = Enumerable.Range(0, 16)
  .OrderBy(i => Math.Abs(c.R - r[i]) + Math.Abs(c.G - g[i]) + Math.Abs(c.B - b[i]))
  .First();