Java Source Code: org.geotools.image.imageio.RecyclingTileFactory


   1: package org.geotools.image.imageio;
   2: 
   3: import java.awt.Point;
   4: import java.awt.image.ComponentSampleModel;
   5: import java.awt.image.DataBuffer;
   6: import java.awt.image.DataBufferByte;
   7: import java.awt.image.DataBufferInt;
   8: import java.awt.image.DataBufferShort;
   9: import java.awt.image.DataBufferUShort;
  10: import java.awt.image.MultiPixelPackedSampleModel;
  11: import java.awt.image.Raster;
  12: import java.awt.image.SampleModel;
  13: import java.awt.image.SinglePixelPackedSampleModel;
  14: import java.awt.image.WritableRaster;
  15: import java.util.ArrayList;
  16: import java.util.Arrays;
  17: import java.util.HashMap;
  18: import java.util.Observable;
  19: import java.util.Observer;
  20: 
  21: import javax.media.jai.CachedTile;
  22: import javax.media.jai.TileCache;
  23: import javax.media.jai.TileFactory;
  24: import javax.media.jai.TileRecycler;
  25: 
  26: import com.sun.media.jai.util.DataBufferUtils;
  27: 
  28: /**
  29:  * A simple implementation of <code>TileFactory</code> wherein the tiles
  30:  * returned from <code>createTile()</code> attempt to re-use primitive arrays
  31:  * provided by the <code>TileRecycler</code> method <code>recycleTile()</code>.
  32:  * 
  33:  * <p>
  34:  * A simple example of the use of this class is as follows wherein image files
  35:  * are read, each image is filtered, and each output written to a file:
  36:  * 
  37:  * <pre>
  38:  * String[] sourceFiles; // source file paths
  39:  * KernelJAI kernel; // filtering kernel
  40:  * 
  41:  * // Create a RenderingHints object and set hints.
  42:  * RenderingHints rh = new RenderingHints(null);
  43:  * RecyclingTileFactory rtf = new RecyclingTileFactory();
  44:  * rh.put(JAI.KEY_TILE_RECYCLER, rtf);
  45:  * rh.put(JAI.KEY_TILE_FACTORY, rtf);
  46:  * rh.put(JAI.KEY_IMAGE_LAYOUT, new ImageLayout().setTileWidth(32).setTileHeight(
  47:  *         32));
  48:  * 
  49:  * int counter = 0;
  50:  * 
  51:  * // Read each image, filter it, and save the output to a file.
  52:	   * for (int i = 0; i &lt; sourceFiles.length; i++) {
  53: *     PlanarImage source = JAI.create(&quot;fileload&quot;, sourceFiles[i]);
  54: *     ParameterBlock pb = (new ParameterBlock()).addSource(source).add(kernel);
  55: * 
  56: *     // The TileFactory hint will cause tiles to be created by 'rtf'.
  57: *     RenderedOp dest = JAI.create(&quot;convolve&quot;, pb, rh);
  58: *     String fileName = &quot;image_&quot; + (++counter) + &quot;.tif&quot;;
  59: *     JAI.create(&quot;filestore&quot;, dest, fileName);
  60: * 
  61: *     // The TileRecycler hint will cause arrays to be reused by 'rtf'.
  62: *     dest.dispose();
  63: * }
  64: * </pre>
  65: * 
  66: * In the above code, if the <code>SampleModel</code> of all source images is
  67: * identical, then data arrays should only be created in the first iteration.
  68: * </p>
  69: * 
  70: * @since JAI 1.1.2
  71: */
  72:public class RecyclingTileFactory extends Observable implements TileFactory,
  73:	          TileRecycler, Observer {
  74:
  75:    private static final boolean DEBUG = false;
  76:
  77:    /**
  78:     * Cache of recycled arrays. The key in this mapping is a <code>Long</code>
  79:     * which is formed for a given two-dimensional array as
  80:     * 
  81:     * <pre>
  82:     * long type; // DataBuffer.TYPE_*
  83:     * 
  84:     * long numBanks; // Number of banks
  85:     * 
  86:     * long size; // Size of each bank
  87:     * 
  88:     * Long key = new Long((type &lt;&lt; 56) | (numBanks &lt;&lt; 32) | size);
  89:     * </pre>
  90:     * 
  91:     * where the value of <code>type</code> is one of the constants
  92:     * <code>DataBuffer.TYPE_*</code>. The value corresponding to each key is
  93:     * an <code>ArrayList</code> of <code>SoftReferences</code> to the
  94:     * internal data banks of <code>DataBuffer</code>s of tiles wherein the
  95:     * data bank array has the type and dimensions implied by the key.
  96:     */
  97:    private HashMap recycledArrays = new HashMap(32);
  98:
  99:    /**
 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, int size) {
 178:        int bytesPerElement = 0;
 179:	          switch (dataType) {
 180:        case DataBuffer.TYPE_BYTE:
 181:            bytesPerElement = 1;
 182:            break;
 183:        case DataBuffer.TYPE_USHORT:
 184:        case DataBuffer.TYPE_SHORT:
 185:            bytesPerElement = 2;
 186:            break;
 187:        case DataBuffer.TYPE_INT:
 188:        case DataBuffer.TYPE_FLOAT:
 189:            bytesPerElement = 4;
 190:            break;
 191:        case DataBuffer.TYPE_DOUBLE:
 192:            bytesPerElement = 8;
 193:            break;
 194:        default:
 195:            throw new UnsupportedOperationException("");
 196:
 197:        }
 198:
 199:        return numBanks * size * bytesPerElement;
 200:    }
 201:
 202:    /**
 203:     * Constructs a <code>RecyclingTileFactory</code>.
 204:     */
 205:	      public RecyclingTileFactory(Observable tileCache) {
 206:        if (tileCache instanceof TileCache)
 207:            this.tileCache = tileCache;
 208:        else
 209:            throw new IllegalArgumentException(
 210:                    "Provided argument is not of type TileCache");
 211:        tileCache.addObserver(this);
 212:    }
 213:
 214:    /**
 215:     * Returns <code>true</code>.
 216:     */
 217:	      public boolean canReclaimMemory() {
 218:        return true;
 219:    }
 220:
 221:    /**
 222:     * Returns <code>true</code>.
 223:     */
 224:	      public boolean isMemoryCache() {
 225:        return true;
 226:    }
 227:
 228:	      public long getMemoryUsed() {
 229:        return memoryUsed;
 230:    }
 231:
 232:	      public void flush() {
 233:	          synchronized (recycledArrays) {
 234:            recycledArrays.clear();
 235:            memoryUsed = 0L;
 236:        }
 237:    }
 238:
 239:	      public WritableRaster createTile(SampleModel sampleModel, Point location) {
 240:
 241:	          if (sampleModel == null) {
 242:            throw new IllegalArgumentException("sampleModel == null!");
 243:        }
 244:
 245:	          if (location == null) {
 246:            location = new Point(0, 0);
 247:        }
 248:
 249:        DataBuffer db = null;
 250:
 251:        int type = sampleModel.getTransferType();
 252:        long numBanks = 0;
 253:        long size = 0;
 254:
 255:	          if (sampleModel instanceof ComponentSampleModel) {
 256:            ComponentSampleModel csm = (ComponentSampleModel) sampleModel;
 257:            numBanks = getNumBanksCSM(csm);
 258:            size = getBufferSizeCSM(csm);
 259:        } else if (sampleModel instanceof MultiPixelPackedSampleModel) {
 260:            MultiPixelPackedSampleModel mppsm = (MultiPixelPackedSampleModel) sampleModel;
 261:            numBanks = 1;
 262:            int dataTypeSize = DataBuffer.getDataTypeSize(type);
 263:            size = mppsm.getScanlineStride() * mppsm.getHeight()
 264:                    + (mppsm.getDataBitOffset() + dataTypeSize - 1)
 265:                    / dataTypeSize;
 266:        } else if (sampleModel instanceof SinglePixelPackedSampleModel) {
 267:            SinglePixelPackedSampleModel sppsm = (SinglePixelPackedSampleModel) sampleModel;
 268:            numBanks = 1;
 269:            size = sppsm.getScanlineStride() * (sppsm.getHeight() - 1)
 270:                    + sppsm.getWidth();
 271:        }
 272:
 273:	          if (size != 0) {
 274:            Object array = getRecycledArray(type, numBanks, size);
 275:	              if (array != null) {
 276:	                  switch (type) {
 277:	                  case DataBuffer.TYPE_BYTE: {
 278:                    byte[][] bankData = (byte[][]) array;
 279:	                      for (int i = 0; i < numBanks; i++) {
 280:                        Arrays.fill(bankData[i], (byte) 0);
 281:                    }
 282:                    db = new DataBufferByte(bankData, (int) size);
 283:                }
 284:                    break;
 285:	                  case DataBuffer.TYPE_USHORT: {
 286:                    short[][] bankData = (short[][]) array;
 287:	                      for (int i = 0; i < numBanks; i++) {
 288:                        Arrays.fill(bankData[i], (short) 0);
 289:                    }
 290:                    db = new DataBufferUShort(bankData, (int) size);
 291:                }
 292:                    break;
 293:	                  case DataBuffer.TYPE_SHORT: {
 294:                    short[][] bankData = (short[][]) array;
 295:	                      for (int i = 0; i < numBanks; i++) {
 296:                        Arrays.fill(bankData[i], (short) 0);
 297:                    }
 298:                    db = new DataBufferShort(bankData, (int) size);
 299:                }
 300:                    break;
 301:	                  case DataBuffer.TYPE_INT: {
 302:                    int[][] bankData = (int[][]) array;
 303:	                      for (int i = 0; i < numBanks; i++) {
 304:                        Arrays.fill(bankData[i], 0);
 305:                    }
 306:                    db = new DataBufferInt(bankData, (int) size);
 307:                }
 308:                    break;
 309:	                  case DataBuffer.TYPE_FLOAT: {
 310:                    float[][] bankData = (float[][]) array;
 311:	                      for (int i = 0; i < numBanks; i++) {
 312:                        Arrays.fill(bankData[i], 0.0F);
 313:                    }
 314:                    db = DataBufferUtils.createDataBufferFloat(bankData,
 315:                            (int) size);
 316:                }
 317:                    break;
 318:	                  case DataBuffer.TYPE_DOUBLE: {
 319:                    double[][] bankData = (double[][]) array;
 320:	                      for (int i = 0; i < numBanks; i++) {
 321:                        Arrays.fill(bankData[i], 0.0);
 322:                    }
 323:                    db = DataBufferUtils.createDataBufferDouble(bankData,
 324:                            (int) size);
 325:                }
 326:                    break;
 327:                default:
 328:                    throw new IllegalArgumentException("");
 329:                }
 330:
 331:	                  if (DEBUG) {
 332:                    System.out.println(getClass().getName()
 333:                            + " Using a recycled array");// XXX
 334:                    // (new Throwable()).printStackTrace(); // XXX
 335:                }
 336:            } else if (DEBUG) {
 337:                System.out.println(getClass().getName() + " No type " + type
 338:                        + " array[" + numBanks + "][" + size + "] available");
 339:            }
 340:        } else if (DEBUG) {
 341:            System.out.println(getClass().getName() + " Size is zero");
 342:        }
 343:
 344:	          if (db == null) {
 345:	              if (DEBUG) {
 346:                System.out.println(getClass().getName()
 347:                        + " Creating new DataBuffer");// XXX
 348:            }
 349:            // (new Throwable()).printStackTrace(); // XXX
 350:            db = sampleModel.createDataBuffer();
 351:        }
 352:
 353:        return Raster.createWritableRaster(sampleModel, db, location);
 354:    }
 355:
 356:    /**
 357:     * Recycle the given tile.
 358:     */
 359:	      public void recycleTile(Raster tile) {
 360:        DataBuffer db = tile.getDataBuffer();
 361:
 362:        Long key = new Long(((long) db.getDataType() << 56)
 363:                | ((long) db.getNumBanks() << 32) | (long) db.getSize());
 364:
 365:	          if (DEBUG) {
 366:            System.out.println("Recycling array for: " + db.getDataType() + " "
 367:                    + db.getNumBanks() + " " + db.getSize());
 368:            // System.out.println("recycleTile(); key = "+key);
 369:        }
 370:
 371:	          synchronized (recycledArrays) {
 372:            Object value = recycledArrays.get(key);
 373:            ArrayList arrays = null;
 374:	              if (value != null) {
 375:                arrays = (ArrayList) value;
 376:            } else {
 377:                arrays = new ArrayList(10);
 378:            }
 379:
 380:            memoryUsed += getDataBankSize(db.getDataType(), db.getNumBanks(),
 381:                    db.getSize());
 382:
 383:            arrays.add(getBankReference(db));
 384:
 385:	              if (value == null) {
 386:                recycledArrays.put(key, arrays);
 387:            }
 388:        }
 389:    }
 390:
 391:    /**
 392:     * Retrieve an array of the specified type and length.
 393:     */
 394:    private Object getRecycledArray(int arrayType, long numBanks,
 395:	              long arrayLength) {
 396:        Long key = new Long(((long) arrayType << 56) | numBanks << 32
 397:                | arrayLength);
 398:
 399:	          if (DEBUG) {
 400:            System.out.println("Attempting to get array for: " + arrayType
 401:                    + " " + numBanks + " " + arrayLength);
 402:            // System.out.println("Attempting to get array for key "+key);
 403:        }
 404:
 405:	          synchronized (recycledArrays) {
 406:            Object value = recycledArrays.get(key);
 407:
 408:	              if (value != null) {
 409:                ArrayList arrays = (ArrayList) value;
 410:	                  for (int idx = arrays.size() - 1; idx >= 0; idx--) {
 411:                    Object array = arrays.remove(idx);
 412:                    memoryUsed -= getDataBankSize(arrayType, (int) numBanks,
 413:                            (int) arrayLength);
 414:	                      if (idx == 0) {
 415:                        recycledArrays.remove(key);
 416:                    }
 417:
 418:	                      if (array != null) {
 419:                        if (DEBUG)
 420:                            System.out.println("good reference");
 421:                        return array;
 422:                    }
 423:
 424:                    if (DEBUG)
 425:                        System.out.println("null reference");
 426:                }
 427:            }
 428:        }
 429:
 430:        // if(DEBUG) System.out.println("getRecycledArray() returning "+array);
 431:
 432:        return null;
 433:    }
 434:
 435:	      public void update(Observable o, Object arg) {
 436:	          if (o.equals(tileCache)) {
 437:	              if (arg instanceof CachedTile) {
 438:                CachedTile tile = (CachedTile) arg;
 439:	                  switch (tile.getAction()) {
 440:                case 1:
 441:                case 2:// REMOVE,REMOVE_FROM_FLUSH
 442:                    this.recycleTile(tile.getTile());
 443:                }
 444:            }
 445:        }
 446:    }
 447:}