Back Contents Next

Alpha Compositing

Alpha compositing is all about the transparency of an image, and we are just going to look at the basic ideas here. The subject of color representation is itself a major topic with a range of different ways of representing color, but we will assume we are dealing with the RGB model throughout and ignore other color models. Note that Java does provide much more extensive support for color modeling than we have the space to discuss in this book, so if you want to know more, look in the classes of the java.awt.color package.

 

When you want to define an image to be transparent to some degree, so that when it is displayed on top of a background image the underlying image shows through, you can specify the degree of transparency by something called an alpha component for each pixel. An image format that has an alpha component for each pixel is said to have an alpha channel. Note that not all image formats support an alpha channel, but PNG files do, and GIF images can have a single transparent color. The alpha component value is used when an image is being overlaid by another image. The alpha value is multiplied by each of the color components to modify the contribution of each color component to the visual appearance of the pixel. The alpha value for a pixel in an image can vary from a minimum of 0.0, meaning completely transparent and therefore invisible since all the color components will be 0, to a maximum of 1.0, meaning completely opaque. In an RGB image with an alpha channel, each pixel is defined by four components, the three color components, red, green and blue, plus the alpha component. This allows the transparency to vary over the image, so some parts of the image could be opaque – with an alpha component of 1.0, and other parts may be more or less transparent, with alpha components for the pixels less than 1.0. It's worth noting that both the source image, the image that you are drawing, and the destination image, the background in other words, can have an alpha channel. If an image has no alpha channel, then the alpha component is assumed to be 1.0.

 

When you draw a source image over a destination image, there are basically two steps to the process. The color components for each pixel in the source image and the corresponding pixels in the destination image will be multiplied by their alpha component – often this will be done once and for all ahead of time for an image to avoid all those multiplications each time you draw an image. The source image is then rendered over the destination image according to the alpha compositing rule that is in effect. There are several alpha compositing rules as we shall see, but they each determine the fraction of the source image components and the fraction of the destination image components that contribute to the components of the result. In general, the components of the source and destination pixels are combined as follows:

 

ColorR = ColorS*AlphaS*FractionS + ColorD*AlphaD*FractionD

AlphaR = AlphaS*FractionS + AlphaD*FractionD

 

The first equation applies to each of the three color components. The subscripts R, S, and D refer to the resultant pixel, the source pixel and the destination pixel, respectively. Thus, ColorR refers to the color of the resultant pixel produced by combining the source and the destination, AlphaS refers to the alpha component for the source pixel and FractionS represents the fraction of the source pixel determined by the compositing rule in effect.

 

This sounds a lot more complicated than it really is, so don't be put off by these equations. The alpha compositing rules that you can use in Java are implemented by the AlphaComposite class that is defined in the java.awt package. The Graphics2D class defines the method setComposite() that takes an AlphaComposite argument in order to set the alpha compositing rule to be used when you draw in the graphics context. Let's take a look at the AlphaComposite class in more detail.

The AlphaComposite Class

There is no constructor for the AlphaComposite class, so you cannot create objects directly. There is a static class member, getInstance(), that will return a reference to an AlphaComposite object with the compositing rule specified by the argument, which is a value of type int. There is also an overloaded version of this method where you can specify an alpha value as a second argument of type float, which is multiplied by the alpha for the source image. This is particularly useful when the source image has no alpha channel. Since an image with no alpha channel has an alpha component that is assumed to be 1.0, the alpha value that you specify in the call to getInstance() becomes the alpha value for all the pixels in the source. Most of the time, your images will not have an alpha channel so this is a way for you to specify the transparency of the source image directly in the graphics context.

 

There are eight possible alpha compositing rules, determined by constants of type int that are defined in the AlphaComposite class. In reviewing these, we will assume that, if the source or destination image has an alpha channel, the color components, ColorS and ColorD, for each pixel have already been pre-multiplied by the alpha component, AlphaS or AlphaD respectively. In the illustration of the effect of each rule described below, the source image is the light gray circle, and this is rendered over the darker gray rectangle (the destination image). The source image has its alpha component set to 0.5f.

 

 

 

