2

I have an Android app that takes photos, converts them to Bitmap using:

private Bitmap generateBitmap(byte[] data) {
    Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length);
    Matrix mat = new Matrix();
    mat.postRotate(-90);
    return Bitmap.createBitmap(bmp, 0, 0, bmp.getWidth(),
            bmp.getHeight(), mat, true);
}

then to PNG using:

bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);

The outputStream is then posted (multipart/form-data) to my web server (jboss). On the server, jax-rs and MultipartForm convert it to a data[] and send on for additional processing that begins with:

BufferedImage image = ImageIO.read(new ByteArrayInputStream(form.getImageFileData()));

The app is in production and works as expected, until now. We activated a new user last week (Samsung Galaxy Note Edge) and every single photo he uploads generates javax.imageio.IIOException: Error reading PNG image data when I invoke ImageIO.read() where the root cause of the stack trace is:

Caused by: java.util.zip.ZipException: incorrect data check

The photos display properly in a browser but I am unable to fully process them because of this exception. I can also open them in an editor rotate them 360 degrees, re-save them and then process them just fine.

Can anyone help me understand what could cause this problem on just this one device or suggest something I can do on the server to work around it and still generate the BufferedImage I need for further processing? Manually editing each photo is not an option.

UPDATE: As suggested, I ran pngcheck on 14 photos from this device. It returned 2 valid and 12 invalid with error: zlib: inflate error = -3 (data error). All 14 fail as described above using ImageIO.

An image with this problem can be seen at: https://tracweb-safecommunity.rhcloud.com/rest/monitoredProfile/106/testResult_8284.png

