1

Whenever I draw, then turn on reflection then draw everything seems fine but then I turn off reflection and the points I draw now don't appear under my mouse cursor. The strange thing is that sometimes it works fine and after I turn on and off reflection a couple of times and draw, the bug occurs. I have no idea what the reason behind it is. Here is the code, I would be extremely grateful if you could help me.

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;

//3 inner classes because if this code is part of a bigger problem, these classes will not be used anywhere else and need to be encapsulated
public class CWTEST extends JFrame{

    public CWTEST(String title){
        super(title);
    }

    public void initialiseGUI(){
        Container mainPanel = this.getContentPane();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        //make the window as big as the screen without covering the windows taskbar
        this.setBounds( 0, 0, screenSize.width, screenSize.height-40);

        //set up the displayPanel where the drawing is done 
        mainPanel.setLayout(new BorderLayout());
        DrawingPanel displayPanel = new DrawingPanel();
        mainPanel.add(displayPanel, BorderLayout.CENTER);

        //set up the controlPanel where the user changes the drawing options
        JPanel controlPanel = new JPanel();
        mainPanel.add(controlPanel, BorderLayout.SOUTH);
        controlPanel.setLayout(new FlowLayout());                                                                                                                           
        controlPanel.setBorder(BorderFactory.createLineBorder(new java.awt.Color(0, 255, 0)));


        JLabel reflectPointsLabel = new JLabel("reflect points");
        controlPanel.add(reflectPointsLabel);

        JCheckBox reflectPointsBox = new JCheckBox();
        reflectPointsBox.addItemListener(new ItemListener(){
            @Override
            public void itemStateChanged(ItemEvent e) {
                if (e.getStateChange() == ItemEvent.SELECTED){
                    displayPanel.reflectPoints = true;
                }else{
                    displayPanel.reflectPoints = false;
                }
            }
        });
        controlPanel.add(reflectPointsBox);
        reflectPointsBox.setSelected(false);



        this.setVisible(true);

        //once the frame is visible we can get the real size of the drawing panel and create a buffered image that matches it
        displayPanel.buffImage = new BufferedImage(displayPanel.getWidth(), displayPanel.getHeight(), BufferedImage.TYPE_3BYTE_BGR);                          
    }




    public class DrawingPanel extends JPanel{
        //default drawing options                                                                                               
        private double circleDiameter = 5; 
        private Color color = Color.BLUE;                                   
        private boolean showSectors;                                        
        private boolean reflectPoints;
        private int numOfSectors = 12;
        //list of points to be drawn
        private ArrayList<MyPoint> pointsList;
        //buffered image on which the drawing is done and then copied to the JPanel
        private BufferedImage buffImage;        

        public DrawingPanel(){
            this.setBackground(Color.BLACK);
            DrawingListener listener = new DrawingListener(this);
            this.addMouseListener(listener);
            this.addMouseMotionListener(listener);
            this.addComponentListener(listener);
            pointsList = new ArrayList<MyPoint>();                          
        }           

        public void paintComponent(Graphics g){
            super.paintComponent(g);
            this.drawBuffImage();
            //copy what's drawn on the buffered image to the JPanel
            g.drawImage(buffImage, 0,0, null);
        }

        public void drawBuffImage(){                                                                                                                                                                                               
            Graphics2D g2d = buffImage.createGraphics();
            g2d.setColor(Color.WHITE);

            //if there are more tan 1 sectors - draw lines to separate them
            if(showSectors && numOfSectors > 1){
                Line2D line = new Line2D.Double(this.getWidth()/2,0,this.getWidth()/2,this.getHeight()/2);

                //draw half a line every time and rotate the next according to the number of sectors
                for(int w = 0; w < numOfSectors; w++){
                    g2d.draw(line);
                    g2d.rotate(Math.toRadians((double) 360/numOfSectors), this.getWidth()/2, this.getHeight()/2);
                }
            }

            //draw each point in the list
            for(int i = 0; i < pointsList.size(); i++){
                MyPoint point = pointsList.get(i);
                g2d.setColor(point.getPointColor());

                Ellipse2D circle = new Ellipse2D.Double(point.getX(), point.getY(), point.getPointDiameter(), point.getPointDiameter());            

                //draw the point once in each sector
                for(int j = 0; j < numOfSectors; j++){                          
                    g2d.fill(circle);
                    g2d.rotate(Math.toRadians(((double) 360/numOfSectors)), this.getWidth()/2, this.getHeight()/2);                                                                
                }                                                                                          

                //if point should be reflected, draw its reflection in each sector
                if(point.isReflected()){
                    g2d.rotate(Math.toRadians((double) 360/(numOfSectors*2)), this.getWidth()/2, this.getHeight()/2);
                    g2d.fill(circle);

                    for(int t = 1; t < numOfSectors; t++){
                        g2d.rotate(Math.toRadians((double) 360/numOfSectors), this.getWidth()/2, this.getHeight()/2);
                        g2d.fill(circle);                                                                                                       
                    }
                }
            }

            g2d.dispose();
        }