SRC_OVER

This is the default rule that applies in a graphics context and is the rule that you are most likely to be using. The fraction of the source that contributes to the result is 1, and the fraction of the destination contributing to the result is 1-AlphaS.Therefore from our general equations, the source pixels are combined with the corresponding destination pixels using the following operations:

 

ColorR = ColorS + (1-AlphaS)*ColorD

AlphaR = AlphaS + (1-AlphaS)*AlphaD

 

The calculation of the resultant color is applied to each of the red, green and blue components of each pixel. You can see from the equations above that if the alpha component for the source, AlphaS, is 1, then the fraction of the destination will be zero so the result is just the original source pixel – in other words the source is opaque. If the alpha component for the source is 0, then the result is just the destination pixel so the source is completely transparent and would be invisible. The illustration shows the source with an alpha of 0.5f so the destination shows through.

 

 

 

SRC

With this rule, the source pixels replace the destination pixels, so the operations determining the resultant color and alpha components for each pixel are:

 

ColorR = ColorS

AlphaR = AlphaS

 

 

 

 

SRC_IN

With this rule, the fraction of the source in the result is the alpha for the destination, AlphaD, and the fraction of the destination in the result is zero. Thus only the source pixels that fall within the area destination image are rendered. All other pixels rendered from the source will have zero color components. As you can see, the outline of the destination image acts like a pastry cutter on the source image. The operations for the rule are:

 

ColorR = ColorS*AlphaD

AlphaR = AlphaS*AlphaD

 

 

 

SRC_OUT

With this rule, only the source pixels outside the area of the destination will be rendered. The outline of the destination image also acts like a pastry cutter here, but the source image inside the outline is discarded, and only the source image lying outside of the destination image is kept. The area of the source that lies inside the boundary of the destination results in pixels with color components that are zero. The calculation of the components of the result is defined as:

 

ColorR = ColorS*(1 - AlphaD)

AlphaR = AlphaS*(1 - AlphaD)

 

 

 

DST_OVER

With this rule, the fraction of each source pixel in the result is 1 - AlphaD, and the fraction of the corresponding destination pixel is 1. Where the source overlaps the destination, the destination is effectively rendered over the source. The operation of this rule is defined by:

 

ColorR = ColorS*(1 - AlphaD) + ColorD

AlphaR = AlphaS*(1 - AlphaD) + AlphaD

 

Since in our illustration the destination has an alpha of 1.0f, the fraction of the source pixel is 0.0 so the destination completely hides the part of the source that is underneath.

 

 

 

DST_IN

With this rule, the fraction of the source in the result is 0 and the fraction of the destination is Alphas. The operations are:

 

ColorR = ColorD*AlphaS

AlphaR = AlphaD*AlphaS

 

Thus the destination is rendered inside the boundary of the source, but with the alpha from the source, so it looks lighter than the rest of the destination.

 

 

 

DST_OUT

The source fraction in the result is zero, and the destination fraction is 1 – AlphaS. Thus the rule operation is:

 

ColorR = ColorD*(1 – AlphaS)

AlphaR = AlphaD*(1 – AlphaS)

 

 

 

CLEAR

The fractions of the source and destination pixels involved in the operation of this rule are both zero. The effect is that pixels corresponding to the source pixels are cleared, so they will have all color components at zero.

 

The AlphaComposite class also defines static members that are AlphaComposite objects. Each object corresponds to one of the rules we have just discussed and they all have an alpha of 1.0f. These members are:

 

SrcOver

SrcIn

SrcOut

Src

DstOver

DstIn

DstOut

Clear

 

As the Clear object has an alpha of 1.0f, using this for alpha compositing will result in an opaque black result.

 

