英文:
Unable to read JPEG 2000 images in Java JAI, Error: "File too long."
问题
我需要一种使用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
它显示 "File too long"(文件太长)。我进行了一些搜索,并找到了这个线程 https://stackoverflow.com/questions/53881800/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。
如果有任何想法,将不胜感激,因为这看起来开始变得不可能。
英文:
I need some way to read certain JPEG 2000 images using Java and load them into a BufferedImage. I've been using JAI-ImageIO to read the JPEG 2000 images, since regular ImageIO.read doesn't support that format. I first made a custom jp2 image using an image editor and the Java program ran smoothly and loaded the image. But that was just a test. The real images are around 100MB in size. 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 https://stackoverflow.com/questions/53881800/loading-jpeg2000-images-using-jai, with the exact same problem as me. According to the thread, the problem was not the file size, but the box size within the jp2 file (whatever that means). 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. I've spent weeks desperately looking for a way to read JPEG 2000 files using Java, but nothing's working. I've checked out JDeli, but it's not free. I just need some way to load these files, it doesn't even have to use JAI.
Any ideas would be much appreciated, since it's starting to seem impossible.
答案1
得分: 3
首先,我绝对不是一个有声望的来源,我以前从未处理过图像。尽管如此。
文件确实由多个“框”组成,而且确实不支持大的“框”。但只有在内容大于2^32字节(这在这里并非如此)时才需要大框。实际上,如果您确实有比那更大的图像,它可能会存储在一个长度为0的框中,根据规范,这意味着它延伸到文件的末尾。
您可以在ISO/IEC 15444-1:2000的第150页阅读有关框的更多信息。
上述所有只是我对原始作者为什么不费心支持这一点的看法。然而,在现实中,没有人禁止在其内容的大小完全不需要的情况下创建大框。这就是您遇到问题的地方。生成该图像的人在其中添加了一些 XML 元数据,并且出于某种原因,他们决定将该元数据存储在一个大框中,尽管其大小不到2 KB。您可以在任何十六进制编辑器中看到它,靠近文件的开头。
考虑到这一点,我们有两个选择:
- “修复”库,使其不会在看到大框时立即失败。参见此提交。
- 将此框转换为普通框,因为实际上它根本不大。
这是一些未经测试的代码,用于转换文件:
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"});
}
文件将被打开并显示正常。
英文:
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. 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.
You can read more info on boxes in ISO/IEC 15444-1:2000 at page 150.
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. You can see it in any hex editor close to the start of the file.
With this in mind we have two options:
- "Fix" the library so it wont fail as soon as it sees a large box. See this commit.
- 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.
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.
答案2
得分: 1
你可以尝试使用 imageio-openjpeg
库作为 ImageIO API 的插件。(https://github.com/dbmdz/imageio-jnr)
它使用了参考实现中的本机代码。这应该会减少问题。
英文:
You can try to use imageio-openjpeg
library as plugin for the ImageIO API. (https://github.com/dbmdz/imageio-jnr)
It used the native code from the reference implementation. This should produce fewer problems.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论