        public void wipeScreen(){
            //wipe everything by covering it with black
            Graphics g = buffImage.createGraphics();   
            g.setColor(Color.BLACK);
            g.fillRect(0,0,buffImage.getWidth(),buffImage.getHeight());
            g.dispose();
        }

        public void addPoint(MyPoint p){
            pointsList.add(p);
        }

        public void setColor(Color color){
            this.color = color;
        }

        public void setCircleDiameter(double circleDiameter){
            this.circleDiameter = circleDiameter;
        }
    }

    public class DrawingListener extends MouseAdapter implements ComponentListener{   
        //the panel on which the user draws
        private DrawingPanel dPanel;

        public DrawingListener(DrawingPanel dPanel){
            this.dPanel = dPanel;
        }

        //add a point to the arraylist every time the user draws, and draw
        public void mousePressed(MouseEvent e) {
            dPanel.addPoint(new MyPoint(e.getX(), e.getY(), dPanel.color, dPanel.circleDiameter, dPanel.reflectPoints));
            dPanel.repaint();
        }

        public void mouseDragged(MouseEvent e) {
            dPanel.addPoint(new MyPoint(e.getX(), e.getY(), dPanel.color, dPanel.circleDiameter, dPanel.reflectPoints));
            dPanel.repaint();
        }

        @Override
        public void componentResized(ComponentEvent e) {
            //whenever component is resized, wipe the screen
            //then the system-triggered repaint occurs and the result is that by making the window smaller, the user zooms in 
            dPanel.wipeScreen();
        }

        public void componentHidden(ComponentEvent arg0) {}
        public void componentMoved(ComponentEvent arg0) {}
        public void componentShown(ComponentEvent arg0) {}
    }


    //each point drawn is an object of the class
    public class MyPoint extends Point{
        private Color pointColor;
        private double pointDiameter;
        private boolean reflected;

        public MyPoint(int x, int y, Color c, double pointDiameter, boolean reflected){
            super(x,y);
            this.pointColor = c;
            this.pointDiameter = pointDiameter;
            this.reflected = reflected;
        }

        public Color getPointColor(){
            return this.pointColor;
        }

        public double getPointDiameter(){
            return this.pointDiameter;
        }

        public boolean isReflected(){
            return this.reflected;
        }
    }

}

public class MainTest {

    public static void main(String[] args){
        CWTEST frame = new CWTEST("Digital Doily");  
        frame.initialiseGUI();  
    }
}

EDIT

When box is not ticked I get 12 points - inner most circle. Then I tick box and I get 24 points (12 original and 12 reflected) - second circle (with reflection). Then I untick box and I get outer most circle - 12 points but they are drawn in the place of the 12 reflected points from the previous circle eventhough my mouse is in the same place like for the 1st circle. The green point is an example of a reflected point - its between 2 normal points. Red dots are where my mouse cursor was at every drawing. Notice that when I make the third circle, reflection is turned off and there is no point drawn under the cursor.

enter image description here

Sreten Jocić
  • 165
  • 1
  • 14
  • *"..but then I turn off reflection and the points I draw now don't appear under my mouse cursor."* I don't see the behaviour described, but then, I also don't see any difference between when the check box is ticked or not. – Andrew Thompson Mar 10 '17 at 10:43
  • @Andrew Thompson I edited it to help you understand. I hope that helps. Sometimes you need to turn on and off reflection a couple of times to get the bug. – Sreten Jocić Mar 10 '17 at 11:33

