ARGB Image, set all visible pixels to white

You’re reading the image 1 pixel at a time.

buf.getRGB(x, y) is extremely slow.

And then, for every pixel, you’re creating a int[4]… don’t expect that to be fast.

The following code, does something different, but you can work from here:


   public static void fade(BufferedImage image, float transparancy, BufferedImage trans)
   {
      if (image.getWidth() != trans.getWidth())
         throw new IllegalArgumentException();
      if (image.getHeight() != trans.getHeight())
         throw new IllegalArgumentException();

      int[] rgb = new int[trans.getWidth() * trans.getHeight()];
      int[] alpha = new int[rgb.length];

      image.getRGB(0, 0, trans.getWidth(), trans.getHeight(), rgb, 0, trans.getWidth());

      if (image.getTransparency() != Transparency.OPAQUE)
      {
         image.getAlphaRaster().getPixels(0, 0, trans.getWidth(), trans.getHeight(), alpha);
         for (int i = 0; i < alpha.length; i++)
            alpha[i] = (int) (alpha[i] * transparancy);
      }
      else
      {
         for (int i = 0; i < alpha.length; i++)
            alpha[i] = (int) (0xFF * transparancy);
      }

      trans.setRGB(0, 0, trans.getWidth(), trans.getHeight(), rgb, 0, trans.getWidth());
      trans.getAlphaRaster().setPixels(0, 0, trans.getWidth(), trans.getHeight(), alpha);
   }

I believe the optimal solution when considering accelerated pipelines would be the following.

Use an intermediary VolatileImage that is as large as your largest sprite frame. (the VolatileImage will need to be either bitmask or full alpha, depending on the alpha depth of your sprites)

When you want to draw a sprite whited out, do the following.

a) Clear an area of the VolatileImage, equal to the size of your sprite frame, to opaque white (0xffffffff)
b) Draw your sprite frame onto the VolatileImage image using the DST_IN AlphaComposite rule. ( Alpha_result = A_dest * A_source ; Color_result = Color_dest * Alpha_source )
c) Draw the area of the VolatileImage, equal to the size of your sprite frame, to your screen buffer. (or whereever you were going to be drawing it)

So long as transparent/translucent VolatileImages are accelerated, and the DST_IN composite rule is accelerated, this solution should be blisteringly fast - many orders of magnitude faster than messing around with per-pixel, or altering the alpha raster manually.

Ya I ended up now with something like

int[] rgb = new int[out.getWidth() * out.getHeight()];
		int[] alpha = new int[rgb.length];

      	out.getRGB(0, 0, out.getWidth(), out.getHeight(), rgb, 0, out.getWidth());

      	out.getAlphaRaster().getPixels(0, 0, out.getWidth(), out.getHeight(), alpha);
         	for (int i = 0; i < rgb.length; i++)
	            rgb[i] = 0xFFFFFF;

      	out.setRGB(0, 0, out.getWidth(), out.getHeight(), rgb, 0, out.getWidth());
      	out.getAlphaRaster().setPixels(0, 0, out.getWidth(), out.getHeight(), alpha);

And its very slow =P
per pixel was better

Draw your sprite frame onto the VolatileImage image using the DST_IN AlphaComposite rule.

Thats way over my head, I guess…
How do you draw an image using an AlphaComposite rule

Something like this should do it :-



// Idealy dst should be a Graphics context onto an accelerated surface i.e. a BufferStrategy
void drawWhiteSprite(Graphics dst, Sprite sprite, VolatileImage scratch) {

   // obviously this section will vary upon how you are storing and managing your sprite frames.
   final BufferedImage currentFrame = sprite.getCurrentFrame();
   final int spriteX = sprite.getX();
   final int spriteY = sprite.getY();
   final int spriteWidth = currentFrame.getWidth();
   final int spriteHeight = currentFrame.getHeight();


   Graphics2D scratchG2d = scratch.createGraphics();
   scratchG2d.setComposite(AlphaComposite.Src);
   scratchG2d.setColor(Color.WHITE);
   scratchG2d.fillRect(0,0,spriteWidth, spriteHeight);
   scratchG2d.setComposite(AlphaComposite.DstIn);
   scratchG2d.drawImage(currentFrame,0,0,null);
   scratchG2d.dispose();
   dst.drawImage(scratch,spriteX,spriteY, spriteX+spriteWidth, spriteY+spriteHeight, 0, 0, spriteWidth, spriteHeight,null);
}

