1
votes

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 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.

2
Do you use same images as in linked question? Because those, if they are same as the sample I got from that site, doesnt actually have large box in them. Well, they do, but it only contains some metadata and I assume you can just cut it out/ignore it.chimmi
I'm no sure what you mean, but here is the image that I'm using: drive.google.com/file/d/1fa27QT8r__PxH9aVqYFKF-xghhU7ehG8/…. I've tried to open this using JAI, but it gives me the error message above.billschmidt626
Well, I can open it just fine with github.com/Unidata/jj2000 . Gimme some time, I'll post how to do that.chimmi

2 Answers

3
votes

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:

  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.

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.

1
votes

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.