0

My app downloads a Facebook user's jpeg profile picture and base64 encodes it. When decoding it, the resulting jpeg quality and size is significantly reduced. How do I avoid this?

Here is the way I'm downloading/encoding:

// Download the profile picture.
BufferedImage image = ImageIO.read(new URL("http://facebook-profile-pic"));
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ImageIO.write(image, "jpeg", bos);

// Base64 encode it.
String imageData = new String(Base64.getEncoder().encode(bos.toByteArray()));

When I take the value of imageData and decode it, the image is way smaller than the one at the original download URL.

niebula
  • 351
  • 1
  • 6
  • 13
  • 1
    `new String(byteArray)` is unreliable, as it uses the underlying system’s default charset to encode the bytes. If that charset is UTF-8 or some ISO-8859 charsets, it will work for Base64-encoded bytes, but if it’s anything else, your data will be corrupted. Use `String imageData = Base64.getEncoder().encodeToString(bos.toByteArray());` instead. – VGR Sep 20 '16 at 20:47
  • JPEG is a lossless file format, and ImageIO uses a specific quality when writing. For higher quality and/or lossless you need to specify the quality explicitly – copeg Sep 20 '16 at 20:48

2 Answers2

0

Encoding to/from Base64 is completely lossless. The quality loss happens probably when you save it. To prevent that, use an ImageWriter directly (ImageIO.write already uses it internally, but gives you no control over the settings):

ImageWriter writer = ImageIO.getImageWritersByFormatName("jpeg").next();
writer.setOutput(bos);

ImageWriteParam param = writer.getDefaultWriteParam();
param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
param.setCompressionQuality(1.0F); // Highest quality

writer.write(image, new IIOImage(image, null, null), param);

Also, it might be that you already download a low quality image depending on what API you use.

Gumbo
  • 1,716
  • 1
  • 15
  • 22
0

When decoding it, the resulting jpeg quality and size is significantly reduced

JPEG is a lossy file format, and ImageIO uses a default lossy compression metric when writing jpg file. For higher quality (or lossless) you need to explicitly handle the compression quality yourself though a JPEGImageWriteParam.

//create the params    
JPEGImageWriteParam jpegParams = new JPEGImageWriteParam(null);
jpegParams.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
jpegParams.setCompressionQuality(1f);//set compression quality

See answers here for writing to files. To write in memory, you will need to use MemoryCacheImageOutputStream

//write to output stream
ByteArrayOutputStream bos = new ByteArrayOutputStream();
MemoryCacheImageOutputStream ios = new MemoryCacheImageOutputStream(bos);
final ImageWriter writer = ImageIO.getImageWritersByFormatName("jpg").next();
writer.setOutput(ios);
writer.write(null, new IIOImage(image, null, null), jpegParams);
Community
  • 1
  • 1
copeg
  • 8,290
  • 19
  • 28