001 /* Copyright (C) 2004, 2006 Free Software Foundation 002 003 This file is part of GNU Classpath. 004 005 GNU Classpath is free software; you can redistribute it and/or modify 006 it under the terms of the GNU General Public License as published by 007 the Free Software Foundation; either version 2, or (at your option) 008 any later version. 009 010 GNU Classpath is distributed in the hope that it will be useful, but 011 WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 General Public License for more details. 014 015 You should have received a copy of the GNU General Public License 016 along with GNU Classpath; see the file COPYING. If not, write to the 017 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 018 02110-1301 USA. 019 020 Linking this library statically or dynamically with other modules is 021 making a combined work based on this library. Thus, the terms and 022 conditions of the GNU General Public License cover the whole 023 combination. 024 025 As a special exception, the copyright holders of this library give you 026 permission to link this library with independent modules to produce an 027 executable, regardless of the license terms of these independent 028 modules, and to copy and distribute the resulting executable under 029 terms of your choice, provided that you also meet, for each linked 030 independent module, the terms and conditions of the license of that 031 module. An independent module is a module which is not derived from 032 or based on this library. If you modify this library, you may extend 033 this exception to your version of the library, but you are not 034 obligated to do so. If you do not wish to do so, delete this 035 exception statement from your version. */ 036 037 038 package java.awt.image; 039 040 import java.awt.RenderingHints; 041 import java.awt.geom.Point2D; 042 import java.awt.geom.Rectangle2D; 043 import java.util.Arrays; 044 045 /** 046 * RescaleOp is a filter that changes each pixel by a scaling factor and offset. 047 * 048 * For filtering Rasters, either one scaling factor and offset can be specified, 049 * which will be applied to all bands; or a scaling factor and offset can be 050 * specified for each band. 051 * 052 * For BufferedImages, the scaling may apply to both color and alpha components. 053 * If only one scaling factor is provided, or if the number of factors provided 054 * equals the number of color components, the scaling is performed on all color 055 * components. Otherwise, the scaling is performed on all components including 056 * alpha. Alpha premultiplication is ignored. 057 * 058 * After filtering, if color conversion is necessary, the conversion happens, 059 * taking alpha premultiplication into account. 060 * 061 * @author Jerry Quinn (jlquinn@optonline.net) 062 * @author Francis Kung (fkung@redhat.com) 063 */ 064 public class RescaleOp implements BufferedImageOp, RasterOp 065 { 066 private float[] scale; 067 private float[] offsets; 068 private RenderingHints hints = null; 069 070 /** 071 * Create a new RescaleOp object using the given scale factors and offsets. 072 * 073 * The length of the arrays must be equal to the number of bands (or number of 074 * data or color components) of the raster/image that this Op will be used on, 075 * otherwise an IllegalArgumentException will be thrown when calling the 076 * filter method. 077 * 078 * @param scaleFactors an array of scale factors. 079 * @param offsets an array of offsets. 080 * @param hints any rendering hints to use (can be null). 081 * @throws NullPointerException if the scaleFactors or offsets array is null. 082 */ 083 public RescaleOp(float[] scaleFactors, 084 float[] offsets, 085 RenderingHints hints) 086 { 087 int length = Math.min(scaleFactors.length, offsets.length); 088 089 scale = new float[length]; 090 System.arraycopy(scaleFactors, 0, this.scale, 0, length); 091 092 this.offsets = new float[length]; 093 System.arraycopy(offsets, 0, this.offsets, 0, length); 094 095 this.hints = hints; 096 } 097 098 /** 099 * 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 }