A closer look at color_t


Roguelike games have historically used an ASCII or ANSI based terminal to present the layout of the game to the player.
In todays world of modern computers, the terminal usage of years past has been replaced by 3d graphic excelerating video
displays, 1080p, 1260x1140 resolutions and graphics bordering on the "better than life". But roguelike games have cemented
their place as a lasting and influential genera. Even as games have evolved to use tilesheets and graphics, most roguelike
games have at the least left an ASCII mode for nostalgia or purists.

The days of ANSI terminals may be gone, but terminal emulators are still alive and well, infact the popular roguelike libraries
libtcod and BearLibTerminal being two examples, are modeled on displaying the game as a terminal emulator. Other's choose the
venerable NCURSES terminal library, the choices are many and varied. One thing they all share in common is the ability to use different
colors to liven the textual, or bitmap tile displays.

Understanding HOW color is represented, generated, and stored opens up more possibilities to make a stunning and unique visual
experience for our games. As children in art class we learn about the primary colors: Red Yellow, and Blue. Digital images, be it on
your cell phones camera, or the text you're reading on a screen are represented
by three primary colors as well, Red, Green, and Blue(RGB). Anyone who has had even a cursory
experience of using HTML will recogize their hexadecimal representations.

RGB values can range from 0 to 255 in decimal notation, with each color R, G, and B having its own 0 - 255 value. The reason being is there
256 bytes in 8 bits. While it takes 3 places in decimal, 256 is only two places in hex, FF, or 0xFF (the 0x being shorthand for "This number is a hex value").
In this way, each "channel" can be stored by its 2 character hex representation in 8 bits. This is why HTML colors are formated the way they
are: #FF0000 for Red, #00FF00 for Green, and #0000FF for Blue.
So far we've only mentioned RGB, but there is also ARGB, the A standing for Alpha channel. The alpha channel is also an 8 bit value, but instead of
representing a color it can have a brightening or dulling effect on the vibracity of the color.

In BearLibTerminal, libtcod, and NCURSES, as well as many other terminal/terminal emulator libraries there is a color_t type. In almost every
single case, color_t is:

typedef unsigned int color_t;
//or
typedef uint32_t color_t;
//(these are the same)
                    
The reason being that a 32 bit unsigned integer is 4 x 8bit = 32bits PERFECT for representing our four channels in hexadecimal format as
                        0xAARRGGBB
                        0xAA for the alpha channel
                        0xRR for the red channel
                        0xGG for the green channel
                        and 0xBB for the blue channel.
                    
