JavaUnderstanding Transforms in Java 2D

Understanding Transforms in Java 2D

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.

Java Programming Notes # 1550


Preface

General

Part of a series

This lesson is part of a series designed to start with Java 3D basics and work up to some very complicated programs, such as the program that I explained in the earlier lesson titled “Understanding Lighting in the Java 3D API” (see Resources).

The first lesson in this series was titled “Back to Basics in the Java 3D API.” The previous lesson was titled “Combining Rotation and Translation in Java 3d.”; This lesson is titled “Understanding Transforms in Java 2D.”

Not a typographical error!

I really did mean Java 2D and not Java 3D in the previous sentence. That was not a typographical error.

I imagine that now you are wondering why I would include a lesson on Java 2D in a series that is designed to teach you about Java 3D. Believe me, there is a good reason, which I will explain a little later.

My current plan is for future lessons to deal with user and object interaction as well as transforms, advanced animation, and textures in Java 3D.

What you will learn

In this lesson, you will learn to understand transforms in Java 2D in a way that you will be able to extend to an understanding of transforms in Java 3D. You will also learn how to write Java 2D code that makes use of that understanding.

Compiling and running Java 2D programs

In previous lessons, I told you that in order to compile and run programs using the Java 3D API, you will need to download and install the Java 3D API software. As of the date of this writing, Java 3D version 1.5.0 is available for download.

In addition, you will need to download and install either Microsoft DirectX or OpenGL to run Java 3D programs. All of the Java 3D sample programs in this series of tutorials were developed and tested using Microsoft DirectX. They were not tested using OpenGL.

However, no special download and installation is required for Java 2D. For some time now, Java 2D has been an integral part of the standard edition of Java 2. I recommend that you use the Sun product often referred to as Java SE 6 (version 1.6.0 or later) if it is available for your platform. If not, version 1.5, and probably version 1.4 should suffice as well.

Viewing tip

I recommend that you open another copy of this document in a separate browser window and use the following links to easily find and view the figures and listings while you are reading about them.

Figures

  • Figure 1. Program output at startup.
  • Figure 2. Matrices for translation, scaling, and rotation.
  • Figure 3. Matrix operations for the three basic transforms.
  • Figure 4. Matrix computation for a point using translation matrix.
  • Figure 5. The six specifiable values displayed on the screen.
  • Figure 6. The default transform.
  • Figure 7. Display the current transform after origin adjustment.
  • Figure 8. Graphic output produced by user input scale values.
  • Figure 9. The new transform matrix values after scaling.
  • Figure 10. Translating by 50 and 30 for x and y.
  • Figure 11. Contents of the current transform matrix after translation.
  • Figure 12. Graphic output after application of the shear transform.
  • Figure 13. Contents of the current transform following the shear.
  • Figure 14. Prepare to explain a rotate transform.
  • Figure 15. Graphic output following 30-degree rotate transform.
  • Figure 16. Contents of the current transform following a rotate transform.
  • Figure 17. Graphic output from the final translate transform.
  • Figure 18. Contents of the final transform matrix.

Listings

  • Listing 1. The displayMatrix method.
  • Listing 2. The drawOrigin method.
  • Listing 3.Beginning of the class named Java2D001.
  • Listing 4. Beginning of the inner class named GUI.
  • Listing 5. Beginning of the inner class named Display.
  • Listing 6. Move the origin to the center of the canvas.
  • Listing 7. Cause the positive y direction to be up instead of down.
  • Listing 8. Draw a cyan rectangle, a circle, and a cross at the origin.
  • Listing 9. Apply a user-specified scale transform.
  • Listing 10. Apply a user specified translate transform.
  • Listing 11. Update the current transform to include shear.
  • Listing 12. Update the current transform to apply a rotate transform.
  • Listing 13. The final translate transform.
  • Listing 14. Program listing for the program named Java2D001.

Supplementary material

I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find a consolidated index at www.DickBaldwin.com.

General background information

Why a lesson on Java 2D?

We have reached the point in this series where you need to learn about transforms in Java 3D. This is not a trivial topic. It is an extremely complex topic. Although transforms are also not a trivial topic in Java 2D, it is much easier to understand transforms in Java 2D than in Java 3D.

While transforms in the two APIs are not implemented in exactly the same way, the transform concept in Java 2D is sufficiently similar to the transform concept in Java 3D that once you understand transforms in 2D, you should be able to make the stretch into 3D. In any event, it can’t hurt for you to understand Java 2D transforms when you embark on an attempt to understand Java 3D transforms.

The cross shown in Figure 1 consists of two lines, each of which is defined by two end points. The end points are connected by straight line segments. While the cross is also a shape in the general sense, it is not a closed shape (see sidebar).

What is a point?

A point is a location in 2D space defined by a pair of coordinate values (x,y). The value for x defines the location of the point relative to an origin along the x-axis in a “Cartesian coordinate system” (see Resources). The value for y defines the location of the point along the y-axis.

Orthogonal axes

A pair of orthogonal (see Resources) axes intersect at the origin. The axis may or may not be visible in the graph. Even when they are not visible, they are assumed to be present.

The x-axis is typically considered to be the horizontal axis with positive values going from left to right. The other axis in the pair of orthogonal axes is typically considered to be the y-axis with positive values going from bottom to top. Note, however that there is no requirement that the graph be displayed in such a way that the orthogonal axes are horizontal and vertical. They are often rotated for display purposes, as is the case in this program.

Note also that the application of a shear transform causes the axes to no longer be orthogonal (see Figure 12). (Is this a true statement? Think about it.)

Direction of the positive y-axis
By default, the positive direction of the vertical axis in a graph generated using Java code goes from top to bottom. However, that was purposely corrected in this program so that the positive direction of the vertical axis for the graph shown in Figure 1, as well as the other graphs in this lesson is from bottom to top.

The y-coordinate

As mentioned earlier, the value for y defines the location of the point relative to the origin along the y-axis in the Cartesian coordinate system. As mentioned above, the y-axis is typically considered to be the vertical axis with positive values going from bottom to top.

Where is the origin?

The origin may be located anywhere on or off the graph, but is often located either at the bottom left or at the center. However, regardless of its location, it is always at the intersection of the orthogonal axes. The origin is located in the center of the left image in Figure 1 and is marked by the cross. The circle and the square in Figure 1 are centered on the origin.

What is a vector?

According to Wikipedia (see Resources), there are several kinds of vectors. A spatial vector is an object defined by both magnitude and direction; in contrast to a scalar, an object with magnitude only. Typically in Java 2D, a point is used to define one end of a vector.

Also, according to Wikipedia, the word vector is used to describe a
one-dimensional, directional matrix; a row vector or a column vector. I will use the word vector in both senses (spatial and matrix) in this lesson. Hopefully the sense in which the word is being used will be clear from the context of the discussion.

What is a transform?

In the sense that it will be used in this lesson, a transform is an operation that converts the coordinate values for each of the points that define a shape into a different set of coordinate values. The shape that is defined by the new coordinate values will probably be recognizable as representing the original shape, although it may be a different size (scale), may be in a different location (translation), may have a different orientation (rotation), may be sheared (shear), may be squashed in one dimension or the other, or may be flipped across one or both of the axes.

What is an affine transform?

The transforms that will be used in this lesson are actually affine transforms. According to the Java 2D documentation:

“The AffineTransform class represents a 2D affine transform that performs a linear mapping from 2D coordinates to other 2D coordinates that preserves the “straightness” and “parallelness” of lines. Affine transformations can be constructed using sequences of translations, scales, flips, rotations, and shears.”

