J2ME - 在 PhotoShop 中创建的 PNG 不显示在模拟器中

J2ME - PNG created in PhotoShop not displaying in emulator

我的 midlet 可以正常显示某些图像,但其他图像则不行。

都是8位的PNG,没有显示的是我在PhotoShop中自己创建的。

所以我想也许我的 PhotoShop (CS6) 设置有误...

PNG-8, Selective, Diffusion, Colors: 256, Dither: 100%, Matte: None, Web Snap: 0%, Convert to sRGB: ticked, Width: 48, Height: 48, Percent: 100%, Quality: Bicubic.

我尝试了其中的一些设置,但无济于事。

有什么想法吗?

有一个 similar problem here 但这与我的相反,因为 PhotoShop 在这种情况下会修补东西,而不是破坏东西...

我的代码是...

image = Image.createImage("/img/loading1.png");

...这是我的堆栈跟踪:

java.io.EOFException
    at javax.imageio.stream.ImageInputStreamImpl.readFully(
ImageInputStreamImpl.java:353)
    at java.io.DataInputStream.readUTF(DataInputStream.java:609)
    at javax.imageio.stream.ImageInputStreamImpl.readUTF(ImageInputStreamImpl.java:332)
    at com.sun.kvem.png.PNGImageReader.parse_iTXt_chunk(PNGImageReader.java:447)
    at com.sun.kvem.png.PNGImageReader.readMetadata(PNGImageReader.java:650)
    at com.sun.kvem.png.PNGImageReader.readImage(PNGImageReader.java:1312)
    at com.sun.kvem.png.PNGImageReader.read(PNGImageReader.java:1582)
    at com.sun.kvem.midp.GraphicsBridge.loadImage(GraphicsBridge.java:2602)
    at com.sun.kvem.midp.GraphicsBridge.createImageFromData(GraphicsBridge.java:2511)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:601)
    at com.sun.kvem.sublime.MethodExecution.process(MethodExecution.java:42)
    at com.sun.kvem.sublime.SublimeExecutor.processRequest(SublimeExecutor.java:63)
    at javax.microedition.lcdui.Image.createImage(Image.java:315)

有问题的图像确实存在 - 在项目和构建的 jar 中。

这是有问题的图片:

根据崩溃日志,J2ME 中的 PNG 解码器在非关键块内失败 iTXt:1

> com.sun.kvem.png.PNGImageReader.readMetadata
  > com.sun.kvem.png.PNGImageReader.parse_iTXt_chunk
    > javax.imageio.stream.ImageInputStreamImpl.readUTF
      > java.io.DataInputStream.readUTF

根据 libpng documentationiTXt 的文本部分必须 是有效的 UTF8:

... The remaining chunk data is the main UTF-8 text, either zlib-compressed or not, according to the compression flag. Since its length can be determined from the chunk length, it is not null-terminated. As with the other two text chunks, newlines should be represented by single line-feed characters (decimal 10), and all other control characters (1-9, 11-31, and 127-159) are discouraged.

所以通常这表明流读取不是有效的 UTF8 文本 - 它包含 'raw' 字节高于不符合的纯 ASCII 运行ge 0..127 UTF8 规则。

我发现样本图像中的情况并非如此。组成一个UTF8编码序列的连续字节只有一组,并且是有效的:

<?xpacket begin="EFBBBF" id=" ..

(粗体部分代表16进制表示的3个数据字节)。我首先怀疑这是错误:

If the BOM character appears in the middle of a data stream, Unicode says it should be interpreted as a "zero-width non-breaking space" (inhibits line-breaking between word-glyphs). In Unicode 3.2, this usage is deprecated in favour of the "Word Joiner" character, U+2060.[1] This allows U+FEFF to be only used as a BOM.
(http://en.wikipedia.org/wiki/Byte_order_mark)

.. 所以一个完全符合的 UTF8 reader 应该检查它的字节并在它遇到 BOM 时抛出一个 UTFDataFormatException 作为第一个值。令人惊讶的是,这似乎不是问题所在!首先,没有任何迹象表明 readUTF 来源除了仅验证 UTF8 代码是否有效 自身 之外还做了任何其他事情,无论其值如何。有很多 'invalid' Unicode 代码点 (不代表有效 Unicode 字符或指令的值),但在我看来它们都被默默地忽略了。但是我注意到常见的 readUTF 函数只实现了 UTF8/Unicode 的一小部分(例如,参见 Oracle 文档中的 Modified UTF-8)。

所以问题出在别处。另一个线索是抛出的错误是 而不是 UTFDataFormatException 而是 EOFException,表明读取缓冲区 运行 超出了它的字节数被承诺收容。

(警告:纯属猜测)

查看 DataInputStream 的来源,我找到了这段代码:

588       public final static String readUTF(DataInput in) throws IOException {
589           int utflen = in.readUnsignedShort();

后跟一个循环来读取 utflen bytes(不是 "Unicode characters")。这对于 iTXt 块是错误的,因为它没有 'first word' 来指示其长度。纯文本中的字节数可以从块长度(根据 PNG 约定,总数据长度不包括长度长字、iTXt 签名本身和最终的 CRC32 代码)减去以零结尾的关键字名称、语言和 "translated keyword" 字符串的长度,以及指示完整纯文本压缩的两个字节。


作为解决方法,从 PNG 图像中删除 iTXt 块。数据本身 - XMP 元数据 - 很可能对您的目的根本没有意义(但请随意阅读 what benefits Adobe thinks it has)。如果您的工作流程不使用它,它只是一大块无用的未压缩文本,在您的样本图像中占 981 字节总数的 814 字节——高达 83%!

您可以使用外部实用程序删除大量数据块;运行例如流行的pngcrush的命令行是

pngcrush -rem alla -rem text InputFile.png OutputFile.png

(来自 en.wikipedia.org/wiki/Pngcrush)。

或直接从 Photoshop 中:如果您使用 "Save As" 菜单选项保存 PNG 'the usual way',元数据会进入并且没有复选框可以删除它。如果您改用 "Save for Web & Devices",您会看到一个带有许多方便选项的大对话框,例如标记为 "Metadata".

的下拉列表

选择"All"我得到了一个更大的文件;我的 Photoshop 版本创建了一个 大量 3K 的 XMP 元数据块,包括一个 2K 完全空的 'filler' 块...
选择 "Copyright" 或 "None" 终于摆脱了所有的垃圾(大概是因为我没有填写任何版权信息),然后你得到一个漂亮的 169 字节长的 PNG,其中唯一的元数据是使用的软件名为 "Adobe ImageReady".


1 这有点讽刺。根据 PNG 规范,

.. A decoder encountering an unknown chunk in which the ancillary bit is 1 can safely ignore the chunk and proceed to display the image.
(source)

这个"ancillary bit"是块ID第一个字节的第5位:0(大写)=关键,1(小写)=辅助,即如果块ID的第一个字符是a大写字母,PNG reader 必须 正确读取和解释其数据,如果不是,则可以静默跳过。

所以从技术上讲,J2ME 的编写者可以安全地忽略这整个块。但是他们把它搞砸了, 尝试 读取它,现在代码在所有程序上崩溃,这些程序只是试图读取恰好包含 iTXt 块的 PNG 中的图像数据。