1 Answers1

3

I do not know the reason why occasionally gets wrong, but it seems that use AffineTransform#getRotateInstance(...)#createTransformedShape(...) instead of Graphics2D#rotate(...) will draw normally.

import java.awt.*;
import java.awt.event.*;
import java.awt.font.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.*;

public class MainTest2 {
  public static void main(String[] args) {
    CWTEST frame = new CWTEST("Digital Doily");
    frame.initialiseGUI();
  }
}

class CWTEST extends JFrame {

  public CWTEST(String title) {
    super(title);
  }

  public void initialiseGUI() {
    Container mainPanel = this.getContentPane();
    this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    //make the window as big as the screen without covering the windows taskbar
    this.setBounds(0, 0, screenSize.width, screenSize.height - 40);

    //set up the displayPanel where the drawing is done
    mainPanel.setLayout(new BorderLayout());
    DrawingPanel displayPanel = new DrawingPanel();
    mainPanel.add(displayPanel, BorderLayout.CENTER);

    //set up the controlPanel where the user changes the drawing options
    JPanel controlPanel = new JPanel();
    mainPanel.add(controlPanel, BorderLayout.SOUTH);
    controlPanel.setLayout(new FlowLayout());
    controlPanel.setBorder(BorderFactory.createLineBorder(new java.awt.Color(0, 255, 0)));


    JLabel reflectPointsLabel = new JLabel("reflect points");
    controlPanel.add(reflectPointsLabel);

    JCheckBox reflectPointsBox = new JCheckBox();
    reflectPointsBox.addItemListener(new ItemListener() {
      @Override
      public void itemStateChanged(ItemEvent e) {
        if (e.getStateChange() == ItemEvent.SELECTED) {
          displayPanel.reflectPoints = true;
        } else {
          displayPanel.reflectPoints = false;
        }
      }
    });
    controlPanel.add(reflectPointsBox);
    reflectPointsBox.setSelected(false);



    this.setVisible(true);

    //once the frame is visible we can get the real size of the drawing panel and create a buffered image that matches it
    displayPanel.buffImage = new BufferedImage(
        displayPanel.getWidth(), displayPanel.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
  }




  public class DrawingPanel extends JPanel {
    //default drawing options
    private double circleDiameter = 5;
    private Color color = Color.BLUE;
    private boolean showSectors;
    private boolean reflectPoints;
    private int numOfSectors = 12;
    //list of points to be drawn
    private ArrayList<MyPoint> pointsList;
    //buffered image on which the drawing is done and then copied to the JPanel
    private BufferedImage buffImage;

    public DrawingPanel() {
      this.setBackground(Color.BLACK);
      DrawingListener listener = new DrawingListener(this);
      this.addMouseListener(listener);
      this.addMouseMotionListener(listener);
      this.addComponentListener(listener);
      pointsList = new ArrayList<MyPoint>();
    }

    public void paintComponent(Graphics g) {
      super.paintComponent(g);
      this.drawBuffImage();
      //copy what's drawn on the buffered image to the JPanel
      g.drawImage(buffImage, 0, 0, null);
    }

    public void drawBuffImage() {
      Graphics2D g2d = buffImage.createGraphics();
      g2d.setColor(Color.WHITE);

////if there are more tan 1 sectors - draw lines to separate them
//if (showSectors && numOfSectors > 1) {
//    Line2D line = new Line2D.Double(this.getWidth() / 2, 0, this.getWidth() / 2, this.getHeight() / 2);
//
//    //draw half a line every time and rotate the next according to the number of sectors
//    for (int w = 0; w < numOfSectors; w++) {
//        g2d.draw(line);
//        g2d.rotate(Math.toRadians((double) 360 / numOfSectors), this.getWidth() / 2, this.getHeight() / 2);
//    }
//    g2d.rotate(0d);
//}

      double angle = 360d / numOfSectors;
      double ancx  = this.getWidth() / 2d;
      double ancy  = this.getHeight() / 2d;

      //draw each point in the list
      for (int i = 0; i < pointsList.size(); i++) {
        MyPoint point = pointsList.get(i);
        g2d.setColor(point.getPointColor());

        Ellipse2D circle = new Ellipse2D.Double(
            point.getX(), point.getY(), point.getPointDiameter(), point.getPointDiameter());

        //draw the point once in each sector
        double rotate = 0d;
        for (int j = 0; j < numOfSectors; j++) {
          rotate += angle;
          AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(rotate), ancx, ancy);
          g2d.fill(at.createTransformedShape(circle));
          //g2d.rotate(Math.toRadians(((double) 360 / numOfSectors)), this.getWidth() / 2, this.getHeight() / 2);
        }

        //if point should be reflected, draw its reflection in each sector
        if (point.isReflected()) {
          //g2d.rotate(Math.toRadians((double) 360 / (numOfSectors * 2)), this.getWidth() / 2, this.getHeight() / 2);
          //g2d.fill(circle);
          rotate = angle / 2d;
          g2d.setColor(Color.RED);
          for (int t = 0; t < numOfSectors; t++) {
            //g2d.rotate(Math.toRadians((double) 360 / numOfSectors), this.getWidth() / 2, this.getHeight() / 2);
            //g2d.fill(circle);
            rotate += angle;
            AffineTransform at = AffineTransform.getRotateInstance(Math.toRadians(rotate), ancx, ancy);
            g2d.fill(at.createTransformedShape(circle));
          }
        }
      }
      g2d.dispose();
    }

    public void wipeScreen() {
      //wipe everything by covering it with black
      if (buffImage != null) {
        Graphics g = buffImage.createGraphics();
        g.setColor(Color.BLACK);
        g.fillRect(0, 0, buffImage.getWidth(), buffImage.getHeight());
        g.dispose();
      }
    }

    public void addPoint(MyPoint p) {
      pointsList.add(p);
    }

    public void setColor(Color color) {
      this.color = color;
    }

    public void setCircleDiameter(double circleDiameter) {
      this.circleDiameter = circleDiameter;
    }
  }