Jonathon
  • 73
  • 8
  • `Caused by: java.util.zip.ZipException: incorrect data check` -- PNG files are not ZIP files. – CommonsWare Apr 02 '16 at 20:32
  • a) images come in all different kinds - maybe this one is not recognized by imageio - you have to get down to read the pixel bytes and reconstruct the image b) is this png visible as an image ? then it may be the transference of bytes bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream); – gpasch Apr 02 '16 at 20:59
  • So either bitmap.compress functions differently on this one phone or there is something unique about all the photos from this phone that ImageIO doesn't like? Both seem unlikely, but that is all I came up with too. Unless anyone has a better idea, I may build a test app just for this guy that uses jpg instead of png. – Jonathon Apr 02 '16 at 22:09
  • 2
    @CommonsWare: but the image payload of PNGs are zipped, and it's reasonable to assume the imageio lib uses util.zip to decompress, rather than including the exact same code again. Jonathan: run such an image through PNG verification tools such as `pngcheck`. If possible, post a link to one. – Jongware Apr 03 '16 at 11:38
  • 1
    Is the linked image one that fails only using `ImageIO` or also `pngcheck`? It would be interesting to see one of each, to figure out what is different. In any case, I believe the PNGs created by this phone are broken (according to the spec). One possible workaround is using the `Toolkit` methods to obtain an `Image`, then convert that to a `BufferedImage`. I tested, and it works with your attached image (I'll post the code if you like). – Harald K Apr 04 '16 at 07:36
  • @haraldK: `pngcheck` (2.3.0, [current version](http://www.libpng.org/pub/png/apps/pngcheck.html)) reports no problems. Note that `pngcheck` does not check the integrity of the zlib compressed stream (per http://stackoverflow.com/a/36071270/2564301), but `pngcrush` also does not report anything and processes it as usual. – Jongware Apr 04 '16 at 11:23
  • 1
    @RadLexus Well, if it does not check the zlib compressed streams, no problems are to be expected. Seems like Jonathon's version does though (given the zlib error message). The linked image contains a single (truncated) IDAT chunk, but all the image data is there, only the zlib checksum is missing or incorrect (not to be confused with the PNG chunk CRC). – Harald K Apr 04 '16 at 12:16
  • @RadLexus: The linked image failed both for me. I used the linux binaries for pngcheck 2.3.0. Thanks for investigating. – Jonathon Apr 05 '16 at 12:36

1 Answers1

2

To me, it really seems that the particular device (vendor specific OS build?) generates broken PNGs. However, it seems that the only thing missing, is some Zip/zlib data integrity check value, and the image can be properly reconstructed, if you ignore the data integrity checks.

For some reason, my original answer (below) does not work for the OP. So here's an alternative (and more verbose) approach using only ImageIO:

InputStream input = new ByteArrayInputStream(form.getImageFileData());
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);

if (!readers.hasNext()) {
    // TODO: Handle, return null or throw exception, whatever is more appropriate
}

ImageReader reader = readers.next();
reader.setInput(input);

try {
    ImageReadParam param = reader.getDefaultReadParam();
    int imageNo = 0;

    int width = reader.getWidth(imageNo);
    int height = reader.getHeight(imageNo);

    // If possible, create a destination image up front
    ImageTypeSpecifier type = reader.getRawImageType(imageNo);
    if (type != null) {
        param.setDestination(type.createBufferedImage(width, height));
    }

    // Decode into the destination
    BufferedImage image;
    try {
        image = reader.read(imageNo, param);
    }
    catch (IOException e) {
        if (e.getCause() instanceof ZipException && param.getDestination() != null) {
            // If we got here, the destination will contain a partial image
            // We'll use that.
            image = param.getDestination();
        }
        else {
            throw e;
        }
    }
}
finally {
    input.close();
}

The last line of the image will be missing due to the ZipException, otherwise, the image looks good.


Here's a possible workaround using Java. I have tested this on OS X, and it works for me, using both Java 1.7.0_71 and 1.8.0_51 (both Oracle JREs), using the supplied test file. A stack trace is printed to the console, and the last line of the image is missing, otherwise, it looks good:

byte[] data = form.getImageFileData();
Image tmp = Toolkit.getDefaultToolkit().createImage(data);
BufferedImage image = new BufferedImageFactory(tmp).getBufferedImage();

This will be a little slower than using ImageIO, so I suggest you first try using ImageIO as before, then use this code only as a fallback when it fails with a java.util.zip.ZipException root cause.

PS: You can probably convert the Image to BufferedImage using a MediaTracker to fully load it (or use the ImageIcon hack), and then painting the result to a BufferedImage too, if you don't mind losing some precision, like the original color model etc.


The BufferedImageFactory class is part of my TwelveMonkeys ImageIO library, is available under BSD license and can be found on GitHub.

Harald K
  • 26,314
  • 7
  • 65
  • 111
  • I will check this out later this week (something urgent came up yesterday) and let you know how it goes. – Jonathon Apr 05 '16 at 12:37
  • I plugged this in and now receive `com.twelvemonkeys.image.ImageConversionException: Image conversion failed: ImageConsumer.IMAGEERROR` thrown `at com.twelvemonkeys.image.BufferedImageFactory$Consumer.imageComplete(BufferedImageFactory.java:478)`. Rather than disrupt the customer, I am testing against the already uploaded image files, so instead of `byte[] data = form.getImageFileData();` I am using `byte[] data = IOUtils.toByteArray(fileInputStream);` in case that makes any difference. – Jonathon Apr 10 '16 at 19:27
  • Stack Trace: `at com.twelvemonkeys.image.BufferedImageFactory$Consumer.imageComplete(BufferedImageFactory.java:478) at sun.awt.image.InputStreamImageSource.errorConsumer(InputStreamImageSource.java:147) at sun.awt.image.InputStreamImageSource.errorAllConsumers(InputStreamImageSource.java:140) at sun.awt.image.InputStreamImageSource.badDecoder(InputStreamImageSource.java:294) at sun.awt.image.InputStreamImageSource.doFetch(InputStreamImageSource.java:265) at sun.awt.image.ImageFetcher.fetchloop(ImageFetcher.java:205) at sun.awt.image.ImageFetcher.run(ImageFetcher.java:169)` – Jonathon Apr 10 '16 at 19:35
  • Strange. What platform/OS and Java version are you on? I did test the code above with your PNG and it worked for me on OS X, Oracle JRE 1.7 or 1.8. I might have an alternative fix, will try to post tomorrow. – Harald K Apr 10 '16 at 19:43
  • Strange indeed. I got the same message on that image using either OS X or RHEL 6.7, both using jboss 7.1.1 and JRE 1.7. – Jonathon Apr 10 '16 at 20:01
  • @Jonathon Are you using the same test image? If not, can you post the image you used to create the above stack trace? I can't reproduce, even tested headless mode. In any case, I posted an alternative solution, see updated answer. – Harald K Apr 11 '16 at 13:00
  • Yes. I used the same image. I added imageio-core version 3.2.1 via maven. I haven't had a chance to try the alternate solutions yet, but will get to that soon. Thanks. – Jonathon Apr 12 '16 at 16:56
  • FWIW this problem wasn't urgent, so I tabled it for a bit. Finally got back to it and the original answer here worked just fine. My problem resulted from trying to reuse an InputStream. Silly me. Thanks again! – Jonathon Jul 02 '16 at 23:57