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 }