How to create BufferedImage with BITMASK Transparency? - java

The BufferedImage class implements Transparency, which has three values:
OPAQUE means no transparency.
TRANSLUCENT means every pixel has an Alpha value between 0 and 1.
BITMASK means every pixel is either opaque or completely transparent.
I can check this value with the getTransparency() method. In my case, I have a PNG file with transparency:
pic = ImageIO.read(new File(filename));
int transparency = pic.getTransparency(); // returns Transparency.TRANSLUCENT
Now I read that images with Transparency.BITMASK can be drawn much faster than those with Transparency.TRANSLUCENT and in my case BITMASK would be enough. I would just color all transparent pixels in one specific color and then save the png without transparency.
Question: How to create a BufferedImage object, which has Transparency.BITMASK from an existing BufferedImage by just defining one color as transparent?

You mean something like...
// Create the buffered image
GraphicsDevice gs = ge.getDefaultScreenDevice();
GraphicsConfiguration gc = gs.getDefaultConfiguration();
BufferedImage bimage = gc.createCompatibleImage(width, height, Transparency.BITMASK);
Things to note:
If you're PNG contains alpha values > 0 and < 255, they are likely to be rounded to either 0 or 1, possibly making the PNG appear jagged...
If you use Transparency.TRANSLUCENT instead, the color mode of the BufferedImage will be compatible with the GraphicsDevice, making it faster to render
I did an animated sequence a few years ago which was made up of 5 separate images, layered on top of each other and played back at separate speeds all on top of a transparent window...When I first tried running it the, the playback was terrible and jumped about the place.
After some playing around, I found that using Transparency.TRANSLUCENT to convert the images to a compatible color model for the GraphicsDevice worked like a charm...

Nothing wrong with the accepted answer, just providing an alternative for completeness (and I think it will work in headless mode). :-)
The transparency of a BufferedImage is controlled by its ColorModel.
So to create a BufferedImage with a given Transparency constant, you can use code like this:
// Use default RGB color space, no discrete alpha channel,
ColorSpace cs = ColorSpace.getInstance(ColorSpace.CS_sRGB);
ColorModel colorModel = new ComponentColorModel(cs, true, false, Transparency.BITMASK, DataBuffer.TYPE_BYTE);
WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE, w, h, 4, null);
BufferedImage image = new BufferedImage(colorModel, raster, colorModel.isAlphaPremultiplied(), null);

Related

Prevent image from changing color when written to a new TYPE_BYTE_INDEXED BufferedImage

I've been working on optimizing color PNG images for storage size by using the TYPE_BYTE_INDEXED BufferedImage to limit the number of unique colors each image can contain. I noticed that when I save an existing image to this format, the white (255,255,255) background color becomes an off-white color (252,252,252).
Is there any way to prevent this color shift from happening? I found this question which has a solution but it feels very hacky and is much slower than the Graphics2D implementation of drawImage.
Here's my code currently that's producing this issue. The original image is being loaded as a TYPE_3BYTE_BGR BufferedImage.
BufferedImage image = ImageIO.read(imageOutputPath.toFile());
BufferedImage indexedImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_INDEXED);
Graphics2D g = indexedImage.createGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
I know that this type of BufferedImage supports white as I've tried adding white margins around the image and it displays correctly but with the off-white image centered on the page. It just seems like the white in my image isn't converting correctly.
Any ideas and help would be greatly appreciated. Thanks!

What's the rule of binarysation for java.awt.image.BufferedImage

I can use this code
BufferedImage image = ImageIO.read(new File("toolbar.png"));
BufferedImage grayImage = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_BYTE_BINARY);
Graphics graphics = grayImage.getGraphics();
graphics.drawImage(image, 0, 0, null);
graphics.dispose();
to convert a BufferedImage from TYPE_3BYTE_BGR to TYPE_3BYTE_BGR.
For example , the input:
the output:
I know that the colored pixel is turned into a white pixel or a black pixel based on a computation rule. What's it and what can I do to adjust it? Thanks for your help in advance.
The conversion as in your code above, is an implicit conversion happening, because the input image (RGB) has more colors that the destination (binary indexed color).
The "rule" for this conversion to binary color can be described as simply "pick the color in the destination that is closest to the one from the input ". As your only colors are black and white, all colors that have the average of R, G and B less than 50% will become black, the rest white. There is no way to "adjust" this, directly.
However, you can control the threshold, either directly or compute it from the input, for better images. See for example this blog for an example.

JAI Add Alpha Channel to RenderedImage

