0

I want to rotate what is painted in a paintComponent(Graphics g) method according to a rotation angle and around the centroid of the corresponding panel.

Here's what I have initially:

Initial state

A control panel (textfield + button) to set the angle (in degrees, from 0 to 359). A main panel containing a little white panel. The little white panel has to be updated (setBounds() and repaint()) when the user clicks on the "Set angle" button. In any case the little white panel has to be centered on its centroid.

Here's what I want for 90 and 135 degrees:

90 degrees state

135 degrees state

Here's my SSCCE (which is not working). Could you tell me what I'm doing wrong?

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTextField;

public class RotatingPaint {
    private static double angle = 0;
    private static JFrame frame = new JFrame();
    private static JTextField angleField = new JTextField("0", 6);
    private static JButton angleButton = new JButton("Set angle");
    private static JPanel controlPanel = new JPanel();
    private static JPanel mainPanel  = new JPanel();
    private static JPanel littleWhitePanel  = new JPanel() {
        @Override
        protected void paintComponent(Graphics g)
        {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            Color oldColor = g2.getColor();
            g2.setColor(Color.RED);
            g2.rotate(angle, getWidth() / 2, getHeight() / 2);
            g2.drawString("Hello World", 2, 12);
            // Revert the transformation matrix back to its initial state
            g2.rotate(-angle, -getWidth() / 2, -getHeight() / 2);
            g2.setColor(oldColor);
        }
    };


    public RotatingPaint() {

        // Inits control panel
        angleButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e){
                angle = Math.toRadians(Integer.parseInt(angleField.getText()));
                Rectangle oldBounds = littleWhitePanel.getBounds();
                Rectangle newBounds = getBoundsOfRotatedRectangle(oldBounds);
                littleWhitePanel.setBounds(newBounds);
                mainPanel.repaint();
            }
        });
        controlPanel.add(angleField);
        controlPanel.add(angleButton);

        // Inits main panel
        mainPanel.setPreferredSize(new Dimension(400, 300));
        mainPanel.setLayout(null);
        littleWhitePanel.setBounds(160, 100, 80, 20);
        littleWhitePanel.setBackground(Color.WHITE);
        mainPanel.add(littleWhitePanel);

        // Inits frame
        frame.setLayout(new BorderLayout());
        frame.add(controlPanel, BorderLayout.NORTH);
        frame.add(mainPanel, BorderLayout.CENTER);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        RotatingPaint app = new RotatingPaint();
    }

    /**
     * Applies the rotate transformation to a Point.
     * @param point the point to be rotated
     */
    private void rotatePoint(Point point) {
        Point centroid = new Point((int) littleWhitePanel.getBounds().getCenterX(), (int) littleWhitePanel.getBounds().getCenterY());
        int x = (int) Math.rint(centroid.x
                + (point.x - centroid.x) * Math.cos(angle)
                - (point.y - centroid.y) * Math.sin(angle));
        int y = (int) Math.rint(centroid.y
                + (point.x - centroid.x) * Math.sin(angle)
                + (point.y - centroid.y) * Math.cos(angle));
        point.x = x;
        point.y = y;
    }

    /**
     * @param rectangle the rectangle to be rotated
     * @return the bounding rectangle of the rotated rectangle
     */
    private Rectangle getBoundsOfRotatedRectangle(Rectangle rectangle) {

        // Getting each corner
        List<Point> corners = new ArrayList<Point>();
        Point topLeftCorner = rectangle.getLocation();
        corners.add(topLeftCorner);
        Point topRightCorner = new Point(topLeftCorner);
        topRightCorner.x += rectangle.width;
        corners.add(topRightCorner);
        Point bottomLeftCorner = new Point(topLeftCorner);
        bottomLeftCorner.y += rectangle.height;
        corners.add(bottomLeftCorner);
        Point bottomRightCorner = new Point(bottomLeftCorner);
        bottomRightCorner.x += rectangle.width;
        corners.add(bottomRightCorner);

        // Transforming each corner
        for (Point corner : corners) {
            rotatePoint(corner);
        }

        // Getting the min/max x and the min/max y
        int minX = corners.get(0).x;
        int minY = corners.get(0).y;
        int maxX = corners.get(0).x;
        int maxY = corners.get(0).y;
        for (int i = 1; i < corners.size(); i++) {
            minX = Math.min(minX, corners.get(i).x);
            minY = Math.min(minY, corners.get(i).y);
            maxX = Math.max(maxX, corners.get(i).x);
            maxY = Math.max(maxY, corners.get(i).y);
        }

        return new Rectangle(minX, minY, maxX - minX, maxY - minY);
    }
}

I manage to reset the bounds of the little white panel but I'm not able to rotate the text correctly. It's pretty hard to describe what isn't working precisely. Please execute that code and check by yourself.

Thank you.

Marc de Verdelhan
  • 2,501
  • 3
  • 21
  • 40
  • 3
    I'd use 3 `AffineTransform` instances concatenated for this. 1) Move center to origin. 2) Rotate 3) Move center to original position. – Andrew Thompson Oct 13 '14 at 12:53
  • Thank you for your comment @AndrewThompson. But isn't what I'm already doing by calling `g2.rotate(angle, getWidth() / 2, getHeight() / 2);`? – Marc de Verdelhan Oct 13 '14 at 13:02
  • 2
    @AndrewThompson: Alternatively, this variation of [`rotate()`](http://docs.oracle.com/javase/8/docs/api/java/awt/geom/AffineTransform.html#rotate-double-double-double-double-) concatenates everything in a single instance. – trashgod Oct 13 '14 at 13:13
  • 1
    @AndrewThompson: See also [`Graphics2D#rotate()`](http://docs.oracle.com/javase/8/docs/api/java/awt/Graphics2D.html#rotate-double-double-double-) – trashgod Oct 13 '14 at 13:21
  • @MarcdeVerdelhan: Don't you want to rotate about the string's center? – trashgod Oct 13 '14 at 13:23
  • @trashgod: Thank you but no. I simplified my example. In the real case I have several `drawString(...)` which are arranged (in X columns and Y rows) by another method. I want to rotate all of them around the centroid of the panel. – Marc de Verdelhan Oct 13 '14 at 13:43
  • @MarcdeVerdelhan: See also this related [example](http://stackoverflow.com/a/5594424/230513). – trashgod Oct 13 '14 at 13:54

1 Answers1

0

Found. This example helped me. I updated the paintComponent(Graphics gr) method like this:

    @Override
    public void paintComponent(Graphics gr) {
        super.paintComponent(gr);
        Graphics2D g2 = (Graphics2D) gr;
        if (angle != 0) {
            // Moving to centroid
            g2.translate(getWidth()/2, getHeight()/2);
            g2.rotate(angle);
            // Moving back but using the string's dimensions
            // instead of the bounds (which have been changed)
            g2.translate(-stringWidth/2, -stringHeight/2);
        }
        g2.drawString("Hello World", 2, 12);
        if (angle != 0) {
            // Revert the transformation matrix back to its initial state
            g2.translate(stringWidth/2, stringHeight/2);
            g2.rotate(-angle);
            g2.translate(-getWidth()/2, -getHeight()/2);
        }
    }

The key is that the Graphics2D#rotate() executes the following calls:

translate(x, y);
rotate(theta);
translate(-x, -y);

But in my case I didn't want to translate back exactly the same amount as the original translation. I had to use the string's dimensions.

Community
  • 1
  • 1
Marc de Verdelhan
  • 2,501
  • 3
  • 21
  • 40