在 Graphics2D 对象上使用仿射变换

Using Affine Transform on Graphics2D Objects

我是 AffineTransform 的新手,但我花了几个小时想出了如何让它做我想做的事。基本上我的目标是制作一个 JInternal Frame,在最小化时缩放 Graphics2D 对象。为了概念验证,我创建了一些代码,可以使用鼠标滚轮进行缩放,并在鼠标拖动时进行转换。

我的问题是,如何将其打包到 JInternalFrame 中,并在 JInternalFrame 最小化时缩放?另外,有没有更好的方法来做我想做的事情?

独立示例:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JComponent;
import javax.swing.JFrame;

public class AffineTransformTest {

    private static TransformingCanvas canvas;

    public static void main(String[] args) {
        TransformingCanvas.initializePointList();
        JFrame frame = new JFrame();
        canvas = new TransformingCanvas(TransformingCanvas.POINT_LIST);
        TranslateHandler translator = new TranslateHandler();
        canvas.addMouseListener(translator);
        canvas.addMouseMotionListener(translator);
        canvas.addMouseWheelListener(new ScaleHandler());
        frame.setLayout(new BorderLayout());
        frame.getContentPane().add(canvas, BorderLayout.CENTER);
        frame.setSize(750, 750);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }

    private static class TransformingCanvas extends JComponent {
        private double translateX;
        private double translateY;
        private double scale;
        private final int PREF_W = 800; //Window width
        private final int PREF_H = 800; //Window height
        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 static java.util.List<Point> POINT_LIST = new ArrayList<>();

        /*
        * This loop will initialize POINT_LIST with the set of points for drawing the ellipses.
        * The for each loop initializes points for the top row and the second for loop draws the
        * right triangle.
        */
        protected static void initializePointList() {

            int ellipsePointsYCoordinate[] = {140, 200, 260, 320, 380, 440, 500, 560, 620};
            int ellipsePointsXCoordinate[] = {140, 200, 260, 320, 380, 440, 500, 560, 620, 680};
            int xx = 80;

            for (int aXt : ellipsePointsXCoordinate) {
                POINT_LIST.add(new Point(aXt, xx));
            }

            for (int i = 0; i < ellipsePointsYCoordinate.length; i++) {
                for (int j = i; j < ellipsePointsYCoordinate.length; j++) {
                    POINT_LIST.add(new Point(ellipsePointsXCoordinate[i], ellipsePointsYCoordinate[j]));
                }
            }
        }

            TransformingCanvas() {
            translateX = 0;
            translateY = 0;
            scale = 1;
            setOpaque(true);
            setDoubleBuffered(true);
        }

        public TransformingCanvas(java.util.List<Point> points) {
            this.points = points;
            int OVAL_WIDTH = 30;

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

        @Override public void paint(Graphics g) {

            AffineTransform tx = new AffineTransform();
            tx.translate(translateX, translateY);
            tx.scale(scale, scale);
            Graphics2D g2 = (Graphics2D) g;
            g2.fillRect(0,0, getWidth(), getHeight());
            g2.setTransform(tx);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            for (Ellipse2D ellipse : ellipses) {
                g2.setColor(ellipseColorMap.get(ellipse));
                g2.fill(ellipse);
                g2.setColor(Color.BLACK);
                g2.setStroke(new BasicStroke(2));
                g2.draw(ellipse);
            }

            /*
            * Set the font characteristics, color, and draw the row labels.
            */
            g.setFont(new Font("TimesRoman", Font.BOLD, 18));
            g.setColor(Color.BLACK);

            //NOTE to self: add label DrawStrings back

            //Draws a 3DRect around the top row of ellipse2D objects
            g2.setColor(Color.lightGray);
            g2.draw3DRect(120, 60, 580, 40, true);
            g2.draw3DRect(121, 61, 578, 38, true);
            g2.draw3DRect(122, 62, 576, 36, true);
            //super.paint(g);
        }
    }

