Render premultiplied data to transparent PNG file in Java - java

Long story short I have premultiplied Texture. I grab a FrameBuffer, clear it with (0,0,0,0), set the blend mode to glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA), and render the Texture. Then I grab the pixels glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, byteBufferPixels); and use that:
BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
screenshot.setRGB(0, 0, width, height, argbInts, 0, width);
try {
ImageIO.write(screenshot, "png", file);
} catch (IOException e) {
e.printStackTrace();
}
I also have tried BufferedImage.TYPE_INT_ARGB_PRE with no change.
The result is this:
How do I get a nice transparent PNG file out of premultiplied pixel data in OpenGL?
Thanks!

The JavaDoc for BufferedImage.setRGB(...) says:
Sets an array of integer pixels in the default RGB color model (TYPE_INT_ARGB) and default sRGB color space, into a portion of the image data.
This means that your input data argbInts needs to be in non-premultiplied form, regardless of the destination image type. It's of course possible to un-multiply the input pixel data before passing it to the setRGB method, but it's a bit tedious for an array with packed ints, so I leave that as an exercise for the reader... 😉
Instead, it is much easier to just change the image to TYPE_INT_ARGB_PRE and set the pixel data directly with the original premultiplied pixels, like this:
BufferedImage screenshot = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB_PRE);
// Safe cast for TYPE_INT_*
int[] imageData = ((DataBufferInt) screenshot.getRaster().getDataBuffer()).getData();
System.arraycopy(argbInts, 0, imageData, 0, argbInts.length));
try {
ImageIO.write(screenshot, "PNG", file);
} catch (IOException e) {
e.printStackTrace();
}

Related

Stripping Alpha Channel from Images

I want to strip the alpha channel (transparent background) from PNGs, and then write them as JPEG images. More correctly, I'd like to make the transparent pixels white. I've tried two techniques, both of which fail in different ways:
Approach 1:
BufferedImage rgbCopy = new BufferedImage(inputImage.getWidth(), inputImage.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = rgbCopy.createGraphics();
graphics.drawImage(inputImage, 0, 0, Color.WHITE, null);
graphics.dispose();
return rgbCopy;
Result: image has a pink background.
Approach 2:
final WritableRaster raster = inputImage.getRaster();
final WritableRaster newRaster = raster.createWritableChild(0, 0, inputImage.getWidth(), inputImage.getHeight(), 0, 0, new int[]{0, 1, 2});
ColorModel newCM = new ComponentColorModel(inputImage.getColorModel().getColorSpace(), false, false, Transparency.OPAQUE, DataBuffer.TYPE_BYTE);
return new BufferedImage(newCM, newRaster, false, null);
Result: Image has a black background.
In both cases, the input image is a PNG and the output image is written as a JPEG as follows: ImageIO.write(bufferedImage, "jpg", buffer). In case it's relevant: this is on Java 8 and I'm using the twelvemonkeys library to resize the image before writing it as a JPEG.
I've experimented with a number of variations of the above code, with no luck. There are a number of previous questions that suggest the above code, but it doesn't seem to be working in this case.
Thanks to Rob's answer, we now know why the colors are messed up.
The problem is twofold:
The default JPEGImageWriter that ImageIO uses to write JPEG, does not write JPEGs with alpha in a way other software understands (this is a known issue).
When passing null as the destination to ResampleOp.filter(src, dest) and the filter method is FILTER_TRIANGLE, a new BufferedImage will be created, with alpha (actually, BufferedImage.TYPE_INT_ARGB).
Stripping out the alpha after resampling will work. However, there is another approach that is likely to be faster and save some memory. That is, instead of passing a null destination, pass a BufferedImage of the appropriate size and type:
public static void main(String[] args) throws IOException {
// Read input
File input = new File(args[0]);
BufferedImage inputImage = ImageIO.read(input);
// Make any transparent parts white
if (inputImage.getTransparency() == Transparency.TRANSLUCENT) {
// NOTE: For BITMASK images, the color model is likely IndexColorModel,
// and this model will contain the "real" color of the transparent parts
// which is likely a better fit than unconditionally setting it to white.
// Fill background with white
Graphics2D graphics = inputImage.createGraphics();
try {
graphics.setComposite(AlphaComposite.DstOver); // Set composite rules to paint "behind"
graphics.setPaint(Color.WHITE);
graphics.fillRect(0, 0, inputImage.getWidth(), inputImage.getHeight());
}
finally {
graphics.dispose();
}
}
// Resample to fixed size
int width = 100;
int height = 100;
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_TRIANGLE);
// Using explicit destination, resizedImg will be of TYPE_INT_RGB
BufferedImage resizedImg = resampler.filter(inputImage, new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB));
// Write output as JPEG
ImageIO.write(resizedImg, "JPEG", new File(input.getParent(), input.getName().replace('.', '_') + ".jpg"));
}
haraldk was correct, there was another piece of code that was causing the alpha channel issue. The code in approach 1 is correct and works.
However if, after stripping the alpha channel, you resize the image using twelvemonkeys ResampleOp as follows:
BufferedImageOp resampler = new ResampleOp(width, height, ResampleOp.FILTER_TRIANGLE);
BufferedImage resizedImg = resampler.filter(rgbImage, null);
This causes the alpha channel to be pink.
The solution is to resize the image before stripping the alpha channel.

