Search code examples
javatiffdeflatejaiartifacts

JAI TIFF codec creating artifacts


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.


Solution

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