Converting transparent gif / png to jpeg using java
Asked Answered
P

7

23

I'd like to convert gif images to jpeg using Java. It works great for most images, but I have a simple transparent gif image:

Input gif image http://img292.imageshack.us/img292/2103/indexedtestal7.gif

[In case the image is missing: it's a blue circle with transparent pixels around it]

When I convert this image using the following code:

File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
File f = new File("indexed_test.jpg");
ImageIO.write(image, "jpg", f);

This code works without throwing an Exception, but results an invalid jpeg image:

Output jpeg image

[In case the image is missing: IE cannot show the jpeg, Firefox shows the image with invalid colors.]

I'm using Java 1.5.

I also tried converting the sample gif to png with gimp and using the png as an input for the Java code. The result is the same.

Is it a bug in the JDK? How can I convert images correctly preferably without 3rd party libraries?

UPDATE:

Answers indicate that jpeg conversion cannot handle transparency correctly (I still think that this is a bug) and suggest a workaround for replacing transparent pixels with predefined color. Both of the suggested methods are quite complex, so I've implemented a simpler one (will post as an answer). I accept the first published answer with this workaround (by Markus). I don't know which implementation is the better. I go for the simplest one still I found a gif where it's not working.

Paresthesia answered 21/1, 2009 at 10:58 Comment(0)
I
43

For Java 6 (and 5 too, I think):

BufferedImage bufferedImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_RGB);
g = bufferedImage.createGraphics();
//Color.WHITE estes the background to white. You can use any other color
g.drawImage(image, 0, 0, bufferedImage.getWidth(), bufferedImage.getHeight(), Color.WHITE, null);
Ignace answered 9/10, 2009 at 18:39 Comment(4)
hi, how to return g to BufferedImage again?Molecule
@akiong ImageIO.write() would do that for youAntechamber
Don't forget to dispose g: g.dispose()Blackcap
@Molecule g is Graphics2D so you can do Graphics2D g2 = null; g2 = jpgImage.createGraphics();Abri
P
11

As already mentioned in the UPDATE of the question I've implemented a simpler way of replacing transparent pixels with predefined color:

public static BufferedImage fillTransparentPixels( BufferedImage image, 
                                                   Color fillColor ) {
    int w = image.getWidth();
    int h = image.getHeight();
    BufferedImage image2 = new BufferedImage(w, h, 
        BufferedImage.TYPE_INT_RGB);
    Graphics2D g = image2.createGraphics();
    g.setColor(fillColor);
    g.fillRect(0,0,w,h);
    g.drawRenderedImage(image, null);
    g.dispose();
    return image2;
}

and I call this method before jpeg conversion in this way:

if( inputImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
    inputImage = fillTransparentPixels(inputImage, Color.WHITE);
}
Paresthesia answered 22/1, 2009 at 9:8 Comment(1)
Does this simpler method consume twice as much memory than in accepted answer?Heighttopaper
G
4

The problem (at least with png to jpg conversion) is that the color scheme isn't the same, because jpg doesn't support transparency.

What we've done successfully is something along these lines (this is pulled from various bits of code - so please forgive the crudeness of the formatting):

File file = new File("indexed_test.gif");
BufferedImage image = ImageIO.read(file);
int width = image.getWidth();
int height = image.getHeight();
BufferedImage jpgImage;

//you can probably do this without the headless check if you just use the first block
if (GraphicsEnvironment.isHeadless()) {
  if (image.getType() == BufferedImage.TYPE_CUSTOM) {
      //coerce it to  TYPE_INT_ARGB and cross fingers -- PNGs give a    TYPE_CUSTOM and that doesn't work with
      //trying to create a new BufferedImage
     jpgImage = new BufferedImage(width,height,BufferedImage.TYPE_INT_ARGB);
  } else {
     jpgImage = new BufferedImage(width, height, image.getType());
  }
} else {
     jgpImage =   GraphicsEnvironment.getLocalGraphicsEnvironment().
        getDefaultScreenDevice().getDefaultConfiguration().
        createCompatibleImage(width, height, image.getTransparency()); 
}

