Mac Safari 上不显示 PNG 格式图像

PNG format images do not display on Mac Safari

某些 Mac 用户无法在 Safari 中显示我们网站上的图片,他们报告说看不到图片或图片是黑色的。这是一个例子:

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

我发现的是:

我会用未优化的图像替换优化的图像,但我不确定这个问题是否与 pngtastic 或 Adob​​e 图像库或其他东西有关。

问题似乎是由于在我使用 pngtastic 优化的 PNG 中使用了 zopfli 压缩。解决方法是使用不同的 pngtastic 压缩选项,然后 PNG 可以在 Photoshop 中读取。

使用不同的压缩算法会导致优化程度降低。

我不确定为什么 zopfli 压缩是个问题,可能是我的代码有错误(尽管仅更改 zopli 选项时相同的代码工作正常)、pngtastic 或 MacOS而且 Adob​​e 不支持 zopfli。

@usr2564301 进行了一些调查,我的示例图像中压缩数据的 Adler-32 校验和似乎不正确。 usr2564301 也测试了 pngtastic 代码并发现它产生了正确的校验和。问题可能在于我如何处理 pngtastic 中的字节流。

下面的代码使用 pngtastic (com.googlecode.pngtastic.core)

执行 PNG 优化
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();
     }
 }

问题出在Zopfli.java,被pngtastic收录了

它使用此 Java 代码来计算 Adler-32 校验和:

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

然而,Java 中的 bytealways signed,因此它可能 return 某些数据输入的校验和值是错误的。此外,s1s2 的裸 int 声明会导致进一步的复杂化。

使用(我的 C 版本)相同的代码和 data 明确声明为 signed char 并且 s1s2 都声明为 signed int,我得到一个错误的校验和 FFFF9180 – 正是你损坏的 PNG 中的校验和。

如果我更改声明以使用 unsigned charunsigned int,它再次 return 是正确的校验和 1BCD6EB2


zopfli 中 Adler-32 校验和的 original C code 自始至终使用 unsigned 类型,因此只有 Java 实现受到此影响。