How to convert a Type_Byte_Binary BufferedImage with Alpha to an TYPE_4BYTE_ABGR

Im using the code
BufferedImage imgIn = ImageIO.read(is);
BufferedImage image;
if (imgIn.getType() != BufferedImage.TYPE_BYTE_INDEXED) {
image = new BufferedImage(imgIn.getWidth(), imgIn.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
ColorConvertOp cco = new ColorConvertOp(imgIn.getColorModel()
.getColorSpace(), image.getColorModel().getColorSpace(), null);
cco.filter(imgIn, image);
} else {
image = imgIn;
}
When I'm pulling in an image with a Bit Depth of 4 it makes the imgIn a Type_Byte_Binary. When It gets converted to Type_4Byte_ABGR anything with partial transparency changes color and loses all alpha. Fully transparent pixels and Fully Opaque pixels remained unchanged.
Is there anything I can do to keep the partially transparent pixels from changing?
Thanks.
Most likely ColorConvertOp isn't what you are after here, because the images are all in RGB (sRGB) color space (IndexColorModel doesn't support other color spaces).
Instead, just paint your TYPE_BYTE_INDEXED or TYPE_BYTE_BINARY onto a TYPE_4BYTE_ABGR image, and any transparency should be preserved.
BufferedImage image = new BufferedImage(imgIn.getWidth(), imgIn.getHeight(), BufferedImage.TYPE_4BYTE_ABGR);
Grphics2D g = image.createGraphics();
try {
g.drawImage(imgIn, 0, 0, null);
}
finally {
g.dispose();
}

Java display base 64 byte array in jsp

