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