当前位置: 首页 > 知识库问答 >
问题:

使用仿射变换缩放图形

傅啸
2023-03-14

我正在制作一个带有Swing的GUI,它使用仿射变换来缩放绘制在JInternalFrame上的图形2D对象。问题是,它在当前状态下有问题,我不知道为什么。

为什么我的代码不能正确伸缩?为什么图形会在调整大小时“跳转”到面板顶部?

以下是我的独立示例:

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.util.*;

public class MainPanel extends JFrame implements ActionListener{

    private static final double version = 1.0;
    private JDesktopPane desktop;
    public static RFInternalFrame frame;

    private java.util.List<Point> POINT_LIST = Arrays.asList(
            //Top Row
            new Point(50, 30),
            new Point(70, 30),
            new Point(90, 30),
            new Point(110, 30),
            new Point(130, 30),
            new Point(150, 30),
            new Point(170, 30),
            new Point(190, 30),
            new Point(210, 30),
            new Point(230, 30),

            //Circle of Radios
            new Point(140, 60),
            new Point(120, 80),
            new Point(100, 100),
            new Point(100, 120),
            new Point(120, 140),
            new Point(140, 160),
            new Point(160, 140),
            new Point(180, 120),
            new Point(180, 100),
            new Point(160, 80));

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }

    private static void createAndShowGui() {
        JFrame frame = new MainPanel();
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setLocationByPlatform(false);
        frame.setVisible(true);
    }

    public MainPanel() {
        super("MainPanel " + version);

        //Make the big window be indented 50 pixels from each edge
        //of the screen.
        int inset = 50;
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        setBounds(inset, inset,
                screenSize.width - inset * 7,
                screenSize.height - inset * 4);

        //Set up the GUI.
        desktop = new JDesktopPane(); //a specialized layered pane
        desktop.setBackground(Color.DARK_GRAY);

        createRFFrame(); //create first RFFrame
        createScenarioFrame(); //create ScenarioFrame

        setContentPane(desktop);
        setJMenuBar(createMenuBar());

        //Make dragging a little faster but perhaps uglier.
        desktop.setDragMode(JDesktopPane.OUTLINE_DRAG_MODE);
    }

    protected JMenuBar createMenuBar() {
        JMenuBar menuBar = new JMenuBar();

        //Set up the lone menu.
        JMenu menu = new JMenu("File");
        menu.setMnemonic(KeyEvent.VK_D);
        menuBar.add(menu);

        //Set up the first menu item.
        JMenuItem menuItem = new JMenuItem("Add Panel");
        menuItem.setMnemonic(KeyEvent.VK_N);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_N, ActionEvent.ALT_MASK));
        menuItem.setActionCommand("new");
        menuItem.addActionListener(this);
        menu.add(menuItem);

        //Set up the second menu item.
        menuItem = new JMenuItem("Quit");
        menuItem.setMnemonic(KeyEvent.VK_Q);
        menuItem.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_Q, ActionEvent.ALT_MASK));
        menuItem.setActionCommand("quit");
        menuItem.addActionListener(this);
        menu.add(menuItem);

        return menuBar;
    }

    //React to menu selections.
    public void actionPerformed(ActionEvent e) {
        if ("new".equals(e.getActionCommand())) { //new
            createRFFrame();
        } else {
            //quit
            quit();
        }
    }

    /*
     * ActivateAllAction activates all radios on the panel, essentially changes the color
     * of each ellipse from INACTIVE to ACTIVE
     */
    private class ActivateAllAction extends AbstractAction {
        public ActivateAllAction(String name) {
            super(name);
            int mnemonic = (int) name.charAt(1);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        /*
         * This will find the selected tab and extract the DrawEllipses instance from it
         * Then for the actionPerformed it will call activateAll() from DrawEllipses
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            Component comp = desktop.getSelectedFrame();
            if (comp instanceof DrawEllipses){
                DrawEllipses desktop = (DrawEllipses) comp;
                desktop.activateAll();
            }
        }
    }

    /*
     * DeactivateAllAction deactivates all radios on the panel, essentially changes the color
     * of each ellipse from ACTIVE to INACTIVE
     */
    private class DeactivateAllAction extends AbstractAction {
        public DeactivateAllAction(String name) {
            super(name);
            int mnemonic = (int) name.charAt(0);
            putValue(MNEMONIC_KEY, mnemonic);
        }

        /*
         * This will find the selected tab and extract the DrawPanel2 instance from it
         * Then for the actionPerformed it will call activateAll() from DrawEllipses
         */
        @Override
        public void actionPerformed(ActionEvent e) {
            Component comp = desktop.getSelectedFrame();
            if (comp instanceof DrawEllipses){
                DrawEllipses desktop = (DrawEllipses) comp;
                desktop.deactivateAll();
            }
        }
    }

    /*
     * Define a JPanel that will hold the activate and deactivate all JButtons
     */
    protected JPanel btnPanel() {
        JPanel btnPanel = new JPanel();

        btnPanel.setBorder(BorderFactory.createLoweredSoftBevelBorder());

        //Set the layout of the frame to a grid bag layout
        btnPanel.setLayout(new GridBagLayout());

        //Creates constraints variable to hold values to be applied to each aspect of the layout
        GridBagConstraints c = new GridBagConstraints();

        //Column 1
        c.gridx = 0;
        btnPanel.add(new JButton(new ActivateAllAction("Activate All")));

        //Column 2
        c.gridx = 1;
        btnPanel.add(new JButton(new DeactivateAllAction("Deactivate All")));
        return btnPanel;
    }

    //not used currently
    protected JPanel drawPanel() {
        JPanel drawPanel = new JPanel();
        drawPanel.setBorder(BorderFactory.createLoweredSoftBevelBorder());
        DrawEllipses drawEllipses = new DrawEllipses(POINT_LIST);
        drawPanel.add(drawEllipses);

        return drawPanel;

    }

    //Create a new internal frame.
    protected void createRFFrame() {
        RFInternalFrame iframe = new RFInternalFrame();
        iframe.setLayout(new BorderLayout());

        DrawEllipses drawEllipses = new DrawEllipses(POINT_LIST);
        iframe.add(drawEllipses);
        iframe.add(btnPanel(), BorderLayout.SOUTH);

        iframe.setVisible(true);
        desktop.add(iframe);

        try {
            iframe.setSelected(true);
        } catch (java.beans.PropertyVetoException e) {}
    }

    protected void createScenarioFrame() {
        ScenarioInternalFrame frame = new ScenarioInternalFrame();
        frame.setLayout(new BorderLayout());

        frame.setVisible(true);
        desktop.add(frame);

        try {
            frame.setSelected(true);
        } catch (java.beans.PropertyVetoException e) {}
    }

    //Quit the application.
    protected void quit() {
        System.exit(0);
    }

}

@SuppressWarnings("serial")
class DrawEllipses extends JPanel {
    private double translateX; //
    private double translateY; //
    protected static double scale; //
    private static final int OVAL_WIDTH = 15;
    private static final Color INACTIVE_COLOR = Color.RED;
    private static final Color ACTIVE_COLOR = Color.green;
    private java.util.List<Point> points; //
    private java.util.List<Ellipse2D> ellipses = new ArrayList<>();
    private Map<Ellipse2D, Color> ellipseColorMap = new HashMap<>();

    public DrawEllipses(java.util.List<Point> points) {
        this.points = points; //
        translateX = 0; //
        translateY = 0; //
        scale = 1; //
        setOpaque(true); //
        setDoubleBuffered(true); //


        for (Point p : points) {
            int x = p.x - OVAL_WIDTH / 2;
            int y = p.y - OVAL_WIDTH / 2;
            int w = OVAL_WIDTH;
            int h = OVAL_WIDTH;
            Ellipse2D ellipse = new Ellipse2D.Double(x, y, w, h);
            ellipses.add(ellipse);
            ellipseColorMap.put(ellipse, INACTIVE_COLOR);
        }

        MyMouseAdapter mListener = new MyMouseAdapter();
        addMouseListener(mListener);
        addMouseMotionListener(mListener);
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        AffineTransform tx = new AffineTransform(); //
        tx.translate(translateX, translateY); //
        tx.scale(scale, scale); //

        Graphics2D g2 = (Graphics2D) g;
        g2.setTransform(tx);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        for (Ellipse2D ellipse : ellipses) {
            g2.setColor(ellipseColorMap.get(ellipse));
            g2.fill(ellipse);
        }
    }

    private class MyMouseAdapter extends MouseAdapter {
        @Override
        public void mousePressed(MouseEvent e) {
            for (Ellipse2D ellipse : ellipses) {
                if (ellipse.contains(e.getPoint())) {
                    Color c = ellipseColorMap.get(ellipse);
                    c =  (c == INACTIVE_COLOR) ? ACTIVE_COLOR : INACTIVE_COLOR;
                    ellipseColorMap.put(ellipse, c);
                }
            }
            repaint();
        }
    }

    //Used for button click action to change all ellipses to ACTIVE_COLOR
    public void activateAll(){
        for (Ellipse2D ellipse : ellipses){
            ellipseColorMap.put(ellipse, ACTIVE_COLOR);
        }
        repaint();
    }

    //Used for button click action to change all ellipses to INACTIVE_COLOR
    public void deactivateAll(){
        for (Ellipse2D ellipse : ellipses){
            ellipseColorMap.put(ellipse, INACTIVE_COLOR);
        }
        repaint();
    }
}

class RFInternalFrame extends JInternalFrame implements ComponentListener {
    protected static double scale = 1; //
    static int openFrameCount = 0;
    static final int xOffset = 300, yOffset = 0;

    public RFInternalFrame() {
        super("RF Panel #" + (++openFrameCount),
                true, //resizable
                true, //closable
                true, //maximizable
                true);//iconifiable

        setSize(300, 300);
        setMinimumSize(new Dimension(300, 300));
        addComponentListener(this);

        if (openFrameCount == 1) {

            setLocation(0,0);
        }
        else if (openFrameCount <= 4) {

            //Set the window's location.
            setLocation(xOffset * (openFrameCount - 1), yOffset * (openFrameCount - 1));
        }
        else if (openFrameCount == 5) {

            setLocation(xOffset - 300, yOffset + 300);
        }
        else if (openFrameCount == 6) {

            setLocation(xOffset + 600, yOffset + 300);
        }
    }

    @Override
    public void componentResized(ComponentEvent e) {
        String str = "";
        if (getWidth() < 300) {
            str = "0." + getWidth();
        } else {
            str = "1." + (getWidth() - 300);
            System.out.println(getWidth() - 300);
        }
        double dou = Double.parseDouble(str);
        MainPanel.frame.scale = dou;
        repaint();
    }

    @Override
    public void componentMoved(ComponentEvent componentEvent) {

    }

    @Override
    public void componentShown(ComponentEvent componentEvent) {

    }

    @Override
    public void componentHidden(ComponentEvent componentEvent) {

    }
}

class ScenarioInternalFrame extends JInternalFrame {
    static int openFrameCount = 0;
    static final int xOffset = 300, yOffset = 300;

    public ScenarioInternalFrame() {
        super("Test Scenario" + (++openFrameCount),
                true, //resizable
                true, //closable
                true, //maximizable
                true);//iconifiable

        //...Create the GUI and put it in the window...

        //...Then set the window size or call pack...
        setSize(600, 300);

        //Set the window's location.
        setLocation(xOffset, yOffset);
    }
}

共有2个答案

南门宇
2023-03-14

根据我的经验,秋千上的绘画将使用双缓冲器完成。表示创建图形缓冲区(即ImageBuffer)。将所有绘图逻辑应用于绘图缓冲区的图形,包括转换,然后将缓冲区绘制到组件的图形中。

这就是我如何解决你的问题。。。

class DrawEllipses extends JComponent { // I change from JPanel to JComponent, this might not be necessary though...
...
...
protected void paintComponent(Graphics g) {
    super.paintComponent(g);

    // create the drawing buffer.
    BufferedImage bi = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_INT_RGB);
    Graphics big = bi.getGraphics();

    // prepare transform
    AffineTransform tx = new AffineTransform(); //
    tx.translate(translateX, translateY); //
    tx.scale(scale, scale); //

    // get the buffer graphics and paint the background white.
    Graphics2D g2 = (Graphics2D) big;
    g2.setColor(Color.WHITE);
    g2.fillRect(0, 0, this.getWidth(), this.getHeight());

    // apply drawing logic to the Graphics of the buffer
    g2.setTransform(tx);
    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
    for (Ellipse2D ellipse : ellipses) {
        g2.setColor(ellipseColorMap.get(ellipse));
        g2.fill(ellipse);
    }

    // finally, draw the buffer to the component graphics.
    g.drawImage(bi, 0, 0, null);
}

试试看。。。希望它能起作用。

舒宏富
2023-03-14

据我所知,Graphics对象已经包含了一个转换,该转换对内部框架标题栏的高度进行了转换。替换转换时,将丢失此转换,因此代码将绘制在标题栏下框架的顶部。

  1. 不要更改传递给绘图组件()方法的Graphics对象的属性。相反,创建一个您可以自定义的Graphics2D对象。
  2. 创建新转换时,您需要在添加新转换之前先应用现有转换。

基本结构如下:

super.paintComponent(g);

Graphics2D g2 = (Graphics2D)g.create();

AffineTransform tx = new AffineTransform(); //
tx.concatenate( g2.getTransform() );
tx.scale(...); 
g2.setTransform(tx);

// do custom painting

g2.dispose(); // release Graphics resources

这只会对画有帮助。你还有几个问题(我解决不了):

>

  • 您的缩放值永远不会更新。您应该将ComponentListener添加到DrawEllipse面板。您可能希望在面板中创建一个方法,在调整面板大小时调用该方法来设置比例。

    一旦你画出缩放过的圆,你的鼠标定位器就不起作用了。所有圆的位置都将不同,因为它们已缩放。在循环遍历圆列表时,可以缩放每个圆。

    此外,当您有问题时,请发布一个适当的SSCCE来演示问题。您有一个关于在面板上使用转换的简单问题。因此,使用面板创建一个框架并在面板上绘制几个圆圈来测试概念。

    所有其他代码都与问题无关。菜单项不相关,第二个内部框架不相关。MouseListener点击代码无关紧要。我们没有时间通读100行代码来理解这个问题。

    编辑:

    我更改了代码的顺序。tx.scale(…)方法,然后才能将转换设置为图形对象。

  •  类似资料:
    • 本文向大家介绍使用OpenCV实现仿射变换—缩放功能,包括了使用OpenCV实现仿射变换—缩放功能的使用技巧和注意事项,需要的朋友参考一下 前面介绍怎么样实现平移的功能,接着下来演示缩放功能。比如在一个文档里插入一个图片,发现这个图片占用太大的面积了,要把它缩小,才放得下,与文字的比例才合适。这样的需求,就需要使用仿射变换的缩放功能,而实现这个功能的方法,就是采用齐次坐标的变换功式: 可看到最后一

    • 我有一个名为的类。在这个类中,我有变量,它是类型。此外,我还有类,它重写函数: 我的airplane.rotateAirplane()函数如下所示: 当我运行我的程序时,只绘制对象。当我删除这条车道时 我也有我的飞机,但没有旋转。

    • 我通过此方法调用draw类: 我试图转换DrawGradient类中的对象,但由于某种原因,DrawCoordinateSystem类中的图形对象会被转换。为什么? DrawCoordinateSystem类: 绘图渐变类:

    • 在第三章“图层几何学”中,我们使用了UIView的transform属性旋转了钟的指针,但并没有解释背后运作的原理,实际上UIView的transform属性是一个CGAffineTransform类型,用于在二维空间做旋转,缩放和平移。CGAffineTransform是一个可以和二维空间向量(例如CGPoint)做乘法的3X2的矩阵(见图5.1)。 图5.1 用矩阵表示的CGAffineTra

    • 缩放变换scale() 缩放变换scale(sx,sy)传入两个参数,分别是水平方向和垂直方向上对象的缩放倍数。例如context.scale(2,2)就是对图像放大两倍。其实,看上去简单,实际用起来还是有一些问题的。我们来看一段代码。 <!DOCTYPE html> <html lang="zh"> <head> <meta charset="UTF-8"> <title>缩放变

    • 我似乎无法让一种坐标格式与另一种格式配合使用。我想我只是没有使用正确的矩阵,但我对它们的了解还不够确定。我希望得到一些帮助,弄清楚我是否在假设我的转换应该是什么。 iText使用左下角作为ISO标准的原点,但pdfbox代码和从pdf中获取坐标的程序都使用左上角作为原点。 我应该做什么转换来调整坐标,以便iText能够以一种有效的方式使用它们? 我有一些代码,使用pdfbox来操作pdf并去除一些