Raster manipulation speed

I have two INT_ARGB BufferedImages equally sized. One contains the image, one containts the separate grey-image alpha mask. The problem is to replace the alpha channel of the first image with the grey image of the second.

I found a pair of solutions:

First solution. Direct pixel data manipulation:



BufferedImage bi = (BufferedImage) bm.getImage();
BufferedImage bi_alpha = (BufferedImage) bm_alpha.getImage();

{
      //
      DataBufferInt dbi = (DataBufferInt)bi.getRaster().getDataBuffer();
      DataBufferInt dbi_alpha = (DataBufferInt)bi_alpha.getRaster().getDataBuffer();
                  
      int[] dst = dbi.getData();
      int[] src = dbi_alpha.getData();
      
      for (int i=0; i<src.length; i++)
      {
            int c = dst[i];
            dst[i] = (c & 0x00FFFFFF) | ((src[i] & 0xFF)<<24);
      }
}


Second solution. The rasters way:



int[] bands = new int[1];
bands[0]=0;

if (wr == null) wr = (bi_alpha.getRaster()).createChild(0,0,bi.getWidth(),bi.getHeight(),0,0,bands);
bi.getAlphaRaster().setRect(0,0,wr);


One is about 7 (seven) times faster than the other. Which is faster ? Guess… :wink:

Well, ok, the first is ther faster, but it has a major drawback: the resulting bufferedimage will never be hw accelerated.
The second solution is cleaner and does not break hardware acceleration, but it’s very slow.

Can anyone improve the above code in order to get fast speeds and keep hw acceleration ?

Not realy answering your question, but why does it need to be fast?
Pre-processing of images should be part of the the compilation.

:edit:

btw,

Do you realy need the createChild bit?
The excess bands should be ignored when you pass in bi_alpha’s raster into setRect(raster);

Well, I’m using a mixture of java3d and java2d for a nice animation project.

As this alpha-combine operation must be done for animation purposes, it must be very very fast. No pre-computation allowed.

Did you notice the alpha blend quality ?

This seems to work fine for me.
Though admittedly, the greyscale alpha image (“Test2.png”) is using a DataBufferByte (ailly PSP optimising my pngs for me…)

As you already appear to have your Color data in a TRANSULCENT managed image, the last line is all that you need to do.


//INT_RGB containing the Color
BufferedImage bi = ImageIO.read(getClass().getResource("Test1.png")); 

//INT_RGB containing the greyscale for the Alpha
BufferedImage biAlpha = ImageIO.read(getClass().getResource("Test2.png")); 
     
dest = getGraphicsConfiguration().createCompatibleImage(bi.getWidth(),bi.getHeight(),Transparency.TRANSLUCENT);
      
//copy bi into the managedImage
dest.createGraphics().drawImage(bi,0,0,null);

//copy all the bands of bi_alpha into the managedImages alpha channel. (it will discard 2 of the bands)
dest.getAlphaRaster().setRect(biAlpha.getRaster()); 

I wonder if Java 2D can do this better with the built-in compositing stuff?
http://java.sun.com/j2se/1.4.2/docs/guide/2d/spec/j2d-awt.html

Maybe implementing this interface?

“CompositeContext - Defines the encapsulated and optimized environment for a composite operation. Used by programmers implementing custom compositing rules.”

Hmmm… after digging a bit deeper it is clear that implementing that interface wouldn’t get you anywhere - it has one method that actually does the compositing which is what you have written above.

And in any case the Java2D compositing uses the WritableRaster which would stop the acceleration, bummer.

Though if you put the greyscale image in the alpha channel instead of the ‘picture’ part of the PNG file then you should be able to use Java2D and the actual compositing might use a native loop or something. If you aren’t chaning the alpha channel often, it might be worth it to move it from the greyscale image to a “real” alpha channel.

Note that the “dispose” method in the CompositeContext interface suggests to me that native acceleration is anticipated in the implementation.

The real problem in my case is that:

  • I have to go offscreen.
  • java3d does not handle transparent backgrounds. Everything is blended to black.
  • java3d does not support grayscale rendering.
  • I’m in a animation loop, so everything should be optimized for subsequent frequent calls.

To get the alpha I need to render the scene twice: one for the real image and one for the alpha mask. The mask is a ARGB greyscale image. I get one band from it and use it as the alpha channel of the scene image.

CompositeContext is a solution I have investigated, but I’m quite sure it would be too slow.

I think that direct pixel manipulation is the best solution for the current Java2D implementation.

What is the ratio of image modifications to image renders?

i.e. are you performing the composite once every frame? every 5 frames? 10 frames?

If you are performing the composite once per frame, the image won’t ever get cached in vram anyway, so it doesn’t matter if it becomes unmanaged.

btw, does this work for the 2nd method? is it any quicker?

dest.getAlphaRaster().setRect(biAlpha.getRaster());

muhahahaha,

You’ll like this… not a lot but you’ll like it =)

