M.E.
M.E.

Reputation: 5496

Using world coordinates in Java2D AWT Frame

I am stuck while trying to implement a World Coordinates to Device Coordinates transformation.

Basically I want to draw in the following World Coordinates:

    // --- World Coordinates
    //
    //                  (xmax,ymax)
    //      ┌────────────────┐
    //      │                │
    //      │                │
    //      │                │
    //      │                │
    //      └────────────────┘
    // (xmin,ymin)
    //
    // (xmin,ymin) = (0, 100)
    // (xmax,ymax) = (1.5, 2.5)

Following this book (page 31), I am trying to implement the affine transformations required to move from a world coordinates viewport into a device coordinates viewport.

    // Introduction to Computer Graphics Using Java 2D and 3D 
    // Frank Klawonn, Ed. Springer 2008
    // Page 31

I have prepared a test class which contains two tests, the first one (just set int test = 1) tests the first two transformation to flip Y axis (origin in the screen in the upper left corner, instead of the bottom left).

That tests works fine, a rectangle and line are draw to show this.

Test1

However, while switching to Test 2, which includes all the expected transformations, I get an empty screen as result:

    // --- Affine Transform 1 and 2
    //
    //   T(0, h) ◦ S(1,−1)
    //
    // --- Affine Transform 3, 4 and 5
    //   
    //                     ╭ umax − umin   vmax − vmin ╮
    //   T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)  
    //                     ╰ xmax − xmin   ymax − ymin ╯
    //

The whole source code for the Test class is included:

package com.example.test2;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;

public class Test2 extends Frame {

        Graphics2D          g2d;

        Insets              insFrame;
        Dimension           sizeFrame;

        public Test2() {

            this.setSize(660,540);            
            this.setUndecorated(false);
            this.setVisible(true);

            this.addWindowListener(new WindowAdapter(){  
                public void windowClosing(WindowEvent e) {  
                    dispose();  
                }  
            });

        }

        @Override
        public void paint(Graphics g) {

            g2d = (Graphics2D) g;

            insFrame = this.getInsets();
            sizeFrame = this.getSize();

            //int test = 1;   // Change to test 2 to test the whole transformation
            int test = 2;

            if ( test == 1 ) {

                // AT1 & AT2 Test
                this.setScale(1);
                g2d.setColor(Color.ORANGE);
                Line2D.Double line = new Line2D.Double(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom);
                g2d.draw(line);
                g2d.setColor(Color.RED);
                g2d.drawRect(0, 0, sizeFrame.width-insFrame.left-insFrame.right-1, sizeFrame.height-insFrame.top-insFrame.bottom-1);

            } else if (test == 2) {

                // AT1, AT2, AT3, AT4 & AT5 Test
                this.setScale(2);
                g2d.setColor(Color.ORANGE);
                Line2D.Double line = new Line2D.Double(0, 1.5, 100, 2.5);
                g2d.draw(line);
                g2d.setColor(Color.RED);
                Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 2.5);
                g2d.draw(rectangle);

            }


        };

        // Required affine transforms to move from 
        // World Coordinates Viewport to 
        // Screen Pixel Coordinates Viewport
        //
        // --- Reference textbook:
        //
        // Introduction to Computer Graphics Using Java 2D and 3D 
        // Frank Klawonn, Ed. Springer 2008
        // Page 31
        // 
        // --- Viewports
        //
        // World Coordinates Viewport (xmin,ymin) - (xmax,ymax)
        // Screen Pixel Coordinates Viewport (umin, vmin) - (umax, vmax)
        // 
        // --- World Coordinates
        //
        //                  (xmax,ymax)
        //      ┌────────────────┐
        //      │                │
        //      │                │
        //      │                │
        //      │                │
        //      └────────────────┘
        // (xmin,ymin)
        //
        // (xmin,ymin) = (0, 100)
        // (xmax,ymax) = (1.5, 2.5)
        //
        // --- User coordinates
        //
        //                  (umax,vmax)
        //      ┌────────────────┐
        //      │                │
        //      │                │
        //      │                │
        //      │                │
        //      └────────────────┘
        // (umin,vmin)
        //
        // (umin,vmin) = (inset.left, heightFrame - inset.bottom)
        // (umax,vmax) = (widthFrame - inset.right, inset.top)
        //
        // --- Affine Transform 1 and 2
        //
        //   T(0, h) ◦ S(1,−1)
        //
        // --- Affine Transform 3, 4 and 5
        //   
        //                     ╭ umax − umin   vmax − vmin ╮
        //   T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)  
        //                     ╰ xmax − xmin   ymax − ymin ╯
        //
        private void setScale(int test) {

            // World Coordinates
            // (xmin,ymin) = (0, 1.5)
            // (xmax,ymax) = (100, 2.5)
            Double xmin = 0.0;
            Double ymin = 1.5;
            Double xmax = 100.0;
            Double ymax = 2.5;

            // User Coordinates
            // (umin,vmin) = (inset.left, heightFrame - inset.bottom)
            // (umax,vmax) = (widthFrame - inset.right, inset.top)
            int umin = insFrame.left;
            int vmin = (int) (sizeFrame.getHeight() - insFrame.bottom);
            int umax = (int) (sizeFrame.getWidth() - insFrame.right);
            int vmax = insFrame.top;


            if (test == 1) {

                // Affine Transformation 1 and 2
                // T(0, h) ◦ S(1,−1)
                AffineTransform at1 = new AffineTransform();
                at1.setToScale(1,-1);
                AffineTransform at2 = new AffineTransform();
                at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
                at1.preConcatenate(at2);
                g2d.transform(at1);

            } else if (test == 2) {

                // Affine Transformation 1 and 2
                // T(0, h) ◦ S(1,−1)
                AffineTransform at1 = new AffineTransform();
                at1.setToScale(1,-1);
                AffineTransform at2 = new AffineTransform();
                at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);

                // Affine Transformation 3, 4 and 5
                //                   ╭ umax − umin   vmax − vmin ╮
                // T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)  
                //                   ╰ xmax − xmin   ymax − ymin ╯
                AffineTransform at3 = new AffineTransform();
                at3.setToTranslation(umin, vmin);
                AffineTransform at4 = new AffineTransform();
                at4.setToScale(1.0*(umax-umin)/(xmax-xmin), 1.0*(vmax-vmin)/(ymax-ymin));
                AffineTransform at5 = new AffineTransform();
                at5.setToTranslation(-xmin,-ymin);
                at4.preConcatenate(at5);
                at3.preConcatenate(at4);
                at2.preConcatenate(at3);
                at1.preConcatenate(at2);
                g2d.transform(at1);

            }


        }

        public static void main( String[] args ) {
            Test2 window = new Test2();

    }

}

