-
Notifications
You must be signed in to change notification settings - Fork 2
Color
As seen in example screenshots this program also has particles changing color. Even though the program itself uses SDL-library, the concept presented here are not depended on the graphics library. Any graphics library, that has ability to do pixel access, can be used, but there might be some parts that need tweaking when using with graphic library of choice. Before moving on how particle color is changed, it is important to understand how 32-bit color works, although the program is currently using 24-bit color for making it run more efficiently. In most cases colors are divided into four channels red, green and blue or RGB for short, and one extra channel for opacity, or transparency, called alpha. All of these components together form a RGBA color scheme. Each of these channel can hold a value that is 8 bit wide, so when there are four channels total amount is 32-bit. For keeping this example simple, this wiki will not address the alpha channel in this document and thus keep color 24-bit.
So what are these color channels for and why they hold a value? Each channel value determine how intense their color presence is. Since each of the channel is 8 bits the value inside each channel can range from 0 in decimal (0000 0000 in binary and 0 in hexadecimal) to 255 in decimal (1111 1111 in binary and FF in hexadecimal), where 0 is the least presence and 255 is the most. This is best illustrated, well, with an illustration:
In this picture each one of the color channels are at max value of 255, when channels are mixed another colors appear. When mixing all the channels with maximum value color is white as shown at the middle of the picture. Conversely, when all the color channels are at value 0, whole picture would be completely black. With varying all these values a lot of different colors can be formed. Since 0 is also a color, there is total of 256 different color variation per channel and when there is three channels for color it equals to 16 777 216 different color variants. Proof: 256 multiplied by 256 multiplied by 256 (or 256 in the power of 3).
One more thing before going on the details how colors are implemented. One may have noticed that first and second paragraph mentioned something about other number systems called binary and hexadecimal. Many times when dealing with colors they are either represented as decimals from 0 to 255 per channel or as hexadecimal as 0 to ff per channel. Hexadecimal is particularly useful when representing color values of all channels because how easy it is to read, for example ff|ff|ff means that all the color channels are at maximum value and the resulting color is white, while ff|ff|00 means that red, the channel before first | is at maximum value, as well as green, at the middle. However blue is at value 00 and the resulting color is yellow (can be also be seen on the picture above). Keep these in mind because they are useful later on when we go through bit shifting and storing color values on own variables from variable that holds total color value from all the channels. This document will go into more detail about binary system and bits later.
//using <stdint.h> library to make sure that variables are fixed size in bits and are not selected by operating environment
#include <stdint.h>
//Store elapsed time since program has started to run to variable elapsed
uint32_t = SDL_GetTicks();
//These three variables are used to store value from algorithm that vary the number between 0 - 255 where 0 is complete black and 255 is complete white.
uint8_t red = ((1 + sin(elapsed * 0.003)) * 128);
uint8_t green = ((1 + sin(elapsed * 0.002)) * 128);
uint8_t blue = ((1 + sin(elapsed * 0.004)) * 128);Presented above is the algorithm for changing value in each color channel. Starting from the sin() function; it returns the sine of an angle (argument) given in radians as a value between 1 and -1. It is a value representing an angle expressed in radians. One radian is equivalent to 180/PI degrees. Sin is a ratio of the length of the side that is opposite that angle to the length of the longest side of the triangle. Deeper knowledge about sine is not required to understand this program, since the only thing that sin function is used in this case is to make the value stored in the channel move between minimum and maximum value of channel. It could as well be cos or tan function because they return same sort of value. Moving on; the value that is passed to the sin-function is the time that the program has been running that is saved from SDL_GetTicks function in milliseconds, again this is something that should be present on many graphics library. Adding 1 to it makes sure that the value stays between 2 and 0, instead of 1 and 0. Finally that value is multiplied by value that is lower than 0.00 because otherwise the color value would change too fast since value would change very rapidly between different radians, such as straight from 0 to 2, and it would look rather messy to human eye. The last multiplication by 128 is to make the value grows between 0 and 255. For reference here is a 20 decimal values for each color channel, their values converted to one single hexadecimal value, and finally showing what color does that value represent. All of them taken from the program running the very same algorithm, so it is “real life” scenario.
| Update # | Red-channel | Green-channel | Blue-channel | Hex-value | Color |
|---|---|---|---|---|---|
| 1 | 74 | 39 | 255 | #4a27ff | |
| 2 | 77 | 38 | 255 | #4d26ff | |
| 3 | 81 | 36 | 255 | #5124ff | |
| 4 | 85 | 34 | 255 | #5522ff | |
| 5 | 88 | 32 | 255 | #5820ff | |
| 6 | 92 | 31 | 254 | #5c1ffe | |
| 7 | 95 | 29 | 253 | #5f1dfd | |
| 8 | 99 | 27 | 252 | #631bfc | |
| 9 | 104 | 25 | 251 | #6819fb | |
| 10 | 107 | 24 | 249 | #6b18f9 | |
| 11 | 111 | 23 | 248 | #6f17f8 | |
| 12 | 115 | 21 | 246 | #7315f6 | |
| 13 | 119 | 19 | 243 | #7713f3 | |
| 14 | 124 | 18 | 241 | #7c12f1 | |
| 15 | 128 | 17 | 238 | #8011ee | |
| 16 | 131 | 15 | 236 | #830fec | |
| 17 | 135 | 14 | 233 | #870ee9 | |
| 18 | 139 | 13 | 230 | #8b0de6 | |
| 19 | 143 | 12 | 227 | #8f0ce3 | |
| 20 | 147 | 11 | 223 | #930bdf |
As can be seen values change gradually so that the transformation is smooth and the final product on the screen looks rather nice:

