RMagick Alpha Compositing - Part I

How do you fit odd-shaped pictures into your thumbnail collection? You know, a portrait photo in an otherwise landscape collection. Or an image that doesn't have a rectangular boundary. One way to handle these kinds of images is to blend them with an image of the desired shape using RMagick's composite method.

Alpha compositing - Making one image out of two

In image processing, alpha compositing is the blending of two images into a single image by combining the pixel data from each image according to a mathematical formula. The traditional alpha compositing model is called the Porter-Duff model. The Porter-Duff model defines 12 different formulas for combining two images - a source image and a destination image - into one. This article explains how to use the Porter-Duff formula called src-over, one of the simplest and most common alpha composite operations. The formula for src-over is:

Replace the destination pixels with the source pixels.

If the source image has pixels that are partially or entirely transparent, the formula is slightly more complicated:

Replace the destination pixels with the source pixels, unless the source pixel is partially or entirely transparent, in which case let some or all of the destination pixel show through.

Alpha compositing with RMagick

RMagick binds Ruby programs to either the ImageMagick or the GraphicsMagick library. Both libraries provide the same alpha compositing API. In RMagick, this API is the composite method in the Magick::Image class. The composite method needs four pieces of information from the program.

  1. The destination image. This is the Magick::Image instance to which the composite method is sent.
  2. The source image.
  3. The position of the source image relative to the destination image.
  4. The composite operation to use.

Over time composite has changed to make it more flexible concerning the position of the source image. Consequently, it can now be called with three different argument lists. The difference between the three lists is the specification of the position of the source image relative to the destination image.

First, you can specify the position as a pair of x- and y-offsets:

 result = dst.composite(src, x, y, comp_op)

Here dst is the destination image and src is the source image. The second and third arguments, x and y, are the x- and y-offsets of the source image relative to the upper-left corner of the destination image. Either or both of these arguments can be negative numbers. A negative x-offset represents a position to the left of the left-hand side of the destination image. A negative y-offset represents a position above the top of the destination image. The final argument (comp_op) is the composite operator, a value in the Magick::CompositeOperator enumeration class.

Second, you can replace with x- and y-offsets with a single Magick::GravityType value.

 result = dst.composite(src, gravity, comp_op)

As before, the first argument is the source image and the final argument is the composite operator.

There are 9 GravityType values:

NorthWestGravity
Positions the source image in the upper-left corner of the destination image
NorthGravity
Positions the source image in the upper center of the destination image
NorthEastGravity
Positions the source image in the upper-right corner of the destination image
EastGravity
Positions the source image in the middle right of the destination image
SouthEastGravity
Positions the source image in the lower-right corner of the destination image
SouthGravity
Positions the source image in the lower center of the destination image
SouthWestGravity
Positions the source image in the bottom-left corner of the destination image
WestGravity
Positions the source image in the middle left of the destination image
CenterGravity
Positions the source image in the middle center of the destination image

The third way to call composite is with both a GravityType value and x- and y-offsets. In this case, the GravityType value modifies the meaning of the x and y arguments.

 result = dst.composite(src, gravity, x, y, comp_op)

As before, the first argument is the source image and the last argument is the composite operator. The second argument is a GravityType value, and the third and fourth arguments are x- and y-offsets, respectively.

  1. If the gravity argument is NorthEastGravity, EastGravity, or SouthEastGravity, measure the x-offset from the right side of the image, instead of the left side.
  2. If the gravity argument is SouthEastGravity, SouthGravity, or SouthWestGravity, measure the y-offset from the bottom of the image, instead of the top.
  3. Otherwise, ignore the value of the gravity argument and measure the x- and y-offsets from the upper-left corner of the image.

Again, the x- or y-offset can be negative, with predictable consequences.

A square border around a rectangular image

As I explained earlier, the src-over composite operation produces a composite image by replacing the pixels in the destination image with the corresponding pixels in the source image. If the source is smaller than the destination, only those pixels in the area of the destination covered by the source are replaced. We can use this property to place a rectangular image onto a square background.

Here's an example. The image on the left is an 80x100 thumbnail, which we want to composite onto the center image, a 128x128 gold-colored background. The composite is on the right and is the same dimensions as the destination. The src-over operator places the source image over the destination image.

source destination composite
source destination composite

Here's a script that performs this composite operation:

1  require 'RMagick'
2
3  gold_fill = Magick::GradientFill.new(0, 0, 0, 0, "#f6e09a", "#cd9245")
4
5  dst = Magick::Image.new(128, 128, gold_fill)
6  src = Magick::Image.read("composite1-src.gif")[0]
7
8  result = dst.composite(src, Magick::CenterGravity, Magick::OverCompositeOp)
9  result.write('composite1.gif')

A square border around an irregular image

Here's an example of src-over with a partially transparent source image. Remember the definition of src-over:

Replace the destination pixels with the source pixels, unless the source pixel is partially or entirely transparent, in which case let some or all of the destination pixel show through.

The amount of the destination pixel that shows through depends on how opaque the source pixel is. As you saw in the previous example, a 100% opaque source pixel hides the destination pixel completely. A source pixel that is only 50% opaque would allow half the color in the destination pixel to show through, and so forth.

In the example below, the source image on the left is a PNG format image with transparent pixels around a cartoon penguin. The destination image is a 128x128 "plasma" image. The src-over operation produces the image on the right by replacing pixels in the destination with opaque pixels from the source. The transparent pixels in the source are also composited onto the destination, but since the source pixels outside the penguin are transparent (that is, 0% opaque) 100% of the destination pixel shows through.

source destination composite
source destination composite

Here's a script that performs this composite operation.

1  require 'RMagick'
2
3  dst = Magick::Image.read("plasma:fractal") {self.size = "128x128"}.first
4  src = Magick::Image.read('tux.png').first
5
6  result = dst.composite(src, Magick::CenterGravity, Magick::OverCompositeOp)
7  result.write('composite2.gif')

When the source is larger than the destination

When the source image is larger than the destination image, source pixels which have no corresponding destination pixels are ignored. Thus the resulting image is always the size of the destination image. The x, y, and gravity arguments control which part of the source contributes to the composite.

Other composite methods

RMagick provides two other compositing methods. The Draw class's composite method has this argument list:

gc.composite(x, y, width, height, src_image, comp_op)

The destination image is the image drawn upon, that is, the argument to the draw method. The x and y arguments are offsets from the top and left-hand side of the destination image. The source image will be scaled to the values of width and height. (Set either or both of these arguments to 0 to prevent scaling.)

In RVG, the image method in the RVG, RVG::Group, and RVG::Pattern classes composites a raster image over the background image. This method does not accept a comp_op argument, though. Instead, it always uses src-over.

For more information

As always, Anthony Thyssen's excellent site has more information about alpha compositing with ImageMagick.This page is a good introduction to alpha compositing and the Porter-Duff model. The SVG 1.2 Specification has detailed information about alpha compositing, complete with all the mathematics you could ask for.

Tim - Feb 18, 2006