Java: Filling a BufferedImage with transparent pixels
Asked Answered
I

6

21

I have an off-screen BufferedImage, constructed with the type BufferedImage.TYPE_INT_ARGB. It can contain anything, and I'm looking for a way to (fairly efficiently) completely overwrite the image with transparent pixels, resulting in an 'invisible' image.

Using something like this:

    (bufimg.getGraphics()).setColor(new Color(10, 10, 100, 0));   
    (bufimg.getGraphics()).fillRect (0, 0, x, y);

Has no effect. One possible method might be just to write over every pixel in the BufferedImage, but I'm not sure this is the best solution. How would you do it?

[edit]
The Graphics documentation advises against using clearRect for off-screen images, but I have tried it with the same results as above.

[edit2]
After experimenting with MeBigFatGuy's code (thanks!), it does clear an image. But it also stops further painting to that image (or appears to). This code for example:

    BufferedImage img = new BufferedImage (600, 600, BufferedImage.TYPE_INT_ARGB);
    Graphics g = img.createGraphics ()    
    g.drawLine (100, 100, 500, 500);
    AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
    g.setComposite(composite);
    g.setColor(new Color(0, 0, 0, 0));
    g.fillRect(0, 0, 600, 600);
    graphicsAI.setColor(new Color (10, 10, 10, 255));
    graphicsAI.drawLine (100, 100, 500, 500);

Results in nothing seen on the image (I'm drawing the image to a JPanel). Is this something to do with the addition of alpha values?

Intra answered 15/4, 2011 at 5:26 Comment(3)
In addition to my answer, here's a somehow related question I asked here (1.3 KView as I type this comment). Make sure to read Stacker's great answer: stackoverflow.com/questions/2825837 (note that I do specifically want precise pixel --that is, without color model conversion-- to be put in the underlying int[] and I want this to be done fastly).Peon
rescue the old composite via g2d.getComposite() and after filling the rect set the composite via g2d.setComposite(oldC);Pathe
I think you should simply call dispose() on your Graphics instance, to restore the previous graphic context.Ulberto
P
11

You could get the underlying int[] array of your BufferedImage (make sure to use a compatible format: that is, one that is backed by an int[]).

Then fill the int[] with ints whose alpha value are 0 (0 will do ; )

A System.arraycopy will be very fast.

You have to know that directly writing in the int[] is a lot faster than using setRGB.

Now BufferedImage are a bit of a black art in Java: depending on what you're doing and on which platform/JVM you're doing it, you may lose hardware acceleration (which may never have been there in the first place anyway). In addition to that, you may very well not care at all about hardware acceleration anyway because you may not be working on, say, a game requiring 60+ FPS to be playable etc.

This is a very complicated topic and there's more than one way to skin the BufferedImage cat. As far as I'm concerned I work directly in the int[] when I've got to mess at the pixel level because I think it makes much more sense than trying to use higher-level drawing primitives and I do really don't care about the potential lost of hardware acceleration.

Peon answered 15/4, 2011 at 9:23 Comment(6)
Btw, there's a pretty serious flush() / non-GC'ed SNAFU that affected several Java VMs for years where BufferedImage without any reference to them anymore still aren't eligible for GC (it's explained in details on the Apple Java-dev mailing list). In my opinion there's much more to BufferedImage than what meets the eyes as soon as you start to "work with pixels" or "push the limits" a bit. Be sure to Google for the flush() / non-GC'ed SNAFU. A funny read if any.Peon
Thanks for the reply, looping through the array does seem like the most viable posibility at the moment. But my (very small) previous experience with manipulating the underlaying array of BufferedImage was ... painful, and my interest in efficiency comes more from that ubiqitous OCD-like urge rather than any particular need from the program itself. So I'm hoping to stay at a higher level, but I'll have a shot at looping through the array tomorrow.Intra
@JBenson: plain looping would still incur a penalty hit for the array bound check made for every pixel: I'd really suggest using System.arraycopy once you've got your first horizontal line filled. stacker's answer in the question I linked in my comment contains arraycopy code you can base your own trial upon. :)Peon
Thanks for the advice, from my (small) testing, using an arraycopy is far superior to just looping over every pixel. Glad to report everything is working correctly and efficiently!Intra
@JBenson: glad to know it helped you... It sure helped me ;) Note that you still need to pay attention to the fact that your BufferedImage must be backed by an int[] (not all are). But you've got control over this: for example, image you use ImageIO to read a BufferedImage, that image may not be backed by an int[], but you can create a BufferedImage that is for sure backed by an int[] and copy, once, all the pixels using getRGB/setRGB. Then use the super-fast int[] pixels manipulation. Also note that there may be very, very, weird multi-threading issues (more in the next comment)Peon
@JBenson: on OS X, I'm sure I saw some really weird synchronization issues while manipulation directly pixels in the int[]. So I took great care to make sure I perform all my pixels manipulation from one and only one thread, using some sort of a "2D pipeline" not dissimilar to 3D-graphics pipelines. Your Mileage May Vary. Glad I could help :)Peon
M
28

