5

I recently saw this question on how to rotate an image in Java. I copy/pasted it directly from that answer. On implementation, it seems to only rotate images that are squares(that is have the same size, width and height). When I try to use it for a non-square image, it seems to cut off that part that would make it be a rectangle, if that makes sense. Like this

How can I fix/work around this?

Edit: The code I'm using. Also, I won't have a scroll bar as this will be a "game", and also won't be in full screen all of the time.

public class Player extends Entity { //Entity has basic values such as (float) x & y values, along with some getters and setters
   double theta;    

   Reticle reticle; //draws a reticle where the cursor was(basically just replaces java.awt.Cursor due to something not neccessary for me to get into)
   Sprite currentImage; //basically just a BufferedImage that you can apply aspect ratios to

   //constructor

   @Override
   public void tick() {
      //(this line) gets the Reticle from the main-method class and set it to this reticle object
      reticleX = reticle.getX(); //basically gets the mouse coordinates
      reticleY = reticle.getY();

      x += dX; //delta or change in X
      y += dY  //delta or change in Y

      checkCollision(); //bounds checking

      //getCentralizedX()/Y() gets the center of the currentImage
      theta = getAngle(getCentralizedX(), getCentralizedY(), reticleX, reticleY);

      currentImage = Images.rotateSprite(currentImage, theta);
    }

    @Override
    public void render(Graphics g) {
        currentImage.render(g, x, y);
        g.drawLine((int) getCentralizedX(), (int) getCentralizedY(), (int) reticleX, (int) reticleY);
    }

    public double getAngle(float startX, float startY, float goalX, float goalY) {
        double angle = Math.atan2(goalY - startY, goalX - startX);
        //if(angle < 0) { //removed this as this was the thing messing up the rotation
            //angle += 360;
        //}
    }

If the angle of the soldier is from 90 < angle < 270, then it is (basically), however, if its its 90 > angle > 270, then it gets a little wonky. Here are some pictures. It is not the angle of the aim-line(the blue line) that is wrong.

Removed all of the images as removing the if(angle < 0) inside of getAngle() fixed the rotation bug. Now the only problem is that it doesn't rotate in place.

EDIT 2: My SSCCE, which uses the same method as my game, but freaks out for some reason.

public class RotateEx extends Canvas implements Runnable {
Player player;

public RotateEx(BufferedImage image) {
    player = new Player(image, 50, 50);
    setPreferredSize(new Dimension(600, 600));
}

public void setDegrees(int degrees) {
    player.theta = Math.toRadians(degrees);
}

public BufferedImage rotateImage(BufferedImage original, double theta) {
    double cos = Math.abs(Math.cos(theta));
    double sin = Math.abs(Math.sin(theta));
    double width = original.getWidth();
    double height = original.getHeight();
    int w = (int) (width * cos + height * sin);
    int h = (int) (width * sin + height * cos);

    BufferedImage out = new BufferedImage(w, h, original.getType());
    Graphics2D g2 = out.createGraphics();
    double x = w / 2; //the middle of the two new values 
    double y = h / 2;

    AffineTransform at = AffineTransform.getRotateInstance(theta, x, y);
    x = (w - width) / 2;
    y = (h - height) / 2;
    at.translate(x, y);
    g2.drawRenderedImage(original, at);
    g2.dispose();

    return out;
}

public void tick() {
    player.tick();
}

public void render() {
    BufferStrategy bs = this.getBufferStrategy();
    if(bs == null) {
        createBufferStrategy(4);
        return;
    }

    Graphics g = bs.getDrawGraphics();
    g.setColor(Color.WHITE);
    g.fillRect(0, 0, getWidth(), getHeight());
    player.render(g);

    g.dispose();
    bs.show();
}

public static void main(String args[]) throws IOException, InterruptedException {
    String loc = "FILELOCATION"; //of course this would be a valid image file
    BufferedImage image = ImageIO.read(new File(loc));

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

    JFrame f = new JFrame();
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.add(ex);
    f.add(slider, BorderLayout.SOUTH);
    f.pack();
    f.setLocationRelativeTo(null);
    f.setVisible(true);

    new Thread(ex).start();
}

@Override
public void run() {
    long lastTime = System.nanoTime();
    final double numTicks = 60.0;
    double n = 1000000000 / numTicks;
    double delta = 0;
    int frames = 0;
    int ticks = 0;
    long timer = System.currentTimeMillis();

    while (true) {
        long currentTime = System.nanoTime();
        delta += (currentTime - lastTime) / n;
        lastTime = currentTime;

        render();
        tick();
        frames++;

        if (delta >= 1) {
            ticks++;
            delta--;
        }
    }
}

class Player {
    public float x, y;
    int width, height;
    public double theta; //how much to rotate, in radians
    BufferedImage currentImage; //this image would change, according to the animation and what frame its on

