4

I am working on a class that can rotate a wheel around the center. The wheel is created using graphics2d, but I can not figure out exactly how to get the wheel to rotate around the center. Currently, the wheel rotates, but not exactly about the origin.

My ultimate goal here is to create the wheel so that it is multicolored as well as a program around it, but my main concern here is getting the rotating wheel to work. If you could point me in the right direction I would be forever grateful!

Here is my current code:

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.*;


public class RotateApp {

private static final int N = 3;

public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {

        public void run() {
            JFrame frame = new JFrame();
            frame.setLayout(new GridLayout(N, N, N, N));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new RotatePanel());
            frame.pack();
            frame.setVisible(true);
            System.out.println();
        }
    });
}
}


class RotatePanel extends JPanel implements ActionListener {

private static final int SIZE = 256;
private static double DELTA_THETA = Math.PI / 90;
private final Timer timer = new Timer(25, this);
private Image image = RotatableImage.getImage(SIZE);
private double dt = DELTA_THETA;
private double theta;

public RotatePanel() {
    this.setBackground(Color.lightGray);
    this.setPreferredSize(new Dimension(
        image.getWidth(null), image.getHeight(null)));
    this.addMouseListener(new MouseAdapter() {

        @Override
        public void mousePressed(MouseEvent e) {
            image = RotatableImage.getImage(SIZE);
            dt = -dt;
        }
    });
    timer.start();
}


public void paintComponent(Graphics g) {
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
    g2d.rotate(theta);
    g2d.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
    g2d.drawImage(image, 0, 0, null);
}

public void actionPerformed(ActionEvent e) {
    theta += dt;
    repaint();
}

@Override
public Dimension getPreferredSize() {
    return new Dimension(SIZE, SIZE);
}

}

class RotatableImage {

private static final Random r = new Random();

static public Image getImage(int size) {
    BufferedImage bi = new BufferedImage(
        size, size, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = bi.createGraphics();
    g2d.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
    g2d.setStroke(new BasicStroke(10.0f));
    g2d.draw(new Line2D.Double(0, 100, 100, 100));
    g2d.draw(new Line2D.Double(100, 100, 200, 100));

    g2d.draw(new Line2D.Double(100, 0, 100, 100));
    g2d.draw(new Line2D.Double(100, 100, 100, 200));

    g2d.draw(new Line2D.Double(25, 25, 100, 100));
    g2d.draw(new Line2D.Double(100, 100, 175, 175));

    g2d.draw(new Line2D.Double(175, 25, 100, 100));
    g2d.draw(new Line2D.Double(100, 100, 25, 175));

    g2d.draw(new Ellipse2D.Double(0, 0, 200, 200));
    g2d.dispose();
    return bi;
}
}
Machavity
  • 30,841
  • 27
  • 92
  • 100
Seth Allen
  • 41
  • 1
  • 4
  • 2
    Translate first, rotate second. Better choice, use `AffineTransform` – MadProgrammer Apr 16 '15 at 23:12
  • 2
    For [example](http://stackoverflow.com/questions/20367149/how-to-use-affinetransform-quadrantrotate-to-rotate-a-bitmap/20368979#20368979), [example](http://stackoverflow.com/questions/20275424/rotating-image-with-affinetransform/20280225#20280225), [example](http://stackoverflow.com/questions/21841582/getting-the-right-image-observer-for-rotating-an-image/21841683#21841683). Yes I know these examples rotate images, but the concept is the same – MadProgrammer Apr 16 '15 at 23:16
  • 1
    Modifying the state of a `Graphics` object is a dangerous thing to do, generally speaking, the `Graphics` context is a shared resource, meaning that any modifications you make to it will passed to the component to be painted. Instead use `Graphics#create` (and cast it to `Graphics2D`) to create a snapshot of the properties of the `Graphics` context and manipulate it. Once you're done, call `Graphics#dispose` on the copy – MadProgrammer Apr 16 '15 at 23:18
  • Thank you for your reply :) I am a beginning programmer so it is going to take a while for me to take what you said and put it into practice. That being said, I will try! – Seth Allen Apr 16 '15 at 23:21
  • Quick solution, swap the translate and rotate calls, so you translate first – MadProgrammer Apr 16 '15 at 23:42

2 Answers2

3

You can use the Rotated Icon class to do the rotation for you so you don't have to worry about all the rotation logic and the rotation logic is in a reusable class.

An example of using this class would be:

import java.awt.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.util.*;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;

public class Rotation4 extends JPanel
{
    private JLabel label;
    private RotatedIcon rotated;
    private int degrees;

    public Rotation4(Image image)
    {
        setLayout( new GridBagLayout() );

        Icon icon = new ImageIcon( image );
        rotated = new RotatedIcon(icon, 0);
        rotated.setCircularIcon(true);
        label = new JLabel(rotated);
        label.setOpaque(true);
        label.setBackground(Color.RED);
        add(label, new GridBagConstraints());
        setDegrees( 0 );
    }