I have two RenderedImages. I want to do an Overlay Operation with these two images and therefore they need to match in data type and the number of bands.
The problem I have is that one image has 3 bands (RGB) and the second image has 4 bands (ARGB).
My question is how can I add an Alpha Channel to the first image so I can do the Overlay Operation?
EDIT
Ok, I found a method of adding an Alpha Channel to the first image. Below is the code. I simply created a single banded constant image and merged it with my first image.
ParameterBlock pb = new ParameterBlock();
pb.add(new Float(finalImage.getWidth())).add(new Float(finalImage.getHeight()));
pb.add(new Byte[] {new Byte((byte)0xFF)});
RenderedImage alpha = JAI.create("constant", pb);
finalImage = BandMergeDescriptor.create(finalImage, alpha, null);
The problem I have now is that everytime I add an overlay the image changes colors. All the colors become nuances of red or pink. When I add a second overlay, the image becomes normal again, but the first overlay changes colors. All black areas become white.
Also the background of the overlay is not transparent. It is grey.
Below are examples of the images, so you see how the change colors:
As you can see, the picture and overlays change colors and the background of the overlay isn't transparent.
Can you help me solve this problem, so that the image is always displayed correctly? Thanks!
You can try to create a new BufferedImage with ARGB-model, and just paint the non-transparent background picture into this new BufferedImage. Then you have a BufferedImage with alpha-channel (although all of the pixels are opaque), so the Composition should hopefully work.
im not sure about TYPE_4BYTE_ARGB as I usually work with BufferedImages of TYPE_INT_ARGB but I have often used the method of drawing a RGB BufferedImage to a new ARGB BufferedImage and then drawing that onto other things without problem. the change in color would suggest an unwanted change is being made to the other channels in the overlay process as it doesn't seem to be specific to a specific image. If your overlay operation is similar to drawing one image onto another with alpha I would probably suggest using the Graphics.drawImage()/drawRenderedImage() method for the overlay itself not to mention the background should not even need alpha in this case.
the code:
public RenderedImage overlay(RenderedImage back, RenderedImage front, AffineTransform overlayTransformation)
{
BufferedImage newBack = new BufferedImage(back.getWidth(), back.getHeight(), TYPE_3BYTE_RGB);
newBack.setData(back.getData());
Graphics2D graphics = (Graphics2D)(newBack.getGraphics());
graphics.drawRenderedImage(front, overlayTransformation);
return newBack;
}
you may want to ensure the original back Raster is not modified though.

How to set a pixel in a BufferedImage To Transparent

I'm Currently making a Game, and I need to set a couple Pixels of a BufferedImage (loaded using ImageIO.read) to be transparent in the fastest, best way.
I can't really find any other topic with this question, and If I do the answer Doesn't really help/fit What I need.
Thanks :)
Use Color(red, green, blue, alpha) with values 0-255. Where alpha is the opacity.
Buffed image being of type with an Alpha channel (RGBA, BGRA)
Color halfTransparant = new Color(0x76, 0x54, 0x32, 128);
With setRGB on arrays this still is not fast, you might access the raster data.
But why using dynamically generated images in time critical situations.
The smart way is to create your image with correct alpha from the start (using an image format with transparency, e.g. PNG and your favorite imaging application e.g. GIMP).
Otherwise you can directly alter pixels in the BufferedImage returned by ImageIO using the BufferedImage API: setRGB(int ARGB) and its bulk manipulation cousins in the BufferedImage's Raster.
One way that should work with all image types (as long as they support alpha), is very fast, and will not disable hardware acceleration of the image is:
BufferedImage image = ...; // From somewhere
Graphics2D g = destination.createGraphics();
try {
g.setComposite(AlphaComposite.Clear);
g.fillRect(x, y, w, h); // Area to make transparent
}
finally {
g.dispose();
}

hasAlpha vs getAlphaRaster

In Java, is it expected to be able to have a BufferedImage such that getColorModel().hasAlpha() will return true, but getAlphaRaster() will return null?
I ask because there's a library I'm using (PDFBox specifically, in the PDJpeg class) that breaks on such an image.
In this particular case I'm creating the image very simply using:
BufferedImage bi = ImageIO.read(new FileInputStream("/Users/dan/Downloads/test.png"));
I've attached the particular image that's failing for me below this question.
Is there some sort of parameter I can pass to ImageIO or some kind of transformation I can do to my BufferedImage after it's loaded so that it won't run into this problem?
I'm running Java 1.7.0_40 if it matters.
Stack trace for completeness:
java.lang.NullPointerException
at java.awt.image.ComponentColorModel.isCompatibleRaster(ComponentColorModel.java:2787)
at java.awt.image.BufferedImage.<init>(BufferedImage.java:629)
at org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg.createImageStream(PDJpeg.java:159)
at org.apache.pdfbox.pdmodel.graphics.xobject.PDJpeg.<init>(PDJpeg.java:133)
Yes. As the JavaDoc states:
This method assumes that for all ColorModel objects other than IndexColorModel, if the ColorModel supports alpha, there is a separate alpha channel which is stored as the last band of image data. If the image uses an IndexColorModel that has alpha in the lookup table, this method returns null since there is no spatially discrete alpha channel.
Your image is a palette PNG with a transparent index. ImageIO will read this into a BufferedImage with IndexColorModel (ie., no discrete alpha channel).
You can convert the image to a different BufferedImage type (like TYPE_INT_RGB), by creating a blank image of the same size, getting its graphics, and draw the original onto it:
BufferedImage origininal = ...;
BufferedImage copy = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g = copy.createGraphics();
try {
g.drawImage(original, 0, 0, null);
}
finally {
g.dispose();
}
You can probably also pass the image type as an ImageTypeSpecifier on the ImageReadParam passed to the ImageReader. But it requires quite a bit more code for the reading part.

Categories