Translation

To translate a shape means to move the shape from one location in the 2D space to a different location in the 2D space. (You will see how this is accomplished later.) We can translate a shape by translating all of the points that define the shape. We can translate a point by adding offsets to the x and y coordinate values that define the location of the point.

If we express the original coordinates of a point as x1 and y1, and express the offsets as tx and ty, then we can express the coordinates of the new location of the point as:

x2 = x1 + tx
y2 = y1 + ty

Scaling

To scale a shape means to change the locations of all the points that represent the shape according to a very specific formula. Typically, but not always, a scaling transform will change the size of the shape, making it larger or smaller. (Note that scaling by a factor of -1 doesn’t change the size.) We can accomplish a scaling transform by multiplying the x coordinate values of all the points that define the shape by one scale factor and multiplying the y coordinate values of all the points that define the shape by the same or a different scale factor.

Enlarging, reducing, squashing, and flipping

If the scale factors used to multiply the x and y coordinate values are the same, the shape will simply become larger or smaller (and could be flipped across the axes if the scale factors are negative). If the scale factors are different, the shape may be squashed in one dimension or the other, or possibly even flipped across one or both axes, but will probably still be recognizable as representing the same shape.

Scale relative to the origin

The scale factors that are used in a scaling transform are expressed relative to the origin. For example, a scale factor of 2.0 will cause a coordinate value for a point to represent the coordinate value for a new point that is twice as far from the origin.

If we express the original coordinates of a point as x1 and y1, and express the scale factors as sx and sy, then we can express the coordinates of the new location of the point as

x2 = x1 * sx
y2 = y1 * sy

where the * indicates multiplication.

Rotation

Rotation is much less intuitive than either translation or scaling, and requires a fair knowledge of trigonometry to understand. However, it is possible to create and use rotation transforms in a cookbook fashion without understanding why they work.

The following equations for rotating a point around the origin in 2D space are derived in “Planar transformations” (see Resources), where v represents the angle of rotation.

x2 = x1 * cos(v) - y1 * sin(v)
y2 = x1 * sin(v) + y1 * cos(v)
Positive angle of rotation
The specification of a positive rotation angle in the user input GUI in Figure 1 also results in a counterclockwise angle of rotation.

Positive angle of rotation

The author who derived these two equations points out that they assume that a positive rotation angle is counterclockwise around the origin.

Shear

Shear is much more complicated. I provided a pretty good description of shear in my earlier article titled “Graphics, Simple Affine Transforms” (see Resources), and I am simply going to leave it at that insofar as shear is concerned. However, I would be remiss if I failed to point out that once a shear transform is applied, the axes are no longer orthogonal (or at least, they no longer appear to be orthogonal).

(I also provided information regarding scaling, translation, and rotation in the earlier lesson titled “Graphics, Simple Affine Transforms,” which you may find helpful.)

Matrices

The article titled “Planar transformations” (see Resources) gives us the matrix equations shown in Figure 2, which can be solved to accomplish translation, scaling, and rotation transforms.

Figure 2. Matrices for translation, scaling, and rotation.

Translation decided by tx and ty
|x2|   |x1|   |tx|
|  | = |  | + |  |
|y2|   |y1|   |tx|

Scaling decided by sx and sy
|x2|   |sx  0|   |x1|
|  | = |     | * |  |
|y2|   |0  sy|   |y1|

Rotation decided by v
|x2|   |cos(v)  -sin(v)|   |x1|
|  | = |               | * |  |
|y2|   |sin(v)   cos(v)|   |y1|

The same author teaches us a little about matrix algebra in the article titled “A little algebra” (see Resources), in case you need some background study to improve your knowledge of matrix algebra.

Homogeneous coordinates

That same author goes on to tell us that if we write the coordinate values for a point in the form of the following column vector, we can write the three basic transforms of translation, scaling, and rotation as multiplication between a 3×3 matrix and a 1×3 column vector.

   | x |
   | y |
   | 1 |

The 3×3 matrices

The 3×3 matrices to which he refers are shown in boldface in Figure 3.

Figure 3. Matrix operations for the three basic transforms.

Translation
|x2|   |1 0 tx| |x1|
|y2| = |0 1 ty|*|y1|
|1 |   |0 0  1| |1 |

Scaling
|x2|   |sx 0 0| |x1|
|y2| = |0 sy 0|*|y1|
|1 |   |0  0 1| |1 |

Rotation
|x2|   |cos(v) -sin(v) 0| |x1|
|y2| = |sin(v) cos(v)  0|*|y1|
|1 |   |0        0     1| |1 |

Therefore, regardless of the type of transform needed (translation, scaling, or rotation), the arithmetic required is the same. A single algorithm for matrix multiplication can be used to perform all three transforms provided that we are able to generate the appropriate 3×3 matrix values.

Also supports combining transforms

As I will explain later, this form also makes it possible to combine several transforms into a single matrix before applying that composite transform to all the points that make up a shape. This can result in improved efficiency.

Homogeneous coordinates

The author of “Planar transformations” describes the form shown in Figure 3 as homogeneous coordinates. Regarding the changes that were made getting from the form in Figure 2 to the form in Figure 3, the author goes on to explain:

“…are expressed in homogeneous coordinates. We will not worry about the third coordinate, the number 1. We’ll not try to give a geometric explanation of this. The whole point is to standardize the mathematics in the transformations. The third coordinate stays the same in the basic transformations and, as we will see later, in combinations of them.”

Some special cases

The author of “Planar transformations” then goes on to discuss some special cases for the 3×3 matrices that can be used in transforms to produce:

  • mirroring – to produce the mirror image of a shape.
  • shearing – a scaling along one of the axes that depends on the coordinate on the other axis.  For example, shearing will turn a rectangle into a parallelogram.
  • projection – projecting a point onto an axis.

You may find these special transforms useful in your future work.

Compound operations

As mentioned earlier, it is possible to combine a series of transforms into a single matrix multiplication by first multiplying the matrices that represent of the individual transforms to produce a single compound matrix. Instead of having to perform a series of individual transforms on each point that represents a shape, a compound transform matrix can be created first, and each point can be transformed by multiplying the column vector that represents that point by the compound matrix that represents the entire set of transforms. The savings in computational requirements can be very large.

The bad news

It is possible to write Java 2D programs where almost your entire thought process revolves around the creation and use of transform matrices down to the element level. (See the transform method of the Graphics2D class for example.) The bad news is that for some programs, this may be the only way to get the job done. There may be no alternative to engaging deeply in matrix thought.

The good news

However, the good news is that it is possible to write most Java 2D programs without having to think too deeply about matrices. This is because the Java 2D API provides convenience methods that abstract most of the matrix operations behind a fairly common Java programming interface. Even when you use the convenience methods, however, it is a good idea to have some appreciation of what is actually taking place behind the curtains.

Back to the basics

Let’s go way back. A computer monitor (at least the old fashioned kind) has a glass screen that has been coated on the inside with specks of luminescent material that will glow red, green, or blue when bombarded with electrons. (The newer flat panel monitors are constructed differently, but the results are essentially the same.) The specks of luminescent material are placed on the glass in a uniform grid. These specks, (or the analogous items in a flat panel display), determine the resolution of the monitor. (You cannot resolve two points on the screen that are closer than the distance between the luminescent specks. The size of the specks also has an impact on resolution. Usually smaller specks will support higher resolution.)

Screen pixels

