Rotating shape and drawing it at the original position

Viewed 194

I'm trying to draw a rotated shape at a given point. To give an example, in the following image, the red rectangle is a non-rotated rectangle drawn at a point and then the blue rectangle is rotated and drawn at the same position. The blue rectangle is the outcome I'm aiming for.

I've been experimenting and trying different methods. Currently, here is what I used for the image:

Point point = new Point(300, 300);
Dimension dim = new Dimension(200, 100);
double radians = Math.toRadians(30);
g.setColor(new java.awt.Color(1f, 0f, 0f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);
translate(g, dim, radians);
g.rotate(radians, point.getX(), point.getY());
g.setColor(new java.awt.Color(0f, 0f, 1f, .5f));
g.fillRect(point.x, point.y, dim.width, dim.height);

private static void translate(Graphics2D g, Dimension dim, double radians) {
    if (radians > Math.toRadians(360)) {
        radians %= Math.toRadians(360);
    }
    
    int xOffsetX = 0;
    int xOffsetY = 0;
    int yOffsetX = 0;
    int yOffsetY = 0;
    if (radians > 0 && radians <= Math.toRadians(90)) {
        xOffsetY -= dim.getHeight();
    } else if (radians > Math.toRadians(90) && radians <= Math.toRadians(180)) {
        xOffsetX -= dim.getWidth();
        xOffsetY -= dim.getHeight();
        yOffsetY -= dim.getHeight();
    } else if (radians > Math.toRadians(180) && radians <= Math.toRadians(270)) {
        xOffsetX -= dim.getWidth();
        yOffsetX -= dim.getWidth();
        yOffsetY -= dim.getHeight();
    } else {
        yOffsetX -= dim.getWidth();
    }
    int x = rotateX(xOffsetX, xOffsetY, radians);
    int y = rotateY(yOffsetX, yOffsetY, radians);
    g.translate(x, y);
}

private static int rotateX(int x, int y, double radians) {
    if (x == 0 && y == 0) {
        return 0;
    }
    return (int) Math.round(x * Math.cos(radians) - y * Math.sin(radians));
}

private static int rotateY(int x, int y, double radians) {
    if (x == 0 && y == 0) {
        return 0;
    }
    return (int) Math.round(x * Math.sin(radians) + y * Math.cos(radians));
}

This works for rectangles but doesn't work for other types of shapes. I'm trying to figure out if there is a way to accomplish this for every type of shape. Also note that the code is just for testing purposes and there are a lot of bad practices in it, like calling Math.toRadians so much.

2 Answers

Something like this?

enter image description here enter image description here enter image description here

It can be achieved using a rotate transform first, then using the bounds of the rotated shape as a basis, the translate transform can be used to shift it back to meet the top most y and leftmost x values of the original rectangle.

See the getImage() method for one implementation of that.

int a = angleModel.getNumber().intValue();
AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
// rotate the original shape with no regard to the final bounds
Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
// get the bounds of the rotated shape
Rectangle2D rotatedRect = rotatedShape.getBounds2D();
// calculate the x,y offset needed to shift it to top/left bounds of original rectangle
double xOff = rectangle.getX()-rotatedRect.getX();
double yOff = rectangle.getY()-rotatedRect.getY();
AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
// shift the new shape to the top left of original rectangle
Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);

Here is the complete source code:

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.border.EmptyBorder;

public class TransformedShape {

    private JComponent ui = null;
    JLabel output = new JLabel();
    JToolBar tools = new JToolBar("Tools");
    ChangeListener changeListener = (ChangeEvent e) -> {
        refresh();
    };
    int pad = 5;
    Rectangle2D.Double rectangle = new Rectangle2D.Double(pad,pad,200,100);
    SpinnerNumberModel angleModel = new SpinnerNumberModel(30, 0, 90, 1);

    public TransformedShape() {
        initUI();
    }

    private BufferedImage getImage() {
        int a = angleModel.getNumber().intValue();
        AffineTransform rotateTransform = AffineTransform.getRotateInstance((a*2*Math.PI)/360d);
        Shape rotatedShape = rotateTransform.createTransformedShape(rectangle);
        Rectangle2D rotatedRect = rotatedShape.getBounds2D();
        double xOff = rectangle.getX()-rotatedRect.getX();
        double yOff = rectangle.getY()-rotatedRect.getY();
        AffineTransform translateTransform = AffineTransform.getTranslateInstance(xOff, yOff);
        Shape rotateAndTranslateShape = translateTransform.createTransformedShape(rotatedShape);
        Area combinedShape = new Area(rotateAndTranslateShape);
        combinedShape.add(new Area(rectangle));
        Rectangle2D r = combinedShape.getBounds2D();
        BufferedImage bi = new BufferedImage((int)(r.getWidth()+(2*pad)), (int)(r.getHeight()+(2*pad)), BufferedImage.TYPE_INT_ARGB);

        Graphics2D g = bi.createGraphics();
        g.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        
        g.setColor(new Color(255,0,0,127));
        g.fill(rectangle);
        
        g.setColor(new Color(0,0,255,127));
        g.fill(rotateAndTranslateShape);
        
        g.dispose();
        
        return bi;
    }

    private void addModelToToolbar(String label, SpinnerNumberModel model) {
        tools.add(new JLabel(label));
        JSpinner spinner = new JSpinner(model);
        spinner.addChangeListener(changeListener);
        tools.add(spinner);
    }

    public final void initUI() {
        if (ui!=null) return;

        ui = new JPanel(new BorderLayout(4,4));
        ui.setBorder(new EmptyBorder(4,4,4,4));
        
        ui.add(output);
        
        ui.add(tools,BorderLayout.PAGE_START);

        addModelToToolbar("Angle", angleModel);
              
        refresh();
    }
    
    private void refresh() {
        output.setIcon(new ImageIcon(getImage()));
    }
    
    public JComponent getUI() {
        return ui;
    }

    public static void main(String[] args) {
        Runnable r = () -> {
            try {
                UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            TransformedShape o = new TransformedShape();
            
            JFrame f = new JFrame(o.getClass().getSimpleName());
            f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
            f.setLocationByPlatform(true);
            
            f.setContentPane(o.getUI());
            f.pack();
            f.setMinimumSize(f.getSize());
            
            f.setVisible(true);
        };
        SwingUtilities.invokeLater(r);
    }
}

You have a shape, any shape.
You have a point (px,py) and you want to rotate the shape around this point and angle ag measured counter-clokwise.

For each point of the shape the proccess has three steps:

  1. Translate to (px,py)
  2. Rotate
  3. Translate back to (0,0)

The translation is fully simple

xNew = xOld - px
yNew = yOld - py

The rotation is a bit less simple

xRot = xNew * cos(ag) - yNew * sin(ag)
yRot = xNew * sin(ag) + yNew * cos(ag)

Finally the translation back:

xDef = xRot + px
yDef = yRot + py

A bit of explanation: Any transformation can be seen in two ways: 1) I move the shape 2) I move the axis-system. If you think about it, you'll find that the trasnsformation is relative: seen from the axis point of view or seen from the shape point of view.
So, you can say "I want coordinates in the translated system", or you can also say "I want the coordinates of the translated shape".
It doesn't matter what point of view you chose, the equations are the same.

I'm explaining this so much, just to achieve you realize which is the positive direction of the angle: clockwise or counter-clockwise.

Related