After you clear the background with the CLEAR composite, you need to set it back to SRC_OVER to draw normally again. ex:

//clear
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR));
g2.fillRect(0,0,256,256);

//reset composite
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));
//draw
g2.setPaint(Color.RED);
g2.fillOval(50,50,100,100);
Menswear answered 9/6, 2011 at 17:47 Comment(0)
P
11

You could get the underlying int[] array of your BufferedImage (make sure to use a compatible format: that is, one that is backed by an int[]).

Then fill the int[] with ints whose alpha value are 0 (0 will do ; )

A System.arraycopy will be very fast.

You have to know that directly writing in the int[] is a lot faster than using setRGB.

Now BufferedImage are a bit of a black art in Java: depending on what you're doing and on which platform/JVM you're doing it, you may lose hardware acceleration (which may never have been there in the first place anyway). In addition to that, you may very well not care at all about hardware acceleration anyway because you may not be working on, say, a game requiring 60+ FPS to be playable etc.

This is a very complicated topic and there's more than one way to skin the BufferedImage cat. As far as I'm concerned I work directly in the int[] when I've got to mess at the pixel level because I think it makes much more sense than trying to use higher-level drawing primitives and I do really don't care about the potential lost of hardware acceleration.

Peon answered 15/4, 2011 at 9:23 Comment(6)
Btw, there's a pretty serious flush() / non-GC'ed SNAFU that affected several Java VMs for years where BufferedImage without any reference to them anymore still aren't eligible for GC (it's explained in details on the Apple Java-dev mailing list). In my opinion there's much more to BufferedImage than what meets the eyes as soon as you start to "work with pixels" or "push the limits" a bit. Be sure to Google for the flush() / non-GC'ed SNAFU. A funny read if any.Peon
Thanks for the reply, looping through the array does seem like the most viable posibility at the moment. But my (very small) previous experience with manipulating the underlaying array of BufferedImage was ... painful, and my interest in efficiency comes more from that ubiqitous OCD-like urge rather than any particular need from the program itself. So I'm hoping to stay at a higher level, but I'll have a shot at looping through the array tomorrow.Intra
@JBenson: plain looping would still incur a penalty hit for the array bound check made for every pixel: I'd really suggest using System.arraycopy once you've got your first horizontal line filled. stacker's answer in the question I linked in my comment contains arraycopy code you can base your own trial upon. :)Peon
Thanks for the advice, from my (small) testing, using an arraycopy is far superior to just looping over every pixel. Glad to report everything is working correctly and efficiently!Intra
@JBenson: glad to know it helped you... It sure helped me ;) Note that you still need to pay attention to the fact that your BufferedImage must be backed by an int[] (not all are). But you've got control over this: for example, image you use ImageIO to read a BufferedImage, that image may not be backed by an int[], but you can create a BufferedImage that is for sure backed by an int[] and copy, once, all the pixels using getRGB/setRGB. Then use the super-fast int[] pixels manipulation. Also note that there may be very, very, weird multi-threading issues (more in the next comment)Peon
@JBenson: on OS X, I'm sure I saw some really weird synchronization issues while manipulation directly pixels in the int[]. So I took great care to make sure I perform all my pixels manipulation from one and only one thread, using some sort of a "2D pipeline" not dissimilar to 3D-graphics pipelines. Your Mileage May Vary. Glad I could help :)Peon
A
5

If you cast the Graphics object to a Graphics2D object, you can set a Composite object thru

