旋转形状并在原始位置绘制

Rotating shape and drawing it at the original position

我正在尝试在给定点绘制旋转形状。举个例子,在下图中,红色矩形是在一个点上绘制的未旋转矩形,然后蓝色矩形在同一位置旋转绘制。蓝色矩形是我想要的结果。

我一直在试验和尝试不同的方法。目前,这是我用于图像的内容:

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));
}

这适用于矩形,但不适用于其他类型的形状。我想弄清楚是否有一种方法可以针对每种类型的形状完成此操作。另请注意,该代码仅用于测试目的,其中有很多不良做法,例如多次调用 Math.toRadians。

你有一个形状,任何形状。
你有一个点 (px,py),你想围绕这个点旋转形状并逆时针测量角度 ag

对于形状的每个点,该过程分为三个步骤:

  1. 翻译成(px,py)
  2. 旋转
  3. 翻译回 (0,0)

翻译完全简单

xNew = xOld - px
yNew = yOld - py

旋转有点不简单

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

终于翻译回来了:

xDef = xRot + px
yDef = yRot + py

一点解释:任何变换都可以通过两种方式看到:1) 我移动形状 2) 我移动轴系统。如果你仔细想想,你会发现这个变换是相对的:从轴的角度看,或者从形状的角度看。
所以,你可以说“我想要翻译系统中的坐标”,或者你也可以说“我想要翻译形状的坐标”。
无论你选择什么观点,方程都是一样的。

我解释了这么多,只是为了让你明白哪个是角度的正方向:顺时针或逆时针。

是这样的吗?

可以先用一个旋转变换来实现,然后以旋转后形状的边界为基础,用平移变换将其平移回满足最上面的y和最左边的x 原始矩形的值。

请参阅 getImage() 方法以了解其一种实现方式。

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);

完整的源代码如下:

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);
    }
}