Frames | No Frames |
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: }