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