Source for java.awt.image.RescaleOp

   1: /* Copyright (C) 2004, 2006  Free Software Foundation
   2: 
   3: This file is part of GNU Classpath.
   4: 
   5: GNU Classpath is free software; you can redistribute it and/or modify
   6: it under the terms of the GNU General Public License as published by
   7: the Free Software Foundation; either version 2, or (at your option)
   8: any later version.
   9: 
  10: GNU Classpath is distributed in the hope that it will be useful, but
  11: WITHOUT ANY WARRANTY; without even the implied warranty of
  12: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13: General Public License for more details.
  14: 
  15: You should have received a copy of the GNU General Public License
  16: along with GNU Classpath; see the file COPYING.  If not, write to the
  17: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  18: 02110-1301 USA.
  19: 
  20: Linking this library statically or dynamically with other modules is
  21: making a combined work based on this library.  Thus, the terms and
  22: conditions of the GNU General Public License cover the whole
  23: combination.
  24: 
  25: As a special exception, the copyright holders of this library give you
  26: permission to link this library with independent modules to produce an
  27: executable, regardless of the license terms of these independent
  28: modules, and to copy and distribute the resulting executable under
  29: terms of your choice, provided that you also meet, for each linked
  30: independent module, the terms and conditions of the license of that
  31: module.  An independent module is a module which is not derived from
  32: or based on this library.  If you modify this library, you may extend
  33: this exception to your version of the library, but you are not
  34: obligated to do so.  If you do not wish to do so, delete this
  35: exception statement from your version. */
  36: 
  37: 
  38: package java.awt.image;
  39: 
  40: import java.awt.RenderingHints;
  41: import java.awt.geom.Point2D;
  42: import java.awt.geom.Rectangle2D;
  43: import java.util.Arrays;
  44: 
  45: /**
  46:  * RescaleOp is a filter that changes each pixel by a scaling factor and offset.
  47:  * 
  48:  * For filtering Rasters, either one scaling factor and offset can be specified,
  49:  * which will be applied to all bands; or a scaling factor and offset can be
  50:  * specified for each band.
  51:  * 
  52:  * For BufferedImages, the scaling may apply to both color and alpha components.
  53:  * If only one scaling factor is provided, or if the number of factors provided
  54:  * equals the number of color components, the scaling is performed on all color
  55:  * components.  Otherwise, the scaling is performed on all components including
  56:  * alpha.  Alpha premultiplication is ignored.
  57:  * 
  58:  * After filtering, if color conversion is necessary, the conversion happens,
  59:  * taking alpha premultiplication into account.
  60:  * 
  61:  * @author Jerry Quinn (jlquinn@optonline.net)
  62:  * @author Francis Kung (fkung@redhat.com)
  63:  */
  64: public class RescaleOp implements BufferedImageOp, RasterOp
  65: {
  66:   private float[] scale;
  67:   private float[] offsets;
  68:   private RenderingHints hints = null;
  69:   
  70:   /**
  71:    * Create a new RescaleOp object using the given scale factors and offsets.
  72:    * 
  73:    * The length of the arrays must be equal to the number of bands (or number of
  74:    * data or color components) of the raster/image that this Op will be used on,
  75:    * otherwise an IllegalArgumentException will be thrown when calling the
  76:    * filter method.
  77:    *  
  78:    * @param scaleFactors an array of scale factors.
  79:    * @param offsets an array of offsets.
  80:    * @param hints any rendering hints to use (can be null).
  81:    * @throws NullPointerException if the scaleFactors or offsets array is null.
  82:    */
  83:   public RescaleOp(float[] scaleFactors,
  84:            float[] offsets,
  85:            RenderingHints hints)
  86:   {
  87:     int length = Math.min(scaleFactors.length, offsets.length);
  88:     
  89:     scale = new float[length];
  90:     System.arraycopy(scaleFactors, 0, this.scale, 0, length);
  91:     
  92:     this.offsets = new float[length];
  93:     System.arraycopy(offsets, 0, this.offsets, 0, length);
  94:     
  95:     this.hints = hints;
  96:   }
  97:   
  98:   /**
  99:    * Create a new RescaleOp object using the given scale factor and offset.
 100:    * 
 101:    * The same scale factor and offset will be used on all bands/components.
 102:    *  
 103:    * @param scaleFactor the scale factor to use.
 104:    * @param offset the offset to use.
 105:    * @param hints any rendering hints to use (can be null).
 106:    */
 107:   public RescaleOp(float scaleFactor,
 108:            float offset,
 109:            RenderingHints hints)
 110:   {
 111:     scale = new float[]{ scaleFactor };
 112:     offsets = new float[]{offset};
 113:     this.hints = hints;
 114:   }
 115: 
 116:   /**
 117:    * Returns the scaling factors.  This method accepts an optional array, which
 118:    * will be used to store the factors if not null (this avoids allocating a 
 119:    * new array).  If this array is too small to hold all the scaling factors,
 120:    * the array will be filled and the remaining factors discarded.
 121:    * 
 122:    * @param scaleFactors array to store the scaling factors in (can be null).
 123:    * @return an array of scaling factors.
 124:    */
 125:   public final float[] getScaleFactors(float[] scaleFactors)
 126:   {
 127:     if (scaleFactors == null)
 128:       scaleFactors = new float[scale.length];
 129:     System.arraycopy(scale, 0, scaleFactors, 0, Math.min(scale.length,
 130:                                                          scaleFactors.length));
 131:     return scaleFactors;
 132:   }
 133: 
 134:   /**
 135:    * Returns the offsets.  This method accepts an optional array, which
 136:    * will be used to store the offsets if not null (this avoids allocating a 
 137:    * new array).  If this array is too small to hold all the offsets, the array 
 138:    * will be filled and the remaining factors discarded.
 139:    * 
 140:    * @param offsets array to store the offsets in (can be null).
 141:    * @return an array of offsets.
 142:    */
 143:   public final float[] getOffsets(float[] offsets)
 144:   {
 145:     if (offsets == null)
 146:       offsets = new float[this.offsets.length];
 147:     System.arraycopy(this.offsets, 0, offsets, 0, Math.min(this.offsets.length,
 148:                                                            offsets.length));
 149:     return offsets;
 150:   }
 151: 
 152:   /**
 153:    * Returns the number of scaling factors / offsets.
 154:    * 
 155:    * @return the number of scaling factors / offsets.
 156:    */
 157:   public final int getNumFactors()
 158:   {
 159:     return scale.length;
 160:   }
 161: 
 162:   /* (non-Javadoc)
 163:    * @see java.awt.image.BufferedImageOp#getRenderingHints()
 164:    */
 165:   public final RenderingHints getRenderingHints()
 166:   {
 167:     return hints;
 168:   }
 169: 
 170:   /**
 171:    * Converts the source image using the scale factors and offsets specified in 
 172:    * the constructor.  The resulting image is stored in the destination image if 
 173:    * one is provided; otherwise a new BufferedImage is created and returned. 
 174:    * 
 175:    * The source image cannot use an IndexColorModel, and the destination image
 176:    * (if one is provided) must have the same size.
 177:    * 
 178:    * If the final value of a sample is beyond the range of the color model, it
 179:    * will be clipped to the appropriate maximum / minimum.
 180:    *
 181:    * @param src The source image.
 182:    * @param dst The destination image.
 183:    * @throws IllegalArgumentException if the rasters and/or color spaces are
 184:    *            incompatible.
 185:    * @return The rescaled image.
 186:    */
 187:   public final BufferedImage filter(BufferedImage src, BufferedImage dst)
 188:   {
 189:     // Initial checks
 190:     if (scale.length != 1
 191:         && scale.length != src.getColorModel().getNumComponents()
 192:         && (scale.length != src.getColorModel().getNumColorComponents()))
 193:       throw new IllegalArgumentException("Source image has wrong number of "
 194:                                          + "bands for these scaling factors.");
 195: 
 196:     if (dst == null)
 197:       dst = createCompatibleDestImage(src, null);
 198:     else if (src.getHeight() != dst.getHeight()
 199:              || src.getWidth() != dst.getWidth())
 200:       throw new IllegalArgumentException("Source and destination images are "
 201:                                          + "different sizes.");
 202: 
 203:     // Prepare for possible colorspace conversion
 204:     BufferedImage dst2 = dst;
 205:     if (dst.getColorModel().getColorSpace().getType() != src.getColorModel().getColorSpace().getType())
 206:       dst2 = createCompatibleDestImage(src, src.getColorModel());
 207: 
 208:     // Figure out how many bands to scale
 209:     int numBands = scale.length;
 210:     if (scale.length == 1)
 211:       numBands = src.getColorModel().getNumColorComponents();
 212:     boolean[] bands = new boolean[numBands];
 213:     // this assumes the alpha, if present, is the last band
 214:     Arrays.fill(bands, true);
 215: 
 216:     // Perform rescaling
 217:     filter(src.getRaster(), dst2.getRaster(), bands);
 218: 
 219:     // Copy alpha band if needed (ie if it exists and wasn't scaled)
 220:     // NOTE: This assumes the alpha component is the last band!
 221:     if (src.getColorModel().hasAlpha()
 222:         && numBands == src.getColorModel().getNumColorComponents())
 223:       {
 224: 
 225:         dst2.getRaster().setSamples(0, 0, src.getWidth(), src.getHeight(),
 226:                                     numBands,
 227:                                     src.getRaster().getSamples(0, 0,
 228:                                                                src.getWidth(),
 229:                                                                src.getHeight(),
 230:                                                                numBands,
 231:                                                                (int[]) null));
 232:       }
 233: 
 234:     // Perform colorspace conversion if needed
 235:     if (dst != dst2)
 236:       new ColorConvertOp(hints).filter(dst2, dst);
 237: 
 238:     return dst;
 239:   }
 240: 
 241:   /* (non-Javadoc)
 242:    * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster)
 243:    */
 244:   public final WritableRaster filter(Raster src, WritableRaster dest)
 245:   {
 246:     // Required sanity checks
 247:     if (scale.length != 1 && scale.length != src.numBands)
 248:       throw new IllegalArgumentException("Number of rasters is incompatible "
 249:                                              + "with the number of scaling "
 250:                                              + "factors provided.");
 251: 
 252:     if (dest == null)
 253:       dest = src.createCompatibleWritableRaster();
 254:     else if (src.getHeight() != dest.getHeight()
 255:              || src.getWidth() != dest.getWidth())
 256:       throw new IllegalArgumentException("Source and destination rasters are "
 257:                                          + "different sizes.");
 258:     else if (src.numBands != dest.numBands)
 259:       throw new IllegalArgumentException("Source and destination rasters "
 260:                                          + "are incompatible.");
 261: 
 262:     // Filter all bands
 263:     boolean[] bands = new boolean[src.getNumBands()];
 264:     Arrays.fill(bands, true);
 265:     return filter(src, dest, bands);
 266:   }
 267:   
 268:   /**
 269:    * Perform raster-based filtering on a selected number of bands.
 270:    * 
 271:    * The length of the bands array should equal the number of bands; a true
 272:    * element indicates filtering should happen on the corresponding band, while
 273:    * a false element will skip the band.
 274:    * 
 275:    * The rasters are assumed to be compatible and non-null.
 276:    * 
 277:    * @param src the source raster.
 278:    * @param dest the destination raster.
 279:    * @param bands an array indicating which bands to filter.
 280:    * @throws NullPointerException if any parameter is null.
 281:    * @throws ArrayIndexOutOfBoundsException if the bands array is too small.
 282:    * @return the destination raster.
 283:    */
 284:   private WritableRaster filter(Raster src, WritableRaster dest, boolean[] bands)
 285:   {
 286:     int[] values = new int[src.getHeight() * src.getWidth()];
 287:     float scaleFactor, offset;
 288:     
 289:     // Find max sample value, to be used for clipping later
 290:     int[] maxValue = src.getSampleModel().getSampleSize();
 291:     for (int i = 0; i < maxValue.length; i++)
 292:       maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1;
 293:     
 294:     // TODO: can this be optimized further?
 295:     // Filter all samples of all requested bands
 296:     for (int band = 0; band < bands.length; band++)
 297:       if (bands[band])
 298:         {
 299:           values = src.getSamples(src.getMinX(), src.getMinY(), src.getWidth(),
 300:                                   src.getHeight(), band, values);
 301: 
 302:           if (scale.length == 1)
 303:             {
 304:               scaleFactor = scale[0];
 305:               offset = offsets[0];
 306:             }
 307:           else
 308:             {
 309:               scaleFactor = scale[band];
 310:               offset = offsets[band];
 311:             }
 312: 
 313:           for (int i = 0; i < values.length; i++)
 314:             {
 315:               values[i] = (int) (values[i] * scaleFactor + offset);
 316: 
 317:               // Clip if needed
 318:               if (values[i] < 0)
 319:                 values[i] = 0;
 320:               if (values[i] > maxValue[band])
 321:                 values[i] = maxValue[band];
 322:             }
 323: 
 324:           dest.setSamples(dest.getMinX(), dest.getMinY(), dest.getWidth(),
 325:                           dest.getHeight(), band, values);
 326:         }
 327:     
 328:     return dest;
 329:   }
 330: 
 331:   /*
 332:    * (non-Javadoc)
 333:    * 
 334:    * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage,
 335:    *      java.awt.image.ColorModel)
 336:    */
 337:   public BufferedImage createCompatibleDestImage(BufferedImage src,
 338:                          ColorModel dstCM)
 339:   {
 340:     if (dstCM == null)
 341:       return new BufferedImage(src.getWidth(), src.getHeight(), src.getType());
 342:     
 343:     return new BufferedImage(dstCM,
 344:                              src.getRaster().createCompatibleWritableRaster(),
 345:                              src.isAlphaPremultiplied(), null);
 346:   }
 347: 
 348:   /* (non-Javadoc)
 349:    * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster)
 350:    */
 351:   public WritableRaster createCompatibleDestRaster(Raster src)
 352:   {
 353:     return src.createCompatibleWritableRaster();
 354:   }
 355:   
 356:   /* (non-Javadoc)
 357:    * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage)
 358:    */
 359:   public final Rectangle2D getBounds2D(BufferedImage src)
 360:   {
 361:     return src.getRaster().getBounds();
 362:   }
 363: 
 364:   /* (non-Javadoc)
 365:    * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster)
 366:    */
 367:   public final Rectangle2D getBounds2D(Raster src)
 368:   {
 369:     return src.getBounds();
 370:   }
 371: 
 372:   /* (non-Javadoc)
 373:    * @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D)
 374:    */
 375:   public final Point2D getPoint2D(Point2D src, Point2D dst)
 376:   {
 377:     if (dst == null)
 378:       dst = (Point2D) src.clone();
 379:     else
 380:       dst.setLocation(src);
 381:     
 382:     return dst;
 383:   }
 384: 
 385: }