简体   繁体   English

无法读取 Java JAI 中的 JPEG 2000 图像,错误:“文件太长。”

[英]Unable to read JPEG 2000 images in Java JAI, Error: “File too long.”

I need some way to read certain JPEG 2000 images using Java and load them into a BufferedImage.我需要一些方法来使用 Java 读取某些 JPEG 2000 图像并将它们加载到 BufferedImage 中。 I've been using JAI-ImageIO to read the JPEG 2000 images, since regular ImageIO.read doesn't support that format.我一直在使用 JAI-ImageIO 来读取 JPEG 2000 图像,因为常规 ImageIO.read 不支持该格式。 I first made a custom jp2 image using an image editor and the Java program ran smoothly and loaded the image.我首先使用图像编辑器制作了一个自定义 jp2 图像,Java 程序运行顺利并加载了图像。 But that was just a test.但这只是一个测试。 The real images are around 100MB in size.真实图像大小约为 100MB。 However, whenever I run the code on them, I get this error:但是,每当我在它们上运行代码时,我都会收到此错误:

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

It says "File too long".它说“文件太长”。 I did some searching and found this thread Loading JPEG2000 Images using JAI , with the exact same problem as me.我做了一些搜索,发现这个线程Loading JPEG2000 Images using JAI ,与我完全相同的问题。 According to the thread, the problem was not the file size, but the box size within the jp2 file (whatever that means).根据线程,问题不在于文件大小,而是 jp2 文件中的框大小(无论这意味着什么)。 The thread also provided a link to the source of the exception https://github.com/Unidata/jj2000/blame/073c3878e4f7799e55d5ff93bc130d01c4260b6d/src/main/java/ucar/jpeg/jj2000/j2k/fileformat/reader/FileFormatReader.java#L149 , but also said that JJ2000 doesn't support this file.该线程还提供了异常https://github.com/Unidata/jj2000/blame/073c3878e4f7799e55d5ff93bc130d01c4260b6d/src/main/java/ucar/jpeg/jj2000/jpeg/jj2000/jpeg/fileReader.java#format/reader/FormatFileReader.k L149 ,还说JJ2000不支持这个文件。 I've spent weeks desperately looking for a way to read JPEG 2000 files using Java, but nothing's working.我花了数周时间拼命寻找一种使用 Java 读取 JPEG 2000 文件的方法,但没有任何效果。 I've checked out JDeli, but it's not free.我已经检查了 JDeli,但它不是免费的。 I just need some way to load these files, it doesn't even have to use JAI.我只需要某种方式来加载这些文件,它甚至不必使用 JAI。

Any ideas would be much appreciated, since it's starting to seem impossible.任何想法都将不胜感激,因为它开始似乎是不可能的。

First of all, Im not in any way a reputable source, I never worked with images before.首先,我绝不是一个有信誉的来源,我以前从未使用过图像。 Nonetheless.尽管如此。

The file is indeed consists of multiple "boxes", and large "boxes" are indeed not supported.该文件确实由多个“框”组成,并且确实不支持大“框”。 But large box is required only when conten is larger than 2^32 bytes which is not the case here.但是只有当内容大于 2^32 字节时才需要大框,这里不是这种情况。 In fact if you actually have image larger than that it would probably be stored in a box with 0 length, which according to spec means that is goes till the end of the file.事实上,如果你实际上有比它更大的图像,它可能会存储在一个长度为0的盒子中,根据规范,这意味着直到文件末尾。

You can read more info on boxes in ISO/IEC 15444-1:2000 at page 150.您可以在第 150 页阅读ISO/IEC 15444-1:2000中有关框的更多信息。

All of the above is just my thoughts on why original authors didnt bother to support this.以上所有只是我对为什么原始作者不费心支持这一点的想法。 In reality however no one prohibits creating large boxes when size of their content doesnt warrant it at all.然而实际上,当内容的大小根本不保证时,没有人禁止创建大盒子。 And this is where your problem comes from.这就是您的问题所在。 Whoever generated that image added some xml metadata to it, and for some reason they decided to store that metadata in a large box, despite its size of less than 2 Kb.生成该图像的人向其中添加了一些 xml 元数据,出于某种原因,他们决定将该元数据存储在一个大盒子中,尽管它的大小小于 2 Kb。 You can see it in any hex editor close to the start of the file.您可以在文件开头附近的任何十六进制编辑器中看到它。

With this in mind we have two options:考虑到这一点,我们有两个选择:

  1. "Fix" the library so it wont fail as soon as it sees a large box. “修复”库,使其在看到一个大盒子时就不会失败。 See this commit .请参阅此提交
  2. Convert this box to a normal box, since it is in fact not large at all.将此框转换为普通框,因为它实际上一点也不大。

Here is some not tested code that converts the file:这是一些转换文件的未经测试的代码:

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 is from https://github.com/Unidata/jj2000 , it has some handy function for working with this kind of file, but in no way necessary. BEBufferedRandomAccessFile来自https://github.com/Unidata/jj2000 ,它有一些方便的 function 用于处理这种文件,但绝不是必要的。

In the end both options results in this library producing a warning on encountering unknown type of box.最后,这两个选项都会导致该库在遇到未知类型的框时产生警告。 Tested with:经测试:

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

File opens and appears normal.文件打开并显示正常。

You can try to use imageio-openjpeg library as plugin for the ImageIO API.您可以尝试使用imageio-openjpeg库作为 ImageIO API 的插件。 ( https://github.com/dbmdz/imageio-jnr ) https://github.com/dbmdz/imageio-jnr

It used the native code from the reference implementation.它使用了参考实现中的本机代码。 This should produce fewer problems.这应该会产生更少的问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM