PNG format images do not display on Mac Safari

4.1k views Asked by At

Images from our website do not display in Safari for some Mac users and they report seeing either no image or a black image. Here is an example:

http://s3-eu-west-2.amazonaws.com/bp18.boxcleverpress.com/Boxclever_logo_chartreuse.png

What I have discovered is:

  • Images display on PC
  • Images display on SOME Macs (I have an older one that is OK)
  • Images display on iPhones and iPads
  • Images are PNG
  • I have optimised the images with pngtastic
  • When images are copied to the Mac and opened with Adobe Photoshop they give the error: the file format module cannot parse the file
  • When I tried to open a pngtastic optimised file in Photoshop Elements on Windows I also get that error
  • When I tried to open the optimised file in Photoshop on Windows I get the error IDAT: incorrect data check

I will replace the optimised images with unoptimised ones but I am not sure if this problem is with pngtastic or Adobe image libraries or something else.

2

There are 2 answers

3
Jongware On BEST ANSWER

The problem lies in Zopfli.java, included by pngtastic.

It uses this Java code to calculate the Adler-32 checksum:

/**
 * Calculates the adler32 checksum of the data
 */
private static int adler32(byte[] data) {
    int s1 = 1;
    int s2 = 1 >> 16;
    int i = 0;
    while (i < data.length) {
        int tick = Math.min(data.length, i + 1024);
        while (i < tick) {
            s1 += data[i++];
            s2 += s1;
        }
        s1 %= 65521;
        s2 %= 65521;
    }

    return (s2 << 16) | s1;
}

However, bytes in Java are always signed, and so it may return a wrong checksum value for some data inputs. Also, the bare int declarations for s1 and s2 cause further complications.

With (my C version of) the same code and data explicitly declared as signed char and both s1 and s2 as signed int, I get a wrong checksum FFFF9180 – exactly the one in your damaged PNG.

If I change the declaration to use unsigned char and unsigned int, it returns the correct checksum 1BCD6EB2 again.


The original C code for the Adler-32 checksum in zopfli uses unsigned types throughout, so it's just the Java implementation that suffers from this.

6
Kevin Sadler On

The problem appears to be due to the use of the zopfli compression in the PNGs that I optimised using pngtastic. The workaround is to use a different pngtastic compression option and the PNGs are then readable in Photoshop.

Using a different compression algorithm will result in less optimisation.

I am not sure why the zopfli compression is a problem, it could be that there is a fault in my code (although the same code works fine when only the zopli option is changed), in pngtastic, or that MacOS and Adobe don't support zopfli.

@usr2564301 has done some investigation and it appears the Adler-32 checksum on the compressed data in my example image is incorrect. usr2564301 has also tested the pngtastic code and found it to produce the correct checksum. The problem might be in how I handle the bytestream out of pngtastic.

The code below performs the PNG optimisation using pngtastic (com.googlecode.pngtastic.core)

public static final String OPT_ZOPFLI = "zopfli";
public static final String OPT_DEFAULT = "default";
public static final String OPT_IMAGEOPTIM = "imageoptim";

private String optimization = OPT_ZOPFLI;

public void optimizePng(File infile, String out) {

     final InputStream in;
     try {
         in = new BufferedInputStream(new FileInputStream(infile));
         final PngImage image = new PngImage(in);

         // optimize
         final PngOptimizer optimizer = new PngOptimizer();

         optimizer.setCompressor(optimization, 1);
         final PngImage optimizedImage = optimizer.optimize(image, false, 9);

         // export the optimized image to a new file
         final ByteArrayOutputStream optimizedBytes = new ByteArrayOutputStream();
         optimizedImage.writeDataOutputStream(optimizedBytes);
         optimizedImage.export(out, optimizedBytes.toByteArray());

       } catch (FileNotFoundException e) {
         e.printStackTrace();
     } catch (IOException e) {
         e.printStackTrace();
     }
 }