| /* AffineTransformOp.java -- This class performs affine |
| transformation between two images or rasters in 2 dimensions. |
| Copyright (C) 2004 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.Graphics2D; |
| import java.awt.Rectangle; |
| import java.awt.RenderingHints; |
| import java.awt.geom.AffineTransform; |
| import java.awt.geom.NoninvertibleTransformException; |
| import java.awt.geom.Point2D; |
| import java.awt.geom.Rectangle2D; |
| import java.util.Arrays; |
| |
| /** |
| * This class performs affine transformation between two images or |
| * rasters in 2 dimensions. |
| * |
| * @author Olga Rodimina (rodimina@redhat.com) |
| */ |
| public class AffineTransformOp implements BufferedImageOp, RasterOp |
| { |
| public static final int TYPE_NEAREST_NEIGHBOR = 1; |
| |
| public static final int TYPE_BILINEAR = 2; |
| |
| /** |
| * @since 1.5.0 |
| */ |
| public static final int TYPE_BICUBIC = 3; |
| |
| private AffineTransform transform; |
| private RenderingHints hints; |
| |
| /** |
| * Construct AffineTransformOp with the given xform and interpolationType. |
| * Interpolation type can be TYPE_BILINEAR, TYPE_BICUBIC or |
| * TYPE_NEAREST_NEIGHBOR. |
| * |
| * @param xform AffineTransform that will applied to the source image |
| * @param interpolationType type of interpolation used |
| */ |
| public AffineTransformOp (AffineTransform xform, int interpolationType) |
| { |
| this.transform = xform; |
| if (xform.getDeterminant() == 0) |
| throw new ImagingOpException(null); |
| |
| switch (interpolationType) |
| { |
| case TYPE_BILINEAR: |
| hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
| RenderingHints.VALUE_INTERPOLATION_BILINEAR); |
| break; |
| case TYPE_BICUBIC: |
| hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
| RenderingHints.VALUE_INTERPOLATION_BICUBIC); |
| break; |
| default: |
| hints = new RenderingHints (RenderingHints.KEY_INTERPOLATION, |
| RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR); |
| } |
| } |
| |
| /** |
| * Construct AffineTransformOp with the given xform and rendering hints. |
| * |
| * @param xform AffineTransform that will applied to the source image |
| * @param hints rendering hints that will be used during transformation |
| */ |
| public AffineTransformOp (AffineTransform xform, RenderingHints hints) |
| { |
| this.transform = xform; |
| this.hints = hints; |
| if (xform.getDeterminant() == 0) |
| throw new ImagingOpException(null); |
| } |
| |
| /** |
| * Creates empty BufferedImage with the size equal to that of the |
| * transformed image and correct number of bands. The newly created |
| * image is created with the specified ColorModel. |
| * If the ColorModel is equal to null, then image is created |
| * with the ColorModel of the source image. |
| * |
| * @param src source image |
| * @param destCM color model for the destination image |
| * @return new compatible destination image |
| */ |
| public BufferedImage createCompatibleDestImage (BufferedImage src, |
| ColorModel destCM) |
| { |
| |
| // if destCm is not specified, use color model of the source image |
| |
| if (destCM == null) |
| destCM = src.getColorModel (); |
| |
| return new BufferedImage (destCM, |
| createCompatibleDestRaster (src.getRaster ()), |
| src.isAlphaPremultiplied (), |
| null); |
| |
| } |
| |
| /** |
| * Creates empty WritableRaster with the size equal to the transformed |
| * source raster and correct number of bands |
| * |
| * @param src source raster |
| * @throws RasterFormatException if resulting width or height of raster is 0 |
| * @return new compatible raster |
| */ |
| public WritableRaster createCompatibleDestRaster (Raster src) |
| { |
| Rectangle rect = (Rectangle) getBounds2D (src); |
| |
| // throw RasterFormatException if resulting width or height of the |
| // transformed raster is 0 |
| |
| if (rect.getWidth () == 0 || rect.getHeight () == 0) |
| throw new RasterFormatException("width or height is 0"); |
| |
| return src.createCompatibleWritableRaster ((int) rect.getWidth (), |
| (int) rect.getHeight ()); |
| } |
| |
| /** |
| * Transforms source image using transform specified at the constructor. |
| * The resulting transformed image is stored in the destination image. |
| * |
| * @param src source image |
| * @param dst destination image |
| * @return transformed source image |
| */ |
| public final BufferedImage filter (BufferedImage src, BufferedImage dst) |
| { |
| |
| if (dst == src) |
| throw new IllegalArgumentException ("src image cannot be the same as the dst image"); |
| |
| // If the destination image is null, then BufferedImage is |
| // created with ColorModel of the source image |
| |
| if (dst == null) |
| dst = createCompatibleDestImage(src, src.getColorModel ()); |
| |
| // FIXME: Must check if color models of src and dst images are the same. |
| // If it is not, then source image should be converted to color model |
| // of the destination image |
| |
| Graphics2D gr = (Graphics2D) dst.createGraphics (); |
| gr.setRenderingHints (hints); |
| gr.drawImage (src, transform, null); |
| return dst; |
| |
| } |
| |
| /** |
| * Transforms source raster using transform specified at the constructor. |
| * The resulting raster is stored in the destination raster. |
| * |
| * @param src source raster |
| * @param dst destination raster |
| * @return transformed raster |
| */ |
| public final WritableRaster filter (Raster src, WritableRaster dst) |
| { |
| if (dst == src) |
| throw new IllegalArgumentException("src image cannot be the same as" |
| + " the dst image"); |
| |
| if (dst == null) |
| dst = createCompatibleDestRaster(src); |
| |
| if (src.getNumBands() != dst.getNumBands()) |
| throw new IllegalArgumentException("src and dst must have same number" |
| + " of bands"); |
| |
| double[] dpts = new double[dst.getWidth() * 2]; |
| double[] pts = new double[dst.getWidth() * 2]; |
| for (int x = 0; x < dst.getWidth(); x++) |
| { |
| dpts[2 * x] = x + dst.getMinX(); |
| dpts[2 * x + 1] = x; |
| } |
| Rectangle srcbounds = src.getBounds(); |
| if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR)) |
| { |
| for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++) |
| { |
| try { |
| transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2); |
| } catch (NoninvertibleTransformException e) { |
| // Can't happen since the constructor traps this |
| e.printStackTrace(); |
| } |
| |
| for (int x = 0; x < dst.getWidth(); x++) |
| { |
| if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1])) |
| continue; |
| dst.setDataElements(x + dst.getMinX(), y, |
| src.getDataElements((int)pts[2 * x], |
| (int)pts[2 * x + 1], |
| null)); |
| } |
| } |
| } |
| else if (hints.containsValue(RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
| { |
| double[] tmp = new double[4 * src.getNumBands()]; |
| for (int y = dst.getMinY(); y < dst.getMinY() + dst.getHeight(); y++) |
| { |
| try { |
| transform.inverseTransform(dpts, 0, pts, 0, dst.getWidth() * 2); |
| } catch (NoninvertibleTransformException e) { |
| // Can't happen since the constructor traps this |
| e.printStackTrace(); |
| } |
| |
| for (int x = 0; x < dst.getWidth(); x++) |
| { |
| if (!srcbounds.contains(pts[2 * x], pts[2 * x + 1])) |
| continue; |
| int xx = (int)pts[2 * x]; |
| int yy = (int)pts[2 * x + 1]; |
| double dx = (pts[2 * x] - xx); |
| double dy = (pts[2 * x + 1] - yy); |
| |
| // TODO write this more intelligently |
| if (xx == src.getMinX() + src.getWidth() - 1 || |
| yy == src.getMinY() + src.getHeight() - 1) |
| { |
| // bottom or right edge |
| Arrays.fill(tmp, 0); |
| src.getPixel(xx, yy, tmp); |
| } |
| else |
| { |
| // Normal case |
| src.getPixels(xx, yy, 2, 2, tmp); |
| for (int b = 0; b < src.getNumBands(); b++) |
| tmp[b] = dx * dy * tmp[b] |
| + (1 - dx) * dy * tmp[b + src.getNumBands()] |
| + dx * (1 - dy) * tmp[b + 2 * src.getNumBands()] |
| + (1 - dx) * (1 - dy) * tmp[b + 3 * src.getNumBands()]; |
| } |
| dst.setPixel(x, y, tmp); |
| } |
| } |
| } |
| else |
| { |
| // Bicubic |
| throw new UnsupportedOperationException("not implemented yet"); |
| } |
| |
| return dst; |
| } |
| |
| /** |
| * Transforms source image using transform specified at the constructor and |
| * returns bounds of the transformed image. |
| * |
| * @param src image to be transformed |
| * @return bounds of the transformed image. |
| */ |
| public final Rectangle2D getBounds2D (BufferedImage src) |
| { |
| return getBounds2D (src.getRaster()); |
| } |
| |
| /** |
| * Returns bounds of the transformed raster. |
| * |
| * @param src raster to be transformed |
| * @return bounds of the transformed raster. |
| */ |
| public final Rectangle2D getBounds2D (Raster src) |
| { |
| // determine new size for the transformed raster. |
| // Need to calculate transformed coordinates of the lower right |
| // corner of the raster. The upper left corner is always (0,0) |
| |
| double x2 = (double) src.getWidth () + src.getMinX (); |
| double y2 = (double) src.getHeight () + src.getMinY (); |
| Point2D p2 = getPoint2D (new Point2D.Double (x2,y2), null); |
| |
| Rectangle2D rect = new Rectangle (0, 0, (int) p2.getX (), (int) p2.getY ()); |
| return rect.getBounds (); |
| } |
| |
| /** |
| * Returns interpolation type used during transformations |
| * |
| * @return interpolation type |
| */ |
| public final int getInterpolationType () |
| { |
| if(hints.containsValue (RenderingHints.VALUE_INTERPOLATION_BILINEAR)) |
| return TYPE_BILINEAR; |
| else |
| return TYPE_NEAREST_NEIGHBOR; |
| } |
| |
| /** |
| * Returns location of the transformed source point. The resulting point |
| * is stored in the dstPt if one is specified. |
| * |
| * @param srcPt point to be transformed |
| * @param dstPt destination point |
| * @return the location of the transformed source point. |
| */ |
| public final Point2D getPoint2D (Point2D srcPt, Point2D dstPt) |
| { |
| return transform.transform (srcPt, dstPt); |
| } |
| |
| /** |
| * Returns rendering hints that are used during transformation. |
| * |
| * @return rendering hints |
| */ |
| public final RenderingHints getRenderingHints () |
| { |
| return hints; |
| } |
| |
| /** |
| * Returns transform used in transformation between source and destination |
| * image. |
| * |
| * @return transform |
| */ |
| public final AffineTransform getTransform () |
| { |
| return transform; |
| } |
| } |