So far color channels have been their own different entities in the program, but this wiki has lumped them together rather nicely. So what kind of code is needed to for combining three different variable values together without actually mathematically adding them with only using +-operator? The answer is bitwise shifting! First let’s take a look at the code:
//Still using #include <stdint.h> to make sure that values are fixed size
//Initialize the 8 byte (32 bits) integer variable with zero value
uint32_t color(0);
//Setting up the red channel value to color and shifting two bytes (8 bits) forward in variable to access different color
color += red;
color <<= 8;
//Setting up the green channel value to color and shifting two bytes (8 bits) forward in variable to access different color
color += green;
color <<= 8;
//Setting up the blue channel value to color, since all the channels are store in color-variable no need to shift bits anymore.
color += blue;As stated at the beginning of of this document understanding binaries is somewhat relevant. Now it is time to dive head first in the world of binaries, so that the code above makes sense. Binary is a number system, which has a base of two. Meaning that there are only two numbers available, 0 and 1. To compare widely used decimal system has a base of ten, meaning there are numbers from 0 to 9 available. When 32 bit integer is created and initialized with syntax uint32_t color(0) it looks like this in binary:
0000 0000 0000 0000 0000 0000 0000 0000
Then what are these bits and bytes in binary system? Looking closely to previous binary number one can notice that they are grouped in fours and there are eight of such groups. That's right! One byte is one of those grouped zeroes seen above and since there are eight of such groups on this binary number one can safely assume that this variable is in fact eight bytes long. After that, the mystery of bits can be also decoded. Since there are eight groups of fours equals thirty-two. Each number is one bit and because of two base nature of binary system one bit can be either 0 or 1. Each bit represent one decimal value starting from right and moving to left, bit values are 1, 2, 4, 8, 16, 32, 64, 128 and so on until there are no more bits remaining. On unsigned data types, such as uint32_t and uint8_t, the leftmost bit can also hold value whereas, in case of signed int or char the last bit is always used to determine if value is positive or negative, and thus unsigned variables cannot hold negative values. On Uint32 variable last bit value is 2147483648 in decimal system and if every bit is active the maximum value is 4294967295 in decimal. Last bit active looks like this in binary:
1000 0000 0000 0000 0000 0000 0000 0000
Every bit active looks like this in binary:
1111 1111 1111 1111 1111 1111 1111 1111
So going back to code block above that introduced bit shifting. For sake of example color variable red holds value 95, which is 0101 1111 in binary, green holds value 243 which is 1111 0011 in binary and finally blue holds value 44 which is 0010 1100. Now to recap 32 bit integer called color looks like this when it initialized with zero value:
0000 0000 0000 0000 0000 0000 0000 0000
When this math operation is run
color += red;Binary value is now:
0000 0000 0000 0000 0000 0000 0000 0101 1111
Meaning that variable color now holds the exact same value as variable red. Now before adding value of green variable bitwise shifting is done to color variable:
color <<= 8;After bitwise shifting color variable looks like this:
0000 0000 0000 0000 0000 0101 1111 0000 0000
Value of red variable is shifted left by amount of bits that is defined after <<=-operator meaning that now when new value is added to color variable from green:
color += green;Resulting:
0000 0000 0000 0000 0000 0101 1111 1111 0011
Which is 24563 in decimal. If math operation += of red and green variables to color variable would be done without bit shifting binary result would have been:
0000 0000 0000 0000 0000 0001 0101 0010
Which is 338 in decimal, since 95 + 243 equals 338. As can be seen rather than doing standard addition, bit shifting allows direct manipulation of binary values creating values that would not be possible otherwise. After final bit shift and addition of variable blue result is:
0000 0000 0101 1111 1111 0011 0010 1100
Like in decimal system leading zeros can be discarded so that nine zeroes starting from the leftmost bit are not used. Hex value of resulting color is #5FF32C. This is how the final color value is formed from three different color channel values, now the last thing needed is to set up a buffer that holds all the pixel values before drawing them on the screen.
The Particle Fire Simulation revision uses array of 32 bit integers to hold every pixel’s color value.
//Still using #include <stdint.h> to make sure that values are fixed size
uint32_t *pixel_buffer;
//Variables screen_width_ and screen_height_ are resolution values, such as 1024x768 or 1920x1080
pixel_buffer = new uint32_t[screen_width_*screen_height_];
//Save current color inside to selected pixel in buffer array
pixel_buffer[(y_pixel * screen_width_) + x_pixel] = color;
Above code example creates pointer called pixel_buffer that points to array of 32 bit integers that is a size of every pixel of a screen. Comments include two resolution values 1024x768 and 1920x1080 so for sake of example here is calculated array sizes for both cases. With former values array would be 786432 and in the latters case 2073600. Finally the color value is saved inside one of the element in an array that is determined inside y_pixel- and x_pixel-variable. Algorithm for accessing pixels being (y_pixel * screen_width_) + x_pixel rather than just y_pixel * x_pixel is because otherwise there would be no way to access first elements. To illustrate this, take a look at following table that is for resolution that has a width of 800 pixels and height of 600 pixels. Each of table element represent one pixel on the screen.
| 🡇 Height | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ |
|---|---|---|---|---|---|---|---|---|---|
| Width 🡆 | X:0 | X:... | X: 200 | X:... | X:400 | X:... | X:600 | X:... | X:800 |
| Y:0 | |||||||||
| Y:.. | |||||||||
| Y:200 | |||||||||
| Y:.. | |||||||||
| Y:300 | |||||||||
| Y:.. | |||||||||
| Y:400 | |||||||||
| Y:.. | |||||||||
| Y:600 |
Rather than drawing a table that shows every pixel of a 800x600 resolution the table above shows nine different pixel values per axis that are along the same pixel range that the example resolution. Since first row of pixels starts from Y:0 with algorithm y_pixel * x_pixel result would always be zero meaning that when program tries to access pixel that is in Y:0 and X:400 it would still draw it at the Y:0 and X:0 location, so the (y_pixel * screen_width_) + x_pixel algorithm makes sure that even this very first line of pixels (0-800 elements in this case) are accessible.
Now when setting, for example color value #5FF32C, to the middle of the screen it is done with the following code block:
//Store the current resolution values to variables
y_pixel = 300
x_pixel = 400
//Save current color #value 5FF32C inside to selected pixel in buffer array
pixel_buffer[(y_pixel * screen_width_) + x_pixel] = color;And the result looking like this, when the pixel values is drawn on the screen:
| 🡇 Height | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ | ------ |
|---|---|---|---|---|---|---|---|---|---|
| Width 🡆 | X:0 | X:... | X: 200 | X:... | X:400 | X:... | X:600 | X:... | X:800 |
| Y:0 | |||||||||
| Y:.. | |||||||||
| Y:200 | |||||||||
| Y:.. | |||||||||
| Y:300 | |||||||||
| Y:.. | |||||||||
| Y:400 | |||||||||
| Y:.. | |||||||||
| Y:600 |
Again this entry is general purpose so it will not go into detail how exactly use SDL-library to draw elements on the screen. For that it is a good idea taking a look at Pixel setting method of Screen class and also how to Color setting method of Swarm class of the project and of course the at the source code itself at repo page.