    public void setDegrees(int degrees)
    {
        this.degrees = degrees;
        rotated.setDegrees( degrees );
        label.revalidate();
        label.repaint();
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                Image bi = RotatableImage.getImage(210);
                final Rotation4 r = new Rotation4(bi);

                    final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
                    slider.addChangeListener(new ChangeListener()
                    {
                        public void stateChanged(ChangeEvent e)
                        {
                            int value = slider.getValue();
                            r.setDegrees( value );
                        }
                    });

                JFrame f = new JFrame();
                f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                f.add(new JScrollPane(r));
                f.add(slider, BorderLayout.SOUTH);
                f.setSize(400, 400);
                f.setLocationRelativeTo(null);
                f.setVisible(true);
            }
        });
    }

    static class RotatableImage
    {
        private static final Random r = new Random();

        static public Image getImage(int size)
        {
            BufferedImage bi = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = bi.createGraphics();
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setPaint(Color.getHSBColor(r.nextFloat(), 1, 1));
            g2d.setStroke(new BasicStroke(10.0f));

            g2d.draw(new Line2D.Double(5, 105, 205, 105));
            g2d.draw(new Line2D.Double(105, 5, 105, 205));
            g2d.draw(new Line2D.Double(35, 35, 175, 175));
            g2d.draw(new Line2D.Double(175, 35, 35, 175));

            g2d.draw(new Ellipse2D.Double(5, 5, 199, 199));

            g2d.setColor(Color.BLACK);
            g2d.fillOval(100, 100, 10, 10);

            g2d.dispose();
            return bi;
        }
    }
}

Note I also had to make changes with your image and your painting. These changes will need to be made whether you use the RotatedIcon or do the rotation code yourself:

  1. The image size was changed to 210. This is because your stroke size is 10, so you need to account for the extra pixels in the circle outline.

  2. You need to change the original of the circle by half the stroke size. So in this case the origin becomes (5, 5).

  3. The size of the oval needs to be changed to 199. This is because of the way the outline of the oval is painted. 1 extra pixel is needed for the outline. If you leave the size at 200 then 1 pixel of the outline will be lost. This is not very noticeable when using a stroke size of 10, but if you use a size of 1, then the outline will be missing at the right and bottom edges.

  4. The locations of your lines needs to be changes. you don't want the line right to the edge of the circle because then you will get a flat line at the edge instead of the rounded line. So I started the line 5 pixels from the start and ended it 5 pixels from the end.

camickr
  • 321,443
  • 19
  • 166
  • 288
2

Ok, with little modification and fewer "spokes", I got your wheel rotate centric (1.) and multicolor (2.):


UPDATE on 1.) To make centric rotation in your (original) code just chage SIZEto 200!

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
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.Random;
import javax.swing.*;

public class RotateApp {

    private static final int N = 3;

    public static void main(String[] args) {
        EventQueue.invokeLater(() -> {
            JFrame frame = new JFrame();
            frame.setLayout(new GridLayout(N, N, N, N));
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new RotatePanel());
            frame.pack();
            frame.setVisible(true);
            System.out.println();
        });
    }
}

class RotatePanel extends JPanel implements ActionListener {

    private static final int SIZE = 256;
    private static final double DELTA_THETA = Math.PI / 90;
    private final Timer timer = new Timer(25, this);
    private Image image = RotatableImage.getImage(SIZE);
    private double dt = DELTA_THETA;
    private double theta;

    public RotatePanel() {
        this.setBackground(Color.lightGray);
        this.setPreferredSize(new Dimension(SIZE, SIZE));
        this.addMouseListener(new MouseAdapter() {

            @Override
            public void mousePressed(MouseEvent e) {
                dt = -dt;
                image = RotatableImage.getImage(SIZE);
            }
        });
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.rotate(theta,128,128);
        g2d.drawImage(image, 0, 0, null);
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        theta += dt;
        repaint();
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(SIZE, SIZE);
    }

}

class RotatableImage {

    private static final Random r = new Random();

    static public Image getImage(int size) {
        BufferedImage bi = new BufferedImage(
                size, size, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = bi.createGraphics();
        g2d.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        final Color c1 = Color.getHSBColor(r.nextFloat(), 1, 1);
        final Color c2 = Color.getHSBColor(r.nextFloat(), 1, 1);
        g2d.setPaint(c1);
        g2d.setStroke(new BasicStroke(10.0f));
        g2d.draw(new Line2D.Double(0, size/2, size, size/2));

        g2d.setPaint(c2);
        g2d.draw(new Line2D.Double(size/2, 0, size/2, size));

        g2d.setPaint(c1);
        g2d.draw(new Ellipse2D.Double(0, 0, size, size));
        g2d.dispose();
        return bi;
    }
}

Explanation:

  1. So the "wobble" in your solution came from the fact, that you sized the image and the container/panel 256x256, but "based" your wheel layout on 200x200 only. I fixed all dimensions, and drew a correct cross, the g2d.rotate(theta,128,128); (!) relates then to the center.

  2. Multi-color(easy): You can invoke setPaint() between each shape! ;)

xerx593
  • 12,237
  • 5
  • 33
  • 64