Is there a equivalent of Android's BitmapFactory.Options isDecodeBounds for TIFF in Java/JAI?

StackOverflow https://stackoverflow.com/questions/15645174

  •  29-03-2022
  •  | 
  •  

I am trying to improve the performance of our system (a Java app running in Tomcat) and now the bottleneck is in one operation, we need to read and return dimension of tiff images, so we use JAI's ImageDecoder and use

ImageDecoder decoder = ImageCodec.createImageDecoder("TIFF", input, param);
RenderedImage r = decoder.decodeAsRenderedImage();
int width = r.getWidth();
int height = r.getHeight();

From sampling data, a lot of time is spent in createImageDecoder. My assumption (without going to source code of ImageCodec) is it's probably trying to decode the input stream.

Coming from Android land, I am hoping there is a similar solution to just decode bounds like setting BitmapFactory.Options.inJustDecodeBounds = true but so far no luck in finding any other library like that. (I am aware that tiff support on Android is missing in AOSP, but that's topic for another day.)

Anyone know a library that does this? Or is there a way to achieve similar goal using JAI/ImageIO?

有帮助吗?

解决方案

It looks like the tiff file format groups this information together in a header, so you could just read the data from the file yourself:

private static Dimension getTiffDimensions(InputStream tiffFile) throws IOException {
    ReadableByteChannel channel = Channels.newChannel(tiffFile);

    ByteBuffer buffer = ByteBuffer.allocate(12);

    forceRead(channel, buffer, 8);
    byte endian = buffer.get();
    if(endian != buffer.get() || (endian != 'I' && endian != 'M')) {
        throw new IOException("Not a tiff file.");
    }

    buffer.order(endian == 'I' ? ByteOrder.LITTLE_ENDIAN : ByteOrder.BIG_ENDIAN);
    if(buffer.getShort() != 42) {
        throw new IOException("Not a tiff file.");
    }

    // Jump to the first image directory. Note that we've already read 8 bytes.
    tiffFile.skip(buffer.getInt() - 8);

    int width = -1;
    int height = -1;
    // The first two bytes of the IFD are the number of fields.
    forceRead(channel, buffer, 2);
    for(int fieldCount = buffer.getShort(); fieldCount > 0 && (width < 0 || height < 0); --fieldCount) {
        forceRead(channel, buffer, 12);
        switch(buffer.getShort()) {
        case 0x0100: // Image width
            width = readField(buffer);
            break;
        case 0x0101: // Image "length", i.e. height
            height = readField(buffer);
            break;
        }
    }
    return new Dimension(width, height);
}

private static void forceRead(ReadableByteChannel channel, ByteBuffer buffer, int n) throws IOException {
    buffer.position(0);
    buffer.limit(n);

    while(buffer.hasRemaining()) {
        channel.read(buffer);
    }
    buffer.flip();
}

private static int readField(ByteBuffer buffer) {
    int type = buffer.getShort();
    int count = buffer.getInt();

    if(count != 1) {
        throw new RuntimeException("Expected a count of 1 for the given field.");
    }

    switch(type) {
    case 3: // word
        return buffer.getShort();
    case 4: // int
        return buffer.getInt();
    default: // char (not used here)
        return buffer.get() & 0xFF;
    }
}

I've tested this with a few different tiff files (run length encoded black & white, color with transparency) and it seems to work fine. Depending on the layout of your tiff file it may have to read a lot of the stream before it finds the size (one of the files I tested, saved by Apple's Preview, had this data at the end of the file).

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top