001: package org.geotools.coverage.grid.io.imageio;
002:
003: import java.awt.Point;
004: import java.awt.image.ComponentSampleModel;
005: import java.awt.image.DataBuffer;
006: import java.awt.image.DataBufferByte;
007: import java.awt.image.DataBufferInt;
008: import java.awt.image.DataBufferShort;
009: import java.awt.image.DataBufferUShort;
010: import java.awt.image.MultiPixelPackedSampleModel;
011: import java.awt.image.Raster;
012: import java.awt.image.SampleModel;
013: import java.awt.image.SinglePixelPackedSampleModel;
014: import java.awt.image.WritableRaster;
015: import java.util.ArrayList;
016: import java.util.Arrays;
017: import java.util.HashMap;
018: import java.util.Observable;
019: import java.util.Observer;
020:
021: import javax.media.jai.CachedTile;
022: import javax.media.jai.TileCache;
023: import javax.media.jai.TileFactory;
024: import javax.media.jai.TileRecycler;
025:
026: import com.sun.media.jai.util.DataBufferUtils;
027:
028: /**
029: * A simple implementation of <code>TileFactory</code> wherein the tiles
030: * returned from <code>createTile()</code> attempt to re-use primitive arrays
031: * provided by the <code>TileRecycler</code> method <code>recycleTile()</code>.
032: *
033: * <p>
034: * A simple example of the use of this class is as follows wherein image files
035: * are read, each image is filtered, and each output written to a file:
036: *
037: * <pre>
038: * String[] sourceFiles; // source file paths
039: * KernelJAI kernel; // filtering kernel
040: *
041: * // Create a RenderingHints object and set hints.
042: * RenderingHints rh = new RenderingHints(null);
043: * RecyclingTileFactory rtf = new RecyclingTileFactory();
044: * rh.put(JAI.KEY_TILE_RECYCLER, rtf);
045: * rh.put(JAI.KEY_TILE_FACTORY, rtf);
046: * rh.put(JAI.KEY_IMAGE_LAYOUT, new ImageLayout().setTileWidth(32).setTileHeight(
047: * 32));
048: *
049: * int counter = 0;
050: *
051: * // Read each image, filter it, and save the output to a file.
052: * for (int i = 0; i < sourceFiles.length; i++) {
053: * PlanarImage source = JAI.create("fileload", sourceFiles[i]);
054: * ParameterBlock pb = (new ParameterBlock()).addSource(source).add(kernel);
055: *
056: * // The TileFactory hint will cause tiles to be created by 'rtf'.
057: * RenderedOp dest = JAI.create("convolve", pb, rh);
058: * String fileName = "image_" + (++counter) + ".tif";
059: * JAI.create("filestore", dest, fileName);
060: *
061: * // The TileRecycler hint will cause arrays to be reused by 'rtf'.
062: * dest.dispose();
063: * }
064: * </pre>
065: *
066: * In the above code, if the <code>SampleModel</code> of all source images is
067: * identical, then data arrays should only be created in the first iteration.
068: * </p>
069: *
070: * @since JAI 1.1.2
071: */
072: public class RecyclingTileFactory extends Observable implements
073: TileFactory, TileRecycler, Observer {
074:
075: private static final boolean DEBUG = false;
076:
077: /**
078: * Cache of recycled arrays. The key in this mapping is a <code>Long</code>
079: * which is formed for a given two-dimensional array as
080: *
081: * <pre>
082: * long type; // DataBuffer.TYPE_*
083: *
084: * long numBanks; // Number of banks
085: *
086: * long size; // Size of each bank
087: *
088: * Long key = new Long((type << 56) | (numBanks << 32) | size);
089: * </pre>
090: *
091: * where the value of <code>type</code> is one of the constants
092: * <code>DataBuffer.TYPE_*</code>. The value corresponding to each key is
093: * an <code>ArrayList</code> of <code>SoftReferences</code> to the
094: * internal data banks of <code>DataBuffer</code>s of tiles wherein the
095: * data bank array has the type and dimensions implied by the key.
096: */
097: private HashMap recycledArrays = new HashMap(32);
098:
099: /**
100: * The amount of memory currrently used for array storage.
101: */
102: private long memoryUsed = 0L;
103:
104: // XXX Inline this method or make it public?
105: private static long getBufferSizeCSM(ComponentSampleModel csm) {
106: int[] bandOffsets = csm.getBandOffsets();
107: int maxBandOff = bandOffsets[0];
108: for (int i = 1; i < bandOffsets.length; i++)
109: maxBandOff = Math.max(maxBandOff, bandOffsets[i]);
110:
111: long size = 0;
112: if (maxBandOff >= 0)
113: size += maxBandOff + 1;
114: int pixelStride = csm.getPixelStride();
115: if (pixelStride > 0)
116: size += pixelStride * (csm.getWidth() - 1);
117: int scanlineStride = csm.getScanlineStride();
118: if (scanlineStride > 0)
119: size += scanlineStride * (csm.getHeight() - 1);
120: return size;
121: }
122:
123: // XXX Inline this method or make it public?
124: private static long getNumBanksCSM(ComponentSampleModel csm) {
125: int[] bankIndices = csm.getBankIndices();
126: int maxIndex = bankIndices[0];
127: for (int i = 1; i < bankIndices.length; i++) {
128: int bankIndex = bankIndices[i];
129: if (bankIndex > maxIndex) {
130: maxIndex = bankIndex;
131: }
132: }
133: return maxIndex + 1;
134: }
135:
136: /** Tile cache I observe. */
137: private final Observable tileCache;
138:
139: /**
140: * Returns a <code>SoftReference</code> to the internal bank data of the
141: * <code>DataBuffer</code>.
142: */
143: private static Object getBankReference(DataBuffer db) {
144: Object array = null;
145:
146: switch (db.getDataType()) {
147: case DataBuffer.TYPE_BYTE:
148: array = ((DataBufferByte) db).getBankData();
149: break;
150: case DataBuffer.TYPE_USHORT:
151: array = ((DataBufferUShort) db).getBankData();
152: break;
153: case DataBuffer.TYPE_SHORT:
154: array = ((DataBufferShort) db).getBankData();
155: break;
156: case DataBuffer.TYPE_INT:
157: array = ((DataBufferInt) db).getBankData();
158: break;
159: case DataBuffer.TYPE_FLOAT:
160: array = DataBufferUtils.getBankDataFloat(db);
161: break;
162: case DataBuffer.TYPE_DOUBLE:
163: array = DataBufferUtils.getBankDataDouble(db);
164: break;
165: default:
166: throw new UnsupportedOperationException("");
167:
168: }
169:
170: return array;
171: }
172:
173: /**
174: * Returns the amount of memory (in bytes) used by the supplied data bank
175: * array.
176: */
177: private static long getDataBankSize(int dataType, int numBanks,
178: int size) {
179: int bytesPerElement = 0;
180: switch (dataType) {
181: case DataBuffer.TYPE_BYTE:
182: bytesPerElement = 1;
183: break;
184: case DataBuffer.TYPE_USHORT:
185: case DataBuffer.TYPE_SHORT:
186: bytesPerElement = 2;
187: break;
188: case DataBuffer.TYPE_INT:
189: case DataBuffer.TYPE_FLOAT:
190: bytesPerElement = 4;
191: break;
192: case DataBuffer.TYPE_DOUBLE:
193: bytesPerElement = 8;
194: break;
195: default:
196: throw new UnsupportedOperationException("");
197:
198: }
199:
200: return numBanks * size * bytesPerElement;
201: }
202:
203: /**
204: * Constructs a <code>RecyclingTileFactory</code>.
205: */
206: public RecyclingTileFactory(Observable tileCache) {
207: if (tileCache instanceof TileCache)
208: this .tileCache = tileCache;
209: else
210: throw new IllegalArgumentException(
211: "Provided argument is not of type TileCache");
212: tileCache.addObserver(this );
213: }
214:
215: /**
216: * Returns <code>true</code>.
217: */
218: public boolean canReclaimMemory() {
219: return true;
220: }
221:
222: /**
223: * Returns <code>true</code>.
224: */
225: public boolean isMemoryCache() {
226: return true;
227: }
228:
229: public long getMemoryUsed() {
230: return memoryUsed;
231: }
232:
233: public void flush() {
234: synchronized (recycledArrays) {
235: recycledArrays.clear();
236: memoryUsed = 0L;
237: }
238: }
239:
240: public WritableRaster createTile(SampleModel sampleModel,
241: Point location) {
242:
243: if (sampleModel == null) {
244: throw new IllegalArgumentException("sampleModel == null!");
245: }
246:
247: if (location == null) {
248: location = new Point(0, 0);
249: }
250:
251: DataBuffer db = null;
252:
253: int type = sampleModel.getTransferType();
254: long numBanks = 0;
255: long size = 0;
256:
257: if (sampleModel instanceof ComponentSampleModel) {
258: ComponentSampleModel csm = (ComponentSampleModel) sampleModel;
259: numBanks = getNumBanksCSM(csm);
260: size = getBufferSizeCSM(csm);
261: } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
262: MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel) sampleModel;
263: numBanks = 1;
264: int dataTypeSize = DataBuffer.getDataTypeSize(type);
265: size = mppsm.getScanlineStride() * mppsm.getHeight()
266: + (mppsm.getDataBitOffset() + dataTypeSize - 1)
267: / dataTypeSize;
268: } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
269: SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sampleModel;
270: numBanks = 1;
271: size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1)
272: + sppsm.getWidth();
273: }
274:
275: if (size != 0) {
276: Object array = getRecycledArray(type, numBanks, size);
277: if (array != null) {
278: switch (type) {
279: case DataBuffer.TYPE_BYTE: {
280: byte[][] bankData = (byte[][]) array;
281: for (int i = 0; i < numBanks; i++) {
282: Arrays.fill(bankData[i], (byte) 0);
283: }
284: db = new DataBufferByte(bankData, (int) size);
285: }
286: break;
287: case DataBuffer.TYPE_USHORT: {
288: short[][] bankData = (short[][]) array;
289: for (int i = 0; i < numBanks; i++) {
290: Arrays.fill(bankData[i], (short) 0);
291: }
292: db = new DataBufferUShort(bankData, (int) size);
293: }
294: break;
295: case DataBuffer.TYPE_SHORT: {
296: short[][] bankData = (short[][]) array;
297: for (int i = 0; i < numBanks; i++) {
298: Arrays.fill(bankData[i], (short) 0);
299: }
300: db = new DataBufferShort(bankData, (int) size);
301: }
302: break;
303: case DataBuffer.TYPE_INT: {
304: int[][] bankData = (int[][]) array;
305: for (int i = 0; i < numBanks; i++) {
306: Arrays.fill(bankData[i], 0);
307: }
308: db = new DataBufferInt(bankData, (int) size);
309: }
310: break;
311: case DataBuffer.TYPE_FLOAT: {
312: float[][] bankData = (float[][]) array;
313: for (int i = 0; i < numBanks; i++) {
314: Arrays.fill(bankData[i], 0.0F);
315: }
316: db = DataBufferUtils.createDataBufferFloat(
317: bankData, (int) size);
318: }
319: break;
320: case DataBuffer.TYPE_DOUBLE: {
321: double[][] bankData = (double[][]) array;
322: for (int i = 0; i < numBanks; i++) {
323: Arrays.fill(bankData[i], 0.0);
324: }
325: db = DataBufferUtils.createDataBufferDouble(
326: bankData, (int) size);
327: }
328: break;
329: default:
330: throw new IllegalArgumentException("");
331: }
332:
333: if (DEBUG) {
334: System.out.println(getClass().getName()
335: + " Using a recycled array");// XXX
336: // (new Throwable()).printStackTrace(); // XXX
337: }
338: } else if (DEBUG) {
339: System.out.println(getClass().getName() + " No type "
340: + type + " array[" + numBanks + "][" + size
341: + "] available");
342: }
343: } else if (DEBUG) {
344: System.out.println(getClass().getName() + " Size is zero");
345: }
346:
347: if (db == null) {
348: if (DEBUG) {
349: System.out.println(getClass().getName()
350: + " Creating new DataBuffer");// XXX
351: }
352: // (new Throwable()).printStackTrace(); // XXX
353: db = sampleModel.createDataBuffer();
354: }
355:
356: return Raster.createWritableRaster(sampleModel, db, location);
357: }
358:
359: /**
360: * Recycle the given tile.
361: */
362: public void recycleTile(Raster tile) {
363: DataBuffer db = tile.getDataBuffer();
364:
365: Long key = new Long(((long) db.getDataType() << 56)
366: | ((long) db.getNumBanks() << 32) | (long) db.getSize());
367:
368: if (DEBUG) {
369: System.out.println("Recycling array for: "
370: + db.getDataType() + " " + db.getNumBanks() + " "
371: + db.getSize());
372: // System.out.println("recycleTile(); key = "+key);
373: }
374:
375: synchronized (recycledArrays) {
376: Object value = recycledArrays.get(key);
377: ArrayList arrays = null;
378: if (value != null) {
379: arrays = (ArrayList) value;
380: } else {
381: arrays = new ArrayList(10);
382: }
383:
384: memoryUsed += getDataBankSize(db.getDataType(), db
385: .getNumBanks(), db.getSize());
386:
387: arrays.add(getBankReference(db));
388:
389: if (value == null) {
390: recycledArrays.put(key, arrays);
391: }
392: }
393: }
394:
395: /**
396: * Retrieve an array of the specified type and length.
397: */
398: private Object getRecycledArray(int arrayType, long numBanks,
399: long arrayLength) {
400: Long key = new Long(((long) arrayType << 56) | numBanks << 32
401: | arrayLength);
402:
403: if (DEBUG) {
404: System.out.println("Attempting to get array for: "
405: + arrayType + " " + numBanks + " " + arrayLength);
406: // System.out.println("Attempting to get array for key "+key);
407: }
408:
409: synchronized (recycledArrays) {
410: Object value = recycledArrays.get(key);
411:
412: if (value != null) {
413: ArrayList arrays = (ArrayList) value;
414: for (int idx = arrays.size() - 1; idx >= 0; idx--) {
415: Object array = arrays.remove(idx);
416: memoryUsed -= getDataBankSize(arrayType,
417: (int) numBanks, (int) arrayLength);
418: if (idx == 0) {
419: recycledArrays.remove(key);
420: }
421:
422: if (array != null) {
423: if (DEBUG)
424: System.out.println("good reference");
425: return array;
426: }
427:
428: if (DEBUG)
429: System.out.println("null reference");
430: }
431: }
432: }
433:
434: // if(DEBUG) System.out.println("getRecycledArray() returning "+array);
435:
436: return null;
437: }
438:
439: public void update(Observable o, Object arg) {
440: if (o.equals(tileCache)) {
441: if (arg instanceof CachedTile) {
442: CachedTile tile = (CachedTile) arg;
443: switch (tile.getAction()) {
444: case 1:
445: case 2:// REMOVE,REMOVE_FROM_FLUSH
446: this.recycleTile(tile.getTile());
447: }
448: }
449: }
450: }
451: }
|