Since the alpha for an image determines the transparency, we can fade an image into the background by repainting the image with a shrinking value for the alpha. Let's see how that works with an example.

Try It Out – Fading an Image

We will fade the Wrox Press logo into the background until it disappears, and then fade it in again cyclically. We can write an applet to do this and while we are about it, we can try using parameters for the applet that we can set in the web page. Here's the code for the applet:

 

import java.awt.*;

import java.awt.image.*;

import javax.swing.*;

 

public class FaderApplet extends JApplet

{

  public void init()

  {

    // Get parameters - if any

    String fade = getParameter("fadeTime");

    if(fade != null)

      fadeTime = Integer.parseInt(fade);

    String fps = getParameter("frameRate");

    if(fps != null)

      frameRate = Integer.parseInt(fps);

 

    maxCount = frameRate*fadeTime;               // Count of steps to complete fade

    imagePanel = new ImagePanel(getSize());

    getContentPane().add(imagePanel);

 

    composite = AlphaComposite.SrcOver;

  }

 

  // Parameter information for anyone that needs it

  public String[][] getParameterInfo()

  {

    String[][] info = {

             {"fadeTime" , "integer", "time to complete fade in seconds"},

             {"frameRate", "integer", "frames per second"               }

                      };

    return info;

  }

 

  public void start()

  {

    timer = new java.util.Timer(true);                     // Timer to run clock       

    count = maxCount;                                      // Set repaint counter

    alphaStep = 1.0f/count;

    long frameInterval = ONE_SECOND/frameRate;

 

    // Use fixed-delay execution to get smooth fade

    timer.schedule(new java.util.TimerTask()

                   {

                      public void run()

                      {

                        imagePanel.repaint();              // Repaint the image

 

                       // Update alpha composite for next frame

                       if(count ==maxCount)

                         countDelta = -1;

                       else if(count == 0)

                         countDelta = 1;

                       count += countDelta;

                       composite = AlphaComposite.getInstance(

                                               AlphaComposite.SRC_OVER,count*alphaStep);

                      }

                     

                      int countDelta = -1;      // Anonymous class member – count incr.

                   },

                    0,                                     // ...starting now

                    frameInterval);                        // Repaint interval

  }

 

  public void stop()

  {

    timer.cancel();

  }

 

  class ImagePanel extends JPanel

  {

    // Panel creates its own image from an image icon

    public ImagePanel(Dimension size)

    {

      ImageIcon icon = new ImageIcon("Images/wrox_logo.gif");

      image = icon.getImage();

 

      // Create a scaled image to fit within the size

      image = image.getScaledInstance(4*size.width/5, 4*size.height/5,

                                                                   Image.SCALE_SMOOTH);

      // Wait for scaled image to load

      MediaTracker tracker = new MediaTracker(this);

      tracker.addImage(image,0);                    // Image to track

      try

     {

      tracker.waitForID(0);

      }

      catch(InterruptedException e)

      {

      System.out.println(e);                       // Exception...

      System.exit(1);                              // ...so abandon ship!

      }

    }

 

    public void paint(Graphics g)

    {

      Graphics2D g2D = (Graphics2D)g;

      Dimension size = getSize();                    // Get panel size

      g2D.setPaint(Color.lightGray);                 // Background color

      g2D.fillRect(0,0,size.width,size.height);      // fill the panel

      g2D.setComposite(composite);                   // Set current alpha

 

      // Scale and draw image

      g2D.drawImage(image,                           // Image to be drawn

                    size.width/10,size.height/10,    // Image position inset

                    null);                          

    }

 

    // ImagePanel data members

    Image image;                 // The image

    int imageWidth;              // and its width

    int imageHeight;             // and height

  }

 

   // Applet data members

   final int ONE_SECOND = 1000;  // One second in milliseconds

   int frameRate = 20;           // Default fade change frequency frames per sec

