I've got a stream which can contain either one or more images, stored contiguously with no seperatng flags. When I use javas ImageIO to open and parse the stream it parses the first image correctly, but it closes the stream, which makes me unable to stream the next image.
So I'm trying to figure out how large each compressed image in the file is, so that I can read the exact number of bytes for that image into a buffer, create a ByteArrayInputStream out of that buffer, and create the BufferedImage using the new smaller buffer. My problem is figuring out how large the image is, width/height don't help since compressed are smaller than width * height.
Is there a way to read through the buffer and find out where each image ends easily? There are no soi, eoi, sof, eof, tags throughout except one eof at the end of the stream.
Or is there a better way to do this? A way to ImageIO.read() that won't close the stream and let me keep working with it?
First of all, ImageIO.read(...)
is a convenience method, that can only ever read a single (the first) image of any file in any format.
If, instead, you obtain an ImageReader
for your input, you can (generally) get the number of images in the input using getNumImages(boolean allowSearch)
, and read (or skip) individual images using read(int imageIndex, ImageReadParam param)
.
Something like:
// Create input stream
try (ImageInputStream input = ImageIO.createImageInputStream(file)) {
// Get the reader
Iterator<ImageReader> readers = ImageIO.getImageReaders(input);
if (!readers.hasNext()) {
throw new IllegalArgumentException("No reader for: " + file);
}
ImageReader reader = readers.next();
try {
reader.setInput(input);
// Optionally, listen for read warnings, progress, etc.
reader.addIIOReadWarningListener(...);
reader.addIIOReadProgressListener(...);
ImageReadParam param = reader.getDefaultReadParam();
// Note: For some formats (ie. GIF), finding the number of images
// requires searching the entire stream. In this case, it may
// be more convenient to just read until you get an IndexOutOfBoundsException
for (int i = 0; i < reader.getNumImages(true); i++) {
// Optionally, control read settings like sub sampling, source region or destination etc.
param.setSourceSubsampling(...);
param.setSourceRegion(...);
param.setDestination(...);
// ...
// Finally read the image, using settings from param
BufferedImage image = reader.read(i, param);
// Optionally, read thumbnails, meta data, etc...
int numThumbs = reader.getNumThumbnails(0);
// ...
}
}
finally {
// Dispose reader in finally block to avoid memory leaks
reader.dispose();
}
}
The JRE-bundled JPEGImageReader
should in theory also support multiple JFIF substreams in a single stream. However, I think you will find that the support is broken (at least I have an open issue to fix this in my ImageIO plugin project).
Unfortunately, finding the length of a JPEG compressed image without decoding (or having the length stored outside the JFIF stream), is not possible.
One thing you could try, is (possibly wrapping the ImageInputStream
) to read the first image, then scan forward for the next SOI ("Start-of-Image", 0xffd8
) marker, seek back two bytes (for the SOI marker) and attempt a read of the next image. The SOI marker can't exist inside the JPEG encoded data.
You can read more about JPEG and JFIF segments on Wikipedia.