使用Java将透明的gif/png转换为jpeg

23

我想使用Java将gif图像转换为jpeg。对于大多数图像,它都能正常工作,但是我有一个简单的透明gif图像:

输入gif图像http://img292.imageshack.us/img292/2103/indexedtestal7.gif

[如果图像丢失:它是一个带有周围透明像素的蓝色圆形]

当我使用以下代码转换此图像时:

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

这段代码可以正常运行,但是会得到一张无效的JPEG图像:

输出的jpeg图像

[如果图片丢失:IE无法显示JPEG,Firefox则显示颜色无效的图像。]

我正在使用Java 1.5。

我也尝试了使用GIMP将示例GIF转换为PNG,并将PNG用作Java代码的输入。结果是相同的。

这是JDK中的一个bug吗?如何正确地转换图像,最好不使用第三方库?

更新:

回答指出JPEG转换不能正确处理透明度(我仍然认为这是一个bug),并建议用预定义的颜色替换透明像素的解决方法。两种建议的方法都相当复杂,所以我实现了一个更简单的方法(将其发布为答案)。我接受使用此解决方法的第一篇答案(由Markus提供)。我不知道哪个实现更好。我选择最简单的方法,但是我发现在某些GIF上它不起作用。

7个回答

43

针对Java 6(以及5,我认为也适用):

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);

嗨,如何将 g 再次返回到 BufferedImage 中? - user2809386
@akiong ImageIO.write()可以为您完成这项工作。 - Jon Onstott
不要忘记释放g:g.dispose() - Tinus Tate
@user2809386 中的“g”是“Graphics2D”,因此您可以执行“Graphics2D g2 = null;” “g2 = jpgImage.createGraphics();” - TuGordoBello

11

如问题更新中所提到的,我已经实现了一种更简单的方法来替换透明像素为预定义颜色:

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;
}

我在JPEG转换之前以这种方式调用此方法:

if( inputImage.getColorModel().getTransparency() != Transparency.OPAQUE) {
    inputImage = fillTransparentPixels(inputImage, Color.WHITE);
}

2
这种更简单的方法会比已接受的答案消耗两倍的内存吗? - Andrey Regentov

4
3个月晚了,但我遇到了非常类似的问题(虽然不是加载gif,而只是生成一个透明图像——比如说没有背景,有一个彩色形状——当保存为jpeg时,所有颜色都混乱了,不仅是背景)。
java2d-interest列表中的这个相当古老的主题中找到了这段代码,我想分享一下,因为经过快速测试,它比你的解决方案要更有效。
        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);

很遗憾,我不能说我完全理解它的作用 ;)


2
这行代码: final DirectColorModel cm = (DirectColorModel) img.getColorModel(); 似乎是一个不可能的强制类型转换——ColorModel 无法转型为 DirectColorModel(我尝试运行了这段代码,但在运行时遇到了强制类型转换异常)。 - Jim Bethancourt

4

问题(至少在将png转换为jpg时)在于颜色方案不同,因为jpg不支持透明度。

我们成功地做了类似以下内容的事情(这是从各种代码片段中提取的 - 请原谅格式的粗糙):

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);

这适用于将PNG转换为JPG和GIF转换为JPG。在透明部分的位置,您将得到白色背景。您可以通过让g2在drawImage调用之前用另一种颜色填充图像来更改此设置。


3
如果你创建了一个类型为BufferedImage.TYPE_INT_ARGB的BufferedImage并保存为JPEG,将会产生奇怪的结果。在我的情况下,颜色被扭曲成橙色。在其他情况下,生成的图像可能无效,其他读者将拒绝加载它。
但是,如果你创建一个类型为BufferedImage.TYPE_INT_RGB的图像,然后将其保存为JPEG,就可以正常工作。
我认为这是Java JPEG图像编写器中的一个bug - 它应该只写入没有透明度的内容(就像.NET GDI+所做的那样)。或者在最坏的情况下抛出一个有意义的异常消息,例如“无法写入具有透明度的图像”。

我认为这是最好的解决方案。简单、客观,同时也指出了JDK中的一个错误或设计缺陷。我同意,那绝对是一个bug,畸形的JPEG图像不太好。 - Spidey
Java认为他们的透明JPEG图片没问题,而其他人是有问题的:http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4836466 - Matthew Simoneau

2

JPEG不支持透明度。因此,即使您正确获取了圆形颜色,您仍将具有黑色或白色背景,这取决于您的编码器和/或渲染器。


黑色或白色(最好可配置)背景对我来说都可以接受。但是当前的代码生成了无效的图像。 - asalamon74
是的,当前代码无法自动将透明转换为白色(或黑色,或其他颜色),它不会抛出任何异常,也不会创建有效的JPEG。所创建的JPEG实际上并不有效,我的Android手机在处理它们时遇到了一些问题(我正在开发一个Web服务器<->Android客户端环境)。 - Spidey

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);

2020年7月9日 编辑:添加了imageIO.write


1
你错过了ImageIO.write - TuGordoBello
好的,我以为你需要这张图片并且知道该怎么处理它 :p - Akin Okegbile

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接