376 lines
12 KiB
Java
376 lines
12 KiB
Java
![]() |
/* 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 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;
|
||
|
}
|
||
|
}
|