    public Player(BufferedImage image, float x, float y) {
        this.x = x;
        this.y = y;
        width = image.getWidth();
        height = image.getHeight();
        currentImage = image;
    }

    public void tick() {
        currentImage = rotateImage(currentImage, theta);
    }

    public void render(Graphics g) {
        g.drawImage(currentImage, (int) x, (int) y, null);
    }
}

}

Community
  • 1
  • 1

2 Answers2

5

When you rotate an image the width and height also change and your code doesn't take this into account.

Here is some old code I have lying around that should work better:

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

public class Rotation
{
    BufferedImage image;
    JLabel label;

    public Rotation(BufferedImage image)
    {
        this.image = image;
    }

    private BufferedImage getImage(double theta)
    {
        //  Determine the size of the rotated image

        double cos = Math.abs(Math.cos(theta));
        double sin = Math.abs(Math.sin(theta));
        double width  = image.getWidth();
        double height = image.getHeight();
        int w = (int)(width * cos + height * sin);
        int h = (int)(width * sin + height * cos);

        //  Rotate and paint the original image onto a BufferedImage

        BufferedImage out = new BufferedImage(w, h, image.getType());
        Graphics2D g2 = out.createGraphics();
        g2.setPaint(UIManager.getColor("Panel.background"));
        g2.fillRect(0,0,w,h);
        double x = w/2;
        double y = h/2;
        AffineTransform at = AffineTransform.getRotateInstance(theta, x, y);
        x = (w - width)/2;
        y = (h - height)/2;
        at.translate(x, y);
        g2.drawRenderedImage(image, at);
        g2.dispose();
        return out;
    }

    private JLabel getLabel()
    {
        ImageIcon icon = new ImageIcon(image);
        label = new JLabel(icon);
        label.setHorizontalAlignment(JLabel.CENTER);
        return label;
    }

    private JSlider getSlider()
    {
        final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
        slider.addChangeListener(new ChangeListener()
        {
            public void stateChanged(ChangeEvent e)
            {
                int value = slider.getValue();
                BufferedImage bi = getImage(Math.toRadians(value));
                label.setIcon(new ImageIcon(bi));
            }
        });
        return slider;
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                try
                {
                    String path = "mong.jpg";
                    ClassLoader cl = Rotation.class.getClassLoader();
                    BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
                    Rotation r = new Rotation(bi);
                    JFrame f = new JFrame();
                    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    f.getContentPane().add(new JScrollPane(r.getLabel()));
                    f.getContentPane().add(r.getSlider(), "South");
                    f.pack();
                    f.setLocation(200,200);
                    f.setVisible(true);
                }
                catch(IOException e)
                {
                    System.out.println(e);
                }
            }
        });
    }
}

Edit:

Another option is to create an Icon, then you can use the Rotated Icon. Then you can rotate and paint the icon in your painting code. Something like:

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

public class Rotation3 extends JPanel
{
    private Icon icon;
    private Icon rotated;
    private int degrees;

    public Rotation3(BufferedImage image)
    {
        icon = new ImageIcon( image );
        setDegrees( 0 );
        setPreferredSize( new Dimension(600, 600) );
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

        double radians = Math.toRadians( degrees );
        rotated = new RotatedIcon(icon, degrees);

        // translate x/y so Icon is rotated around a specific point (300, 300)

        int x = 300 - (rotated.getIconWidth() / 2);
        int y = 300 - (rotated.getIconHeight() / 2);
        rotated.paintIcon(this, g, x, y);

        g.setColor(Color.RED);
        g.fillOval(295, 295, 10, 10);
    }

    public void setDegrees(int degrees)
    {
        this.degrees = degrees;
        double radians = Math.toRadians( degrees );
        rotated = new RotatedIcon(icon, degrees);
        repaint();
    }

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                try
                {
                    String path = "dukewavered.gif";
                    ClassLoader cl = Rotation3.class.getClassLoader();
                    BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
                    final Rotation3 r = new Rotation3(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.pack();
                    f.setLocationRelativeTo(null);
                    f.setVisible(true);
                }
                catch(IOException e)
                {
                    System.out.println(e);
                }
            }
        });
    }
}

Edit 2:

Even easier than I thought here is an example:

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

public class Rotation2 extends JPanel
{
    BufferedImage image;
    int degrees;
    int point = 250;

    public Rotation2(BufferedImage image)
    {
        this.image = image;
        setDegrees( 0 );
        setPreferredSize( new Dimension(600, 600) );
    }

