2
votes

I have been using JAI to write and read TIFF images, but I recently encountered a problem when encoding a gray image where I get 7 black (0) pixels at the end of each strip (8 lines per strip):

7-black-pixels-artifact on each 8th line

I've been able to reproduce it only when setting DEFLATE compression with deflate level 0 using the SSCE below. Any other value seem to work.

I have also witnessed sometimes that strips of the image were being shifted left by 30 pixels or so. I was not able to get a stable reproduction of that problem though, but if you know some hidden tips about encoding TIFF in JAI, it might also get rid of that.

private static byte[] genImage(int width, int height) {
    byte[] pix = new byte[width * height];
    Arrays.fill(pix, (byte)0xcf);
    for (int j = 0; j < height; j++) {
        Arrays.fill(pix, j*width + width/3, j*width + 2*width/3, (byte)0x7f);
    }
    return pix;
}

public static void main(String[] args) throws Exception {
    int width = 256;
    int height = 256;
    byte[] pix = genImage(width, height);
    BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
    System.arraycopy(pix, 0, ((DataBufferByte)img.getRaster().getDataBuffer()).getData(), 0, width * height);
    TIFFEncodeParam comp = new TIFFEncodeParam();
    comp.setCompression(TIFFEncodeParam.COMPRESSION_DEFLATE);
    comp.setDeflateLevel(0); // Changing 0 to values 1-9 "solves" the problem

    FileOutputStream fos = new FileOutputStream("bugjai.tiff");
    ImageEncoder encoder = ImageCodec.createImageEncoder("tiff", fos, comp);
    encoder.encode(img);
    fos.close();
}

EDIT:

I made further investigation checking the file content on an 8x8 image. I deflated the byte content with level 0 (using java.util.zip.Deflater) an got an additional 11 bytes. I inserted these bytes at the end of my strip and patched the header to reflect the new length (value 4B instead of 40 at position 75h), and voilĂ ! No more 7 pixels.

Diff OK/NOK

So it seems there is a misencoding in JAI "forgetting" to put/count bytes when deflating. However, TIFF6 specification says at the beginning of page 15:

The Value is expected to begin on a word boundary; the corresponding Value Offset will thus be an even number

Which confuses me: "begin" refers to the position in the file? The "patched" value switched from 40 to 4B which is not even. Could JAI somehow miscalculate something and round down?

ADDENDUM:

It seems the problem occurs when the "compressed" data is bigger than the original: in here, the original data is 64 bytes long when the compressed data is 75 bytes long. JAI is probably assuming the maximum size of the image cannot be more than the size of the original data.

1
You write that you inserted bytes and patched the length. The part of the spec you quoted talks about value offset. So there's no contradiction in using an odd value for the length here AFAICS. But yes, it seems you have found a JAI bug. - Harald K
@haraldK I was hoping you would answer ;) I was also thinking an odd value shouldn't be a problem here but who knows where bugs nest. I'm not sure I should be happy about finding a bug in JAI at it seems it is not maintained anymore (maybe soon replaced by your excellent TwelveMonkeys?) - Matthieu
Thanks! ;-) Agree about JAI, and I think it's sad Oracle seems to have left it in limbo. Well, I am working on TIFF write support for my library, but there are some pieces still missing. Maybe I should just check in what I have, and hope someone can pick it up from there? - Harald K

1 Answers

1
votes

So after some additional testing (see the EDIT and ADDENDUM) in the question, there seem to be a bug in JAI, capping the maximum number of bytes to width * heigth. I guess they assumed no compression would ever increase the size of an image ... except it is when you deflate with a factor 0 (which I understand as "store"), where you get the pixels, plus a header and tailer. As JAI caps the number of bytes written, the tailer is not properly written (if at all), and data are not fully decoded.

Lesson is: use at least a deflate factor of 1 (which is also labelled BEST_SPEED / MIN_COMPRESSION in the javadoc).