Although this is not technically correct from a purest viewpoint, I am going to refer to these specks as screen pixels (short for picture elements). The number of pixels per inch on the screen determines the resolution of the screen. For example, the flat screen on my laptop is a little less than 12 inches wide and a little less than nine inches tall. Supposedly it has a horizontal resolution of 1024 pixels and a vertical resolution of 768 pixels.

Theoretically, if I write a Java program to plot a single point on the screen, only one of the screen pixels will be illuminated to display that point. (Realistically, more than one adjacent pixel may be illuminated due to electronic variability in the electronics that drive the screen display.)

Aspect ratio
The aspect ratio of an image is its displayed width divided by its displayed height, often expressed as x:y.

The aspect ratio

The aspect ratio on my laptop is very close to 1:1 at about 87 pixels per inch in both dimensions. The important thing about having a good aspect ratio is that when I use a program to draw a circle on the screen, it will look like a circle to the viewer instead of looking like an ellipse. However, if the aspect ratio is different from 1:1, I could compensate for that problem using a scaling transform.

Overriding the paint method

Onscreen graphics are produced in Java 2D by overriding the paint method belonging to the Canvas object on which the graphics are to be drawn. (In addition to the Canvas class, there are a few other classes in the AWT, such as Frame and Panel that contain a paint method that can be overridden to draw onscreen graphics.)

Illuminating pixels

The paint method draws its pictures by illuminating pixels on the screen. Regardless of what you may think you are doing when you write the program, the location of the pixel that is to be illuminated is eventually resolved to be a specified number of pixels to the right of and a specified number of pixels down from the pixel that occupies the upper left corner of the canvas.

Relocating the origin

By default, the origin for the Cartesian coordinate system is at the upper left corner of the canvas. As you will see in the program later, we can apply a translate transform to move the origin to a different location on the canvas. Having done that, when we write program code to draw shapes, we specify the locations of the points to be plotted relative to the new origin. When we do that, the transform that was applied to relocate the origin is automatically used to resolve the locations of the points back to a coordinate system that has its origin at the upper left corner of the canvas.

The positive y direction

By default, the positive y direction is down the screen, which is the opposite of what we often like to see. We can apply a scaling transform to cause the positive y direction to be up instead of down. Once again, however, when we do that and then plot a point, the transform is automatically used to resolve the location of the point back to the default with the origin at the upper left corner of the canvas and the positive y direction going down the screen.

A practical example

The image on the left in Figure 1 shows a rectangle, a circle, and a cross that are centered on an origin that was translated to the center of the canvas. For example, the left end of the horizontal line in the cross was specified to be at (-7,0) and the right end was specified to be at (7,0).

When I applied the two transforms to relocate the origin and to correct for the problem with the positive y direction, this resulted in the current compound transform shown by the boldface transform matrix in Figure 4.

Figure 4. Matrix computation for a point using translation matrix.

|x2|   |1.00  0.00 113.50|   |-7|   |105|
|y2| = |0.00 -1.00 113.50| * | 0| = |113|
|1 |   |   0     0   1   |   | 1|   |  1|

Do the arithmetic

Assuming that the resulting pixel location is truncated to the next lower integer, performing the computation shown in Figure 4 would cause the actual location of the left end of the line in pixels relative to the upper left corner of the canvas to be (105,113).

Converting to actual screen coordinates
This is followed by another similar computation either in the Java virtual machine or in operating system to determine the actual screen coordinates of the two ends of the line. The answer will depend on the current location of the Frame containing the canvas on the screen.

Performing a similar computation for the coordinate at the right end of the short horizontal line in Figure 1 would result in a location of (120,113). Even though I specified in the program that the line should be drawn from (-7,0) to (7,0), it was actually drawn from (105,113) to (120,113) relative to the upper left corner of the canvas.

A virtual coordinate system

Fortunately, once we apply the transform to relocate the origin and correct for the positive y direction, we can think and program in terms of virtual coordinates relative to our new virtual origin. All of the complicated matrix arithmetic necessary to convert our virtual coordinates to actual coordinates relative to the upper left corner of the canvas takes place automatically behind the curtain.

Hopefully this discussion will have served as an introduction to the discussions and explanations that follow.

Let’s see some code!

With that as a technical background, it’s time to examine a program that makes it easy to experiment with and hopefully easy to understand how to create and to use transforms in Java 2D.

Preview

In this lesson, I will present and explain a program named Java2D001. The purpose of this program is to illustrate transforms in Java 2D. (This program is very similar to the Java 3D program named Java3D010 that I will present and explain in a future lesson.)

The user input GUI

The user input GUI shown on the right side of Figure 1 allows the user to specify parameter values for a sequence of five transforms of the types shown in the leftmost column in the GUI in Figure 1. The transforms are performed in the order shown from top to bottom in Figure 1.

Invalid numeric data
Clicking the Replot button when one of the input fields contains invalid numeric data (including a blank field) will cause the program to abort with a NumberFormatException.

The Replot button

A Replot button at the bottom of the GUI allows the user to modify input parameters, re-compute the transforms, apply the new transforms, and produce a new output based on the new transforms.

Display the transform matrices

In addition to providing a graphic output display on the left in Figure 1, the program also gets and displays the specifiable values in the AffineTransform object each time it is updated to support a new transform.

Program testing

The program was tested using Java SE 6 running under Windows XP.

Discussion and sample code

Will explain in fragments

As is my custom, I will present and explain this program in fragments. A complete listing of the program is provided in Listing 14.

Before getting into the main body of the program, I will present and explain two utility methods that are used throughout the program. The first utility method is a method named displayMatrix.

The six specifiable values
This refers to the six values in the top two rows of the transform matrices shown in Figure 3. In other words, the bottom row consisting solely of 0 and 1 are not included in the printed output.

The displayMatrix method

This method is shown in its entirety in Listing 1. The method receives a reference to an AffineTransform object. It extracts and displays the six specifiable values contained in the transform matrix on the command line screen in the format shown in Figure 5.

Listing 1. The displayMatrix method.

    void displayMatrix(AffineTransform theTransform){

      //Retrieve the contents of the AffineTransform into
      // an array of type double.
      double[] theMatrix = new double[6];
      theTransform.getMatrix(theMatrix);

      //See http://www.particle.kth.se/~lindsey/JavaCourse
      // /Book/Part1/Tech/Chapter05/formatterPrintf.html
      // for instructions on the use of System.out.printf.

      //Display first row of values by displaying every
      // other element in the array starting with element
      // zero.
      System.out.printf ("%5.2f %5.2f %5.2f %n", 
                  theMatrix[0],theMatrix[2],theMatrix[4]);
      //Display second row of values displaying every
      // other element in the array starting with element
      // number one.
      System.out.printf ("%5.2f %5.2f %5.2f %n", 
                 theMatrix[1],theMatrix[3],theMatrix[5]);

    }//end displayMatrix

 

Figure 5. The six specifiable values displayed on the screen.

 1.00  0.00 113.50
-0.00 -1.00 113.50

Extract AffineTransform contents into an array

The code in this method is relatively straightforward. It receives an incoming parameter, which is a reference to an object of type AffineTransform. It calls the getMatrix method of the AffineTransform class, which deposits the data values in a six-element array of type double that is passed to the method to receive the data.

The display format

The most complicated thing about the method is the use of the System.out.printf statement (that first became available in Java version 1.5) to format each double value into five columns with two digits to the right of the decimal point. Listing 1 contains the URL of a web site that provides a good explanation of how to do the formatting, so I will simply refer you to that website (see Resources for an active hyperlink to this site).

Beyond that, the comments in Listing 1 should suffice to explain the code.