//copy the original to the new image
Graphics2D g2 = null;
try {
 g2 = jpg.createGraphics();

 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, 
                    RenderingHints.VALUE_INTERPOLATION_BICUBIC);
 g2.drawImage(image, 0, 0, width, height, null);
}
finally {
   if (g2 != null) {
       g2.dispose();
   }
}

File f = new File("indexed_test.jpg");

ImageIO.write(jpgImage, "jpg", f);

This works for png to jpg and gif to jpg. And you will have a white background where the transparent bits were. You can change this by having g2 fill the image with another color before the drawImage call.

Gloria answered 21/1, 2009 at 16:58 Comment(0)
P
4

3 months late, but I am having a very similar problem (although not even loading a gif, but simply generating a transparent image - say, no background, a colored shape - where when saving to jpeg, all colors are messed up, not only the background)

Found this bit of code in this rather old thread of the java2d-interest list, thought I'd share, because after a quick test, it is much more performant than your solution:

        final WritableRaster raster = img.getRaster();
        final WritableRaster newRaster = raster.createWritableChild(0, 0, img.getWidth(), img.getHeight(), 0, 0, new int[]{0, 1, 2});

        // create a ColorModel that represents the one of the ARGB except the alpha channel
        final DirectColorModel cm = (DirectColorModel) img.getColorModel();
        final DirectColorModel newCM = new DirectColorModel(cm.getPixelSize(), cm.getRedMask(), cm.getGreenMask(), cm.getBlueMask());

        // now create the new buffer that we'll use to write the image
        return new BufferedImage(newCM, newRaster, false, null);

Unfortunately, I can't say I understand exactly what it does ;)

Percy answered 13/5, 2009 at 16:37 Comment(1)
The line final DirectColorModel cm = (DirectColorModel) img.getColorModel(); seems to be an impossible cast -- ColorModel can't be cast to DirectColorModel (I tried the code and got a cast exception at runtime)Adamsite
T
3

If you create a BufferedImage of type BufferedImage.TYPE_INT_ARGB and save to JPEG weird things will result. In my case the colors are scewed into orange. In other cases the produced image might be invalid and other readers will refuse loading it.

But if you create an image of type BufferedImage.TYPE_INT_RGB then saving it to JPEG works fine.

I think this is therefore a bug in Java JPEG image writer - it should write only what it can without transparency (like what .NET GDI+ does). Or in the worst case thrown an exception with a meaningful message e.g. "cannot write an image that has transparency".

Thadthaddaus answered 5/1, 2011 at 10:14 Comment(2)
Best solution in my opinion. Simple, objective, and also states a bug or design flaw in the JDK. I agree, that's definitely a bug, malformed JPEG is not cool.Aiguillette
Java thinks their transparent JPEGs are fine and everyone else is the problem: bugs.sun.com/bugdatabase/view_bug.do?bug_id=4836466Barbour
T
2

JPEG has no support for transparency. So even when you get the circle color correctly you will still have a black or white background, depending on your encoder and/or renderer.

Traduce answered 21/1, 2009 at 11:1 Comment(2)
Black or white (preferably configurable) background would be acceptable for me. But currently the code creates invalid image.Paresthesia
Yes, the current code doesn't auto-translate transparent to white (or black, or whatever), it doesn't throw any exceptions and it doesn't create a valid JPEG. The created JPEGs aren't really valid, my Android phone has some trouble processing them (I'm developing a webserver <-> android client environment).Aiguillette
T
1
BufferedImage originalImage = ImageIO.read(getContent());
BufferedImage newImage = new BufferedImage(originalImage.getWidth(), originalImage.getHeight(), BufferedImage.TYPE_3BYTE_BGR);

    for (int x = 0; x < originalImage.getWidth(); x++) {
        for (int y = 0; y < originalImage.getHeight(); y++) {
            newImage.setRGB(x, y, originalImage.getRGB(x, y));
        }
    }
 ImageIO.write(newImage, "jpg", f);

7/9/2020 Edit: added imageIO.write

Trachytic answered 7/3, 2016 at 18:41 Comment(2)
you missed ImageIO.writeAbri
Fair, I assumed one needed the image and knew what to do with it :pTrachytic

© 2022 - 2024 — McMap. All rights reserved.