| /* Copyright (C) 2004, 2006 Free Software Foundation |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package java.awt.image; |
| |
| import java.awt.RenderingHints; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.util.Arrays; |
| |
| /** |
| * RescaleOp is a filter that changes each pixel by a scaling factor and offset. |
| * |
| * For filtering Rasters, either one scaling factor and offset can be specified, |
| * which will be applied to all bands; or a scaling factor and offset can be |
| * specified for each band. |
| * |
| * For BufferedImages, the scaling may apply to both color and alpha components. |
| * If only one scaling factor is provided, or if the number of factors provided |
| * equals the number of color components, the scaling is performed on all color |
| * components. Otherwise, the scaling is performed on all components including |
| * alpha. Alpha premultiplication is ignored. |
| * |
| * After filtering, if color conversion is necessary, the conversion happens, |
| * taking alpha premultiplication into account. |
| * |
| * @author Jerry Quinn (jlquinn@optonline.net) |
| * @author Francis Kung (fkung@redhat.com) |
| */ |
| public class RescaleOp implements BufferedImageOp, RasterOp |
| { |
| private float[] scale; |
| private float[] offsets; |
| private RenderingHints hints = null; |
| |
| /** |
| * Create a new RescaleOp object using the given scale factors and offsets. |
| * |
| * The length of the arrays must be equal to the number of bands (or number of |
| * data or color components) of the raster/image that this Op will be used on, |
| * otherwise an IllegalArgumentException will be thrown when calling the |
| * filter method. |
| * |
| * @param scaleFactors an array of scale factors. |
| * @param offsets an array of offsets. |
| * @param hints any rendering hints to use (can be null). |
| * @throws NullPointerException if the scaleFactors or offsets array is null. |
| */ |
| public RescaleOp(float[] scaleFactors, |
| float[] offsets, |
| RenderingHints hints) |
| { |
| int length = Math.min(scaleFactors.length, offsets.length); |
| |
| scale = new float[length]; |
| System.arraycopy(scaleFactors, 0, this.scale, 0, length); |
| |
| this.offsets = new float[length]; |
| System.arraycopy(offsets, 0, this.offsets, 0, length); |
| |
| this.hints = hints; |
| } |
| |
| /** |
| * Create a new RescaleOp object using the given scale factor and offset. |
| * |
| * The same scale factor and offset will be used on all bands/components. |
| * |
| * @param scaleFactor the scale factor to use. |
| * @param offset the offset to use. |
| * @param hints any rendering hints to use (can be null). |
| */ |
| public RescaleOp(float scaleFactor, |
| float offset, |
| RenderingHints hints) |
| { |
| scale = new float[]{ scaleFactor }; |
| offsets = new float[]{offset}; |
| this.hints = hints; |
| } |
| |
| /** |
| * Returns the scaling factors. This method accepts an optional array, which |
| * will be used to store the factors if not null (this avoids allocating a |
| * new array). If this array is too small to hold all the scaling factors, |
| * the array will be filled and the remaining factors discarded. |
| * |
| * @param scaleFactors array to store the scaling factors in (can be null). |
| * @return an array of scaling factors. |
| */ |
| public final float[] getScaleFactors(float[] scaleFactors) |
| { |
| if (scaleFactors == null) |
| scaleFactors = new float[scale.length]; |
| System.arraycopy(scale, 0, scaleFactors, 0, Math.min(scale.length, |
| scaleFactors.length)); |
| return scaleFactors; |
| } |
| |
| /** |
| * Returns the offsets. This method accepts an optional array, which |
| * will be used to store the offsets if not null (this avoids allocating a |
| * new array). If this array is too small to hold all the offsets, the array |
| * will be filled and the remaining factors discarded. |
| * |
| * @param offsets array to store the offsets in (can be null). |
| * @return an array of offsets. |
| */ |
| public final float[] getOffsets(float[] offsets) |
| { |
| if (offsets == null) |
| offsets = new float[this.offsets.length]; |
| System.arraycopy(this.offsets, 0, offsets, 0, Math.min(this.offsets.length, |
| offsets.length)); |
| return offsets; |
| } |
| |
| /** |
| * Returns the number of scaling factors / offsets. |
| * |
| * @return the number of scaling factors / offsets. |
| */ |
| public final int getNumFactors() |
| { |
| return scale.length; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.BufferedImageOp#getRenderingHints() |
| */ |
| public final RenderingHints getRenderingHints() |
| { |
| return hints; |
| } |
| |
| /** |
| * Converts the source image using the scale factors and offsets specified in |
| * the constructor. The resulting image is stored in the destination image if |
| * one is provided; otherwise a new BufferedImage is created and returned. |
| * |
| * The source image cannot use an IndexColorModel, and the destination image |
| * (if one is provided) must have the same size. |
| * |
| * If the final value of a sample is beyond the range of the color model, it |
| * will be clipped to the appropriate maximum / minimum. |
| * |
| * @param src The source image. |
| * @param dst The destination image. |
| * @throws IllegalArgumentException if the rasters and/or color spaces are |
| * incompatible. |
| * @return The rescaled image. |
| */ |
| public final BufferedImage filter(BufferedImage src, BufferedImage dst) |
| { |
| // Initial checks |
| if (scale.length != 1 |
| && scale.length != src.getColorModel().getNumComponents() |
| && (scale.length != src.getColorModel().getNumColorComponents())) |
| throw new IllegalArgumentException("Source image has wrong number of " |
| + "bands for these scaling factors."); |
| |
| if (dst == null) |
| dst = createCompatibleDestImage(src, null); |
| else if (src.getHeight() != dst.getHeight() |
| || src.getWidth() != dst.getWidth()) |
| throw new IllegalArgumentException("Source and destination images are " |
| + "different sizes."); |
| |
| // Prepare for possible colorspace conversion |
| BufferedImage dst2 = dst; |
| if (dst.getColorModel().getColorSpace().getType() != src.getColorModel().getColorSpace().getType()) |
| dst2 = createCompatibleDestImage(src, src.getColorModel()); |
| |
| // Figure out how many bands to scale |
| int numBands = scale.length; |
| if (scale.length == 1) |
| numBands = src.getColorModel().getNumColorComponents(); |
| boolean[] bands = new boolean[numBands]; |
| // this assumes the alpha, if present, is the last band |
| Arrays.fill(bands, true); |
| |
| // Perform rescaling |
| filter(src.getRaster(), dst2.getRaster(), bands); |
| |
| // Copy alpha band if needed (ie if it exists and wasn't scaled) |
| // NOTE: This assumes the alpha component is the last band! |
| if (src.getColorModel().hasAlpha() |
| && numBands == src.getColorModel().getNumColorComponents()) |
| { |
| |
| dst2.getRaster().setSamples(0, 0, src.getWidth(), src.getHeight(), |
| numBands, |
| src.getRaster().getSamples(0, 0, |
| src.getWidth(), |
| src.getHeight(), |
| numBands, |
| (int[]) null)); |
| } |
| |
| // Perform colorspace conversion if needed |
| if (dst != dst2) |
| new ColorConvertOp(hints).filter(dst2, dst); |
| |
| return dst; |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.RasterOp#filter(java.awt.image.Raster, java.awt.image.WritableRaster) |
| */ |
| public final WritableRaster filter(Raster src, WritableRaster dest) |
| { |
| // Required sanity checks |
| if (scale.length != 1 && scale.length != src.numBands) |
| throw new IllegalArgumentException("Number of rasters is incompatible " |
| + "with the number of scaling " |
| + "factors provided."); |
| |
| if (dest == null) |
| dest = src.createCompatibleWritableRaster(); |
| else if (src.getHeight() != dest.getHeight() |
| || src.getWidth() != dest.getWidth()) |
| throw new IllegalArgumentException("Source and destination rasters are " |
| + "different sizes."); |
| else if (src.numBands != dest.numBands) |
| throw new IllegalArgumentException("Source and destination rasters " |
| + "are incompatible."); |
| |
| // Filter all bands |
| boolean[] bands = new boolean[src.getNumBands()]; |
| Arrays.fill(bands, true); |
| return filter(src, dest, bands); |
| } |
| |
| /** |
| * Perform raster-based filtering on a selected number of bands. |
| * |
| * The length of the bands array should equal the number of bands; a true |
| * element indicates filtering should happen on the corresponding band, while |
| * a false element will skip the band. |
| * |
| * The rasters are assumed to be compatible and non-null. |
| * |
| * @param src the source raster. |
| * @param dest the destination raster. |
| * @param bands an array indicating which bands to filter. |
| * @throws NullPointerException if any parameter is null. |
| * @throws ArrayIndexOutOfBoundsException if the bands array is too small. |
| * @return the destination raster. |
| */ |
| private WritableRaster filter(Raster src, WritableRaster dest, boolean[] bands) |
| { |
| int[] values = new int[src.getHeight() * src.getWidth()]; |
| float scaleFactor, offset; |
| |
| // Find max sample value, to be used for clipping later |
| int[] maxValue = src.getSampleModel().getSampleSize(); |
| for (int i = 0; i < maxValue.length; i++) |
| maxValue[i] = (int)Math.pow(2, maxValue[i]) - 1; |
| |
| // TODO: can this be optimized further? |
| // Filter all samples of all requested bands |
| for (int band = 0; band < bands.length; band++) |
| if (bands[band]) |
| { |
| values = src.getSamples(src.getMinX(), src.getMinY(), src.getWidth(), |
| src.getHeight(), band, values); |
| |
| if (scale.length == 1) |
| { |
| scaleFactor = scale[0]; |
| offset = offsets[0]; |
| } |
| else |
| { |
| scaleFactor = scale[band]; |
| offset = offsets[band]; |
| } |
| |
| for (int i = 0; i < values.length; i++) |
| { |
| values[i] = (int) (values[i] * scaleFactor + offset); |
| |
| // Clip if needed |
| if (values[i] < 0) |
| values[i] = 0; |
| if (values[i] > maxValue[band]) |
| values[i] = maxValue[band]; |
| } |
| |
| dest.setSamples(dest.getMinX(), dest.getMinY(), dest.getWidth(), |
| dest.getHeight(), band, values); |
| } |
| |
| return dest; |
| } |
| |
| /* |
| * (non-Javadoc) |
| * |
| * @see java.awt.image.BufferedImageOp#createCompatibleDestImage(java.awt.image.BufferedImage, |
| * java.awt.image.ColorModel) |
| */ |
| public BufferedImage createCompatibleDestImage(BufferedImage src, |
| ColorModel dstCM) |
| { |
| if (dstCM == null) |
| return new BufferedImage(src.getWidth(), src.getHeight(), src.getType()); |
| |
| return new BufferedImage(dstCM, |
| src.getRaster().createCompatibleWritableRaster(), |
| src.isAlphaPremultiplied(), null); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.RasterOp#createCompatibleDestRaster(java.awt.image.Raster) |
| */ |
| public WritableRaster createCompatibleDestRaster(Raster src) |
| { |
| return src.createCompatibleWritableRaster(); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.BufferedImageOp#getBounds2D(java.awt.image.BufferedImage) |
| */ |
| public final Rectangle2D getBounds2D(BufferedImage src) |
| { |
| return src.getRaster().getBounds(); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.RasterOp#getBounds2D(java.awt.image.Raster) |
| */ |
| public final Rectangle2D getBounds2D(Raster src) |
| { |
| return src.getBounds(); |
| } |
| |
| /* (non-Javadoc) |
| * @see java.awt.image.BufferedImageOp#getPoint2D(java.awt.geom.Point2D, java.awt.geom.Point2D) |
| */ |
| public final Point2D getPoint2D(Point2D src, Point2D dst) |
| { |
| if (dst == null) |
| dst = (Point2D) src.clone(); |
| else |
| dst.setLocation(src); |
| |
| return dst; |
| } |
| |
| } |