The drawOrigin method

The next utility method that I will discuss is the method named drawOrigin, which is presented in its entirety in Listing 2. This method will draw a circle containing a cross at the current origin, as shown in the center of the left image in Figure 1.

Listing 2. The drawOrigin method.

        void drawOrigin(Graphics g){
          Graphics2D g2 = (Graphics2D)g;
          g2.drawLine(-7,0,7,0);
          g2.drawLine(0,-7,0,7);
          g2.drawOval(-5,-5,10,10);
        }//end drawOrigin

If you know anything about Java graphics, you should have no difficulty understanding the code in this method. If you don’t understand the code in Listing 2, let me suggest that you go to my web site (see Resources) and start studying Java from the beginning.

Beginning of the class named Java2D001

The main class for the program named Java2D001 begins in Listing 3.

Listing 3. Beginning of the class named Java2D001.

class Java2D001 extends Frame{
  GUI gui = new GUI();
  
  TextField sxTxt = new TextField("1.0");
  TextField syTxt = new TextField("1.0");
  TextField txTxt = new TextField("0.0");
  TextField tyTxt = new TextField("0.0");
  TextField shxTxt = new TextField("0.0");
  TextField shyTxt = new TextField("0.0");
  TextField rotTxt = new TextField("0.0");
  TextField tx2Txt = new TextField("0.0");
  TextField ty2Txt = new TextField("0.0");
  
  Label naTxt = new Label("");
  //----------------------------------------------------//

  public static void main(String[] args){
    new Java2D001();
  }//end main
  //----------------------------------------------------//

  Java2D001(){//constructor
    setBounds(236,0,235,254);
    setTitle("Copyright 2007, R.G.Baldwin");

    //Construct the user input panel and add it to the
    // CENTER of the Frame.
    Panel inputPanel = new Panel();
    inputPanel.setLayout(new GridLayout(0,3));

    inputPanel.add(new Label("Xform Type",Label.CENTER));
    inputPanel.add(new Label("X",Label.CENTER));
    inputPanel.add(new Label("Y",Label.CENTER));

    inputPanel.add(new TextField("Scale"));
    inputPanel.add(sxTxt);
    inputPanel.add(syTxt);

    inputPanel.add(new TextField("Translate"));
    inputPanel.add(txTxt);
    inputPanel.add(tyTxt);

    inputPanel.add(new TextField("Shear"));
    inputPanel.add(shxTxt);
    inputPanel.add(shyTxt);

    inputPanel.add(new TextField("Rotate (deg)"));
    inputPanel.add(rotTxt);
    inputPanel.add(naTxt);

    inputPanel.add(new TextField("Translate"));
    inputPanel.add(tx2Txt);
    inputPanel.add(ty2Txt);

    add(inputPanel,BorderLayout.CENTER);

    //Add a button with an ActionListener that allows the
    // user to modify transform parameters, recompute the
    // transform, replot the graphic output, and
    // re-display the matrix data.
    Button button = new Button("Replot");
    button.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
        gui.sx = Double.parseDouble(sxTxt.getText());
        gui.sy = Double.parseDouble(syTxt.getText());
        gui.tx = Double.parseDouble(txTxt.getText());
        gui.ty = Double.parseDouble(tyTxt.getText());
        gui.shx = Double.parseDouble(shxTxt.getText());
        gui.shy = Double.parseDouble(shyTxt.getText());
        //Convert angle from degrees to radians.
        gui.rotAngle = (Double.parseDouble(rotTxt.
                              getText()) * Math.PI/180.0);
        gui.tx2 = Double.parseDouble(tx2Txt.getText());
        gui.ty2 = Double.parseDouble(ty2Txt.getText());
        gui.display.repaint();}//end actionPerformed
      }//end new ActionListener
    );//end addActionListener
    
    add(button,BorderLayout.SOUTH);
    setVisible(true);

  }//end constructor

The code in Listing 3 begins by instantiating a new object of an inner class named GUI and saving a reference to that object in an instance variable named gui. Then it proceeds to construct the user input GUI shown on the right in Figure 1.

The ActionListener

The ActionListener that is registered on the Replot button:

  • Gets ll of the user input values in the fields in the input GUI
  • Converts them into numeric format
  • Stores them in instance variables in the object of the inner class named GUI

This code calls the parseDouble method to convert the String data from the input fields into numeric values of type double. If this method is called on a string that cannot be converted into a numeric value of type double (including an empty string resulting from a blank field), it will throw a NumberFormatException. That, in turn, will cause the program to abort because I didn’t do anything to handle the exception.

Beyond this explanation and the comments in Listing 4, there should be nothing else about the code in Listing 3 that requires an explanation.

Beginning of the inner class named GUI

The inner class named GUI begins in Listing 4.