its the composeAlpha(…) method thats the important bit, the rest is just wrapping.

Unless DstIn Composites are acceleratable with arbitary ColorModels, I doubt it will be faster than Method 1 or 2. Ofcourse, this solution does give you the potencial of it being accelerated 1 day…

Still, its worth benchmarking it =)

Nice trick Abuse,

it works very well. I’ll benchmark it asap. I was thinking about a similar solution using drawImage() and… et volia’, you did it for me ! Let’s hope it flies…

Due to some java3d quality issues (drivers?) I’m supposed to do some supersampling fsaa by rendering a 2x bitmap and scale it back to 1x with some nice bilerp.

I think I’ll achieve this by

  • rendering the scene into a 2x ARGB offscreen Canvas3D
  • scaling it (drawimage) into the 1x ARBG bufferedimage
  • rendering the scene alpha into the same 2X ARGB offscreen Canvas3D
  • scaling it (drawimage) into another 1x bufferedimage constructed by sharing the same alpharaster of the dest one.

This method saves me some extra memory and it is supposed to be quite fast.

Mik

Follow up:

I created a custom Composite and, yes, it’s faster than creating a new “tricked” image.
Sadly, due to direct raster manipulation I don’t think it will never be hw accelerated.

THIS IS THE EXTENSIBLECOMPOSITE


package it.classx.util.java2d;

import java.awt.Color;
import java.awt.Composite;
import java.awt.CompositeContext;
import java.awt.RenderingHints;
import java.awt.color.ColorSpace;
import java.awt.image.ColorConvertOp;
import java.awt.image.ColorModel;
import java.awt.image.DataBufferInt;
import java.awt.image.Raster;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;

/**
 * A flexible extensible composite
 * Creation date: (19/07/2004 11.50.21)
 * @author: Mik of ClassX
 */
public abstract class ExtensibleComposite implements java.awt.Composite, java.awt.CompositeContext
{
      /** The source color model for this context. */
      protected ColorModel srcColors = null;

      /** The destination color model for this context. */
      protected ColorModel dstColors = null;

      /** The source color space for this context. */
      protected ColorSpace srcColorSpace = null;