note, I just typed that off the top of my head - so it almost certainly won’t compile :wink:

brilliant, thank you

this is the code I ended up with

public VolatileImage whiteOut(BufferedImage in, GraphicsConfiguration gc)
	{
        VolatileImage out = gc.createCompatibleVolatileImage(in.getWidth(),in.getHeight(), in.getTransparency());

        out.setAccelerationPriority(1F);

   		Graphics2D scratchG2d = out.createGraphics();
   		scratchG2d.setComposite(AlphaComposite.Src);
   		scratchG2d.setColor(Color.WHITE);
   		scratchG2d.fillRect(0,0,(int)in.getWidth(), (int)in.getHeight());
   		scratchG2d.setComposite(AlphaComposite.DstIn);
   		scratchG2d.drawImage(in,0,0,null);
   		scratchG2d.dispose();

   		return out;
	}

well those volatileimages sure are unstable, after a time they sometimes vanish and stuff, but I guess thats ok, since Im using it for blinking anway
ans also its just a short effect, the unstable problems only occur after a while

It’ be faster if you didn’t recreate the VolatleImage image every time you want to draw it whited out; also, as you rightly point out VolatileImages can lose their contents at any time - so you should put the code that uses them in a while(!vi.contentsLost()) loop. (which would require you to restructure your code - as you would want to create your VolatileImage before entering the redraw loop)

I’d be intrigued to know what the performance of this solution is in the 3 possible acceleration scenarios - Fully accelerated(fast), no acceleration(slow[er then per-pixel?]), VI surface accelerated but not DST_IN op (slowest).

Sounds like the job for a J2D rendering benchmark :stuck_out_tongue:

oh yeah, its beautiful now

maximum number of entities on screen and no reduction of performance

I know exactly what you mean - and this is how to do it:


package createmask;

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/**
 *
 * @author swpalmer(a)gmail.com
 */
public class Main {

