6

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.

Rob
  • 5,512
  • 10
  • 41
  • 45
  • 2
    Can you paint the whole raster white before painting the image over the top? – weston Mar 16 '16 at 08:34
  • 1
    Are you saying that your image has a pink background after running the code in 1)? Or is there more code involved? I suggest you create a full [MVCE](http://stackoverflow.com/help/mcve). Don't forget to add test data (PNG files), for full verification. – Harald K Mar 16 '16 at 11:50
  • I tried (by reconstructing the missing parts) your approach 1, which should work. And it did. For me, it worked fine, using both Java 7 and Java 8. FWIW I'm on OS X, 10.11. 3. Most likely, there's something in the code you don't show which causes this issue. – Harald K Mar 16 '16 at 13:20

2 Answers2

7

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"));
}
Harald K
  • 26,314
  • 7
  • 65
  • 111
  • This is much cleaner than my proposed solution, but when I test it with a PNG, the output image is black. I'm sure I'm missing something though, because on the face of it, it SHOULD work. – Rob Mar 17 '16 at 17:37
  • You are right, I somehow forgot that, as my test PNG was Indexed, and the transparent color was white... I'll update the answer. – Harald K Mar 18 '16 at 09:34
  • @Rob Updated answer to add white background. – Harald K Mar 18 '16 at 14:16
  • 4
    Thanks, this works like a charm. For anyone else looking at this solution, if you are using just the portion of the code to fill the alpha channel (i.e. not resizing it), you need to copy the image to a new of TYPE_INT_RGB otherwise ImageIO will write it incorrectly. – Rob Mar 18 '16 at 16:33
1

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.

halfer
  • 19,824
  • 17
  • 99
  • 186
Rob
  • 5,512
  • 10
  • 41
  • 45