Listing 4. Beginning of the inner class named GUI.

  //This is an inner class.
  class GUI extends Frame{
    //The following values are set by the ActionListener
    // that is registered on the Replot button before a
    // call to the repaint method is made.
    double sx = 1.0; //scale X
    double sy = 1.0; //scale Y
    double tx = 0.0; //translate X
    double ty = 0.0; //translate Y
    double shx = 0.0;//shear X
    double shy = 0.0;//shear Y
    double rotAngle = 0.0;//rotate
    double tx2 = 0.0;//translate X again
    double ty2 = 0.0;//translate Y again

    Display display = new Display();

    GUI(){//constructor
      setBounds(0,0,235,254);
      setTitle("Copyright 2007, R.G.Baldwin");
      add(display,BorderLayout.CENTER);
      setVisible(true);

      //Window listener to terminate program.
      addWindowListener(new WindowAdapter(){
        public void windowClosing(WindowEvent e){
          System.exit(0);}});
    }//end constructor

This is the class that is used to create, display, and maintain the graphic output display shown in the left image in Figure 1. The most significant things about the code in Listing 4 are the two boldface statements, which:

  • Instantiate a new object of another inner class named Display.
  • Add that object as a display canvas in the CENTER of the Frame object shown as the left image in Figure 1.

Beginning of the inner class named Display

This is where the program starts to get interesting.  An object of theDisplay class provides a Canvas object on which shapes are drawn.

Listing 5. Beginning of the inner class named Display.

      class Display extends Canvas{
        //Override the paint() method to draw and
        // manipulate a rectangle based on user provided
        // transform parameters.
        public void paint(Graphics g){
          //Downcast the Graphics object to a Graphics2D
          // object.
          Graphics2D g2 = (Graphics2D)g;

          System.out.println();//Blank line
          System.out.println("Display new matrix data.");

          //Display contents of default AffineTransform
          // object.
          System.out.println("Default Transform");
          displayMatrix(g2.getTransform());

In order to create graphics using Java’s AWT components, you must extend a class that provides a paint method and override the paint method to provide the graphic behavior that you need. Although several different AWT classes can be used for this purpose, the Canvas class was designed by Sun specifically for this purpose.

Display extends Canvas and overrides paint

As you can see, the Display class that begins in Listing 5 extends the Canvas class. The code in Listing 5 shows the beginning of the overridden paint method.

The paint method receives an incoming parameter that is a reference to an object of the Graphics class. As a practical matter, you can consider the Graphics object to represent the rectangular portion of the screen where you will be drawing your graphic shapes.

The Graphics and Graphics2D classes

The Graphics class was a part of Java from very early on. It provides a nominal set of drawing methods for drawing primitive shapes in addition to lines and points. Along the way, a new class named Graphics2D was added to the Java library as a subclass of the Graphics class. It significantly improved the ability of the programmer to create onscreen graphics. Because it is a subclass of the Graphics class, if you want to use any of the methods defined in the Graphics2D class, you must downcast the incoming reference to type Graphics2D.

The first statement in the overridden paint method in Listing 5 performs this downcast, saving the reference in a variable named g2. This is the reference variable that will be used to perform most of the work in the overridden paint method.

Get and display a transform

The last statement in Listing 5 calls the getTransform method on g2 and passes the returned value to the method named displayMatrix that I explained earlier in conjunction with Listing 1. According to the documentation, the getTransform method “Returns a copy of the current Transform in the Graphics2D context.”

The default transform

Because this call is made immediately upon entering the overridden paint method, the result will be the default transform that is applied to virtual coordinates to convert them to absolute coordinates relative to the upper left corner of the canvas. The transform is shown in Figure 6.

Figure 6. The default transform.

Display new matrix data.
Default Transform
 1.00  0.00  0.00
 0.00  1.00  0.00

The transform matrix shown in Figure 6 is referred to in “Planar transformations” (see Resources) as an identity matrix, which “transforms a point to itself.” In other words, when the paint method begins execution, the transform that is applied to virtual coordinates makes no changes at all to those coordinates (except perhaps truncating floating coordinate values to integer coordinate values) and plots those coordinates in pixels relative to the upper left corner of the canvas.

From this point forward, whenever the program performs a transform, the newly created transform matrix is multiplied by the current transform matrix to produce a new current transform matrix. I will use the getTransform method numerous times to get and display the values in the current transform.

Move the origin to the center of the canvas

As I have stated more times than you probably wanted to hear, the default origin is located at the upper left corner of the canvas. I wasn’t happy with that. I wanted the origin to be in the center of the canvas. Listing 6 calls the translate method of the Graphics2D class to rectify this situation and move the virtual origin to the center of the display area.

Listing 6. Move the origin to the center of the canvas.

          g2.translate(getWidth()/2.0,getHeight()/2.0);

According to Sun, the translate method:

“Concatenates the current Graphics2D Transform with a translation transform. Subsequent rendering is translated by the specified distance relative to the previous position.”

In other words, after applying this transform, I can draw something at the virtual coordinates (0,0) and it will be drawn in the center of the canvas, as shown by the cross in Figure 1. The modified current transform will be used to automatically convert my virtual coordinates to actual coordinates relative to the upper left corner of the canvas. This is what I attempted to explain earlier in conjunction with the discussion surrounding Figure 4.

Cause the positive y direction to be up instead of down

However, I still wasn’t satisfied. The code in Listing 6 moved the virtual origin to the center as desired, but the positive y direction was still going down the screen. Listing 7 calls the scale method to flip the sign on all subsequent y coordinate values to cause the positive y direction to be up instead of down.

Listing 7. Cause the positive y direction to be up instead of down.

          g2.scale(1,-1);
          
          System.out.println("After correction for "
                 + "origin and direction of positive Y.");
          System.out.println("Negative y-scale corrects "
                        + "for direction of positive Y.");
          displayMatrix(g2.getTransform());

According to the documentation, the scale method:

“Concatenates the current Graphics2D Transform with a scaling transformation. Subsequent rendering is resized according to the specified scaling factors relative to the previous scaling.”

In other words, from this point forward, all virtual y coordinate values will be multiplied by -1 in the process of transforming them into actual y coordinate values relative to the upper left corner of the canvas.

Display the current transform

Listing 7 also gets and displays the current transform, producing the screen output shown in Figure 7.

Figure 7. Display the current transform after origin adjustment.

After correction for origin and direction of positive Y.
Negative y-scale corrects for direction of positive Y.
 1.00  0.00 113.50
 0.00 -1.00 113.50

The information shown in Figure 7 is the information that I used to manually create the transform matrix shown in boldface in Figure 4. Once again, I attempted to explain the impact of this transform in the discussion surrounding Figure 4.

Up to this point, the screen output produced by the program will be the same each time that it is run. Following this point, the screen output depends on the user input values in the GUI shown in Figure 1.

Draw a rectangle, a circle, and a cross at the origin

The code in Listing 8 draws a cyan rectangle, a circle, and cross centered on the virtual origin as shown in Figure 1. Note, however, that when the program is first run, the default user input parameters are as shown in the input GUI in Figure 1. This set of parameters results in several more rectangles, circles, and crosses being drawn on top of those that are colored cyan. The last rectangle drawn is the one that you see, which just happens to be blue instead of cyan. (See Figures 12 and 17 for examples where the different sets of shapes have been separated in the drawing area.)

Listing 8. Draw a cyan rectangle, a circle, and a cross at the origin.

          //Draw a CYAN rectangle centered on the
          // current origin.
          g2.setColor(Color.CYAN);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));

          //Draw the current origin as a circle containing
          // a cross.
          drawOrigin(g);

Apply a user-specified scale transform

Listing 9 calls the scale method again to update the transform to include a scale component. Then it displays the values contained in the current transform.

Listing 9. Apply a user-specified scale transform.

          System.out.println(
                            "Update for Scale Transform");
          //Call the scale method to update the transform.
          g2.scale(sx,sy);
          displayMatrix(g2.getTransform());

          //Draw a RED rectangle centered on the origin.
          g2.setColor(Color.RED);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

When the program first starts running, the default values in the user input fields in Figure 1 are “do-nothing” values. Thus, there will be no change in the values in the transform from those shown in Figure 7. However, if the user enters values (other than 1.0) into either or both of the Scale fields shown in Figure 1 and then clicks the Replot button, there will be a change in the values contained in the current transform. There will also be a change in the graphic output as well.

Graphic output produced by user input scale values

For example, Figure 8 shows the result of scaling the x coordinate values by a factor of 5, scaling the y coordinate values by a factor of 10, and leaving all of the other input fields unchanged.

Figure 8. Graphic output produced by user input scale values.

You can see the cyan rectangle showing through in Figure 8 because it was drawn before this scaling transform took effect. The scaling transform had an effect on all of the rectangles, circles, and crosses drawn after it took effect.

The new transform matrix values after scaling

Figure 9 shows the values contained in the current transform following the execution of the code in Listing 9.

Figure 9. The new transform matrix values after scaling.

Update for Scale Transform
 5.00   0.00 113.50
 0.00 -10.00 113.50

Note that a scaling transform impacts only the first value in the first row and the second value in the second row, as indicated by the locations of sx and sy in the scaling matrix shown in Figure 3.

Draw a red rectangle

In addition to updating the current transform to include a scaling transform, the code in Listing 9 draws a red rectangle, circle, and cross centered on the origin. However, it isn’t visible in Figure 8 because it gets covered up with the blue material that is drawn later.

Apply a user specified translate transform

Listing 10 updates the transform to include a user specified translate transform. The code in Listing 10 also draws an orange rectangle, a circle, and a cross centered on the current virtual origin.

Listing 10. Apply a user specified translate transform.

          //Update transform to include a translate
          // component,  and display the values.
          System.out.println(
                        "Update for Translate Transform");
          g2.translate(tx,ty);
          displayMatrix(g2.getTransform());

          //Draw an ORANGE rectangle centered on the
          // origin.
          g2.setColor(Color.ORANGE);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

Translating by 50 and 30 for x and y

Figure 10 shows the result of setting each of the scale factors to 1.5, setting the translate parameter for the x coordinate to 50, setting the translate parameter for the y coordinate to 30, and clicking the Replot button.

Figure 10. Translating by 50 and 30 for x and y.

A very important result

This is a very important result. You can now see the cyan-colored shapes at the original virtual origin. You can also see the red shapes centered on the same original virtual origin.

There is an orange rectangle, circle, and cross located under the blue versions of the same shapes so you can’t see them. Recall that those shapes were drawn centered on the virtual origin after the application of the translate transform.

Figure 10 demonstrates that what happens when you apply a translate transform is that the location of the current virtual origin changes. In this case, the current virtual origin has been moved 50 pixels to the right and 30 pixels up. We know that because the rectangles, circles, and crosses are always centered on the current virtual origin.

Contents of the current transform matrix after translation

Figure 11 shows the contents of the current transform matrix after the application of the translate transform.

Figure 11. Contents of the current transform matrix after translation.

Update for Translate Transform
 1.50  0.00 188.50
 0.00 -1.50 68.50

The important thing to note about the data in Figure 11 is the change in the two values in the third column. These two values represent tx and ty in the translate transform shown in Figure 3. After the application of the original translate transform to relocate the virtual origin, both of these values were 113.5. The new values shown in Figure 11 were determined not only by the translation distances, but also by the scale transform values of 1.5. You can verify this by doing the matrix multiplication by hand if you care to do so.

Update the current transform to include shear

Listing 11 updates the current transform to include shear, and then draws a black rectangle, circle, and cross centered on the current virtual origin.

Listing 11. Update the current transform to include shear.

          System.out.println(
                            "Update for Shear Transform");
          g2.shear(shx,shy);
          displayMatrix(g2.getTransform());

          //Draw a BLACK rectangle centered on the origin.
          g2.setColor(Color.BLACK);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

Graphic output after application of the shear transform

Figure 12 shows the result of leaving the other input parameters the same and clicking the Replot button with the shear parameter values shown.

Figure 12. Graphic output after application of the shear transform.

Orange shapes are now showing

Now you can see the orange shapes that were previously covered by black shapes and blue shapes. However, the new black shapes are obscured by blue shapes.

As you can see, the effect of shear is to cause a rectangle to become a parallelogram and to cause similar deformities to occur for other shapes such as circles and lines.

A very important aspect of a shear transform is illustrated. Recall that the small blue circle in Figure 12 is centered on the virtual origin. Recall also that the blue cross is also centered on the virtual origin and the lines that comprise the cross are aligned with the virtual axes. The virtual axes are no longer orthogonal. The angles at the intersections of the virtual axes are no longer 90 degrees.

Contents of the current transform following the shear

Figure 13 shows the contents of the current transform matrix after the application of the shear transform.

Figure 13. Contents of the current transform following the shear.

Update for Shear Transform
 1.50  0.30 188.50
-0.60 -1.50 68.50

If you compare this with Figure 11, you will see that the only changes were to the boldface elements in Figure 13. As it turns out, these two elements are impacted by the application of a shear transform. As you will see later, these two elements (plus some other elements as well) are also impacted by a rotate transform.

Prepare to explain a rotate transform

The next transform to be considered is a rotate transform. To prepare for that, I am going to reset the scale transform parameters to unity and reset the shear transform parameters to zero in order to cause the current transform matrix to be simpler. The result of doing this is shown in Figure 14.

Figure 14. Prepare to explain a rotate transform.

Update for Shear Transform
 1.00  0.00 163.50
-0.00 -1.00 83.50

The transform matrix shown in Figure 14 reflects the original translation and scaling to deal with the location of the virtual origin and the positive y direction plus translation by 50 on x and 30 on y. No other transforms are reflected by the matrix values in Figure 14.

Update the current transform to apply a rotate transform

Listing 12 applies a rotate transform and then draws a green rectangle, circle, and cross centered on the virtual origin.

Listing 12. Update the current transform to apply a rotate transform.

          //Update transform to provide rotation and
          // display, the transform values.    
          System.out.println(
                           "Update for Rotate Transform");

          g2.rotate(rotAngle);
          displayMatrix(g2.getTransform());

          //Draw a GREEN rectangle centered on the origin.
          g2.setColor(Color.GREEN);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

Graphic output following 30-degree rotate transform

Figure 15 shows the result of applying a 30-degree rotate transform. (The rectangles are smaller because I reduced the scale transform factors back to 1.0.)

Figure 15. Graphic output following 30-degree rotate transform.

The green rectangle, circle, and cross (along with the blue shapes that are hiding the green shapes) were all rotated 30 degrees counterclockwise. This means that the orthogonal axes associated with the new virtual origin are no longer horizontal and vertical. Rather, they are now tilted by 30 degrees relative to the horizontal and the vertical.

Contents of the current transform following a rotate transform

Figure 16 shows the contents of the current transform matrix following the application of the rotate transform.

Figure 16. Contents of the current transform following a rotate transform.

Update for Rotate Transform
 0.87 -0.50 163.50
-0.50 -0.87 83.50

Trigonometry

Note in particular the boldface values, and compare them with the values indicated for the Rotation transform at the bottom of Figure 3. (The algebraic signs won’t match those shown in Figure 3 because of the sign-flipping scale transform performed earlier to change the positive y direction)

Go to Google and type in “what is sine 30 degrees”

Google will tell you:

“sine(30 degrees) = 0.5

(Just in case you didn’t already know that you can do that at Google, you have learned something else that is useful in this lesson.)

Now do the same for cosine of 30 degrees and you will be told:

“cosine(30 degrees) = 0.866025404”

Going back to Figure 3, you can see that two of the elements in the transform matrix in Figure 16 should be the sine of 30 degrees and the other two should be the cosine of 30 degrees. Taking into account the fact that I specified a display format that rounded the values to two decimal digits, the values in Figure 16 are exactly what they should be according to the information provided by the article titled “Planar transformations” (see Resources). Had I not applied the sign flipping scale transform earlier, even the algebraic signs in Figure 16 would match the algebraic signs shown in Figure 3.

The final translate transform

Listing 13 applies one additional translate transform before the end of the overridden paint method. Then it draws the blue rectangle, circle, and cross that we have seen hiding shapes of other colors up to this point.

Listing 13. The final translate transform.

          //Update transform to include another translate
          // component and display the values.
          System.out.println(
                 "Update for Second Translate Transform");
          g2.translate(tx2,ty2);
          displayMatrix(g2.getTransform());

          //Draw a BLUE rectangle centered on the origin.
          g2.setColor(Color.BLUE);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

        }//end overridden paint()

