Skip to main content

efficient image loading

2 replies [Last post]
aburnett397
Offline
Joined: 2010-12-15
Points: 0

EDIT: sorry this is in the wrong forum. I thought JAI was the standard javax.imagio API but I don't know how to take this topic down
Hi,
I'm trying to find out how to load a bunch of images efficiently in terms of memory. Is there any way using JAI to specify a byte [] as the target of a reading operation? And to reuse the same byte [] for subsequent reads? I'm trying to do this by :
-allocating a byte [] b
-creating a DataBuffer db backed by b
-creating a BufferedImage bi backed by db
-creating a param object p for an ImageReader with the destination set to bi
-reading the image with p thus filling b with raw data
So far, the read operation fails with the exception : "ImageReadParam num source & dest bands differ!"
Even if I knew what this meant, I get a feeling that there will be a bunch of other problems down the line, for instance the SampleModel given to create the DataBuffer object asks for dimensions which are unknown in advance.
This question could be summarized by : Is it possible to read an encoded image directly to a byte array with the decoded data in some specified format (ARGB in 4 bytes for instance) without using intermediate buffers at all.
If JAI can not do this, does anyone know of a lib that can?
Thanks for any help on this,
Cheers

Reply viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
rgd
Offline
Joined: 2005-08-23
Points: 0

A SampleModel does not need to use everything in a DataBuffer. The
whole point of the SM/DB separation is to be able to do things like
extracting bands from pixel interleaved data, crops, subsampling, etc.
using the same underlying DB. So the fact that you don't know the size
of the SM ahead of time may not be a problem, as long as the DB is big
enough.

It sounds to me like you're on a possible right track. Most metadata
queries (size of image, number of bands) can be done without reading the
entire image. So you could do those queries, set up the BI
appropriately and then read the data.

Ahhh, I knew something was tickling in the back of my brain. Look at
TileRecycler, specifically the javadocs for RecyclingTileFactory. I
don't know whether that would work with the imageread operator or not,
but it's worth a look.

-Bob

On 12/15/10 5:34 AM, forums@java.net wrote:
> Hi,
>
> I'm trying to find out how to load a bunch of images efficiently in
> terms of
> memory. Is there any way using JAI to specify a byte [] as the target of a
> reading operation? And to reuse the same byte [] for subsequent reads? I'm
> trying to do this by :
>
> -allocating a byte [] b
>
> -creating a DataBuffer db backed by b
>
> -creating a BufferedImage bi backed by db
>
> -creating a param object p for an ImageReader with the destination set
> to bi
>
> -reading the image with p thus filling b with raw data
>
> So far, the read operation fails with the exception : "ImageReadParam num
> source & dest bands differ!"
>
> Even if I knew what this meant, I get a feeling that there will be a
> bunch of
> other problems down the line, for instance the SampleModel given to create
> the DataBuffer object asks for dimensions which are unknown in advance.
>
> This question could be summarized by : Is it possible to read an encoded
> image directly to a byte array with the decoded data in some specified
> format
> (ARGB in 4 bytes for instance) without using intermediate buffers at all.
>
> If JAI can not do this, does anyone know of a lib that can?
>
> Thanks for any help on this,
>
> Cheers
>
>

aburnett397
Offline
Joined: 2010-12-15
Points: 0

Thanks for the tips, bob.
I ended up sticking to my original plan and as you said, it's easy to check on some metadata before reading in order to set up an appropriate buffered image.
I put the mechanism in a little class, if anyone is interested :
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.io.File;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.stream.FileImageInputStream;

public class ReusableImageReader
{
byte [] buffer;
BufferedImage image;

public ReusableImageReader()
{
this.buffer = null;
this.image = null;
}

public BufferedImage getImage() {return image;}
public byte [] getBuffer() {return buffer;}

static final Point origin = new Point(0, 0);
public void read(File file) throws IOException
{
if (image == null)
{
image = ImageIO.read(file);
buffer = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
}
else
{
FileImageInputStream input = new FileImageInputStream(file);
Iterator<ImageReader> it = ImageIO.getImageReaders(input);
ImageReader reader = null;
if (it.hasNext())
reader = it.next();
if (reader == null)
throw new IOException("Unable to find reader for "+file.getCanonicalPath());
reader.setInput(input);

int required = reader.getWidth(0)*reader.getHeight(0)*image.getColorModel().getPixelSize()/8;
if (required > buffer.length)
{
image = reader.read(0);
buffer = ((DataBufferByte)image.getRaster().getDataBuffer()).getData();
}
else
{
if (reader.getWidth(0)!=image.getWidth() || reader.getHeight(0)!=image.getHeight())
{
Hashtable<String, Object> props = new Hashtable<String, Object>();
String [] propNames = image.getPropertyNames();
if (propNames != null)
for (String name : propNames)
props.put(name, image.getProperty(name));
image = new BufferedImage(image.getColorModel(),
Raster.createWritableRaster(
image.getRaster().getSampleModel().createCompatibleSampleModel(reader.getWidth(0), reader.getHeight(0)),
image.getRaster().getDataBuffer(), origin),
image.isAlphaPremultiplied(), props);
}

ImageReadParam param = new ImageReadParam();
param.setDestination(image);
reader.read(0, param);
}
}
}
}

To use it, just build a new one and call read on it with a file. This will set the object's image and corresponding buffer. If you call read again, a new buffer will only be allocated if the current one is too small. A new BufferedImage will be created if there is any difference in the image dimensions (only the BufferedImage layer is allocated, not the underlying DataBuffer). Beware that the read method will probably fail if the images are of different formats (untested). As a result, this works best for a bunch of images of the same format and size.