1 /* Copyright (C) 2005-2011 Fabio Riccardi */
2 
3 package com.lightcrafts.jai.utils;
4 
5 /*
6  * $RCSfile: RecyclingTileFactory.java,v $
7  *
8  * Copyright (c) 2005 Sun Microsystems, Inc. All rights reserved.
9  *
10  * Use is subject to license terms.
11  *
12  * $Revision: 1.1 $
13  * $Date: 2005/02/11 04:57:18 $
14  * $State: Exp $
15  */
16 
17 import java.awt.Point;
18 import java.awt.image.ComponentSampleModel;
19 import java.awt.image.DataBuffer;
20 import java.awt.image.DataBufferByte;
21 import java.awt.image.DataBufferInt;
22 import java.awt.image.DataBufferShort;
23 import java.awt.image.DataBufferUShort;
24 import java.awt.image.MultiPixelPackedSampleModel;
25 import java.awt.image.Raster;
26 import java.awt.image.SampleModel;
27 import java.awt.image.SinglePixelPackedSampleModel;
28 import java.awt.image.WritableRaster;
29 import java.lang.ref.SoftReference;
30 import java.lang.reflect.Array;
31 import java.util.ArrayList;
32 import java.util.HashMap;
33 import java.util.Observable;
34 import com.lightcrafts.media.jai.util.DataBufferUtils;
35 
36 import com.lightcrafts.mediax.jai.TileFactory;
37 import com.lightcrafts.mediax.jai.TileRecycler;
38 
39 /**
40  * A simple implementation of <code>TileFactory</code> wherein the tiles
41  * returned from <code>createTile()</code> attempt to re-use primitive
42  * arrays provided by the <code>TileRecycler</code> method
43  * <code>recycleTile()</code>.
44  *
45  * <p>
46  * A simple example of the use of this class is as follows wherein
47  * image files are read, each image is filtered, and each output
48  * written to a file:
49  * <pre>
50  * String[] sourceFiles; // source file paths
51  * KernelJAI kernel; // filtering kernel
52  *
53  * // Create a RenderingHints object and set hints.
54  * RenderingHints rh = new RenderingHints(null);
55  * RecyclingTileFactory rtf = new RecyclingTileFactory();
56  * rh.put(JAI.KEY_TILE_RECYCLER, rtf);
57  * rh.put(JAI.KEY_TILE_FACTORY, rtf);
58  * rh.put(JAI.KEY_IMAGE_LAYOUT,
59  *        new ImageLayout().setTileWidth(32).setTileHeight(32));
60  *
61  * int counter = 0;
62  *
63  * // Read each image, filter it, and save the output to a file.
64  * for(int i = 0; i < sourceFiles.length; i++) {
65  *     PlanarImage source = JAI.create("fileload", sourceFiles[i]);
66  *     ParameterBlock pb =
67  *         (new ParameterBlock()).addSource(source).add(kernel);
68  *
69  *     // The TileFactory hint will cause tiles to be created by 'rtf'.
70  *     RenderedOp dest = JAI.create("convolve", pb, rh);
71  *     String fileName = "image_"+(++counter)+".tif";
72  *     JAI.create("filestore", dest, fileName);
73  *
74  *     // The TileRecycler hint will cause arrays to be reused by 'rtf'.
75  *     dest.dispose();
76  * }
77  * </pre>
78  * In the above code, if the <code>SampleModel</code> of all source
79  * images is identical, then data arrays should only be created in the
80  * first iteration.
81  * </p>
82  *
83  * @since JAI 1.1.2
84  */
85 public class LCRecyclingTileFactory extends Observable
86     implements TileFactory, TileRecycler {
87 
88     private static final boolean DEBUG = false;
89 
90     /* XXX
91     public static void main(String[] args) throws Throwable {
92         RecyclingTileFactory rtf = new RecyclingTileFactory();
93 
94         WritableRaster original =
95             Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
96                                            1024, 768, 1, null);
97 
98         rtf.recycleTile(Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
99                                                        1024, 768, 1, null));
100         rtf.recycleTile(Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
101                                                        1024, 768, 1, null));
102 
103         rtf.createTile(original.getSampleModel(),
104                        new Point(original.getMinX(),
105                                  original.getMinY()));
106         rtf.createTile(original.getSampleModel(),
107                        new Point(original.getMinX(),
108                                  original.getMinY()));
109         rtf.createTile(original.getSampleModel(),
110                        new Point(original.getMinX(),
111                                  original.getMinY()));
112 
113         //System.out.println(original.hashCode()+" "+original);
114         //System.out.println(recycled.hashCode()+" "+recycled);
115 
116         System.exit(0);
117     }
118     */
119 
120     /**
121      * Cache of recycled arrays.  The key in this mapping is a
122      * <code>Long</code> which is formed for a given two-dimensional
123      * array as
124      *
125      * <pre>
126      * long type;     // DataBuffer.TYPE_*
127      * long numBanks; // Number of banks
128      * long size;     // Size of each bank
129      * Long key = new Long((type << 56) | (numBanks << 32) | size);
130      * </pre>
131      *
132      * where the value of <code>type</code> is one of the constants
133      * <code>DataBuffer.TYPE_*</code>.  The value corresponding to each key
134      * is an <code>ArrayList</code> of <code>SoftReferences</code> to the
135      * internal data banks of <code>DataBuffer</code>s of tiles wherein the
136      * data bank array has the type and dimensions implied by the key.
137      */
138     private HashMap recycledArrays = new HashMap(32);
139 
140     /**
141      * The amount of memory currrently used for array storage.
142      */
143     private long memoryUsed = 0L;
144 
145     // XXX Inline this method or make it public?
getBufferSizeCSM(ComponentSampleModel csm)146     private static long getBufferSizeCSM(ComponentSampleModel csm) {
147         int[] bandOffsets = csm.getBandOffsets();
148         int maxBandOff=bandOffsets[0];
149         for (int i=1; i<bandOffsets.length; i++)
150             maxBandOff = Math.max(maxBandOff,bandOffsets[i]);
151 
152         long size = 0;
153         if (maxBandOff >= 0)
154             size += maxBandOff+1;
155         int pixelStride = csm.getPixelStride();
156         if (pixelStride > 0)
157             size += pixelStride * (csm.getWidth()-1);
158         int scanlineStride = csm.getScanlineStride();
159         if (scanlineStride > 0)
160             size += scanlineStride*(csm.getHeight()-1);
161         return size;
162     }
163 
164     // XXX Inline this method or make it public?
getNumBanksCSM(ComponentSampleModel csm)165     private static long getNumBanksCSM(ComponentSampleModel csm) {
166         int[] bankIndices = csm.getBankIndices();
167         int maxIndex = bankIndices[0];
168         for(int i = 1; i < bankIndices.length; i++) {
169             int bankIndex = bankIndices[i];
170             if(bankIndex > maxIndex) {
171                 maxIndex = bankIndex;
172             }
173         }
174         return maxIndex + 1;
175     }
176 
177     /**
178      * Returns a <code>SoftReference</code> to the internal bank
179      * data of the <code>DataBuffer</code>.
180      */
getBankReference(DataBuffer db)181     private static SoftReference getBankReference(DataBuffer db) {
182         Object array = null;
183 
184         switch(db.getDataType()) {
185         case DataBuffer.TYPE_BYTE:
186             array = ((DataBufferByte)db).getBankData();
187             break;
188         case DataBuffer.TYPE_USHORT:
189             array = ((DataBufferUShort)db).getBankData();
190             break;
191         case DataBuffer.TYPE_SHORT:
192             array = ((DataBufferShort)db).getBankData();
193             break;
194         case DataBuffer.TYPE_INT:
195             array = ((DataBufferInt)db).getBankData();
196             break;
197         case DataBuffer.TYPE_FLOAT:
198             array = DataBufferUtils.getBankDataFloat(db);
199             break;
200         case DataBuffer.TYPE_DOUBLE:
201             array = DataBufferUtils.getBankDataDouble(db);
202             break;
203         default:
204             throw new UnsupportedOperationException("Unsupported Data Type");
205 
206         }
207 
208         return new SoftReference(array);
209     }
210 
211     /**
212      * Returns the amount of memory (in bytes) used by the supplied data
213      * bank array.
214      */
getDataBankSize(int dataType, int numBanks, int size)215     private static long getDataBankSize(int dataType, int numBanks, int size) {
216         int bytesPerElement = 0;
217         switch(dataType) {
218         case DataBuffer.TYPE_BYTE:
219             bytesPerElement = 1;
220             break;
221         case DataBuffer.TYPE_USHORT:
222         case DataBuffer.TYPE_SHORT:
223             bytesPerElement = 2;
224             break;
225         case DataBuffer.TYPE_INT:
226         case DataBuffer.TYPE_FLOAT:
227             bytesPerElement = 4;
228             break;
229         case DataBuffer.TYPE_DOUBLE:
230             bytesPerElement = 8;
231             break;
232         default:
233             throw new UnsupportedOperationException("Unsupported Data Type");
234 
235         }
236 
237         return numBanks*size*bytesPerElement;
238     }
239 
240     /**
241      * Constructs a <code>RecyclingTileFactory</code>.
242      */
LCRecyclingTileFactory()243     public LCRecyclingTileFactory() {}
244 
245     /**
246      * Returns <code>true</code>.
247      */
canReclaimMemory()248     public boolean canReclaimMemory() {
249         return true;
250     }
251 
252     /**
253      * Returns <code>true</code>.
254      */
isMemoryCache()255     public boolean isMemoryCache() {
256         return true;
257     }
258 
getMemoryUsed()259     public long getMemoryUsed() {
260         return memoryUsed;
261     }
262 
flush()263     public void flush() {
264         synchronized(recycledArrays) {
265             recycledArrays.clear();
266             memoryUsed = 0L;
267         }
268     }
269 
createTile(SampleModel sampleModel, Point location)270     public WritableRaster createTile(SampleModel sampleModel,
271                                      Point location) {
272 
273         if(sampleModel == null) {
274             throw new IllegalArgumentException("sampleModel == null!");
275         }
276 
277         if(location == null) {
278            location = new Point(0,0);
279         }
280 
281         DataBuffer db = null;
282 
283         int height = sampleModel.getHeight();
284         int type = sampleModel.getTransferType();
285         long numBanks = 0;
286         long size = 0;
287 
288         if(sampleModel instanceof ComponentSampleModel) {
289             ComponentSampleModel csm = (ComponentSampleModel)sampleModel;
290             numBanks = getNumBanksCSM(csm);
291             size = height == 1 ? csm.getScanlineStride()
292                                : getBufferSizeCSM(csm);
293         } else if(sampleModel instanceof MultiPixelPackedSampleModel) {
294             MultiPixelPackedSampleModel mppsm =
295                 (MultiPixelPackedSampleModel)sampleModel;
296             numBanks = 1;
297             int dataTypeSize = DataBuffer.getDataTypeSize(type);
298             size = mppsm.getScanlineStride()*height +
299                 (mppsm.getDataBitOffset() + dataTypeSize - 1)/dataTypeSize;
300         } else if(sampleModel instanceof SinglePixelPackedSampleModel) {
301             SinglePixelPackedSampleModel sppsm =
302                 (SinglePixelPackedSampleModel)sampleModel;
303             numBanks = 1;
304             size = height == 1 ? sppsm.getScanlineStride()
305                                : sppsm.getScanlineStride()*(height - 1) +
306                                        sppsm.getWidth();
307         }
308 
309         if(size != 0) {
310             Object array =
311                 getRecycledArray(type, numBanks, size);
312             if(array != null) {
313                 switch(type) {
314                 case DataBuffer.TYPE_BYTE:
315                     {
316                         byte[][] bankData = (byte[][])array;
317                         /*for(int i = 0; i < numBanks; i++) {
318                             Arrays.fill(bankData[i], (byte)0);
319                         }*/
320                         db = new DataBufferByte(bankData, (int)size);
321                     }
322                     break;
323                 case DataBuffer.TYPE_USHORT:
324                     {
325                         short[][] bankData = (short[][])array;
326                         /*for(int i = 0; i < numBanks; i++) {
327                             Arrays.fill(bankData[i], (short)0);
328                         }*/
329                         db = new DataBufferUShort(bankData, (int)size);
330                     }
331                     break;
332                 case DataBuffer.TYPE_SHORT:
333                     {
334                         short[][] bankData = (short[][])array;
335                         /*for(int i = 0; i < numBanks; i++) {
336                             Arrays.fill(bankData[i], (short)0);
337                         }*/
338                         db = new DataBufferShort(bankData, (int)size);
339                     }
340                     break;
341                 case DataBuffer.TYPE_INT:
342                     {
343                         int[][] bankData = (int[][])array;
344                         /*for(int i = 0; i < numBanks; i++) {
345                             Arrays.fill(bankData[i], 0);
346                         }*/
347                         db = new DataBufferInt(bankData, (int)size);
348                     }
349                     break;
350                 case DataBuffer.TYPE_FLOAT:
351                     {
352                         float[][] bankData = (float[][])array;
353                         /* for(int i = 0; i < numBanks; i++) {
354                             Arrays.fill(bankData[i], 0.0F);
355                         }*/
356                         db = DataBufferUtils.createDataBufferFloat(bankData,
357                                                                    (int)size);
358                     }
359                     break;
360                 case DataBuffer.TYPE_DOUBLE:
361                     {
362                         double[][] bankData = (double[][])array;
363                         /*for(int i = 0; i < numBanks; i++) {
364                             Arrays.fill(bankData[i], 0.0);
365                         }*/
366                         db = DataBufferUtils.createDataBufferDouble(bankData,
367                                                                     (int)size);
368                     }
369                     break;
370                 default:
371                     throw new IllegalArgumentException("Unsupported Data Type");
372                 }
373 
374                 if(DEBUG) {
375                     System.out.println(getClass().getName()+
376                                        " Using a recycled array of type: " + type + " array["+numBanks+"]["+size+"]");// XXX
377                     //(new Throwable()).printStackTrace(); // XXX
378                 }
379             } else if(DEBUG) {
380                 System.out.println(getClass().getName()+
381                                    " No type "+type+
382                                    " array["+numBanks+"]["+size+"] available");
383             }
384         } else if(DEBUG) {
385             System.out.println(getClass().getName()+
386                                " Size is zero");
387         }
388 
389         if(db == null) {
390             if(DEBUG) {
391                 System.out.println(getClass().getName()+
392                                    " Creating new DataBuffer");// XXX
393             }
394             //(new Throwable()).printStackTrace(); // XXX
395             db = sampleModel.createDataBuffer();
396         }
397 
398         return Raster.createWritableRaster(sampleModel,
399                                            db,
400                                            location);
401     }
402 
403     /**
404      * Recycle the given tile.
405      */
recycleTile(Raster tile)406     public void recycleTile(Raster tile) {
407         DataBuffer db = tile.getDataBuffer();
408 
409         Long key = new Long(((long)db.getDataType() << 56) |
410                             ((long)db.getNumBanks() << 32) |
411                             (long)db.getSize());
412 
413         if(DEBUG) {
414             System.out.println("Recycling array for: "+
415                                db.getDataType()+" "+
416                                db.getNumBanks()+" "+
417                                db.getSize());
418             //System.out.println("recycleTile(); key = "+key);
419         }
420 
421         synchronized(recycledArrays) {
422             Object value = recycledArrays.get(key);
423             ArrayList arrays = null;
424             if(value != null) {
425                 arrays = (ArrayList)value;
426             } else {
427                 arrays = new ArrayList();
428             }
429 
430             memoryUsed += getDataBankSize(db.getDataType(),
431                                           db.getNumBanks(),
432                                           db.getSize());
433 
434             arrays.add(getBankReference(db));
435 
436             if(value == null) {
437                 recycledArrays.put(key, arrays);
438             }
439         }
440     }
441 
442     /**
443      * Retrieve an array of the specified type and length.
444      */
getRecycledArray(int arrayType, long numBanks, long arrayLength)445     private Object getRecycledArray(int arrayType,
446                                     long numBanks,
447                                     long arrayLength) {
448         Long key = new Long(((long)arrayType << 56) |
449                             numBanks << 32 |
450                             arrayLength);
451 
452         if(DEBUG) {
453             System.out.println("Attempting to get array for: "+
454                                arrayType+" "+numBanks+" "+arrayLength);
455             //System.out.println("Attempting to get array for key "+key);
456         }
457 
458         synchronized(recycledArrays) {
459             Object value = recycledArrays.get(key);
460 
461             if(value != null) {
462                 ArrayList arrays = (ArrayList)value;
463                 for(int idx = arrays.size() - 1; idx >= 0; idx--) {
464                     SoftReference bankRef = (SoftReference)arrays.remove(idx);
465                     memoryUsed -= getDataBankSize(arrayType,
466                                                   (int)numBanks,
467                                                   (int)arrayLength);
468                     if(idx == 0) {
469                         recycledArrays.remove(key);
470                     }
471 
472                     Object array = bankRef.get();
473                     if(array != null) {
474                         return array;
475                     }
476 
477                     if(DEBUG) System.out.println("null reference");
478                 }
479             }
480         }
481 
482         // array is null
483         switch(arrayType) {
484         case DataBuffer.TYPE_BYTE:
485             return Array.newInstance(byte.class,
486                                       new int[]{(int)numBanks, (int)arrayLength});
487         case DataBuffer.TYPE_USHORT:
488         case DataBuffer.TYPE_SHORT:
489             return Array.newInstance(short.class,
490                                       new int[]{(int)numBanks, (int)arrayLength});
491         case DataBuffer.TYPE_INT:
492             return Array.newInstance(int.class,
493                                       new int[]{(int)numBanks, (int)arrayLength});
494         case DataBuffer.TYPE_FLOAT:
495             return Array.newInstance(float.class,
496                                       new int[]{(int)numBanks, (int)arrayLength});
497         case DataBuffer.TYPE_DOUBLE:
498             return Array.newInstance(double.class,
499                                       new int[]{(int)numBanks, (int)arrayLength});
500         default:
501             //throw new IllegalArgumentException("Unsupported Data Type");
502             return null;
503         }
504 
505         //if(DEBUG) System.out.println("getRecycledArray() returning "+array);
506     }
507 }
508