Search code examples
javaswingaffinetransformpath-2d

Trouble with getting Path2D.Double to change with .transform


My program is supposed to allow users to input the first six numbers of a matrix into text fields and then hit an apply button, changing the Path2D using the .transform method with the arguments being the six inputted numbers. My problem is that whenever I type something in and hit apply, the transformation is extremely different than it should be and the original arrow remains behind as well.

It's really bizarre and I have no clue where the problem is coming from. Everything should be in the right spot with the Affine Transform, but the transformation is coming out all wrong.

This is what it should look like: enter image description here

This is what it looks like for me: enter image description here

I will put the full code below so you can run it yourself to see if you might be able to figure it out. Thank you!

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.GeneralPath;
import java.awt.geom.Path2D;
import javax.swing.event.*;

public class Project3 extends JPanel implements ActionListener {

    public static Project3 p = new Project3();
    Path2D.Double arrow = new Path2D.Double();
    public static JTextField
            num1 = new JTextField("1"), num2 = new JTextField("0"),
            num3 = new JTextField("0"), num4 = new JTextField("0"),
            num5 = new JTextField("1"), num6 = new JTextField("0");

    public Project3() {
        setBackground(Color.WHITE);
    }

    public Path2D.Double drawArrow() {
        arrow.setWindingRule(GeneralPath.WIND_EVEN_ODD);
        arrow.moveTo(0, 0);
        arrow.lineTo(0, -100); 
        arrow.moveTo(0, -200);
        arrow.lineTo(100, -100);
        arrow.lineTo(50, -100);
        arrow.lineTo(50, 100);
        arrow.quadTo(0, 0, -50, 100);
        arrow.lineTo(-50, -100);
        arrow.lineTo(-100, -100);
        arrow.lineTo(0, -200);
        arrow.closePath();
        return arrow;
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        g2.translate(250, 250);
        GradientPaint gradient = new GradientPaint(0, 0, Color.LIGHT_GRAY, 15, 15, Color.BLACK, true);
        g2.setPaint(gradient);
        g2.setStroke(new BasicStroke(12, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
        g2.draw(drawArrow());
    }

    public static void main(String[] args) {
        JFrame frame = new JFrame("Project 3");
        frame.setSize(500, 600);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Container cp = frame.getContentPane();
        cp.setLayout(new BorderLayout());

        JPanel panel = new JPanel();
        panel.setLayout(new BorderLayout());
        cp.add(panel, BorderLayout.CENTER);
        panel.add(p, BorderLayout.CENTER);
        panel = new JPanel();
        panel.setLayout(new GridLayout(0, 2));
        cp.add(panel, BorderLayout.SOUTH);

        JPanel textPanel = new JPanel();
        textPanel.setLayout(new GridLayout(2, 3));
        panel.add(textPanel);
        textPanel.add(num1);
        textPanel.add(num2);
        textPanel.add(num3);
        textPanel.add(num4);
        textPanel.add(num5);
        textPanel.add(num6);

        JPanel btPanel = new JPanel();
        btPanel.setLayout(new GridLayout(0, 1));
        panel.add(btPanel);
        JButton apply = new JButton("Apply");
        apply.addActionListener(p);
        btPanel.add(apply);
        JButton reset = new JButton("Reset");
        reset.addActionListener(p);
        btPanel.add(reset);

        frame.setVisible(true);

    }

    @Override
    public void actionPerformed(ActionEvent e) {
        String command = e.getActionCommand();
        switch (command) {
            case "Apply":
                double args1 = Double.parseDouble(num1.getText());
                double args2 = Double.parseDouble(num2.getText());
                double args3 = Double.parseDouble(num3.getText());
                double args4 = Double.parseDouble(num4.getText());
                double args5 = Double.parseDouble(num5.getText());
                double args6 = Double.parseDouble(num6.getText());          
                arrow.transform(new AffineTransform(args1, args2, args3, args4, args5, args6));
                repaint();
                break;
            case "Reset":
                arrow.transform(new AffineTransform(1, 0, 0, 0, 1, 0));
                repaint();
                break;
        }
    }

}

Solution

  • Part of your problem seems to be the order of your AffineTransform parameters. You may be assuming that your JTextFields are in the same form as the 2x3 matrix, but they're not. Add borders to the JTextFields to see what I mean:

        num00.setBorder(BorderFactory.createTitledBorder("m00"));
        num10.setBorder(BorderFactory.createTitledBorder("m10"));
        num01.setBorder(BorderFactory.createTitledBorder("m01"));
        num11.setBorder(BorderFactory.createTitledBorder("m11"));
        num02.setBorder(BorderFactory.createTitledBorder("m02"));
        num12.setBorder(BorderFactory.createTitledBorder("m12"));
    

    and:

            double m00 = Double.parseDouble(num00.getText());
            double m10 = Double.parseDouble(num10.getText());
            double m01 = Double.parseDouble(num01.getText());
            double m11 = Double.parseDouble(num11.getText());
            double m02 = Double.parseDouble(num02.getText());
            double m12 = Double.parseDouble(num12.getText());
            AffineTransform transform = new AffineTransform(m00, m10, m01, m11,
                    m02, m12); 
    

    and you'll see how things are not in the order you are assuming. Instead, add your JTextfields so that they'll match the matrix order:

        textPanel.add(num00);
        textPanel.add(num10);
        textPanel.add(num02);
        textPanel.add(num01);
        textPanel.add(num11);
        textPanel.add(num12);
    

    and this will improve your findings.

    Something like:

    import javax.swing.*;
    
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.AffineTransform;
    import java.awt.geom.GeneralPath;
    import java.awt.geom.NoninvertibleTransformException;
    import java.awt.geom.Path2D;
    import java.util.Deque;
    import java.util.LinkedList;
    
    import javax.swing.event.*;
    
    public class Project3 extends JPanel implements ActionListener {
    
        public static Project3 p = new Project3();
        Path2D arrow; // !!
        public static
        JTextField 
        num00 = new JTextField("0"), 
        num10 = new JTextField("1"), 
        num01 = new JTextField("1"), 
        num11 = new JTextField("0"),            
        num02 = new JTextField("0"), 
        num12 = new JTextField("0");
        private static Deque<AffineTransform> atStack = new LinkedList<>(); 
    
        public Project3() {
            setBackground(Color.WHITE);
            arrow = drawArrow(); // !! create the arrow only once
        }
    
        public Path2D drawArrow() { // !!
            arrow = new Path2D.Double(); // !!
            arrow.setWindingRule(GeneralPath.WIND_EVEN_ODD);
            arrow.moveTo(0, 0);
            arrow.lineTo(0, -100);
            arrow.moveTo(0, -200);
            arrow.lineTo(100, -100);
            arrow.lineTo(50, -100);
            arrow.lineTo(50, 100);
            arrow.quadTo(0, 0, -50, 100);
            arrow.lineTo(-50, -100);
            arrow.lineTo(-100, -100);
            arrow.lineTo(0, -200);
            arrow.closePath();
            arrow.transform(AffineTransform.getTranslateInstance(250, 250)); // !! shift it here
            return arrow;
        }
    
        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2 = (Graphics2D) g;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON); // !!
            // !! g2.translate(250, 250);  // translate the arrow, not Graphics
            GradientPaint gradient = new GradientPaint(0, 0, Color.LIGHT_GRAY, 15,
                    15, Color.BLACK, true);
            g2.setPaint(gradient);
            g2.setStroke(new BasicStroke(12, BasicStroke.CAP_ROUND,
                    BasicStroke.JOIN_MITER));
            // g2.draw(drawArrow()); // !!  don't re-create the arrow
            g2.draw(arrow);
        }
    