AlphaComposite composite = AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.0f);
Graphics2D g2d = (Graphics2D) image.getGraphics();
g2d.setComposite(composite);
g2d.setColor(new Color(0, 0, 0, 0));
g2d.fillRect(0, 0, 10, 10);
Ardel answered 15/4, 2011 at 5:32 Comment(1)
This is interesting, while your code does indeed fill the image, it seems to stop any further painting on the image. I've edited the original post with more information.Intra
D
3

For the sake of completeness, here is a working, testing, and fast function that is cross-platform compliant.

  static public BufferedImage createTransparentBufferedImage(int width, int height) {
     // BufferedImage is actually already transparent on my system, but that isn't
     // guaranteed across platforms.
     BufferedImage bufferedImage = new BufferedImage(width, height, 
                        BufferedImage.TYPE_INT_ARGB);
     Graphics2D graphics = bufferedImage.createGraphics();

     // To be sure, we use clearRect, which will (unlike fillRect) totally replace
     // the current pixels with the desired color, even if it's fully transparent.
     graphics.setBackground(new Color(0, true));
     graphics.clearRect(0, 0, width, height);
     graphics.dispose();

     return bufferedImage;
  }
Drin answered 11/10, 2020 at 18:29 Comment(1)
In my case, this worked and fixed it so my clearRect instruction worked as expected to make the image transparent and this solution seems simpler and more straight forward than the other approaches.Stonemason
C
1

Despite you saying it doesn't work, I used clearRect quite fine.

Clears the specified rectangle by filling it with the background color of the current drawing surface. This operation does not use the current paint mode.

Beginning with Java 1.1, the background color of offscreen images may be system dependent. Applications should use setColor followed by fillRect to ensure that an offscreen image is cleared to a specific color.


Fills the specified rectangle. The left and right edges of the rectangle are at x and x + width - 1. The top and bottom edges are at y and y + height - 1. The resulting rectangle covers an area width pixels wide by height pixels tall. The rectangle is filled using the graphics context's current color.

It is not clearly stated here that one will set the rectangle to the background color, while the other will paint with the foreground color on top of the current colors, but it's what it seems to do.

This is pure speculation, but I think the note about offscreen images relates to Graphics objects obtained from offscreen AWT components, as they are native. I can hardly imagine how the background color of a BufferedImage could be system dependent. As the API doc is for Graphics, this could be a generalization not applying to the BufferedImage case.

My testing code:

JFrame jf = new JFrame();
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

BufferedImage img = new BufferedImage(200, 300, BufferedImage.TYPE_INT_ARGB);

Graphics2D g = img.createGraphics();

//fill right half with opaque white
g.setColor(Color.WHITE);
g.fillRect(100, 0, 100, 300);

//leave top third as it is

//fill middle third with transparent color
g.setColor(new Color(0, true));
g.fillRect(0, 100, 200, 100);

//clear bottom third with transparent color
g.setBackground(new Color(0, true));
g.clearRect(0, 200, 200, 100);

g.dispose();

jf.add(new JLabel(new ImageIcon(img)));

jf.pack();
jf.setVisible(true);

the result is two white squares, top right. Where no white was painted, or clearRect was used to overwrite the white, the result is a light gray, the frame's default background color.

Performance-wise, it's regular drawing. arraycopy might well be faster, I don't know, but at least this is likely hardware accelerated just as any other drawing operation.

A plus point versus the array solution is a) no additional memory and b) independence from the color model; this should work no matter how the image was set up.

A minus point versus the Composite solution is that it only allows clearing rectangles; setting the composite allows you to clear any kind of shape.

Campagna answered 4/8, 2014 at 13:6 Comment(2)
Your test is a little faulty as (at least in my case) a newly created BufferedImage is already empty (and transparent). You should fill it first with another color so you can properly observe whether drawing or clearing with a fully transparent color is actually doing anything. I added that in, and the results show that clearRect() works, but fillRect() with 0 alpha does nothing (which is as it ought to be). I gave you +1 anyway.Drin
See the //fill right half with opaque white. My test contains six areas: left no fill (default background)/right white fill + top not cleared/middle transparent fill/bottom transparent clear. The most interesting areas are of course those where background & clearing attempts overlap. Painting the resulting image to a JFrame, which has a gray default background, should reveal everything there is to see. Or am I missing something? What did your test reveal in addition to mine?Campagna
I
0

Setting the background of the graphics Object seems to do the job:

g.setBackground(new Color(0, 0, 0, 0));

(at least when drawing images for scaling purposes)

Irrupt answered 23/2, 2015 at 20:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.