Bit Masking and Shifting

Often times we want to apply search algorithms to images. In order to do this, we first need to read the image data into a multi-dimensional array (or matrix) and split the RGBA values into the numeric representations.

using Images, ImageIO, Colors

const imgpath = "/local/usr/starimage.png";

img = load(imgpath);
imgbits = reinterpret(UInt32, img);

Each pixel is an unsigned 32-bit integer in hexadecimal, e.g. 0xff0b0c0e.

Julia makes it easy to extract packed values using the reinterpret function.

reinterpret(UInt8, [imgbits[1,1]])

> 4-element reinterpret(UInt8, ::Vector{UInt32}):
>  0x0e
>  0x0c
>  0x0b
>  0xff

Notice that not only did it reverse the order of the bits, but it split the 32-bit integer into four 8-bit integers which perfectly associate to red, green, blue, and alpha.

We can also do this manually by applying a "mask and shift" approach. I'll write a function which maps over an array of tuples, where each tuple contains a bitmask and a shift amount.

bitmasks = [0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000]
bitshifts = [0, 8, 16, 24]

bitcontext = zip(bitmasks, bitshifts) |> collect
> 4-element Vector{Tuple{UInt32, Int64}}:
>  (0x000000ff, 0)
>  (0x0000ff00, 8)
>  (0x00ff0000, 16)
>  (0xff000000, 24)

Next I'll write a function which will iterate over the bitcontext tuples, first applying the bitmask and then shifting by the number of bits specified.

function extractpixel(pix)
    map(x -> UInt8((pix & x[1]) >> x[2]), bitcontext)
end

We first perform a bit and operation between 0xff0b0c0e and 0x000000ff which yields 0x0000000e. We finally shift it by \(0\) since we're already in the lowest bit position.

In general though, we will perform a bit and operation followed by a shift.

(0xff0b0c0e & 0x00ff0000) will yield 0x000b0000. But we want the 0b moved to the right-most position, so we need to shift by \(16\) bits, 0x000b0000 >> 16.

extractpixel(imgbits[1,1])
> 4-element Vector{UInt8}:
>  0x0e
>  0x0c
>  0x0b
>  0xff