  public class DrawingListener extends MouseAdapter implements ComponentListener {
    //the panel on which the user draws
    private final DrawingPanel dPanel;

    public DrawingListener(DrawingPanel dPanel) {
      this.dPanel = dPanel;
    }

    //add a point to the arraylist every time the user draws, and draw
    public void mousePressed(MouseEvent e) {
      //System.out.println(e.getPoint());
      dPanel.addPoint(new MyPoint(e.getX(), e.getY(), dPanel.color, dPanel.circleDiameter, dPanel.reflectPoints));
      dPanel.repaint();
    }

    public void mouseDragged(MouseEvent e) {
      dPanel.addPoint(new MyPoint(e.getX(), e.getY(), dPanel.color, dPanel.circleDiameter, dPanel.reflectPoints));
      dPanel.repaint();
    }

    @Override
    public void componentResized(ComponentEvent e) {
      //whenever component is resized, wipe the screen
      //then the system-triggered repaint occurs and the result is that by making the window smaller, the user zooms in
      dPanel.wipeScreen();
    }

    public void componentHidden(ComponentEvent arg0) {}
    public void componentMoved(ComponentEvent arg0) {}
    public void componentShown(ComponentEvent arg0) {}
  }


  //each point drawn is an object of the class
  public class MyPoint extends Point {
    private Color pointColor;
    private double pointDiameter;
    private boolean reflected;

    public MyPoint(int x, int y, Color c, double pointDiameter, boolean reflected) {
      super(x, y);
      this.pointColor = c;
      this.pointDiameter = pointDiameter;
      this.reflected = reflected;
    }

    public Color getPointColor() {
      return this.pointColor;
    }

    public double getPointDiameter() {
      return this.pointDiameter;
    }

    public boolean isReflected() {
      return this.reflected;
    }
  }
}
aterai
  • 9,658
  • 4
  • 35
  • 44
  • Thank you! I'm still curious what caused the bug. – Sreten Jocić Mar 10 '17 at 11:49
  • 1
    @ScottyNguyen: repeated transformations, e.g. calls to `rotate()`, are _concatenated_ in the manner discussed [here](http://stackoverflow.com/a/3843569/230513); repeated calls to `createTransformedShape()`, with no intervening change in the `AffineTransform`, are not. – trashgod Mar 10 '17 at 12:26