    static File srcFile = new File("C:\\Users\\scott\\dev\\whiteout.png");
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {
        final BufferedImage srcImg = ImageIO.read(srcFile);
        final BufferedImage outImg = whiteOutImg(srcImg);
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                showMe(srcImg,outImg);
            }
        });
    }

    private static void showMe(final BufferedImage srcImg, final BufferedImage outImg) {
        JFrame frame = new JFrame("Creating a mask from a 32-bit image");
        JPanel panel = new JPanel();
        panel.setBackground(Color.MAGENTA);
        panel.add(new JLabel(new ImageIcon(srcImg)));
        panel.add(new JLabel(new ImageIcon(outImg)));
        frame.setContentPane(panel);
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static BufferedImage whiteOutImg(BufferedImage srcImg) {
        BufferedImage outImg = new BufferedImage(srcImg.getWidth(), srcImg.getHeight(), BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = outImg.createGraphics();
        g.setComposite(AlphaComposite.Src);
        g.drawImage(srcImg, 0, 0, null);
        g.setComposite(AlphaComposite.SrcIn);
        g.setColor(Color.WHITE);
        g.fillRect(0, 0, outImg.getWidth(), outImg.getHeight());
        g.dispose();
        return outImg;
    }

}

Yeah, I already had it running smoothly, so thanks for the help, but I already did it like that, by now =D

Resurrecting an ooold thread here.

I am still using this technique, just a little different.
Whenever a enemy is hit, in my game, I want it to flash red. This is independent of the current sprite and should be able to be applied on all sprites.
Now, this red “overlay” then slowly loses its Alpha, becomes invisible.
You know… you hit an enemy its rather red, then it fades. Many games have similar effects.

So I wrote this:


private void createBlinkImg(GraphicsConfiguration gc)
    {
        if (tryUsingVolatile)
        {
            vola = null;
            vola = gc.createCompatibleVolatileImage(visual[atmMotion].getImageFrame().getWidth(),visual[atmMotion].getImageFrame().getHeight(), VolatileImage.TRANSLUCENT);
            vola.setAccelerationPriority(1.0f);
            scratchG2d = vola.createGraphics();
            //scratchG2d = (Graphics2D)vola.getGraphics();
        }
        else
        {
            blinkImg = null;
            blinkImg = gc.createCompatibleImage(visual[atmMotion].getImageFrame().getWidth(),visual[atmMotion].getImageFrame().getHeight(), BufferedImage.TRANSLUCENT);
            blinkImg.setAccelerationPriority(1.0f);
            scratchG2d = blinkImg.createGraphics();
            //scratchG2d = (Graphics2D)blinkImg.getGraphics();
        }
        scratchG2d.setComposite(AlphaComposite.Src);
        scratchG2d.setColor(new Color(255,0,0,blinkedFrames));
        scratchG2d.fillRect(0,0,(int)visual[atmMotion].getImageFrame().getWidth(), (int)visual[atmMotion].getImageFrame().getHeight());
        scratchG2d.setComposite(AlphaComposite.DstIn);
        scratchG2d.drawImage(visual[atmMotion].getImageFrame(),0,0,null);
        scratchG2d.dispose();
        scratchG2d = null;
    }

If an enemy is actually hit, this method is run and the “blinkImg” or “vola” is drawn after the sprite, overlaying it.
The reason, the usage of the volatileimage is limited to an argument, is when using the DirectX rendering pipeline, volatileimages will often loose the contents and glitch up and eventually become white. This doesn’t happen with the OpenGl pipeline and obviously never when using BufferedImage.

Problem is: This is rather slow if used a lot.
Like hitting multiple enemies on the screen, they all get the red fade, and every single frame, the red overlay has to be calculated new, because the alpha value changes (constantly drops, as mentioned).

This method is run for every sprite as long as the effect’s alpha is not yet zero.

Any ideas how to make it faster ?

VolatileImages losing their contents is part of the API; your code has to account for it. (though I wish the API had been designed differently so this wasn’t necessary…)
Have a read of the volatile image tutorial.

As for your specific problem:

  • don’t recreate the scratch VolatileImage - create one large enough for your largest sprite & keep hold of it.
  • make sure all your drawing operations are being accelerated by using the java2d logging/tracing options

If you want the flash itself to be alpha’ed, then you should use the DST_ATOP rule instead.
It’ll maintain your source’s transparency channel but composite the colour channels of source & destination.

Here’s a little test app. I knocked up the last time this thread was touched. Forgive the lack of documentation & crude means of animating, it was hacked together :wink:
Oh, and don’t use this as an example of how to do the compositing in a performant manner - this is purely a visualization tool for the compositing rules.


import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MultipleGradientPaint.CycleMethod;
import java.awt.RadialGradientPaint;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.JPanel;


public class CompositeTest extends JPanel {
	

	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;


	public enum AlphaCompositeRules {
		CLEAR(AlphaComposite.CLEAR),
		SRC(AlphaComposite.SRC),
		DST(AlphaComposite.DST),
		SRC_OVER(AlphaComposite.SRC_OVER),
		DST_OVER(AlphaComposite.DST_OVER),
		SRC_IN(AlphaComposite.SRC_IN),
		DST_IN(AlphaComposite.DST_IN),
		SRC_OUT(AlphaComposite.SRC_OUT),
		DST_OUT(AlphaComposite.DST_OUT),
		SRC_ATOP(AlphaComposite.SRC_ATOP),
		DST_ATOP(AlphaComposite.DST_ATOP),
		XOR(AlphaComposite.XOR);
		
		private int rule;
		private AlphaCompositeRules(int rule) {
			this.rule = rule;
		}
		
		public AlphaComposite getInstance() {
			return getInstance(1.0f);
		}
		
		public AlphaComposite getInstance(float alpha) {
			return AlphaComposite.getInstance(rule, alpha);
		}
	};

	private static final int IMG_SIZE = 64;
	
	private BufferedImage sprite = new BufferedImage(IMG_SIZE ,IMG_SIZE , BufferedImage.TYPE_INT_ARGB);
	private BufferedImage scratch = new BufferedImage(IMG_SIZE, IMG_SIZE, BufferedImage.TYPE_INT_ARGB);
	
	
	public CompositeTest() {
		setBackground(Color.black);
		Graphics2D g2d = sprite.createGraphics();
		
		g2d.setPaint(new RadialGradientPaint(new Rectangle2D.Float(0,0,IMG_SIZE,IMG_SIZE), new float[]{0.0f,1.0f},new Color[]{Color.WHITE, Color.BLUE}, CycleMethod.NO_CYCLE));
		g2d.setClip(new Ellipse2D.Float(0,0,IMG_SIZE,IMG_SIZE));
		g2d.fillRect(0, 0, IMG_SIZE, IMG_SIZE);
		g2d.dispose();
	}
	
	public Dimension getPreferredSize() {
		return new Dimension(IMG_SIZE *2, IMG_SIZE *(AlphaCompositeRules.values().length+1));
	}

	private void clear(BufferedImage img, Color c) {
		Graphics2D g = img.createGraphics();
		g.setColor(c);
		g.setComposite(AlphaComposite.Src);
		g.fillRect(0,0,img.getWidth(), img.getHeight());
		g.dispose();
	}
	
	private int alpha = 0;
	private int alphaDelta = 1;
	
	public void paint(Graphics g) {
		super.paint(g);
		alpha+=alphaDelta;
		if(alpha==255 || alpha==0) {
			alphaDelta=-alphaDelta;
		}
		
		clear(scratch, new Color(255,0,0,alpha));
		g.drawImage(scratch, 0, 0, null);
		g.drawImage(sprite, IMG_SIZE, 0, null);
		g.setColor(Color.white);
		g.drawString("\u03B1=" + alpha, 0, IMG_SIZE/2);
		
		
		for(int i = 0;i < AlphaCompositeRules.values().length;i++) {
			clear(scratch, new Color(255,0,0,alpha));
			Graphics2D scratchG = scratch.createGraphics();
			
			scratchG.setComposite(AlphaCompositeRules.values()[i].getInstance());
			scratchG.drawImage(sprite, 0, 0, null);
			scratchG.dispose();
			
			g.drawImage(scratch, IMG_SIZE, (i+1)*IMG_SIZE,null);
			g.setColor(Color.white);
			g.drawString(AlphaCompositeRules.values()[i].toString(), 0, (i+1)*IMG_SIZE+IMG_SIZE/2);
		}
		
		repaint();
		
	}
	
	
	public static void main(String[]args) {
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
		frame.getContentPane().add(new CompositeTest());
		frame.pack();
		frame.setVisible(true);
	}
}

thanks for the test app, its really good to look at, and compare.

I have read the http://gpwiki.org/index.php/Java:Tutorials:VolatileImage before.
Problem is: using the DX pipeline, the mentioned glitch happens, and when using a validation loop, as you should if you use volatileimage, it will result in a infinite loop
It also only happens if you have more than one screen connected and enabled in windows
I have actually found this Bug in the java bug database, so yeah its kinda strange.

I built a similar effect years ago and just used BufferedImages. I turned the images to white on the fly and stored them permanently.

The bit you might be interested in is that in order to get around the memory issues of having hundreds/thousands of images in memory was to store them behind a WeakReference object within a map. When I went to draw I’d grab the image out, if it’s null I’d recreate it and store it in the map, then draw using the image. This way the JVM could garbage collect them (if it needed to) and the major JVMs are encouraged to garbage collect the least used weak references first.

In an ideal world this means you’ll only ever be storing the whited (or redded) images for the enemies who are currently on screen.

Quite the attractive concept. Gonna look into that.

I believe you can also use the other types of references (SoftReference and PhantomReference) if you wanted to try to optimize it further. I can’t remember the exact differences, but I believe the JVM will prioritise certain types of references over others when looking to reclaim memory.

So if you find a situation where certain generated images are used waaaaaay more then others then you can cache those behind a different type of reference to reflect this.