I have a class called Graphic that creates a new BufferedImage, draws a new Graphics2D and returns this image as base64 encoded String:
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// Draw background
g2.setColor(Color.decode("#FFFFFF"));
g2.fillRect(0, 0, grafikBreite, grafikHoehe);
g2.setColor(Color.decode("#000000"));
// Draw some rectangles and other stuff...
drawStuff(g2);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] imageInByte = {};
try {
JPEGImageEncoder j = new JPEGImageEncoderImpl(baos);
j.encode(image);
imageInByte = baos.toByteArray();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
return javax.xml.bind.DatatypeConverter.printBase64Binary(imageInByte);
In my jsp-File I want to display this image using, where graphic is the previously created base64 byte array:
<img src="data:image/jpg;base64,<c:out value="${graphic}"/>"/>
The image is displayed, but the problem is that the image has red background and the other colors used are also wrong. If I save the created base64 string as jpeg-File on hard disk, all colors are displayed correctly.
Has someone an idea why HTML displays the image with strange colors?
Thanks for help
First a bit cleaning up:
g2.setColor(Color.WHITE);
g2.fillRect(0, 0, grafikBreite, grafikHoehe);
g2.setColor(Color.BLACK);
drawStuff(g2);
g2.dispose(); // TODO
Dispose after createGraphics.
Then one could try the more generic, portable ImageIO class. The parametrising for antialiasing and such goes a bit different, but then JPEG is a lossy format anyway. Just to try a different angle.
ImageIO.write(image, "jpg", baos);
baos.close();
imageInByte = baos.toByteArray();
And then I did the closing first. (It has no effect by javadoc.)
One could try .png and a another type, ABGR.
I think ImageIO does the trick, or your code with ABGR.

building a image from a 1d array

i have a 1 D array pixel which contains pixel values of a 512x512 grayscale image. i want to write it into a png file. i wrote the following code , but it just creates a blank image.
public void write(int width ,int height, int[] pixel) {
try {
// retrieve image
BufferedImage writeImage = new BufferedImage(512,512,BufferedImage.TYPE_BYTE_GRAY);
File outputfile = new File("saved.png");
WritableRaster raster = (WritableRaster) writeImage.getData();
raster.setPixels(0,0,width,height,pixel);
ImageIO.write(writeImage, "png", outputfile);
} catch (IOException e) {
}
The Raster returned is a copy of the image data is not updated if the image is changed.
Try setting the new Raster object back to the image.
WritableRaster raster = (WritableRaster)writeImage.getData();
raster.setPixels(0, 0, width, height, pixel);
writeImage.setData(raster);

Transparent regions of a BufferedImage are writing out black

I know this is something to do with compositing but I can't work out what. In an earlier section of code, a particular list of pixels in a BufferedImage are set to be transparent black:
for(Pixel p : closed){
Color c = new Color(image.getRGB(p.x, p.y));
Color newC = new Color(0,0,0, 0);
image.setRGB(p.x, p.y, newC.getRGB() & 0x00000000);
}
if(andCrop){
image = image.getSubimage(left, top, right-left, bottom-top);
}
return image;
Then I attempt to write the image out:
try {
BufferedImage out = new BufferedImage(image.getWidth(), image.getHeight(), java.awt.Transparency.TRANSLUCENT);
Graphics2D g2d = out.createGraphics();
g2d.setComposite(AlphaComposite.Clear);
g2d.fillRect(0, 0, image.getWidth(), image.getHeight());
g2d.setComposite(AlphaComposite.Src);
g2d.drawImage(image, 0, 0, image.getWidth(), image.getHeight(), null);
g2d.dispose();
File outputfile = new File(file);
ImageIO.write(out, "png", outputfile);
} catch (IOException e) {
}
Now, I know that 'out' is clear before I attempt to draw the image onto it. What I'm not getting is what's wrong with my compositing. Instead of coming out as transparent, I'm getting full-black.
All bufferedimages used are INT_ARGB.
EDIT - This has been solved. The image source was from ImageIO.read and the BufferedImage returned did not support alpha. A quick post-read conversion let the rest of the code run smoothly.
Things that comes to my mind... (thanks to Andrew):
java.awt.Transparency.TRANSLUCENT = 3
TYPE_INT_ARGB = 2
TYPE_INT_ARGB_PRE = 3
public BufferedImage(int width,
int height,
int imageType)
Constructs a BufferedImage of one of the predefined image types. (TYPE_...)
http://docs.oracle.com/javase/1.4.2/docs/api/java/awt/image/BufferedImage.html
so it seems as basically it's a mixup.
Besides, what is the effect you want to achieve? you clear an empty image, then draw fully transparent pixels to it... I just don't get it.
Whelp, this has been downvoted now so I'm not sure this will be relevant, but the issue was that the original BufferedImage was being read in by ImageIO, and this image was not supporting ARGB. A quick post-read conversion allowed the rest of the code to work.

Categories