   int fadeTime = 3;             // Default time to fade completely in seconds

   int count;                    // Repaint cycle counter

   int maxCount;

 

   ImagePanel imagePanel;        // Panel displaying the image

   AlphaComposite composite;     // Alpha value for the image

   float alphaStep;              // Alpha increment for fade step

   java.util.Timer timer;        // Timer to control fading 

}

 

We can optionally supply parameters for the applet, so you can use the following html:

 

<applet code="FaderApplet.class" width=”300” height=”330”>

<param name=”frameRate” value=”20”>

<param name=”fadeTime” value=”3”>

</applet>

 

You can use appletviewer to run the applet with an HTML file with the contents above. You should see the image fade out then back in again.

How It Works

In the init() method we try to read the two parameter values. If the <PARAM> tags are not specified, then the String object returned by the getParameter() calls will be null. In this case the data members fadeTime and frameRate will be left at their default values. We use the values in these members to calculate maxCount, the count of the number of steps needed to completely fade the image.

 

Since the ImagePanel class loads its own icon image, all we have to do in the init() method is create the ImagePanel object and add it to the content pane for the applet. We pass the size of the applet to the ImagePanel constructor, which will scale the image to fit. We also initialize the composite member that is the AlphaComposite object used in the paint() method for the imagePanel object to SrcOver, which implements the SRC_OVER rule with the alpha set to 1.

 

The Timer object that we create in the start() method manages the animation. After initializing count to the number of steps to a complete fade, we calculate the increment for the alpha value between steps, and the time interval in milliseconds between one step and the next. We use the timer's schedule() method to fade the image. In the run() method for the TimerTask object, the alpha is set to a new value – greater that the previous value if we are fading in, and less than the previous value if we are fading out – after each repaint of the panel. An AlphaComposite object with the rule SRC_OVER and the current alpha value is stored in the composite member of the applet class. In this way we cycle the alpha for the AlphaComposite object from 0.0f to 1.0f and back again.

 

The ImagePanel constructor loads the image as an ImageIcon object – you will recall that the constructor takes care of loading the image, and will not return until loading is complete. The constructor then extracts the Image object from the ImageIcon object. Since we want to be sure the image fits comfortably in the space available to the applet, we call the getScaledInstance() method for the Image object to creates a new object that is scaled to the x and y dimensions supplied as the first two arguments. The third argument to the getScaledInstance() method is an integer that must be one of five constant values that are defined in the Image class, and that select a particular scaling algorithm:

 

SCALE_DEFAULT

The default scaling algorithm

SCALE_FAST

A fast scaling algorithm

SCALE_SMOOTH

An algorithm designed for a smooth resultant image rather than speed

SCALE_REPLICATE

Use the algorithm defined by the ReplicateScaleFilter class. This algorithm duplicates pixels to scale up, or deletes pixels to scale down.

SCALE_AREA_AVERAGING

Use the algorithm defined by the AreaAveragingScaleFilter class.

 

Scaling an image is achieved by applying an algorithm that calculates the pixel values for the new image from those of the old. Some algorithms such as that defined by the ReplicateScaleFilter are very simple, and hence very fast to execute. Others such as the SCALE_SMOOTH algorithm are more complex, and hence slower, but produce a much better result.

 

The getScaledInstance() method returns immediately, even if the scaled image has not yet been constructed, so we use a tracker to wait for the image to complete.

 

The image is drawn in the paint() method for the imagePanel object. The paint() method starts by filling the panel in light gray to provide the background to the image. The current composite object is then set in the graphics context and the alpha for this determines the transparency of the image when we draw it. The image is then drawn using the drawImage() method. The second and third arguments are the coordinates of the top left corner of the image. The last argument can be a reference to an ImageObserver, but we just supply null as we have ensured that the image is completely loaded in the ImagePanel constructor.


Back Contents Next
©1999 Wrox Press Limited, US and UK.