Graphic output from the final translate transform

The graphic output from the final translate transform is shown in Figure 17.

Figure 17. Graphic output from the final translate transform.

For this case, I left all of the other parameters the same as Figure 15 but added a final translate transform of 25 pixels in the positive x direction only Note that I purposely did not translate in the y direction.

This is very revealing

What you see in Figure 17 is very revealing. The virtual origin did not move along a horizontal path as you might have expected it to do.

Had I not applied a rotation transform earlier, a translation in the positive x direction only would have caused the rectangle that is centered on the new virtual origin to move horizontally. However, because of the earlier rotate transform, the x axis is no longer horizontal. Rather it is tilted by 30 degrees relative to the horizontal. As a result, the new virtual origin was translated by 25 pixels along a line that is tilted at 30 degrees relative to the horizontal.

Thus, a translate transform causes the virtual origin to move without changing the orientation of the x and y axes. A rotate transform causes the x and y axes to be rotated around the virtual origin without moving the virtual origin. It is as though a push pin is used to keep the origin on the graph paper fixed in the same spot and then the graph paper is rotated around that push pin.

Contents of the final transform matrix

Figure 18 shows the contents of the transform matrix following the final translate transform.

Figure 18. Contents of the final transform matrix.

Update for Second Translate Transform
 0.87 -0.50 185.15
