Pixel binary layout w premultiplied alpha

June 3, 2013

Greetings

In previous posts, RGB pixels and RGBA pixels were covered. In this post, premultiplication of the RGB components will be covered.

A premultiplied pixel has an algorithmic advantage as compared to a non-premultiplied pixel. Specifically, Alpha Compositing is a method of combining a foreground image with a background that is made significantly more simple when the input pixels are already premultiplied. For a detailed description of the math involved, see this article or this wikipedia page. This post assumes premultiplication is required because the CoreGraphics layer in iOS and MacOSX supports only premultiplied alpha components. On iOS, the native endian is little endian and the preferred pixel layout is known as BGRA.

The following source code shows how one could implement conversion of plain RGBA values to premultiplied RGBA values. The pixels will be stored as a 32 bit unsigned integer. When a pixel is fully opaque, the alpha channel is 0xFF and the RGB values remain the same. When a pixel is fully transparent, the premultiplication results in the value zero for each component. The pixel values get more interesting when the alpha component is in the range 1 to 254. Converting the 3 RGB channels means 3 floating point multiply operations and another to calculate (alpha / 255.0).


#include <stdint.h> // for uint32_t
#include <stdio.h> // for printf()

uint32_t rgba_to_pixel(uint8_t red, uint8_t green,
                       uint8_t blue, uint8_t alpha)
{
  if (alpha == 0) {
    // Any pixel that is fully transparent can be represented by zero
    return 0;
  } else if (alpha == 0xFF) {
    // Any pixel that is fully opaque need not be multiplied by 1.0
  } else {
    float alphaf = alpha / 255.0;
    red = (int) (red * alphaf + 0.5);
    green = (int) (green * alphaf + 0.5);
    blue = (int) (blue * alphaf + 0.5);
  }

  return (alpha << 24) | (red << 16) | (green << 8) | blue;
}

void print_pixel_rgba(char *desc, uint32_t pixel)
{
  uint32_t alpha = (pixel >> 24) & 0xFF;
  uint32_t red = (pixel >> 16) & 0xFF;
  uint32_t green = (pixel >> 8) & 0xFF;
  uint32_t blue = (pixel >> 0) & 0xFF;

  printf("%10s pixel 0x%.8X : (R G B A) (%d, %d, %d, %d)\n",
         desc, pixel, red, green, blue, alpha);
}

int main(int argc, char **argv)
{
  uint32_t red = rgba_to_pixel(255, 0, 0, 255);
  print_pixel_rgba("red", red);

  uint32_t green = rgba_to_pixel(0, 255, 0, 127);
  print_pixel_rgba("50% green", green);

  uint32_t black = rgba_to_pixel(0, 0, 0, 127);
  print_pixel_rgba("50% black", black);

  uint32_t white_transparent = rgba_to_pixel(255, 255, 255, 0);
  print_pixel_rgba("0% white", white_transparent);

  uint32_t white = rgba_to_pixel(255, 255, 255, 191);
  print_pixel_rgba("75% white", white);

  return 0;
}

Compile the source with gcc like so:

$ gcc -o encode_decode_pixels_prergba encode_decode_pixels_prergba.c

$ ./encode_decode_pixels_prergba
       red pixel 0xFFFF0000 : (R G B A) (255, 0, 0, 255)
 50% green pixel 0x7F007F00 : (R G B A) (0, 127, 0, 127)
 50% black pixel 0x7F000000 : (R G B A) (0, 0, 0, 127)
  0% white pixel 0x00000000 : (R G B A) (0, 0, 0, 0)
 75% white pixel 0xBFBFBFBF : (R G B A) (191, 191, 191, 191)

Note how the green pixel (0, 255, 0, 127) becomes (0, 127, 0, 127) since the green component is equal to (255 * (127/255)). Note also that when the alpha component is zero all three color components will be zero, as seen in the 0% white pixel example. A developer would need to implement this type of conversion when creating code that reads pixels from an image file.

In actual code, 3 floating point multiply operations for every pixel would be far too slow. A more optimal implementation could avoid the multiply and divide operations by using a table that does 3 table lookups for each pixel.

This post covered the logic needed to implement a manual conversion of a non-premultiplied pixel into a premultiplied pixel for use with a graphics layer that requires alpha components that are already premultiplied.