Upvotes: 0

Views: 322

Answers (1)

Thomas Kläger
Thomas Kläger

Reputation: 21435

I've looked into the book and I find the usage of AffineTransform.preConcatenate() confusing. I prefer to use AffineTransform.concatenate(), since for me this gives a more natural flow.

Let's look at the first example (transform, then swap y direction).

This is your code:

    AffineTransform at1 = new AffineTransform();
    at1.setToScale(1,-1);
    AffineTransform at2 = new AffineTransform();
    at2.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
    at1.preConcatenate(at2);

With using concatenate, you could write it as:

    AffineTransform at1 = new AffineTransform();
    at1.setToTranslation(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
    AffineTransform at2 = new AffineTransform();
    at2.setToScale(1,-1);
    at1.concatenate(at2);

or, even shorter:

    AffineTransform at = new AffineTransform();
    at.translate(insFrame.left, sizeFrame.getHeight() - insFrame.bottom - 1);
    at.scale(1,-1);

You can see the flow "translate, then scale" directly represented in the code.


For the second test you can apply the same approach:

    // Affine Transformation 3, 4 and 5
    //                   ╭ umax − umin   vmax − vmin ╮
    // T(umin, vmin) ◦ S | ----------- , ----------- | ◦ T(-xmin, -ymin)
    //                   ╰ xmax − xmin   ymax − ymin ╯
    AffineTransform at = new AffineTransform();
    at.translate(umin, vmin);
    at.scale((umax-umin)/(xmax-xmin), (vmax-vmin)/(ymax-ymin));
    at.translate(-xmin, -ymin);

Note this: umin, vmin already denotes the lower left screen coordinate and umax, vmax the upper right screen coordinate, so no additional translation or y axis flipping is necessary!


Some closing notes:

  • the default width of a line in Java2D is one scaled unit. With your choice of ymin and ymax, one scaled unit fills the complete height of your window. To not get only a filled rectangle, you should set the line width to the minimum possible value with g2d.setStroke(new BasicStroke(0.0f)); before the g2d.draw() calls.
  • the parameters for Rectangle2D.Double() are x, y, w, h, so your code to create the rectangle object in the second example should probably read Rectangle2D.Double rectangle = new Rectangle2D.Double(0, 1.5, 100, 1); (with a height of 2.5 the upper border will not show on screen).

Upvotes: 1

Related Questions