-0.50 -0.87 71.00

Compare Figure 18 with Figure 16 and you will see that only the two values highlighted in boldface in Figure 18 were modified by applying this translate transform.

The transform method

In addition to the scale, translate, shear, and rotate methods used above to modify the current transform, there is also a method named transform that can be used to modify the current transform. The transform method requires an AffineTransform object as an incoming parameter and could be useful if you need to create and use a custom transform.

Recap

We have covered a lot of ground here, so let’s recap what we have learned.

The absolute coordinate system

Shapes are drawn by illuminating pixels on the computer screen in a physical area of the screen that is occupied by a Java Canvas object or other suitable Java plotting surface.

The Java 2D system always illuminates pixels to draw shapes in an absolute Cartesian coordinate system.

The absolute coordinate system contains two orthogonal axes that intersect at the absolute origin.

One axis in the absolute coordinate system is horizontal and the other is vertical.

The two axes in the absolute coordinate system intersect at the upper left corner of the canvas, thus causing the absolute origin to be at the upper left corner.

Specific coordinates in the absolute coordinate system are given in screen resolution pixels relative to the absolute origin.

The positive direction for horizontal coordinates in the absolute coordinate system is to the right, while the positive direction for vertical coordinates in the absolute coordinate system is down the screen.

The virtual coordinate system

Pixels are illuminated and shapes are drawn in a Java 2D program using a virtual coordinate system.

The origin in the virtual coordinate system may or may not coincide with the origin in the absolute coordinate system.

The scale of the virtual coordinate system may or may not be the same as the scale of the absolute coordinate system.

The positive directions relative to the virtual origin may or may not be the same as the positive directions relative to the absolute origin.

The axes in the virtual coordinate system may or may not be horizontal and vertical whereas the axes in the absolute coordinate system are always horizontal and vertical.

The axes in the virtual coordinate system may or may not be orthogonal whereas the axes in the absolute coordinate system are always orthogonal.

Transforming virtual coordinates into absolute coordinates

The Java 2D plotting system maintains a transform at all times. The purpose of the transform is to convert the coordinates of a point in the virtual coordinate system into coordinates in the absolute coordinate system.

Whenever a point is plotted in the virtual coordinate system, the current transform is used to convert the virtual coordinates of that point into absolute coordinates. The absolute coordinates relative to the upper left corner of the canvas are then converted to absolute coordinates relative to the upper left corner of the screen, and a specific screen pixel is illuminated to represent the point being plotted.

The default transform is an identity matrix, which is a “do nothing” transform. Hence, the default virtual coordinate system is an exact match for the absolute coordinate system. The default origin is at the upper left corner of the canvas. The default positive horizontal direction is to the right. The default positive vertical direction is down. Default coordinate values are given in screen resolution pixels relative to the origin.

Modifying the current transform

Calling any of the following Graphics2D methods will modify the current transform that is maintained by the Java 2D system:

  • scale
  • translate
  • shear
  • rotate
  • transform

Changing the virtual coordinate system

Modifying the current transform effectively causes a change in the virtual coordinate system. The change affects only those shapes that are drawn after the method is called to modify the current transform. The change does not retroactively apply to shapes that were drawn prior to the call to the method.

Calling the scale method causes distances relative to the virtual origin to be measured differently. It does not modify the location of the virtual origin or the orientation of the virtual axes.

Calling the translate method causes the virtual origin to be moved to a different location. It does not modify how distances are measured or the orientation of the virtual axes.

Calling the shear method changes the orientation of the virtual axes such that they are no longer orthogonal. It does not modify the location of the virtual origin or how distances are measured.

Calling the rotate method changes the orientation of the virtual axes. Both virtual axes are rotated by the same angle around the virtual origin. If the axes were previously orthogonal, they remain orthogonal. If the virtual axes were not previously orthogonal, the angles at the intersections of the virtual axes are not modified. Calling this method does not modify the location of the virtual origin or how distances are measured relative to the virtual origin.

Calling the transform method changes the virtual coordinate system in a manner that is prescribed by the AffineTransform object that is passed as a parameter to the method.

The bottom line

Hopefully, what you have learned in this lesson will better prepare you to use transforms when programming with the Java 2D API. Hopefully, it will also better prepare you to understand the considerably more complex use of transforms in Java 3D, which will be the topic of a future lesson.

Run the program

I encourage you to compile and execute the code from Listing 14. Experiment with the code, making changes, and observing the results of your changes.

Summary

In this lesson, you learned to understand transforms in Java 2D in a way that you will be able to extend to an understanding of transforms in Java 3D. You also learned how to write code that makes use of that understanding.

What’s next?

The topics for future lessons include transforms in Java 3D, interactive Java 3D programs, advanced animation, and surfaces.

Download

Resources

Complete program listing

A complete listing of the program discussed in this lesson is shown in Listing 14 below.

Listing 14. Program listing for the program named Java2D001.

/*Java2D001.java
Copyright 2007, R.G.Baldwin

The purpose of this program is to illustrate transforms in
Java 2D space.

This program is very similar to the Java 3D program named 
Java3D010.

A user input GUI allows the user to specify values for 
transforms of the following types:

Scale
Translate
Rotate
Shear

A Replot button allows the user to modify input 
parameters, recompute the transforms, and produce a new
output.  Clicking the Replot button when one of the
input fields contains invalid numeric data will cause the
program to abort with a NumberFormatException.

In addition to providing a graphic output display, the 
program also gets and displays the specifiable values in 
the AffineTransform object each time it is updated to
support a new transform.

Tested using Java SE 6, and Java 3D 1.5.0 running under
Windows XP.
*********************************************************/
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.*;

class Java2D001 extends Frame{
  GUI gui = new GUI();

  TextField sxTxt = new TextField("1.0");
  TextField syTxt = new TextField("1.0");
  TextField txTxt = new TextField("0.0");
  TextField tyTxt = new TextField("0.0");
  TextField shxTxt = new TextField("0.0");
  TextField shyTxt = new TextField("0.0");
  TextField rotTxt = new TextField("0.0");
  TextField tx2Txt = new TextField("0.0");
  TextField ty2Txt = new TextField("0.0");

  Label naTxt = new Label("");
  //----------------------------------------------------//

  public static void main(String[] args){
    new Java2D001();
  }//end main
  //----------------------------------------------------//

