Custom Alpha Compositing
March 27th, 2007 |
Every so often (can’t be more than once every two or three days), Swing doesn’t quite do what we need, and we end up writing customized code. In this case, all the available AlphaComposite instances provided with Java were variations on the theme of combining the colors and alpha channel of both source images into a target image. (Wikipedia’s Alpha Compositing article is good background on the topic).
What if what you really wanted was the color from one image and the alpha channel from another? You’d be out of luck, but for the talents of Brien. Here’s what you normally get with a standard AlphaComposite.SRC_OVER sort of technique. In the following two examples, the icon is opaque and the rectangle is partially opaque black fading to transparency.

What we needed looks more like this:

Read on to find out how we did it, and why.
While it seems like this might be overkill, this was only a simple example. In practice, we wanted a complex circular fade applied to another relatively complex graphic that couldn’t be easily replaced with primitive graphics operations. With SourceAlphaComposite.java, we were able to produce two graphics to get exactly the effect we wanted. Be on the lookout for another cool usage of this class in the next posting.
Assuming your image ColorModel supports transparency, the transparency of any given pixel is encoded in the bit patterns of the raster. We picked an image type that makes things easy: in a BufferedImage of type BufferedImage.TYPE_4BYTE_ABGR the opacity of the pixel may be found in the fourth byte of the pixel information. We extend the interface CompositeContext and implement its compose() method. The method has this fingerprint:
public void compose(Raster src, Raster dstIn, WritableRaster dstOut)
The algorithm is simple:
- blit the
dstInargument to the output,dstOut. - Loop over each pixel in the
srcargument, overwriting its opacity information into the corresponding pixel indstOut.
A word of warning, however: since this class involves direct manipulation of the alpha channel of a BufferedImage, and not all image types support alpha, let alone in the same way, we picked the image types most convenient to us. Our code example only works with BufferedImages of type BufferedImage.TYPE_4BYTE_ABGR or BufferedImage.TYPE_INT_ARGB; implementation of support for other image types should be pretty straightforward.
In any case, check out the source code and let me know if you have any questions, comments, or enhancements to share.
package com.palantir.ui;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
/**
* A {@link Composite} implementation that uses the destination RGB and the source alpha.
* This can be used to perform alpha gradients.
*/
public class SourceAlphaComposite implements Composite, CompositeContext {
public static SourceAlphaComposite createComposite(final BufferedImage bimage) throws UnsupportedBufferException {
return createComposite(bimage.getType());
}
/**
* This factory currently supports <code>TYPE_4BYTE_ABGR</code> and <code>TYPE_INT_ARGB</code>.
*/
public static SourceAlphaComposite createComposite(int type) throws UnsupportedBufferException {
switch ( type ) {
case BufferedImage.TYPE_4BYTE_ABGR:
case BufferedImage.TYPE_INT_ARGB:
return new SourceAlphaComposite( 3 );
default:
throw new UnsupportedBufferException();
}
}
private final int alphaIndex;
public SourceAlphaComposite(final int alphaIndex) {
if ( alphaIndex < 0 ) {
throw new IllegalArgumentException( "There is no way a negative index will work." );
}
this.alphaIndex = alphaIndex;
}
public int getAlphaIndex() {
return alphaIndex;
}
public CompositeContext createContext(
final ColorModel srcColorModel, final ColorModel dstColorModel, final RenderingHints hints
) {
return this;
}
public void dispose() {
// Do nothing
}
public void compose(final Raster src, final Raster dstIn, final WritableRaster dstOut) {
final int
w = dstOut.getWidth(),
h = dstOut.getHeight();
final int n = src.getNumBands();
final int[] spixel = new int[ n ], opixel = new int[ n ], dpixel = new int[ n ];
for ( int x = 0; w > x; x++ )
for ( int y = 0; h > y; y++ ) {
src.getPixel( x, y, spixel );
dstIn.getPixel( x, y, dpixel );
// Use the destination color (except use the source alpha, below):
System.arraycopy( dpixel, 0, opixel, 0, opixel.length );
final int dalpha = dpixel[ alphaIndex ];
if ( 0 != dalpha ) {
// Use the source alpha:
opixel[ alphaIndex ] = dalpha * spixel[ alphaIndex ] / 0xFF;
}
dstOut.setPixel( x, y, opixel );
}
}
/**
* Exception we throw for images that we don't support.
*/
public static class UnsupportedBufferException extends Exception {
public UnsupportedBufferException() {
}
public UnsupportedBufferException(final Exception cause) {
super( cause );
}
}
}







Hello,
For some reason I can’t modify the alpha on the destination writable raster.
When I call dstOut.getNumBands() I get 3 which I suspect is the source of the problem because when I call src.getNumBands() I get 4. The destination raster seems to be missing a band.
Any idea of how I can make sure the writable raster has 4 bands?
Thanks,
Dan.
May 25th, 2007 at 2:04 pm
It sounds like your output buffer/raster doesn’t support transparency. Though I’m unfamiliar with getNumBands() and it’s horribly documented, it appears to be the number of channels supported. You need Red, Green, Blue, and Alpha, but probably only have RGB. You’ll see above that SourceAlphaComposite references supporting BufferedImage.TYPE_4BYTE_ABGR BufferedImage.TYPE_INT_ARGB. Both of these image types support alpha transparency, which not all images do. Possibly you can change the instantiation of whatever you’re painting to. If you have no control over what you’re painting to, perhaps you can paint your layers to a BufferedImage of identical size first, then paint the composited picture to the destination.
In other news, further research reveals that AlphaComposite.DstIn is a provided Composite implementation that does most of what we needed SourceAlphaComposite for.
-Carl
May 25th, 2007 at 2:58 pm