      /** The destination color space for this context. */
      protected ColorSpace dstColorSpace = null;
/**
 * Composes the two source {@link Raster} objects and 
 * places the result in the destination 
 * {@link WritableRaster}.  Note that the destination 
 * can be the same object as either the first or second 
 * source. Note that <code>dstIn</code> and 
 * <code>dstOut</code> must be compatible with the 
 * <code>dstColorModel</code> passed to the 
 * {@link Composite#createContext(java.awt.image.ColorModel, java.awt.image.ColorModel, java.awt.RenderingHints) createContext} 
 * method of the <code>Composite</code> interface.
 * @param src the first source for the compositing operation
 * @param dstIn the second source for the compositing operation
 * @param dstOut the <code>WritableRaster</code> into which the 
 * result of the operation is stored
 * @see Composite
 */
public void compose(java.awt.image.Raster src, java.awt.image.Raster dstIn, java.awt.image.WritableRaster dstOut)
{
      //
      // Sanity check: Raster sizes should be compatible
      //
      if (src.getWidth() != dstIn.getWidth()
            || src.getHeight() != dstIn.getHeight()
            || src.getMinX() != dstIn.getMinX()
            || src.getMinY() != dstIn.getMinY())
            throw new Error("Incompatible sizes or origin for src and dstIn rasters");

      //
      // Raster data. For clarity
      //
      int x = src.getMinX();
      int y = src.getMinY();
      int w = src.getWidth();
      int h = src.getHeight();

      //
      // First, convert to sRGB if necessary
      //
      ColorModel srgbCM = ColorModel.getRGBdefault();
      boolean srcNeedsConvert = !srcColors.equals(srgbCM);
      if (srcNeedsConvert)
      {
            WritableRaster newSrc = srgbCM.createCompatibleWritableRaster(w, h);
            ColorConvertOp srcTosRGB = new ColorConvertOp(srcColorSpace, srgbCM.getColorSpace(), null);
            srcTosRGB.filter(src, newSrc);
            src = newSrc.createWritableTranslatedChild(x, y);
      }

      boolean dstNeedsConvert = !dstColors.equals(srgbCM);
      WritableRaster dstOutSav = dstOut;
      if (dstNeedsConvert)
      {
            WritableRaster newDstIn = srgbCM.createCompatibleWritableRaster(w, h);
            ColorConvertOp dstTosRGB = new ColorConvertOp(dstColorSpace, srgbCM.getColorSpace(), null);
            dstTosRGB.filter(dstIn, newDstIn);
            dstIn = newDstIn.createWritableTranslatedChild(x, y);

            WritableRaster newDstOut = srgbCM.createCompatibleWritableRaster(w, h);
            dstTosRGB.filter(dstOut, newDstOut);
            dstOut = newDstOut.createWritableTranslatedChild(x, y);
      }

      //
      // Now, access the data banks for faster processing. Because we are sure
      // that we are using the DirectColorModel, we know that the internal
      // DataBuffers are DataBufferInt and have a single bank.
      // Because the relevant buffers may not start at index 0, take the (x,y)
      // starting offset into account. Furthermore, the scanline stride may be
      // bigger than the rasters width (in case the raster is a child raster, for
      // example). Therefore, we remember to use the scanline stride to adjust offsets.
      //
      DataBufferInt srcDB = (DataBufferInt) src.getDataBuffer();
      DataBufferInt dstInDB = (DataBufferInt) dstIn.getDataBuffer();
      DataBufferInt dstOutDB = (DataBufferInt) dstOut.getDataBuffer();
      int srcRGB[] = srcDB.getBankData()[0];
      int dstInRGB[] = dstInDB.getBankData()[0];
      int dstOutRGB[] = dstOutDB.getBankData()[0];
      int srcOffset = srcDB.getOffset();
      int dstInOffset = dstInDB.getOffset();
      int dstOutOffset = dstOutDB.getOffset();
      int srcScanStride = ((SinglePixelPackedSampleModel) src.getSampleModel()).getScanlineStride();
      int dstInScanStride = ((SinglePixelPackedSampleModel) dstIn.getSampleModel()).getScanlineStride();
      int dstOutScanStride = ((SinglePixelPackedSampleModel) dstOut.getSampleModel()).getScanlineStride();
      int srcAdjust = srcScanStride - w;
      int dstInAdjust = dstInScanStride - w;
      int dstOutAdjust = dstOutScanStride - w;

      //
      // Now, iterate through the buffer data.
      // 
      int sr = 0, sg = 0, sb = 0, sa = 0; // src rgb pixel values
      int dir = 0, dig = 0, dib = 0, dia = 0; // dstIn rgb pixel values
      int dor = 0, dog = 0, dob = 0, doa = 0; // dstOut rgb pixel values

      int sRGB = 0, diRGB = 0, doRGB = 0;
      int si = srcOffset;
      int dii = dstInOffset;
      int doi = dstOutOffset;

      // ScanLine iteration
      for (int i = 0; i < h; i++)
      {
             // Column iteration
            for (int j = 0; j < w; j++)
            {
                  // compose pixels
                  dstOutRGB[doi] = composeRGB(srcRGB[si],dstInRGB[dii]);
                  
                  // Move to next pixel
                  si++;
                  dii++;
                  doi++;
            }

            // Move to next scanline
            si += srcAdjust;
            dii += dstInAdjust;
            doi += dstOutAdjust;
      }

      //
      // Convert to dstOut ColorModel if necessary
      //
      if (dstNeedsConvert)
      {
            ColorConvertOp srgbToDst = new ColorConvertOp(srgbCM.getColorSpace(), dstColorSpace, null);
            srgbToDst.filter(dstOut, dstOutSav);
      }
}
// the implementor must override this method
// srcPix = the first source int ARGB pixel of the compositing operation
// dstInPix = the second source int ARGB pixel of the compositing operation
//
// returns the int ARGB result of the compositing operation

public abstract int composeRGB(int srcPix, int dstInPix);
/**
 * Creates a context containing state that is used to perform
 * the compositing operation.  In a multi-threaded environment,
 * several contexts can exist simultaneously for a single
 * <code>Composite</code> object.
 * @param srcColorModel  the {@link ColorModel} of the source
 * @param dstColorModel  the <code>ColorModel</code> of the destination
 * @param hints the hint that the context object uses to choose between
 * rendering alternatives
 * @return the <code>CompositeContext</code> object used to perform the
 * compositing operation.
 */
public java.awt.CompositeContext createContext(
      java.awt.image.ColorModel srcColorModel,
      java.awt.image.ColorModel dstColorModel,
      java.awt.RenderingHints hints)
{
      // extract colormodel and colorspace
      srcColors = srcColorModel;
      dstColors = dstColorModel;
      //
      srcColorSpace = srcColors.getColorSpace();
      dstColorSpace = dstColors.getColorSpace();

      return this;
}
/**
 * Releases resources allocated for a context.
 */
public void dispose()
{
}
}

AND THIS IS THE COMPOSITE CODE


package it.classx.util.java2d;

/**
 * Writes the BLUE channel of the source to the ALPHA of the destination
 * Creation date: (19/07/2004 12.35.47)
 * @author: Mik of ClassX
 */
public class BlueToTransparenceComposite extends ExtensibleComposite
{
      /**
       * dst(ARGB) = srcBlue,dstInRed,dstInGreen,dstInBlue
       */
      public int composeRGB(int srcPix, int dstInPix)
      {
            return ((srcPix << 24) | (dstInPix & 0x00FFFFFF));
      }
}

some of the src code in the compose() method comes from the GLF (V.Hardy).

Cheers,

Mik