In this way we can use simple bitwise operations to create and blend any color or colors that we wish. Bitwise operations can be used to do math
"cheaply" by being able to change the individual bits representing a number in our computers memory. In most C like languages (C++, perl, go, etc
but strangely, Java's are slightly different). The bitwise operators themselves are & (AND), | (OR), ^ (XOR), ~ (NOT). These operators are used in
comparing, or relating two numbers. There is also the bit shift operators << (shift left) and >> (shift right). These are used to "shift"
the position of each bit in the number. Bitwise and bitshift operators are described below:
                         & - bitwise AND when comparing the bit position of two numbers, if the bit in both numbers is '1' then the value is '1', if they are both '0'
                         the value is '1', and if one is bit is '1' and the other bit is '0', the number is zero.
                         ex:      1101
                                & 1001
                                -------
                                  1011
                         | - (OR) if either bit is 1, the value is '1'
                          ex:   10101
                              | 10110
                               -------
                                10111
                         ^ - (XOR) the bits must be different to be '1'
                                 1100
                              ^  1101
                              --------
                                 0001
                         ~ - (NOT) simple negation. can be used for changing positive numbers to negative numbers, etc.
                               ~0001 becomes 1110
                        << - shift bits left 'x' places
                             01011 << 2 becomes 01100
                        >> - shift bits right 'x' places
                             01011 >> 2 becomes 00010
                     

Needless to say, when used properly these "low level" operations are both extremely powerful and useful. There is also one more bitwise operation that
is used, but its more of a "technique", not an operator. That technique is called bit masking. Bit masking can be used to "mask" the bits of a number so that
you are only operating on certain bits, like using masking tape while painting to cover the areas you dont want to paint on.

Lets say we have the color violet at full brightness:

typedef unsigned int color_t;
                        
color_t violet = 0xFF7F00FF;
//Alpha: 0xFF = 255
//Red: 0x7F = 127
//Green: 0x00 = 0
//Blue: 0xFF = 255;
                     

This is where the bitwise operations come in, and i'll show you why. We know the decimal and hex values of each color and they need to be combined to blend into
the final color, violet, but we have a problem:

                         
color_t violet = 0xFF7F00FF;
unsigned int alpha = 0xFF;
unsigned int red = 0x7F;
unsigned int green = 0x00;
unsigned int blue = 0xFF;
color_t violet = alpha + red + green + blue;
                     
Lets see how our Violet turned out:Violet. Uh oh. Clearly that wast going to work because what we need isnt
addition. Yes, we're combining the colors, but were not adding the values in the traditional sense like you would with paint. What we need to do is combine the values
by "stringing" them together in a one after the other.
//when we type 255 as 0xFF
unsigned int alpha = 0xFF; 
//this is shorthand, the actual value is 
unsigned int alpha = 0xFF000000;
//the same goes for the other values
unsigned int red = 0x7F;
unsigned int red = 0x7F000000;
                       
We saw above that we can shift the place of bits using the << operator. And we know that every 2 digits represents 8 bits of our 32 bit value, so following the
convention of 0xAARRGGBB we know we want the alpha channel to be the first 8 bits, and the red channel to be the 8 bits immeditely after that, which means that technically
the alpha channel doesnt need to be shifted at all, to start we can simply to the following:
typedef unsigned int color_t;
color_t violet;
unsigned int alpha = 0xFF;
color_t violet = alpha;
unsigned int red = 0x7F;
                       
But how to we put the red next to the alpha, without changing the value of the alpha position? we already know that addition wont work. The answer, is the | (OR) operator:
color_t violet = 0xFF;
unsigned int alpha = 0xFF;
unsigned int red = 0x7F;
color_t violet = violet | red << 16;
// this now makes violet becomes
color_t violet = 0xFF7F;
                         
Now we're getting somewhere. By combining the bitwise OR operator with the shift left operator we can construct our entire 32 bit unsigned int representation of 0xAARRGGBB:
typedef unsigned int color_t;
unsigned int alpha = 0xFF;
unsigned int red = 0x7F;
unsigned int green = 0x00;
unsigned int blue = 0xFF;
color_t violet = alpha << 24 | red << 16 | green << 8 | blue;
                            
Lets try our Violet color now: Violet success!

Now that we can put colors together, lets work on taking colors apart from there whole representation in to their respective ARGB values. In order to do this, we will again make use of bit shifting to obtain the bits
that were after. Since we shifted left to put them in place on the number, it makes sense that we will be shifting right to retrieve
the desired portions. Once again we'll want to use a bitmask to ensure we are influencing the desired bits
for each operation. For this we'll be using the (&) AND operator.

typedef unsigned int color_t;
color_t violet = 0xFF7F00FF;
unsigned int alpha = (violet >> 24); //take the left most 8 bits
unsigned int red = (violet >> 16) & 255;   // then the next
unsigned int green = (violet >> 8) & 255;  // and then next
unsigned int blue = (violet) & 255;          // and finally the last 8 bits
                               

So why would we want to seperate the a color into its individual channels? Well, this way we have a very high level of precision over the color we are using, but more importantly
it enables to very easily implement things such as fading one color into another. A basic function for blending two colors is as follows:

// create a struct to split the color into its individual channels
struct aRGB {
    public:
    unsigned int a,r,g,b;
    aRGB(unsigned int col)
    {
      a = (col >> 24);
      r = (col >> 16) & 255;
      g = (col >> 8) & 255;
      b = (col) & 255;
    }
};
//this function blends the individual channels of two colors and recombines them
//into a new color
color_t fadeColors(color_t first, color_t second, float mix)
{
    aRGB color_one = aRGB(first);
    aRGB color_two = aRGB(second);
    color_t ret_alpha = color_one.a + color_two.a / 2;
    color_t ret_r = (unsigned int)(color_one.r*(1-mix) + color_two.r*(mix));
    color_t ret_g = (unsigned int)(color_one.g*(1-mix) + color_two.g*(mix));
    color_t ret_b = (unsigned int)(color_one.b*(1-mix) + color_two.b*(mix));
    color_t ret_argb = ret_alpha << 24 |  ret_r << 16 | ret_g << 8 | ret_b << 0;
    return ret_argb;
}
                            

We use a struct to hold the individual channels of each color, the value of 'mix' controls the amount of blending. The lower the value of mix, the more of the first color is represented,
the higher the value of mix, more of the second color is represented. If we use a loop to incrememntly step the mix from one color towards another, we can create blended text:

void blended_text(std::string str)
{
     int i;
     float mix;
     color_t result;
     color_t green = 0xFF00FF00;
     color_t red = 0xFFFF0000;
     for (i = 0; i < str.length(); i++)
     {
        mix =(float)i/str.length();
        result = fadeColors(green, red, mix);
        terminal_color(result);
        terminal_put(2+i,5,str.at(i));
     }
    
}
Now that you have a better understanding of how colors can be stored, represented, and manipulated as 32bit unsigned integers, a whole host of color related fun awaits. And the bitwise operations presented here
are just the tip of the ice berg when it comes to what those can do, but that's a topic for another article(or maybe a book...).
As always, the code presented here is available at my Github. That's it for now! -max.


(c) 2020 Max Goren
MaxCodes.info
maxgoren@icloud.com