How does it work - Bit shifting & masking

tl;dr

I explore how bitwise manipulations allow you to store the red, green, blue and alpha components of a colour in a single integer.


Today I'm going to step through a helper method that allows you to create a UIColor instance from the hex colour values that designers love to give you. The category method in question looks like this:

+ (UIColor *)pas_colorWithRGBAHex:(u_int32_t)rgba;

When I first saw an implementation of this many years ago I remember staring at the source and feeling my eyes glaze over. Let's break this apart and demystify what's really going on.


u_int32_t

One of the questions you might ask when looking at this method is why be so specific with the type of the parameter and how do we know it needs to be a u_int32_t and not something else like u_int64_t or int? To answer that we'll look at some sample input provided by a designer in the form of a lovely green colour.

#01A902FF /* a lovely green */

This is actually 4 pairs of hexadecimal numbers, reading from left to right we have:

  • The Red component is #01
  • The Green component is #A9
  • The Blue component is #02
  • The Alpha component is #FF

In base 10, our normal counting system, this would be 1, 169, 2, 255 respectively.

Each hexadecimal component covers the range 00..FF, which is equivalent to 0..255. To be able to represent all the numbers from 0..255 in binary we would need 8 bits.

bits

A bit can only represent 21 values as it can either be 1 or 0. By looking at groups of bits we can increase the number of values that can be represented. For example a 4 bit number can be configured in 24 or 2 x 2 x 2 x 2 different combinations, which means we can use it to represent 16 unique values.

To represent 0..255 we would need 8 bits as 28 or 2 x 2 x 2 x 2 x 2 x 2 x 2 x 2 equals 256.

So knowing that we have to represent 4 colour components that each require 8 bits we can finally understand why we chose u_int32_t as the type of the argument (4 x 8 = 32).


rgba

The next logical question is how do we extract the four 8 bit values from a single integer?

Let's start by looking at how our hexadecimal #01A902FF (green) value actually looks under the hood when represented by a u_int32_t. Keep in mind that a u_int32_t is just a collection of 32 bits that can each have a value of 1 or 0:

1101010010000001011111111 // binary solo (Flight of the Conchords reference)

Let's do that again but with some spacing and some markers to show which bits represent our RGBA components.

+--  R  --+--  G  --+--  B  --+--  A  --+
|         |         |         |         |
 0000 0001 1010 1001 0000 0010 1111 1111

When a bit is set to 1 it means that the unit that the bit corresponds to should be included in the total. The units are powers of 2, with the power to raise by incrementing by 1 each time and starting from the right with 20. Here's a few examples using just the first 8 bits:

| 2^7 | 2^6 | 2^5 | 2^4 | 2^3 | 2^2 | 2^1 | 2^0 |
   1     1     1     1     1     1     1     1    = 255 = (128  + 64 + 32 + 16 + 8 + 4 + 2 + 1)
   0     1     0     0     0     0     0     0    = 64  = (64)
   0     0     1     0     1     0     1     0    = 42  = (32 + 8 + 2)

With that slight detour out of the way we can crack on with extracting the Alpha component, which is the easiest to work with. Currently our u_int32_t is capable of representing the values 0..4294967295, but we only want to extract an 8 bit value with a range of 0..255. We need to somehow look at just the 8 bits that we care about - this can be achieved with bit masking.


&

The logical AND, which is represented by the single ampersand character (&), is used to compare two values by looking at the bits that the values are represented by. When using the logical AND a bit is only set to 1 in the resulting value if it is 1 in both the left and right values used in an expression.

15 is represented by the binary 0b1111. So 15 & 15 would equal 15 and look like this:

0b1111   /*
0b1111 &  * All the bits are on in both values so all
------    * of the bits in the resulting value are on
0b1111    */

0 is represented by the binary 0b0000. So 0 & 15 would equal 0 and look like this:

0b0000   /*
0b1111 &  * There are no bits that are on in both values
------    * so the resulting value has no bits on
0b0000    */

10 is represented by the binary 0b1010 and 12 looks like 0b1100. So 10 & 12 would result in 8 and look like this:

0b1010   /*
0b1100 &  * Only the 8 bit is on in both values so only
------    * the 8 bit is on in the result
0b1000    */

This may not seem very helpful but this is the key to being able to treat our 32 bit value as if it was only 8 bits. To get our Alpha component we need to turn of the 24 bits that we don't care about and ensure that the first 8 bits keep their current values.

To achieve this we use a bit pattern that turns on only the first 8 bits and then logical AND this with our RGBA value:

rgba & #000000FF

#000000FF is hex for 255, which is the first 8 bits turned on (0b1111 1111) - here is how the above looks at the bit level:

+--  R  --+--  G  --+--  B  --+--  A  --+
|         |         |         |         |
 0000 0001 1010 1001 0000 0010 1111 1111
 0000 0000 0000 0000 0000 0000 1111 1111  &
 ---------------------------------------
 0000 0000 0000 0000 0000 0000 1111 1111

Great this has masked out the last 24 bits of our u_int32_t and has given us the result of 255 for the Alpha component.

Hopefully this makes sense and we are ready to try and extract more components. Let's jump to the Green value for a challenge. With our new technique we may naively assume that we just want to mask out all of the data that we don't want. We could do that with the following:

rgba & #00FF0000

#00FF0000 is the same as 0b0000 0000 1111 1111 0000 0000 0000 0000 - at the bit level we get:

+--  R  --+--  G  --+--  B  --+--  A  --+
|         |         |         |         |
 0000 0001 1010 1001 0000 0010 1111 1111
 0000 0000 1111 1111 0000 0000 0000 0000  &
 ---------------------------------------
 0000 0000 1010 1001 0000 0000 0000 0000 

This has indeed masked out the data we don't care about, but the resulting value is 11075584 which is well beyond the 0..255 range we was looking for. This is because the bits that are set to 1 represent 65536 + 524288 + 2097152 + 8388608.

What we need to do is shift the data to the right so that the bits representing the Green component move from their current place, in the middle, all the way over to the right so that they are in the first 8 bits of our variable. If the Green bit pattern (1010 1001) occurred in the first 8 bits we would get 1 + 8 + 32 + 128 = 169.

We move the values to the right with the right shift operator.


>>

We want to shift the 8 bits that represent the Green component all the way to the right. There are 16 bits in front of the Green component so we need to shift our number 16 times to the right - simple

rgba >> 16

As the bits are shifted to the right they will fall of the right hand side, then new values will need to be added to the left to pad the newly created space. What the padding values are depends on the variable's type and a few other things. After doing rgba >> 16 our bits now look like this

+------  new  ------+--  R  --+--  G  --+
|                   |         |         |
 0000 0000 0000 0000 0000 0000 1010 1001

This has gotten the bits of the Green component into a position where we can just mask them off like we did with the Alpha component.


If we follow this logic for each component we end up with the final demystified implementation of:

+ (UIColor *)pas_colorWithRGBAHex:(u_int32_t)rgba;
{
    int r = 0xFF & (rgba >> 24);
    int g = 0xFF & (rgba >> 16);
    int b = 0xFF & (rgba >> 8);
    int a = 0xFF & (rgba);

    return [UIColor colorWithRed:r / 255.0f
                           green:g / 255.0f
                            blue:b / 255.0f
                           alpha:a / 255.0f];
}

For each component we follow the pattern of:

  • Shift the bits in the rgba variable to the right until the data we are interested in is in the first 8 bits
  • Mask of the first 8 bits to ensure that any other bits are ignored

This category method requires the hex value to be defined in the form of 2 hexadecimal numbers for each colour component - you could easily make different methods to handle different formats now you know how it all works. This category method would be used like this:

UIColor *greenColor = [UIColor pas_colorWithRGBAHex:0x01A902FF];

Wrap up

This post only looks at the right shift >> and logical AND & operators but hopefully it allows you to begin to imagine in your minds eye how bits are stored and can be manipulated.

Essentially under the hood the u_int32_t variable is just held as a sequence of bits. We are exploiting this structure to encode more than one piece of information in a single variable. We then have to use various bitwise operations to extract the meaning that we encoded into the integer.