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
我发现的是:
- 图像在 PC 上显示
- 图像显示在一些 Mac 上(我有一个旧的没问题)
- 图像显示在 iPhone 和 iPad 上
- 图片为 PNG
- 我用 pngtastic 优化了图片
- 将图像复制到 Mac 并使用 Adobe Photoshop 打开时,出现错误:文件格式模块无法解析文件
- 当我尝试在 Windows 上的 Photoshop Elements 中打开 pngtastic 优化文件时,我也遇到了该错误
- 当我在 Windows 上尝试在 Photoshop 中打开优化后的文件时,出现错误 IDAT:不正确的数据检查
我会用未优化的图像替换优化的图像,但我不确定这个问题是否与 pngtastic 或 Adobe 图像库或其他东西有关。
问题似乎是由于在我使用 pngtastic 优化的 PNG 中使用了 zopfli 压缩。解决方法是使用不同的 pngtastic 压缩选项,然后 PNG 可以在 Photoshop 中读取。
使用不同的压缩算法会导致优化程度降低。
我不确定为什么 zopfli 压缩是个问题,可能是我的代码有错误(尽管仅更改 zopli 选项时相同的代码工作正常)、pngtastic 或 MacOS而且 Adobe 不支持 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 中的 byte
是 always signed,因此它可能 return 某些数据输入的校验和值是错误的。此外,s1
和 s2
的裸 int
声明会导致进一步的复杂化。
使用(我的 C 版本)相同的代码和 data
明确声明为 signed char
并且 s1
和 s2
都声明为 signed int
,我得到一个错误的校验和 FFFF9180
– 正是你损坏的 PNG 中的校验和。
如果我更改声明以使用 unsigned char
和 unsigned int
,它再次 return 是正确的校验和 1BCD6EB2
。
zopfli 中 Adler-32 校验和的 original C code 自始至终使用 unsigned
类型,因此只有 Java 实现受到此影响。
某些 Mac 用户无法在 Safari 中显示我们网站上的图片,他们报告说看不到图片或图片是黑色的。这是一个例子:
http://s3-eu-west-2.amazonaws.com/bp18.boxcleverpress.com/Boxclever_logo_chartreuse.png
我发现的是:
- 图像在 PC 上显示
- 图像显示在一些 Mac 上(我有一个旧的没问题)
- 图像显示在 iPhone 和 iPad 上
- 图片为 PNG
- 我用 pngtastic 优化了图片
- 将图像复制到 Mac 并使用 Adobe Photoshop 打开时,出现错误:文件格式模块无法解析文件
- 当我尝试在 Windows 上的 Photoshop Elements 中打开 pngtastic 优化文件时,我也遇到了该错误
- 当我在 Windows 上尝试在 Photoshop 中打开优化后的文件时,出现错误 IDAT:不正确的数据检查
我会用未优化的图像替换优化的图像,但我不确定这个问题是否与 pngtastic 或 Adobe 图像库或其他东西有关。
问题似乎是由于在我使用 pngtastic 优化的 PNG 中使用了 zopfli 压缩。解决方法是使用不同的 pngtastic 压缩选项,然后 PNG 可以在 Photoshop 中读取。
使用不同的压缩算法会导致优化程度降低。
我不确定为什么 zopfli 压缩是个问题,可能是我的代码有错误(尽管仅更改 zopli 选项时相同的代码工作正常)、pngtastic 或 MacOS而且 Adobe 不支持 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 中的 byte
是 always signed,因此它可能 return 某些数据输入的校验和值是错误的。此外,s1
和 s2
的裸 int
声明会导致进一步的复杂化。
使用(我的 C 版本)相同的代码和 data
明确声明为 signed char
并且 s1
和 s2
都声明为 signed int
,我得到一个错误的校验和 FFFF9180
– 正是你损坏的 PNG 中的校验和。
如果我更改声明以使用 unsigned char
和 unsigned int
,它再次 return 是正确的校验和 1BCD6EB2
。
zopfli 中 Adler-32 校验和的 original C code 自始至终使用 unsigned
类型,因此只有 Java 实现受到此影响。