    private static class TranslateHandler implements MouseListener,
            MouseMotionListener {
        private int lastOffsetX;
        private int lastOffsetY;

        public void mousePressed(MouseEvent e) {
            // capture starting point
            lastOffsetX = e.getX();
            lastOffsetY = e.getY();
        }

        public void mouseDragged(MouseEvent e) {

            // new x and y are defined by current mouse location subtracted
            // by previously processed mouse location
            int newX = e.getX() - lastOffsetX;
            int newY = e.getY() - lastOffsetY;

            // increment last offset to last processed by drag event.
            lastOffsetX += newX;
            lastOffsetY += newY;

            // update the canvas locations
            canvas.translateX += newX;
            canvas.translateY += newY;

            // schedule a repaint.
            canvas.repaint();
        }

        public void mouseClicked(MouseEvent e) {}
        public void mouseEntered(MouseEvent e) {}
        public void mouseExited(MouseEvent e) {}
        public void mouseMoved(MouseEvent e) {}
        public void mouseReleased(MouseEvent e) {}
    }

    private static class ScaleHandler implements MouseWheelListener {
        public void mouseWheelMoved(MouseWheelEvent e) {
            if(e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL) {

                // make it a reasonable amount of zoom
                // .1 gives a nice slow transition
                canvas.scale += (.1 * e.getWheelRotation());
                // don't cross negative threshold.
                // also, setting scale to 0 has bad effects
                canvas.scale = Math.max(0.00001, canvas.scale);
                canvas.repaint();
            }
        }
    }
}  

我不完全明白为什么要在 Graphics 不可见时缩放它们,但这里有一个粗略的操作说明:

  1. JDesktopPane 设置为 JFrame 的内容窗格。
  2. 创建一个 JInternalFrame 并将其添加到 JDesktopPane
  3. 向内部框架添加一个 InternalFrameListener,然后如果内部框架被最小化,您可以在 public void internalFrameIconified(InternalFrameEvent arg0) 方法中放入您想要执行的所有操作。

这是在您的代码中实现的(还没有任何 AffineTransform,只是基本概念):

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

import javax.swing.JComponent;
import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.event.InternalFrameEvent;
import javax.swing.event.InternalFrameListener;

public class AffineTransformTest {

    private static TransformingCanvas canvas;

    public static void main(String[] args) {
        TransformingCanvas.initializePointList();
        JFrame frame = new JFrame();
        canvas = new TransformingCanvas(TransformingCanvas.POINT_LIST);
        JDesktopPane desktop = new JDesktopPane();
        TranslateHandler translator = new TranslateHandler();
        canvas.addMouseListener(translator);
        canvas.addMouseMotionListener(translator);
        canvas.addMouseWheelListener(new ScaleHandler());
        MyInternalFrame iFrame = new MyInternalFrame();
        iFrame.setLayout(new BorderLayout());
        iFrame.add(canvas, BorderLayout.CENTER);
        iFrame.setVisible(true);
        desktop.add(iFrame);
        frame.setContentPane(desktop);
        frame.setSize(750, 750);
        frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
        frame.setVisible(true);
    }

    private static class TransformingCanvas extends JComponent {
        private double translateX;
        private double translateY;
        private double scale;
        private final int PREF_W = 800; // Window width
        private final int PREF_H = 800; // Window height
        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 static java.util.List<Point> POINT_LIST = new ArrayList<>();

        /*
         * This loop will initialize POINT_LIST with the set of points for
         * drawing the ellipses. The for each loop initializes points for the
         * top row and the second for loop draws the right triangle.
         */
        protected static void initializePointList() {

            int ellipsePointsYCoordinate[] = { 140, 200, 260, 320, 380, 440,
                    500, 560, 620 };
            int ellipsePointsXCoordinate[] = { 140, 200, 260, 320, 380, 440,
                    500, 560, 620, 680 };
            int xx = 80;

            for (int aXt : ellipsePointsXCoordinate) {
                POINT_LIST.add(new Point(aXt, xx));
            }

            for (int i = 0; i < ellipsePointsYCoordinate.length; i++) {
                for (int j = i; j < ellipsePointsYCoordinate.length; j++) {
                    POINT_LIST.add(new Point(ellipsePointsXCoordinate[i],
                            ellipsePointsYCoordinate[j]));
                }
            }
        }

        TransformingCanvas() {
            translateX = 0;
            translateY = 0;
            scale = 1;
            setOpaque(true);
            setDoubleBuffered(true);
        }