        public static void main(String[] args) {
            JFrame frame = new JFrame("Project 3");
            frame.setSize(500, 600);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
            Container cp = frame.getContentPane();
            cp.setLayout(new BorderLayout());
    
            JPanel panel = new JPanel();
            panel.setLayout(new BorderLayout());
            cp.add(panel, BorderLayout.CENTER);
            panel.add(p, BorderLayout.CENTER);
            panel = new JPanel();
            panel.setLayout(new GridLayout(0, 2));
            cp.add(panel, BorderLayout.SOUTH);
    
            JPanel textPanel = new JPanel();
            textPanel.setLayout(new GridLayout(2, 3));
            panel.add(textPanel);
            num00.setBorder(BorderFactory.createTitledBorder("m00"));
            num10.setBorder(BorderFactory.createTitledBorder("m10"));
            num01.setBorder(BorderFactory.createTitledBorder("m01"));
            num11.setBorder(BorderFactory.createTitledBorder("m11"));
            num02.setBorder(BorderFactory.createTitledBorder("m02"));
            num12.setBorder(BorderFactory.createTitledBorder("m12"));
    
            textPanel.add(num00);
            textPanel.add(num10);
            textPanel.add(num02);
            textPanel.add(num01);
            textPanel.add(num11);
            textPanel.add(num12);
    
            JPanel btPanel = new JPanel();
            btPanel.setLayout(new GridLayout(0, 1));
            panel.add(btPanel);
            JButton apply = new JButton("Apply");
            apply.addActionListener(p);
            btPanel.add(apply);
            JButton reset = new JButton("Reset");
            reset.addActionListener(p);
            btPanel.add(reset);
    
            frame.setVisible(true);
    
        }
    
        @Override
        public void actionPerformed(ActionEvent e) {
            String command = e.getActionCommand();
            switch (command) {
            case "Apply":
                double m00 = Double.parseDouble(num00.getText());
                double m10 = Double.parseDouble(num10.getText());
                double m01 = Double.parseDouble(num01.getText());
                double m11 = Double.parseDouble(num11.getText());
                double m02 = Double.parseDouble(num02.getText());
                double m12 = Double.parseDouble(num12.getText());
                AffineTransform transform = new AffineTransform(m00, m10, m01, m11,
                        m02, m12); 
                arrow.transform(transform);
                atStack.addFirst(transform); // save the transform
                repaint();
                break;
            case "Reset":
                // !! arrow.transform(new AffineTransform(1, 0, 0, 0, 1, 0));
                while (atStack.size() > 0) {
                    AffineTransform at = atStack.removeFirst();
    
                    // inverse fails if determinant is 0
                    if (at.getDeterminant() == 0) {
                        return;
                    }
                    try {
                        arrow.transform(at.createInverse());
                    } catch (NoninvertibleTransformException e1) {
                        e1.printStackTrace();
                    }
                }
                repaint();
                break;
            }
        }
    
    }
    

    I also used a queue as a stack to hold applied Transforms, to allow them to be un-done in order when reset.