    @Override
    protected void paintComponent(Graphics g)
    {
        super.paintComponent(g);

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

        double radians = Math.toRadians( degrees );
        g2.translate(point, point);
        g2.rotate(radians);
        g2.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
        g2.drawImage(image, 0, 0, null);

        g2.dispose();

        g.setColor(Color.RED);
        g.fillOval(point - 5, point - 5, 10, 10);
    }

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

    public static void main(String[] args)
    {
        EventQueue.invokeLater(new Runnable()
        {
            public void run()
            {
                try
                {
//                  String path = "mong.jpg";
                    String path = "dukewavered.gif";
                    ClassLoader cl = Rotation2.class.getClassLoader();
                    BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
                    final Rotation2 r = new Rotation2(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.pack();
                    f.setLocationRelativeTo(null);
                    f.setVisible(true);
                }
                catch(IOException e)
                {
                    System.out.println(e);
                }
            }
        });
    }
}

The rotation code was taken from: How to rotate an image gradually in Swing?

Community
  • 1
  • 1
camickr
  • 321,443
  • 19
  • 166
  • 288
  • In my "game", I plan on calculating then rotating an image every game-loop depending on where the mouse is, so the player faces the mouse. Wouldn't this be inefficient since you're doing all of those calculations each loop?. My bad on not elaborating. – Gannon Prudhomme Feb 27 '15 at 01:30
  • @GannonPrudhomme, just changed the example. Don't micro-optimize until you have a problem. – camickr Feb 27 '15 at 01:32
  • Will do. I only try to "micro-optimize" as to be able to recognize and prevent problems, but I understand what you mean. – Gannon Prudhomme Feb 27 '15 at 01:56
  • I basically copy/pasted it, and it still isn't working. Now it's doing some really-wonky animation and still partially cutting off part of the image, except that the location at which the image is cut off now changes. – Gannon Prudhomme Feb 27 '15 at 02:21
  • @GannonPrudhomme, did the code I posted work because it works fine for me. `I basically copy/pasted it, and it still isn't working.` - "isn't working" doesn't provide any information. I'm not a mind reader, I don't know what you copied and how you actually implemented it so I'm not sure how you expect further help. Post a proper [SSCCE](http://sscce.org/) that demonstrates your problem. You can also check out the edit for another option. – camickr Feb 27 '15 at 04:54
  • You have a point, and I understand I am at fault, that's my bad. I will record and post a GIF of what it does when I get home, but basically, it 1) (sometimes, not sure why not always) cuts off part of the image, and 2) it doesn't rotate in the center, which I believe is because it is not a perfect square, but I am not sure. – Gannon Prudhomme Feb 27 '15 at 14:01
  • Scratch 1), it doesn't cut off part of the image, but it still doesn't rotate around a center point. – Gannon Prudhomme Feb 27 '15 at 21:07
  • @GannonPrudhomme, what is the reason for the down vote. Does the code I gave you not rotate about the center or not? – camickr Feb 28 '15 at 23:13
  • Didn't down-vote, just removed my upvote. I understand people are busy and I really do appreciate the help but I figured you were kind of just "done" with this and were purposely ignoring me, which was inconsiderate of me and unnecessary to jump to that conclusion. And that is correct, it is not rotating around the center. – Gannon Prudhomme Feb 28 '15 at 23:20
  • @GannonPrudhomme, The code I posted `DOES` rotate around the center of the panel. All you need to do is maximize the frame and play with the slider on the bottom. The problem is "your code" does not rotate around the center and you have NOT posted a SSCCE demonstrating the problem, so how do you expect further help? – camickr Feb 28 '15 at 23:23
  • Just added the code I use plus the pictures of testing. – Gannon Prudhomme Mar 01 '15 at 00:20
  • @GannonPrudhomme, That is NOT a SSCCE. How do I copy/paste/compile and execute that code? The code I gave you shows how to rotate an image. It then uses a layout manager to center the component within the bounds of its parent. You need to manage where the image should be painted, based on some point on your game panel. If you have an normal image of (100 x 50) the center point is (50, 50), not (50, 25). So you paint the image at (0, 25). When rotated 90 degrees you paint the image at (25, 0). So you need to mange (x, y) of your render(...) method around your center point. – camickr Mar 01 '15 at 16:56
  • Added code to show how custom painting can be done using the `RotatedIcon`. This approach is closer to the logic you currently have for drawing an image since it is not dependent on layout managers. – camickr Mar 01 '15 at 20:49
  • I think the difference between what I'm doing and what you're doing is that in your examples you're using an `ImageIcon` and add the `BufferedImage` onto it, then add that to the `JFrame`, while I just use a straight `BufferedImage` and paint it with `Graphics`, which I think might make a difference, but you know better than I do. I'll test further – Gannon Prudhomme Mar 01 '15 at 21:53
  • Also I read your "SSCCE" website and it didn't seem applicable in my situation as it would be very difficult to send you my source and almost unnecessary as I have ~20 classes, half of which are required to run. I sent you what I believed was necessary and I apologize that it didn't meet your requirements. – Gannon Prudhomme Mar 01 '15 at 22:09
  • @GannonPrudhomme, `Also I read your "SSCCE" website and it didn't seem applicable in my situation` - You don't understand what a SSCCE is. 20 classes are irrelevant to the question. The rules/logic of your game is not important. Your question is about rotating an image. Ccreate a simple example that demonstrates the problem. Look at my SSCCE with the solution and you can see how easily this can be demonstrated in a single class, once all the noise of the real application is removed. Usually when you create a SSCCE you will find the error. Check out the last edit, that should be what you need. – camickr Mar 01 '15 at 22:56
  • You're correct, I did misunderstand. I'm writing one right now. On your most recent edit, they reason it doesn't apply to me is because I believe that is rotating the whole Graphics, while I want the rotation method to be given a `BufferedImage` and a radian value and rotate that many radians, in the center of the `BufferedImage`, and return a new rotated `BufferedImage`. Can you also further describe what you meant by "If you have an normal image of (100 x 50) the center point is (50, 50), not (50, 25). So you paint the image at (0, 25)." – Gannon Prudhomme Mar 02 '15 at 21:28
  • The Rotation3 example does exactly what you want. It returns a separate rotated object that you can paint, but you need to calculate the x/y coordinates of where the Icon should be painted. The Rotation2 code is much simpler as the rotation and translation of where to paint the image is done in the Graphics object. The Graphics object is only used to paint the image so it doesn't affect other painting. – camickr Mar 02 '15 at 22:55
  • My IDE(Eclipse Luna) doesn't seem to think `RotatedIcon` is a valid class. I googled it and I couldn't even find oracle documentation on it. It's not a separate library, is it? – Gannon Prudhomme Mar 02 '15 at 23:08
  • `It's not a separate library, is it?` - Yes, I gave you a link to the class in my answer. A bit frustrating you haven't even tried that code yet. By the way I updated the code to simplify it. Hopefully the code to center the Icon around a point (300, 300) on the panel makes sense. Your Player constructor needs to be changed to receive the point you want to center your image on, not the point that represents the top/left of the image. – camickr Mar 03 '15 at 01:00
  • Oh, I've clicked on it before but it seems to blend in once the site knows you have, so I could barely tell a difference because I was focusing on the code. Also, couldn't I just use the width and height to see where the center would be, or no because of that width/height calculations(using cos and sin to calculate the "new" values). If I did change it to accept the center x/y, it would really mess up all of my bounds checking. – Gannon Prudhomme Mar 03 '15 at 01:11
  • `I've clicked on it before but it seems to blend in once the site knows you have` yes, I have that same complaint. They "upgraded" the LAF of the forum a month ago and changed the color of viewed links. I can barely see the difference as well. `it would really mess up all of my bounds checking.` - well that is a different problem because the size of the image changes as the image is rotated. I guess you now will need to track the x/y location of where the image is painted. Anyway, the problem of rotating an image about its center has been solved (in multiple ways) – camickr Mar 03 '15 at 02:10
  • Honestly, I was being kind of a jerk and very unappreciative. I believed that it didn't work and kind of "neglected" the `RotatedIcon` as I try to avoid libraries as much as possible, but I was attempting to implement it this morning and it's working perfectly. I couldn't thank you enough. – Gannon Prudhomme Mar 03 '15 at 14:11
  • `I try to avoid libraries as much as possible` - I agree. Avoid complex third party libraries that force you into a design platform. In your current class you created a method `rotateImage(...)`. A better design is to create a separate class with a static method to return the rotated image. Then you can reuse the code anywhere. Over time you create a personal library of reusable classes that make writing your application simpler. That's all the "RotatedIcon" is. A piece of reusable code that can be used as needed. – camickr Mar 03 '15 at 16:03
  • So there is no reason you can't still use your `rotateImage(...)` method to create the rotated image. You just need to remember the additional step to center the image around a specified point. – camickr Mar 03 '15 at 16:18
0

A far far easier way is to use the following library. Just call img.rotate(30) or whatever and it rotates, no messing about with AWT.

http://www.javaxt.com/javaxt-core/io/Image/

Lee Burch
  • 51
  • 1
  • 2