  Java2D001(){//constructor
    setBounds(236,0,235,254);
    setTitle("Copyright 2007, R.G.Baldwin");

    //Construct the user input panel and add it to the
    // CENTER of the Frame.
    Panel inputPanel = new Panel();
    inputPanel.setLayout(new GridLayout(0,3));

    inputPanel.add(new Label("Xform Type",Label.CENTER));
    inputPanel.add(new Label("X",Label.CENTER));
    inputPanel.add(new Label("Y",Label.CENTER));

    inputPanel.add(new TextField("Scale"));
    inputPanel.add(sxTxt);
    inputPanel.add(syTxt);

    inputPanel.add(new TextField("Translate"));
    inputPanel.add(txTxt);
    inputPanel.add(tyTxt);

    inputPanel.add(new TextField("Shear"));
    inputPanel.add(shxTxt);
    inputPanel.add(shyTxt);

    inputPanel.add(new TextField("Rotate (deg)"));
    inputPanel.add(rotTxt);
    inputPanel.add(naTxt);

    inputPanel.add(new TextField("Translate"));
    inputPanel.add(tx2Txt);
    inputPanel.add(ty2Txt);

    add(inputPanel,BorderLayout.CENTER);

    //Add a button with an ActionListener that allows the
    // user to modify transform parameters, recompute the
    // transform, replot the graphic output, and
    // re-display the matrix data.
    Button button = new Button("Replot");
    button.addActionListener(new ActionListener(){
      public void actionPerformed(ActionEvent e){
        gui.sx = Double.parseDouble(sxTxt.getText());
        gui.sy = Double.parseDouble(syTxt.getText());
        gui.tx = Double.parseDouble(txTxt.getText());
        gui.ty = Double.parseDouble(tyTxt.getText());
        gui.shx = Double.parseDouble(shxTxt.getText());
        gui.shy = Double.parseDouble(shyTxt.getText());
        //Convert angle from degrees to radians.
        gui.rotAngle = (Double.parseDouble(rotTxt.
                              getText()) * Math.PI/180.0);
        gui.tx2 = Double.parseDouble(tx2Txt.getText());
        gui.ty2 = Double.parseDouble(ty2Txt.getText());
        gui.display.repaint();}//end actionPerformed
      }//end new ActionListener
    );//end addActionListener

    add(button,BorderLayout.SOUTH);
    setVisible(true);

  }//end constructor
  //----------------------------------------------------//

  //This is an inner class.
  class GUI extends Frame{
    //The following values are set by the ActionListener
    // that is registered on the Replot button before a
    // call to the repaint method is made.
    double sx = 1.0; //scale X
    double sy = 1.0; //scale Y
    double tx = 0.0; //translate X
    double ty = 0.0; //translate Y
    double shx = 0.0;//shear X
    double shy = 0.0;//shear Y
    double rotAngle = 0.0;//rotate
    double tx2 = 0.0;//translate X again
    double ty2 = 0.0;//translate Y again
    Display display = new Display();
    
    GUI(){//constructor
      setBounds(0,0,235,254);
      setTitle("Copyright 2007, R.G.Baldwin");
      add(display,BorderLayout.CENTER);
      setVisible(true);

      //Window listener to terminate program.
      addWindowListener(new WindowAdapter(){
        public void windowClosing(WindowEvent e){
          System.exit(0);}});
    }//end constructor
    //--------------------------------------------------//

      //This is an inner class, that provides a Canvas
      // object on which the shapes are drawn.
      class Display extends Canvas{
        //Override the paint() method to draw and
        // manipulate a rectangle based on user provided
        // transform parameters.
        public void paint(Graphics g){
          //Downcast the Graphics object to a Graphics2D
          // object.
          Graphics2D g2 = (Graphics2D)g;

          System.out.println();//Blank line
          System.out.println("Display new matrix data.");

          //Display contents of default AffineTransform
          // object.
          System.out.println("Default Transform");
          displayMatrix(g2.getTransform());

          //Apply a translation to move the origin to the
          // center of the display area.
          g2.translate(getWidth()/2.0,getHeight()/2.0);

          //Flip the sign on y values to cause the
          // positive y-direction to be up instead of
          // down, which is the default.
          g2.scale(1,-1);

          System.out.println("After correction for "
                 + "origin and direction of positive Y.");
          System.out.println("Negative y-scale corrects "
                        + "for direction of positive Y.");
          displayMatrix(g2.getTransform());

          //Draw a CYAN rectangle centered on the
          // current origin.
          g2.setColor(Color.CYAN);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));

          //Draw the current origin as a circle containing
          // a cross.
          drawOrigin(g);

          //Update transform to include a scale component,
          // and display the values.
          System.out.println(
                            "Update for Scale Transform");
          //Call the scale method to update the transform.
          g2.scale(sx,sy);
          displayMatrix(g2.getTransform());

          //Draw a RED rectangle centered on the origin.
          g2.setColor(Color.RED);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

          //Update transform to include a translate
          // component,  and display the values.
          System.out.println(
                        "Update for Translate Transform");
          g2.translate(tx,ty);
          displayMatrix(g2.getTransform());

          //Draw an ORANGE rectangle centered on the
          // origin.
          g2.setColor(Color.ORANGE);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

          //Update transform to include a shear component,
          // and display the values.
          System.out.println(
                            "Update for Shear Transform");
          g2.shear(shx,shy);
          displayMatrix(g2.getTransform());

          //Draw a BLACK rectangle centered on the origin.
          g2.setColor(Color.BLACK);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

          //Update transform to provide rotation and
          // display, the transform values.    
          System.out.println(
                           "Update for Rotate Transform");

          g2.rotate(rotAngle);
          displayMatrix(g2.getTransform());

          //Draw a GREEN rectangle centered on the origin.
          g2.setColor(Color.GREEN);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

          //Update transform to include another translate
          // component and display the values.
          System.out.println(
                 "Update for Second Translate Transform");
          g2.translate(tx2,ty2);
          displayMatrix(g2.getTransform());

          //Draw a BLUE rectangle centered on the origin.
          g2.setColor(Color.BLUE);
          g2.draw(new Rectangle2D.Double(-10,-10,20,20));
          drawOrigin(g);

        }//end overridden paint()
        //----------------------------------------------//

        //This method will draw a circle containing a
        // cross at the current origin.
        void drawOrigin(Graphics g){
          Graphics2D g2 = (Graphics2D)g;
          g2.drawLine(-7,0,7,0);
          g2.drawLine(0,-7,0,7);
          g2.drawOval(-5,-5,10,10);
        }//end drawOrigin
        //----------------------------------------------//
      }//end inner class Display
      //================================================//

    //This method receives a reference to an
    // AffineTransform and displays the six specifiable
    // values in the transform matrix.
    void displayMatrix(AffineTransform theTransform){

      //Retrieve the contents of the AffineTransform into
      // an array of type double.
      double[] theMatrix = new double[6];
      theTransform.getMatrix(theMatrix);

      //See http://www.particle.kth.se/~lindsey/JavaCourse
      // /Book/Part1/Tech/Chapter05/formatterPrintf.html
      // for instructions on the use of System.out.printf.

      //Display first row of values by displaying every
      // other element in the array starting with element
      // zero.
      Sys0em.out.printf ("%5.2f %5.2f %5.2f %n", 
                  theMatrix[0],theMatrix[2],theMatrix[4]);
      //Display second row of values displaying every
      // other element in the array starting with element
      // number one.
      System.out.printf ("%5.2f %5.2f %5.2f %n", 
                 theMatrix[1],theMatrix[3],theMatrix[5]);

    }//end displayMatrix
    //--------------------------------------------------//
  }//end class GUI
  //====================================================//

}//end class Java2D001

Copyright

Copyright 2007, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.

About the author

Richard Baldwin is a college professor (at Austin Community College in Austin, TX) and private consultant whose primary focus is a combination of Java, C#, and XML. In addition to the many platform and/or language independent benefits of Java and C# applications, he believes that a combination of Java, C#, and XML will become the primary driving force in the delivery of structured information on the Web.

Richard has participated in numerous consulting projects and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin’s Programming Tutorials, which have gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.

In addition to his programming expertise, Richard has many years of practical experience in Digital Signal Processing (DSP). His first job after he earned his Bachelor’s degree was doing DSP in the Seismic Research Department of Texas Instruments. (TI is still a world leader in DSP.) In the following years, he applied his programming and DSP expertise to other interesting areas including sonar and underwater acoustics.

Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.

Baldwin@DickBaldwin.com

-end-

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories