/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.coverage.grid.io.imageio;
import java.awt.Point;
import java.awt.image.ComponentSampleModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.DataBufferShort;
import java.awt.image.DataBufferUShort;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.SinglePixelPackedSampleModel;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Observable;
import java.util.Observer;
import javax.media.jai.CachedTile;
import javax.media.jai.TileCache;
import javax.media.jai.TileFactory;
import javax.media.jai.TileRecycler;
import com.sun.media.jai.util.DataBufferUtils;
/**
* A simple implementation of <code>TileFactory</code> wherein the tiles
* returned from <code>createTile()</code> attempt to re-use primitive arrays
* provided by the <code>TileRecycler</code> method <code>recycleTile()</code>.
*
* <p>
* A simple example of the use of this class is as follows wherein image files
* are read, each image is filtered, and each output written to a file:
*
* <pre>
* String[] sourceFiles; // source file paths
* KernelJAI kernel; // filtering kernel
*
* // Create a RenderingHints object and set hints.
* RenderingHints rh = new RenderingHints(null);
* RecyclingTileFactory rtf = new RecyclingTileFactory();
* rh.put(JAI.KEY_TILE_RECYCLER, rtf);
* rh.put(JAI.KEY_TILE_FACTORY, rtf);
* rh.put(JAI.KEY_IMAGE_LAYOUT, new ImageLayout().setTileWidth(32).setTileHeight(
* 32));
*
* int counter = 0;
*
* // Read each image, filter it, and save the output to a file.
* for (int i = 0; i < sourceFiles.length; i++) {
* PlanarImage source = JAI.create("fileload", sourceFiles[i]);
* ParameterBlock pb = (new ParameterBlock()).addSource(source).add(kernel);
*
* // The TileFactory hint will cause tiles to be created by 'rtf'.
* RenderedOp dest = JAI.create("convolve", pb, rh);
* String fileName = "image_" + (++counter) + ".tif";
* JAI.create("filestore", dest, fileName);
*
* // The TileRecycler hint will cause arrays to be reused by 'rtf'.
* dest.dispose();
* }
* </pre>
*
* In the above code, if the <code>SampleModel</code> of all source images is
* identical, then data arrays should only be created in the first iteration.
* </p>
*
* @since JAI 1.1.2
*
*
* @source $URL$
*/
public class RecyclingTileFactory extends Observable implements TileFactory,
TileRecycler, Observer {
private static final boolean DEBUG = false;
/**
* Cache of recycled arrays. The key in this mapping is a <code>Long</code>
* which is formed for a given two-dimensional array as
*
* <pre>
* long type; // DataBuffer.TYPE_*
*
* long numBanks; // Number of banks
*
* long size; // Size of each bank
*
* Long key = new Long((type << 56) | (numBanks << 32) | size);
* </pre>
*
* where the value of <code>type</code> is one of the constants
* <code>DataBuffer.TYPE_*</code>. The value corresponding to each key is
* an <code>ArrayList</code> of <code>SoftReferences</code> to the
* internal data banks of <code>DataBuffer</code>s of tiles wherein the
* data bank array has the type and dimensions implied by the key.
*/
private HashMap recycledArrays = new HashMap(32);
/**
* The amount of memory currrently used for array storage.
*/
private long memoryUsed = 0L;
// XXX Inline this method or make it public?
private static long getBufferSizeCSM(ComponentSampleModel csm) {
int[] bandOffsets = csm.getBandOffsets();
int maxBandOff = bandOffsets[0];
for (int i = 1; i < bandOffsets.length; i++)
maxBandOff = Math.max(maxBandOff, bandOffsets[i]);
long size = 0;
if (maxBandOff >= 0)
size += maxBandOff + 1;
int pixelStride = csm.getPixelStride();
if (pixelStride > 0)
size += pixelStride * (csm.getWidth() - 1);
int scanlineStride = csm.getScanlineStride();
if (scanlineStride > 0)
size += scanlineStride * (csm.getHeight() - 1);
return size;
}
// XXX Inline this method or make it public?
private static long getNumBanksCSM(ComponentSampleModel csm) {
int[] bankIndices = csm.getBankIndices();
int maxIndex = bankIndices[0];
for (int i = 1; i < bankIndices.length; i++) {
int bankIndex = bankIndices[i];
if (bankIndex > maxIndex) {
maxIndex = bankIndex;
}
}
return maxIndex + 1;
}
/** Tile cache I observe. */
private final Observable tileCache;
/**
* Returns a <code>SoftReference</code> to the internal bank data of the
* <code>DataBuffer</code>.
*/
private static Object getBankReference(DataBuffer db) {
Object array = null;
switch (db.getDataType()) {
case DataBuffer.TYPE_BYTE:
array = ((DataBufferByte) db).getBankData();
break;
case DataBuffer.TYPE_USHORT:
array = ((DataBufferUShort) db).getBankData();
break;
case DataBuffer.TYPE_SHORT:
array = ((DataBufferShort) db).getBankData();
break;
case DataBuffer.TYPE_INT:
array = ((DataBufferInt) db).getBankData();
break;
case DataBuffer.TYPE_FLOAT:
array = DataBufferUtils.getBankDataFloat(db);
break;
case DataBuffer.TYPE_DOUBLE:
array = DataBufferUtils.getBankDataDouble(db);
break;
default:
throw new UnsupportedOperationException("");
}
return array;
}
/**
* Returns the amount of memory (in bytes) used by the supplied data bank
* array.
*/
private static long getDataBankSize(int dataType, int numBanks, int size) {
int bytesPerElement = 0;
switch (dataType) {
case DataBuffer.TYPE_BYTE:
bytesPerElement = 1;
break;
case DataBuffer.TYPE_USHORT:
case DataBuffer.TYPE_SHORT:
bytesPerElement = 2;
break;
case DataBuffer.TYPE_INT:
case DataBuffer.TYPE_FLOAT:
bytesPerElement = 4;
break;
case DataBuffer.TYPE_DOUBLE:
bytesPerElement = 8;
break;
default:
throw new UnsupportedOperationException("");
}
return numBanks * size * bytesPerElement;
}
/**
* Constructs a <code>RecyclingTileFactory</code>.
*/
public RecyclingTileFactory(Observable tileCache) {
if (tileCache instanceof TileCache)
this.tileCache = tileCache;
else
throw new IllegalArgumentException(
"Provided argument is not of type TileCache");
tileCache.addObserver(this);
}
/**
* Returns <code>true</code>.
*/
public boolean canReclaimMemory() {
return true;
}
/**
* Returns <code>true</code>.
*/
public boolean isMemoryCache() {
return true;
}
public long getMemoryUsed() {
return memoryUsed;
}
public void flush() {
synchronized (recycledArrays) {
recycledArrays.clear();
memoryUsed = 0L;
}
}
public WritableRaster createTile(SampleModel sampleModel, Point location) {
if (sampleModel == null) {
throw new IllegalArgumentException("sampleModel == null!");
}
if (location == null) {
location = new Point(0, 0);
}
DataBuffer db = null;
int type = sampleModel.getTransferType();
long numBanks = 0;
long size = 0;
if (sampleModel instanceof ComponentSampleModel) {
ComponentSampleModel csm = (ComponentSampleModel) sampleModel;
numBanks = getNumBanksCSM(csm);
size = getBufferSizeCSM(csm);
} else if (sampleModel instanceof MultiPixelPackedSampleModel) {
MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel) sampleModel;
numBanks = 1;
int dataTypeSize = DataBuffer.getDataTypeSize(type);
size = mppsm.getScanlineStride() * mppsm.getHeight()
+ (mppsm.getDataBitOffset() + dataTypeSize - 1)
/ dataTypeSize;
} else if (sampleModel instanceof SinglePixelPackedSampleModel) {
SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sampleModel;
numBanks = 1;
size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1)
+ sppsm.getWidth();
}
if (size != 0) {
Object array = getRecycledArray(type, numBanks, size);
if (array != null) {
switch (type) {
case DataBuffer.TYPE_BYTE: {
byte[][] bankData = (byte[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], (byte) 0);
}
db = new DataBufferByte(bankData, (int) size);
}
break;
case DataBuffer.TYPE_USHORT: {
short[][] bankData = (short[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], (short) 0);
}
db = new DataBufferUShort(bankData, (int) size);
}
break;
case DataBuffer.TYPE_SHORT: {
short[][] bankData = (short[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], (short) 0);
}
db = new DataBufferShort(bankData, (int) size);
}
break;
case DataBuffer.TYPE_INT: {
int[][] bankData = (int[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], 0);
}
db = new DataBufferInt(bankData, (int) size);
}
break;
case DataBuffer.TYPE_FLOAT: {
float[][] bankData = (float[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], 0.0F);
}
db = DataBufferUtils.createDataBufferFloat(bankData,
(int) size);
}
break;
case DataBuffer.TYPE_DOUBLE: {
double[][] bankData = (double[][]) array;
for (int i = 0; i < numBanks; i++) {
Arrays.fill(bankData[i], 0.0);
}
db = DataBufferUtils.createDataBufferDouble(bankData,
(int) size);
}
break;
default:
throw new IllegalArgumentException("");
}
if (DEBUG) {
System.out.println(getClass().getName()
+ " Using a recycled array");// XXX
// (new Throwable()).printStackTrace(); // XXX
}
} else if (DEBUG) {
System.out.println(getClass().getName() + " No type " + type
+ " array[" + numBanks + "][" + size + "] available");
}
} else if (DEBUG) {
System.out.println(getClass().getName() + " Size is zero");
}
if (db == null) {
if (DEBUG) {
System.out.println(getClass().getName()
+ " Creating new DataBuffer");// XXX
}
// (new Throwable()).printStackTrace(); // XXX
db = sampleModel.createDataBuffer();
}
return Raster.createWritableRaster(sampleModel, db, location);
}
/**
* Recycle the given tile.
*/
public void recycleTile(Raster tile) {
DataBuffer db = tile.getDataBuffer();
Long key = new Long(((long) db.getDataType() << 56)
| ((long) db.getNumBanks() << 32) | (long) db.getSize());
if (DEBUG) {
System.out.println("Recycling array for: " + db.getDataType() + " "
+ db.getNumBanks() + " " + db.getSize());
// System.out.println("recycleTile(); key = "+key);
}
synchronized (recycledArrays) {
Object value = recycledArrays.get(key);
ArrayList arrays = null;
if (value != null) {
arrays = (ArrayList) value;
} else {
arrays = new ArrayList(10);
}
memoryUsed += getDataBankSize(db.getDataType(), db.getNumBanks(),
db.getSize());
arrays.add(getBankReference(db));
if (value == null) {
recycledArrays.put(key, arrays);
}
}
}
/**
* Retrieve an array of the specified type and length.
*/
private Object getRecycledArray(int arrayType, long numBanks,
long arrayLength) {
Long key = new Long(((long) arrayType << 56) | numBanks << 32
| arrayLength);
if (DEBUG) {
System.out.println("Attempting to get array for: " + arrayType
+ " " + numBanks + " " + arrayLength);
// System.out.println("Attempting to get array for key "+key);
}
synchronized (recycledArrays) {
Object value = recycledArrays.get(key);
if (value != null) {
ArrayList arrays = (ArrayList) value;
for (int idx = arrays.size() - 1; idx >= 0; idx--) {
Object array = arrays.remove(idx);
memoryUsed -= getDataBankSize(arrayType, (int) numBanks,
(int) arrayLength);
if (idx == 0) {
recycledArrays.remove(key);
}
if (array != null) {
if (DEBUG)
System.out.println("good reference");
return array;
}
if (DEBUG)
System.out.println("null reference");
}
}
}
// if(DEBUG) System.out.println("getRecycledArray() returning "+array);
return null;
}
public void update(Observable o, Object arg) {
if (o.equals(tileCache)) {
if (arg instanceof CachedTile) {
CachedTile tile = (CachedTile) arg;
switch (tile.getAction()) {
case 1:
case 2:// REMOVE,REMOVE_FROM_FLUSH
this.recycleTile(tile.getTile());
}
}
}
}
}