        public TransformingCanvas(java.util.List<Point> points) {
            this.points = points;
            int OVAL_WIDTH = 30;

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

        @Override
        public void paint(Graphics g) {

            AffineTransform tx = new AffineTransform();
            tx.translate(translateX, translateY);
            tx.scale(scale, scale);
            Graphics2D g2 = (Graphics2D) g;
            g2.fillRect(0, 0, getWidth(), getHeight());
            g2.setTransform(tx);
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

            for (Ellipse2D ellipse : ellipses) {
                g2.setColor(ellipseColorMap.get(ellipse));
                g2.fill(ellipse);
                g2.setColor(Color.BLACK);
                g2.setStroke(new BasicStroke(2));
                g2.draw(ellipse);
            }

            /*
             * Set the font characteristics, color, and draw the row labels.
             */
            g.setFont(new Font("TimesRoman", Font.BOLD, 18));
            g.setColor(Color.BLACK);

            // NOTE to self: add label DrawStrings back

            // Draws a 3DRect around the top row of ellipse2D objects
            g2.setColor(Color.lightGray);
            g2.draw3DRect(120, 60, 580, 40, true);
            g2.draw3DRect(121, 61, 578, 38, true);
            g2.draw3DRect(122, 62, 576, 36, true);
            // super.paint(g);
        }
    }

    private static class TranslateHandler implements MouseListener,
            MouseMotionListener {
        private int lastOffsetX;
        private int lastOffsetY;

        public void mousePressed(MouseEvent e) {
            // capture starting point
            lastOffsetX = e.getX();
            lastOffsetY = e.getY();
        }

        public void mouseDragged(MouseEvent e) {

            // new x and y are defined by current mouse location subtracted
            // by previously processed mouse location
            int newX = e.getX() - lastOffsetX;
            int newY = e.getY() - lastOffsetY;

            // increment last offset to last processed by drag event.
            lastOffsetX += newX;
            lastOffsetY += newY;

            // update the canvas locations
            canvas.translateX += newX;
            canvas.translateY += newY;

            // schedule a repaint.
            canvas.repaint();
        }

        public void mouseClicked(MouseEvent e) {
        }

        public void mouseEntered(MouseEvent e) {
        }

        public void mouseExited(MouseEvent e) {
        }

        public void mouseMoved(MouseEvent e) {
        }

        public void mouseReleased(MouseEvent e) {
        }
    }

    private static class ScaleHandler implements MouseWheelListener {
        public void mouseWheelMoved(MouseWheelEvent e) {

        }
    }

    static class MyInternalFrame extends JInternalFrame implements
            InternalFrameListener {

        public MyInternalFrame() {
            super("iFrame", true, // resizable
                    true, // closable
                    true, // maximizable
                    true);// iconifiable
            setSize(300, 300);
            addInternalFrameListener(this);

        }

        @Override
        public void internalFrameActivated(InternalFrameEvent arg0) {

        }

        @Override
        public void internalFrameClosed(InternalFrameEvent arg0) {

        }

        @Override
        public void internalFrameClosing(InternalFrameEvent arg0) {

        }

        @Override
        public void internalFrameDeactivated(InternalFrameEvent arg0) {

        }

        @Override
        public void internalFrameDeiconified(InternalFrameEvent arg0) {

        }

        @Override
        public void internalFrameIconified(InternalFrameEvent arg0) {
            System.out.println("Minimized");


            //What you want to  do
        }

        @Override
        public void internalFrameOpened(InternalFrameEvent arg0) {

        }
    }
}

如果现在您仍然无法实现您想要的,请进一步解释,因为我没有真正看到缩放不可见图形的意义。

编辑:

现在我明白了真正的问题是什么:您可以将 ComponentListener 添加到您的 JInternalFrame。然后在 public void componentResized(ComponentEvent e) { 方法中放入您想要更改的所有内容(可能是 Graphics 的宽度和高度变量)。使用上面的代码,只需将 MyInternalFrame class 更改为:

static class MyInternalFrame extends JInternalFrame implements
        ComponentListener {

    public MyInternalFrame() {
        super("iFrame", true, // resizable
                true, // closable
                true, // maximizable
                true);// iconifiable
        setSize(300, 300);
        addComponentListener(this);

    }

    @Override
    public void componentHidden(ComponentEvent e) {

    }

    @Override
    public void componentMoved(ComponentEvent e) {

    }

    @Override
    public void componentResized(ComponentEvent e) {
        System.out.println("Resized");
        System.out.println("Width: " + getWidth());
        System.out.println("Height: " + getHeight());
        System.out.println();

    }

    @Override
    public void componentShown(ComponentEvent e) {

    }


}

编辑 2:

我将告诉您如何调整它的大小(将 componentResized 方法更改为此):

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