There is more than one way to think about a pattern of bits in memory. Usually, we think of the bits stored in an integer as representing a single number. The higher-order bits contribute greater value than the lower-order bits and their values are summed to arrive at a single number, so the following pattern:
00001101 00110000 00000111
Would be interpreted as:
2^0 + 2^1 + 2^3 + 2^13 + 2^14 + 2^15 + 2^17 + 2^18 = 864,263
But we're free to think of this pattern as three individual groups of 8-bit numbers (each representing the numeric value of a color component). However, to convert a bit pattern in the higher-order bits into the number we intend it to represent, we need to interpret those bits as though they appeared in the rightmost 8-bit group. This is why we shift.
For example, to get the value of the leftmost 8-bit group, we need to mask all bits not in that group, and then shift those bits 16 places to the right, so that they occupy the rightmost position:
// first, assume n = 00001101 00110000 00000111
(n & 0xFF0000) >> 16 // red
Zeros are automatically shifted into the vacated bits, leaving:
00000000 00000000 00001101
Which we can now interpret as:
13
Similarly, we can calculate the value of the bits in the center and rightmost groups:
(n & 0x00FF00) >> 8 // green
n & 0x0000FF // blue (no shift necessary)
Setting the value of one of these components requires shifting in the other direction. For example, the following shifts 75
into the center 8-bit position:
n = (n & (0xFF00FF)) | (75 << 8)
We're first resetting the green value (with the 0xFF00FF
mask) and then shifting the number 75
8 bits to the left. Finally, we combine these two numbers.
We can see that this works by shifting it back out again:
(n & 0x00FF00) >> 8 // => 75
Resetting or maximizing one of the components is even simpler. As we've seen in the earlier example, zero-ing a component can be done like this:
n = n & 0xFF00FF // clear out green
The following mask, on the other hand, maximizes the value of the green component:
n = n | 0x00FF00