Java BufferedImage 转换为 PNG 格式的 Base64 字符串

30

我试图将屏幕截图输出为一个Base64编码的字符串,但是进展不顺。目前我的代码使用了一个Base64库(http://iharder.sourceforge.net/current/java/base64/):

    Robot robot = new Robot();
    Rectangle r = new Rectangle( Toolkit.getDefaultToolkit().getScreenSize() );
    BufferedImage bi = robot.createScreenCapture(r);
    ByteArrayOutputStream os = new ByteArrayOutputStream();
    OutputStream b64 = new Base64.OutputStream(os);
    ImageIO.write(bi, "png", os);
    ByteArrayOutputStream out = new ByteArrayOutputStream();
    out.writeTo(b64);
    String result = out.toString("UTF-8");
每次我运行这个程序,"result"总是空字符串,但我不明白为什么。有什么想法吗?
注意:我不想把png写入磁盘文件。
5个回答

26

我按照xehpuk的回答进行操作,但在某些浏览器中使用data url渲染时,某些图片的最后几行像素缺失。我怀疑这是因为浏览器尽力解释数据,但是由于缺少了最后几个字节的数据,所以它只显示它能够解释的。

输出流的包装似乎是这个问题的原因。 Base64.wrap(OutputStream os)的文档解释如下:

建议在使用后立即关闭返回的输出流,在此期间,它将刷新所有可能剩余的字节到底层输出流。

因此,根据数据的长度,由于没有调用close()方法,可能会导致最后几个字节没有从流中刷新出来。我的解决办法是不要包装流,直接对流进行编码:

public static String imgToBase64String(final RenderedImage img, final String formatName)
{
  final ByteArrayOutputStream os = new ByteArrayOutputStream();

  try
  {
    ImageIO.write(img, formatName, os);
    return Base64.getEncoder().encodeToString(os.toByteArray());
  }
  catch (final IOException ioe)
  {
    throw new UncheckedIOException(ioe);
  }
}

这解决了在浏览器中呈现时缺少像素行的问题。


你有一个示例图片吗?ImageIO.write() 调用底层的 ImageOutputStream 上的 close() 方法,该方法应关闭 Base64.EncOutputStream 并将所有剩余的字节写入包装的 OutputStream。我想知道我的错误在哪里。 - xehpuk
@xehpuk 我认为你可能误解了,ImageIO.write() 明确说明它不会在 OutputStream 上调用 close() 方法:此方法在写操作完成后不会关闭提供的 OutputStream;如果需要,调用者有责任关闭流。 - Robert Hunt
我知道它不会关闭流。它只会关闭内部创建的流。 - xehpuk
@xehpuk 是的 - 这就是为什么 Javadoc 在这种情况下强调了非标准行为 - 关闭内部流不会关闭提供的 OutputStream - Robert Hunt
我收到了我们的一个客户的投诉。他们正在使用http://www.libpng.org/pub/png/apps/pngcheck.html。对其进行包装会导致在浏览器中呈现时出现故障。当我使用Roberts的解决方案时,即使在浏览器中呈现,也会呈现缺少的几个字节并通过测试。 - Andrew James Ramirez

17

使用Java 8进行图像的Base64编码和解码:

public static String imgToBase64String(final RenderedImage img, final String formatName) {
    final ByteArrayOutputStream os = new ByteArrayOutputStream();
    try (final OutputStream b64os = Base64.getEncoder().wrap(os)) {
        ImageIO.write(img, formatName, b64os);
    } catch (final IOException ioe) {
        throw new UncheckedIOException(ioe);
    }
    return os.toString();
}

public static BufferedImage base64StringToImg(final String base64String) {
    try {
        return ImageIO.read(new ByteArrayInputStream(Base64.getDecoder().decode(base64String)));
    } catch (final IOException ioe) {
        throw new UncheckedIOException(ioe);
    }
}

使用它来应对您的截图场景:

final Robot robot = new Robot();
final Rectangle r = new Rectangle(Toolkit.getDefaultToolkit().getScreenSize());
final BufferedImage bi = robot.createScreenCapture(r);
final String base64String = imgToBase64String(bi, "png");

17
以下语句方向错误:
out.writeTo(b64);

它用out的空字节数组覆盖了Base 64数据。

那么out的目的是什么?我认为你不需要它。

更新:

并且您直接将图像写入os,而不是通过Base 64编码器进行编写。

以下代码应该可以工作:

...
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStream b64 = new Base64.OutputStream(os);
ImageIO.write(bi, "png", b64);
String result = os.toString("UTF-8");

我的javac出现了错误。它说找不到符号,然后指向Base64OutputStream(os)之间的.。我正在使用jdk1.7.0_51和commons-codec-1.4.jar。 - Tgwizman
1
我已经去掉了句号,现在是 new Base64OutputStream(os),导入的是 org.apache.commons.codec.binary.Base64OutputStream。它可以正常工作。 - Tgwizman
1
嗨!我正在使用完全相同的代码,但我总是在new Base64OutputStream(os)构造函数中获得java.lang.VerifyErrorosjava.io.ByteArrayOutputStreamBase64OutputStream来自commons-codec-1.10.jar的org.apache.commons.codec.binary.Base64OutputStream。我错过了什么吗? - Chechulin
听起来好像你的代码是针对一个版本的Base64OutputStream构建的,但在另一个版本上运行。你的类路径中有几个版本的commons-codec-x.xx.jar吗?其中一个是直接引用的,其他的是通过额外的第三方库引用的吗?(你可以考虑在这里提出一个单独的问题。) - Codo

2
这对我有效:
将图像编码为Base64字符串
public static String encodeToString(BufferedImage image, String type) {
    String imageString = null;
    ByteArrayOutputStream bos = new ByteArrayOutputStream();

    try {
        ImageIO.write(image, type, bos);
        byte[] imageBytes = bos.toByteArray();

        Base64.Encoder encoder = Base64.getEncoder();
        imageString = encoder.encodeToString(imageBytes);

        bos.close();
    } catch (IOException e) {
        e.printStackTrace();
    }
    return imageString;
}

将Base64字符串解码为图像

public static BufferedImage decodeToImage(String imageString) {
    BufferedImage image = null;
    byte[] imageByte;
    try {
        Base64.Decoder decoder = Base64.getDecoder();
        imageByte = decoder.decode(imageString);
        ByteArrayInputStream bis = new ByteArrayInputStream(imageByte);
        image = ImageIO.read(bis);
        bis.close();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return image;
}

1
实际上,两种不同的解决方案结合起来对我有用。我的使用情况是从zip文件中读取图像。以下是对我有效的代码:
BufferedImage bgTileSprite = ImageIO.read(inputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bgTileSprite, "png", os);
String result = Base64.getEncoder().encodeToString(os.toByteArray());
LOGGER.info(result);

我通过将结果转换为实际图像来确认结果。效果非常好。


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