30

I'm trying to get a screenshot output as a base64 encoded string but not getting very far. The code I have so far uses a Base64 library ( 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");

Each time I run this, "result" is always an empty string but I don't understand why. Any ideas?

Note: I don't want to have to write the png to a file on disk.

user72003
  • 425
  • 1
  • 5
  • 7

5 Answers5

26

I followed xehpuk's answer but had issues with certain images having the last few rows of pixels missing when rendered in certain browsers via a data url (Chrome and Firefox, Safari seemed to render them fine). I suspect this is because the browser is doing it's best to interpret the data but the last few bytes of data was missing so it shows what it can.

The wrapping of the output stream seems to be the cause of this problem. The documentation for Base64.wrap(OutputStream os) explains:

It is recommended to promptly close the returned output stream after use, during which it will flush all possible leftover bytes to the underlying output stream.

So depending on the length of the data, it's possible the last few bytes are not flushed from the stream because close() isn't called on it. My solution to this was to not bother wrapping the stream and just encode the stream directly:

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

This resolved the issues with the missing rows of pixels when rendered in a browser.

Community
  • 1
  • 1
Robert Hunt
  • 7,914
  • 5
  • 40
  • 43
  • Do you have an example image? `ImageIO.write()` calls `close()` on the underlying `ImageOutputStream` which should close the `Base64.EncOutputStream` which should write all remaining bytes to the wrapped `OutputStream`. I'd like to know where I'm mistaken. – xehpuk Apr 12 '17 at 23:55
  • @xehpuk I think you may be mistaken, `ImageIO.write()` explicitly states that it does not call `close()` on the `OutputStream`: ***This method does not close the provided `OutputStream` after the write operation has completed; it is the responsibility of the caller to close the stream, if desired.*** – Robert Hunt Apr 13 '17 at 10:05
  • I know it doesn't close the stream. It closes the stream it creates internally. – xehpuk Apr 13 '17 at 22:27
  • @xehpuk Yes - which is why the Javadoc highlights the non-standard behavior in this case - closing the internal stream doesn't close the provided `OutputStream`. – Robert Hunt Apr 14 '17 at 07:57
  • I had this complain from one of our client. They are using http://www.libpng.org/pub/png/apps/pngcheck.html. Wrapping it causes it to fail when rendered in browsers. When I used Roberts solution. The missing few bytes are rendered and passes the test even if rendered in browsers. – Andrew James Ramirez Sep 03 '20 at 05:47
17

Base64 encoding and decoding of images using Java 8:

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

Use it like this for your screenshot scenario:

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");
xehpuk
  • 7,814
  • 3
  • 30
  • 54
17

The following statement works in the wrong direction:

out.writeTo(b64);

It overwrites the Base 64 data with the empty byte array of out.

What's the purpose of out anyway? I don't think you need it.

Update:

And you write the image directly to os instead of writing through the Base 64 encoder.

The following code should work:

...
ByteArrayOutputStream os = new ByteArrayOutputStream();
OutputStream b64 = new Base64.OutputStream(os);
ImageIO.write(bi, "png", b64);
String result = os.toString("UTF-8");
Codo
  • 75,595
  • 17
  • 168
  • 206
  • My javac is throwing an error. It says cannot find symbol, then points at the `.` between `Base64` and `OutputStream(os)`. I'm using jdk1.7.0_51 and commons-codec-1.4.jar. – Tgwizman Mar 18 '14 at 23:37
  • 1
    I removed the period and it's now `new Base64OutputStream(os)` and the import is `org.apache.commons.codec.binary.Base64OutputStream`. It works – Tgwizman Mar 18 '14 at 23:43
  • 1
    Hi! I'm using exactly the same code, but I always get java.lang.VerifyError in the `new Base64OutputStream(os)` constructor. `os` is `java.io.ByteArrayOutputStream`. `Base64OutputStream` is `org.apache.commons.codec.binary.Base64OutputStream` from commons-codec-1.10.jar. Am I missing something? – Chechulin Dec 23 '14 at 06:51
  • It sounds as if your code is built against one version of Base64OutputStream and run against another one. Do you have several version of commons-codec-x.xx.jar in your classpath? One directly and others via additional third party libraries? (You might consider to ask a separate question about it here.) – Codo Dec 23 '14 at 15:56
2

This works for me:

Encode Image to Base64 String

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

Decode Base64 String to Image

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;
}
Manuel Schmitzberger
  • 5,162
  • 3
  • 36
  • 45
1

Actually, the combination of two different solutions worked for me. My use case is to read images from a zip file. Here is the code that worked for me :

BufferedImage bgTileSprite = ImageIO.read(inputStream);
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(bgTileSprite, "png", os);
String result = Base64.getEncoder().encodeToString(os.toByteArray());
LOGGER.info(result);

I Confirmed the result by converting the result to actual image. It works wonder.

ASK
  • 1,136
  • 1
  • 10
  • 14