The AffineTransforms
map 2D structures in one space to another space while maintaining
straight lines and the parallelism in the original image. The operations
include translation, scaling, flipping, rotation,
and shearing.
In Chapter 6: Supplements: Java
2D review we discussed the application of affine
transforms to shapes drawn with the Graphics2D
tools. Here we see that such transforms can be applied to images.
An AffineTransformOp
object uses an instance of AffineTransform
to apply the transform to a source image to create a new destination
image (they must be different BufferedImage
objects.) For example, the following code shows how to apply a shearing
operation to an image:
AffineTransform shearer = AffineTransform.getShearInstance
(shx, shy);
AffineTransFormOp shear_op = new AffineTransformOp (shearer,
interpolation );
BufferedImage dest_image = shearOp.filter (source_image,
null);
Each point in the source image at (x,y)
will move to (x
+ shx*y, y + shy * x) in the destination image. After an
transform, a pixel in a destination image will usually not correspond
directly to a particular pixel in the source. So the transforms
require an interpolation algorithm to determine the colors of the
destination pixels.
The interpolation setting in the second argument of the constructor
allow you to choose among a nearest neighbor algorithm, a
bilinear interpolation algorithm, and a bicubic interpolation
algorithm became effective with Java 5.0. The AffineTransformOp
class offers three static constants to choose from for the interpolation
setting:
- AffineTransformOp.TYPE_NEAREST_NEIGHBOR
- The nearest neighbor technique applies the color of the
nearest transformed source pixel to the destination pixel.
- AffineTransformOp.TYPE_BILINEAR
- The bilinear interpolation instead uses a combination
of colors from a set of transformed source pixels around the position
of the destination pixel. See the description of a bilinear algorithm
in the specification for
javax.media.jai.InterpolationBilenear.
- AffineTransformOp.TYPE_BICUBIC
-this is a new algorithm implemented in J2SE5.0 that uses a "performs
interpolation using the piecewise cubic polynomial". See
the description of a bicubic algorithm in the specification for
javax.media.jai.InterpolationBicubic.
If the value of the interpolation argument in the AffineTransformOp
constructor is set to null,
then the nearest neightbor algorithm is used.
Note also that these transforms can result in cutting off some
parts of the source image that extend past the borders of the destination
image. Also, some operations, such as a rotation, can leave some
areas black where no image data remains.
The TransformsApplet
shown below demonstrates the shear transform operation on an image.
The user enters the x and y shear factors via two sliders.
TransformApplet
Resources: saturnVoyager.jpg
|
import
javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import javax.swing.event.*;
import java.awt.geom.*;
/** Demonstrate an affine transform of an image. **/
public class TransformApplet extends JApplet
implements
ChangeListener {
BufferedImage fSrcImage, fDstImage;
ImageIcon fDstIcon;
JLabel fShxLabel, fShyLabel;
JSlider fShxSlider,fShySlider;
double fShxFactor = 0.0;
double fShyFactor = 0.0;
/**
* Build the interface with the source
and filter output image
* displayed side by side.
**/
public void init () {
setLayout (new BorderLayout ());
fSrcImage = getBufImage ("saturnVoyager.jpg");
if (fSrcImage == null) {
System.out.println
("Error in reading image file!");
return;
}
JPanel control_panel = new JPanel(new
GridLayout (2,1));
control_panel.add (fShxLabel =
new JLabel ("shx 0 ",SwingConstants.RIGHT) );
control_panel.add (fShxSlider =
new JSlider (Adjustable.HORIZONTAL, 0, 10, 0));
fShxSlider.addChangeListener (this);
control_panel.add (fShyLabel =
new JLabel ("shy 0 ",SwingConstants.RIGHT) );
control_panel.add (fShySlider =
new JSlider (Adjustable.HORIZONTAL, 0, 10, 0));
fShySlider.addChangeListener (this);
// Transform image with default
settings.
transformFilter();
// Use ImageIcon objects to hold
the two images
ImageIcon src_icon = new ImageIcon
(fSrcImage);
fDstIcon = new ImageIcon (fDstImage);
// Put the icons on labels
JLabel src_display = new JLabel
(src_icon);
JLabel dst_display = new JLabel
(fDstIcon);
// And then display the labels on
the scroll panes
JScrollPane src_pane = new JScrollPane
(src_display);
JScrollPane dst_pane = new JScrollPane
(dst_display);
// Use a JSplitPane to show the
source and destination
// images side by side.
JSplitPane split_pane =
new JSplitPane (JSplitPane.HORIZONTAL_SPLIT,
true,
src_pane, dst_pane);
split_pane.setResizeWeight (0.5);
split_pane.setContinuousLayout (true);
// Add the DrawingPanel to the contentPane.
add (split_pane, BorderLayout.CENTER);
add (control_panel, BorderLayout.SOUTH);
} // init
/**
* This class implements ChangeListener
for the
* slider. So the ChangeEvents come
here when the
* slider is moved.
**/
public void stateChanged (ChangeEvent evt) {
// Use the label to show the numerical
value of the slider.
fShxLabel.setText ("shx " + fShxSlider.getValue
());
// Get the values from the slider
and set a new scale factor.
fShxFactor = fShxSlider.getValue
();
// Use the label to show the numerical
value of the slider.
fShyLabel.setText ("shy " + fShySlider.getValue
());
// Get the values from the slider
and set a new scale factor.
fShyFactor = fShySlider.getValue
();
// Transform the image.
transformFilter();
// Reset the destination images
with the modified version.
fDstIcon.setImage(fDstImage);
// Repaint to show the modified
image.
repaint ();
} // stateChanged
/** Create the filter to execute an affine transform
amd
* apply it to the source image.
**/
void transformFilter () {
// Some extreme combinations of
filters can result in
// invalid operations so catch the
resulting exceptions
// and reset images to the default.
try {
AffineTransform shearer
=
AffineTransform.getShearInstance
(fShxFactor, fShyFactor);
AffineTransformOp shear_op
=
new AffineTransformOp
(shearer, null);
fDstImage = shear_op.filter
(fSrcImage, null);
}
catch (Exception e) {
System.out.println("Shearing
exception = " + e);
// Reset back to default
AffineTransform shearer
=
AffineTransform.getShearInstance
(0.0, 0.0);
AffineTransformOp shear_op
=
new AffineTransformOp
(shearer, null);
fDstImage = shear_op.filter
(fSrcImage, null);
}
} // transformFilter
/**
* Download the image
file and convert to a
* BufferedImage object.
**/
BufferedImage getBufImage (String image_name){
// Get the image
Image img = getImage (getCodeBase(),
image_name);
// and use a MediaTracker to load
it before converting it to
// a BufferedImage.
try {
MediaTracker tracker
= new MediaTracker (this);
tracker.addImage (img,0);
tracker.waitForID (0);
} catch (InterruptedException e)
{ return null; }
int width = img.getWidth (this);
int height= img.getHeight (this);
BufferedImage buffered_image =
new BufferedImage (width,
height, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = buffered_image.createGraphics
();
g2.drawImage (img,0 ,0, null);
return buffered_image;
} // getBufImage
} // class TransformApplet
|
It will be left as an exercise to demonstrate the other types of
affine transforms.
References & Web Resources
Latest update: March 8, 2006
|