【问题标题】:Unable to read JPEG 2000 images in Java JAI, Error: "File too long."无法在 Java JAI 中读取 JPEG 2000 图像,错误:“文件太长。”
【发布时间】:2020-11-15 08:21:53
【问题描述】:

我需要一些方法来使用 Java 读取某些 JPEG 2000 图像并将它们加载到 BufferedImage 中。我一直在使用 JAI-ImageIO 来读取 JPEG 2000 图像,因为常规 ImageIO.read 不支持该格式。我首先使用图像编辑器制作了一个自定义 jp2 图像,Java 程序运行顺利并加载了图像。但这只是一个测试。真实图像大小约为 100MB。但是,每当我在它们上运行代码时,我都会收到此错误:

Exception in thread "main" java.lang.RuntimeException: An uncaught runtime exception has occurred
    at com.sun.media.imageioimpl.plugins.jpeg2000.J2KReadState.initializeRead(J2KReadState.java:708)
    at com.sun.media.imageioimpl.plugins.jpeg2000.J2KReadState.<init>(J2KReadState.java:209)
    at com.sun.media.imageioimpl.plugins.jpeg2000.J2KImageReader.read(J2KImageReader.java:449)
    at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1468)
    at java.desktop/javax.imageio.ImageIO.read(ImageIO.java:1315)
    at JPEG2000Handler.getImage(JPEG2000Handler.java:18)
    at JPEG2000Handler.main(JPEG2000Handler.java:13)
Caused by: java.io.IOException: File too long.
    at jj2000.j2k.fileformat.reader.FileFormatReader.readFileFormat(FileFormatReader.java:207)
    at com.sun.media.imageioimpl.plugins.jpeg2000.J2KReadState.initializeRead(J2KReadState.java:418)
    ... 6 more

它说“文件太长”。我做了一些搜索,发现这个线程Loading JPEG2000 Images using JAI,和我的问题完全相同。根据线程,问题不在于文件大小,而是 jp2 文件中的框大小(无论这意味着什么)。该线程还提供了异常https://github.com/Unidata/jj2000/blame/073c3878e4f7799e55d5ff93bc130d01c4260b6d/src/main/java/ucar/jpeg/jj2000/j2k/fileformat/reader/FileFormatReader.java#L149的来源链接,但也表示JJ2000不支持该文件。我花了数周时间拼命寻找一种使用 Java 读取 JPEG 2000 文件的方法,但没有任何效果。我已经检查了 JDeli,但它不是免费的。我只需要某种方式来加载这些文件,它甚至不必使用 JAI。

任何想法都将不胜感激,因为它似乎开始变得不可能了。

【问题讨论】:

  • 您是否使用与链接问题中相同的图像?因为那些,如果它们与我从那个站点得到的样本相同,实际上并没有大盒子。嗯,他们有,但它只包含一些元数据,我假设你可以把它删掉/忽略它。
  • 我不确定您的意思,但这是我正在使用的图像:drive.google.com/file/d/1fa27QT8r__PxH9aVqYFKF-xghhU7ehG8/…。我尝试使用 JAI 打开它,但它给了我上面的错误消息。
  • 好吧,我可以用 github.com/Unidata/jj2000 打开它。给我一些时间,我会发布如何做到这一点。

标签: java image javax.imageio jai jpeg2000


【解决方案1】:

首先,我绝不是一个有信誉的来源,我以前从未使用过图像。尽管如此。

该文件确实由多个“框”组成,并且确实不支持大“框”。但是只有当内容大于 2^32 字节时才需要大框,这里不是这种情况。事实上,如果您实际拥有的图像大于该图像,它可能会存储在一个长度为 0 的盒子中,根据规范,这意味着它会一直保存到文件的末尾。

您可以在第 150 页的 ISO/IEC 15444-1:2000 中阅读有关框的更多信息。

以上所有只是我对为什么原始作者不费心支持这一点的想法。然而实际上,当内容的大小根本不保证时,没有人禁止创建大盒子。这就是您的问题所在。生成该图像的人向其中添加了一些 xml 元数据,出于某种原因,他们决定将该元数据存储在一个大盒子中,尽管它的大小小于 2 Kb。您可以在文件开头附近的任何十六进制编辑器中看到它。

考虑到这一点,我们有两个选择:

  1. “修复”库,使其在看到大框时不会失败。见this commit
  2. 将此框转换为普通框,因为它实际上一点也不大。

这里是一些未经测试的转换文件的代码:

public static void main(String[] args) throws IOException {
    RandomAccessIO in = new BEBufferedRandomAccessFile("SENTINEL-TEMP-ZIP-a8e1c94558e10dc1-6-2.jp2", "r");
    DataOutputStream out = new DataOutputStream(new FileOutputStream(new File("_SENTINEL-TEMP-ZIP-a8e1c94558e10dc1-6-2.jp2")));
    boolean done = false;
    while (!done) {
        try {
            int boxLength = in.readInt();
            if (boxLength == 1) {
                //convert large box
                int boxType = in.readInt();//skip box type
                long actualBoxLength = in.readLong();//the box is actually small
                if (actualBoxLength > Integer.MAX_VALUE) {
                    throw new RuntimeException("Unable to fix large box, size exceeds int");
                }
                out.writeInt((int) actualBoxLength - 8);
                out.writeInt(boxType);
                copyBytes(in, out, (int) actualBoxLength - 16);
            } else {
                //copy other stuff
                out.writeInt(boxLength);
                copyBytes(in, out, boxLength != 0 ? boxLength - 4 : 0);
            }
        } catch (EOFException e) {
            done = true;
        }
    }
    out.close();
    in.close();
}

private static void copyBytes(RandomAccessIO in, DataOutputStream out, int length) throws IOException {
    if (length != 0) {
        //copying set amount
        byte[] bytes = new byte[length];
        in.readFully(bytes, 0, bytes.length);
        out.write(bytes, 0, bytes.length);
    } else {
        //copying to the end of file
        byte[] bytes = new byte[10240];
        int lastPos = 0;
        try {
            while (true) {
                lastPos = in.getPos();
                in.readFully(bytes, 0, bytes.length);
                out.write(bytes, 0, bytes.length);
            }
        } catch (EOFException e) {
            out.write(bytes, 0, in.length() - lastPos);
        }
    }
}

BEBufferedRandomAccessFile 来自https://github.com/Unidata/jj2000,它有一些方便的功能来处理这种文件,但没有必要。

最后,这两个选项都会导致该库在遇到未知类型的框时产生警告。测试:

public static void main(String[] args) {
    JJ2KDecoder.main(new String[]{"-i", "_SENTINEL-TEMP-ZIP-a8e1c94558e10dc1-6-2.jp2", "-debug"});
}

文件打开并显示正常。

【讨论】:

    【解决方案2】:

    您可以尝试使用 imageio-openjpeg 库作为 ImageIO API 的插件。 (https://github.com/dbmdz/imageio-jnr)

    它使用了参考实现中的本机代码。这应该会产生更少的问题。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多