0

I am having trouble writing a function that takes a BufferedImage and a Cardinal Direction and rotates the image accordingly. I have looked on many threads on stack and tried implementing myself while going over the docs for some time now and the best I'm getting is that the image seems to be rotating, however the last pixel is correct and the rest are set white or transparent not sure.

Here is where I have got so far:

private BufferedImage rotateImg(BufferedImage image, String direction){

    int width = image.getWidth();
    int height = image.getHeight();
    BufferedImage rotated = null;

    for(int y = 0; y < height; y++){
        for(int x = 0; x < width; x++){
            switch(direction){
                case "SOUTH":
                    rotated = new BufferedImage(width, height, image.getType());
                    rotated.setRGB((width - 1) - x, (height - 1) - y, image.getRGB(x,y));
                    break;
                case "WEST":
                    //ROTATE LEFT
                    rotated = new BufferedImage(height, width, image.getType());
                    rotated.setRGB(y, (width - 1) - x, image.getRGB(x,y));
                    break;
                case "EAST":
                    //ROTATE RIGHT
                    rotated = new BufferedImage(height, width, image.getType());
                    rotated.setRGB((height - 1) - y,  x, image.getRGB(x,y));
                    break;
                default:
                    return image;
            }
        }
    }
    return rotated;
}

Below there are four images but as they are so small its really hard to see them. A bit of browser zoom will show them.

enter image description here enter image description here enter image description here enter image description here

When you get close the cyan pixel is staying where it should for the rotation. Its just im loosing the rest of the image.

Paul Hashmi
  • 456
  • 5
  • 18
  • 1
    This https://stackoverflow.com/questions/20959796/rotate-90-degree-to-right-image-in-java might help – Mr R Sep 02 '22 at 21:59
  • I have had a look already but will have a second thanks Mr R. – Paul Hashmi Sep 02 '22 at 22:01
  • 1
    the actual code is creating a new image inside the loop for each iteration - only the last image with the last pixel will be returned. Better have the loop inside the switch, once for each case... (Also check `AffineTransform`) – user16320675 Sep 03 '22 at 00:44
  • 1
    What is your goal here? Do you want to learn the low-level details of how to do this yourself? Or are you interested in making something that works as quickly as possible? If the later, then you should find a library (either built into Java or available on Maven) to help you accomplish the task. If you are wanting to learn how to do this yourself, then you are well on your way. As pointed out there are some mistakes in your logic. I suggest reading [this article](https://ericlippert.com/2014/03/05/how-to-debug-small-programs/) for tips to debug your code. – Code-Apprentice Sep 03 '22 at 18:03

2 Answers2

3

I don't know if there's a fixed requirement to rotate this image by individual pixels or not, but I'm far to simple minded to even be bothered trying.

Instead, I'd (personally) drop straight into the Graphics API itself, for example...

public static BufferedImage rotateBy(BufferedImage source, double degrees) {
    // The size of the original image
    int w = source.getWidth();
    int h = source.getHeight();
    // The angel of the rotation in radians
    double rads = Math.toRadians(degrees);
    // Some nice math which demonstrates I have no idea what I'm talking about
    // Okay, this calculates the amount of space the image will need in
    // order not be clipped when it's rotated
    double sin = Math.abs(Math.sin(rads));
    double cos = Math.abs(Math.cos(rads));
    int newWidth = (int) Math.floor(w * cos + h * sin);
    int newHeight = (int) Math.floor(h * cos + w * sin);

    // A new image, into which the original can be painted
    BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g2d = rotated.createGraphics();
    // The transformation which will be used to actually rotate the image
    // The translation, actually makes sure that the image is positioned onto
    // the viewable area of the image
    AffineTransform at = new AffineTransform();
    at.translate((newWidth - w) / 2, (newHeight - h) / 2);

    // And we rotate about the center of the image...
    int x = w / 2;
    int y = h / 2;
    at.rotate(rads, x, y);
    g2d.setTransform(at);
    // And we paint the original image onto the new image
    g2d.drawImage(source, 0, 0, null);
    g2d.dispose();

    return rotated;
}

This method will create a new image large enough to fit the rotated version of the source image.

You could then drop it into a helper class, add some helper methods and have a basic worker (and re-usable) solution, for example...

public class ImageUtilities {

    public enum Direction {
        NORTH, SOUTH, EAST, WEST
    }

    public static BufferedImage rotateBy(BufferedImage source, Direction direction) {
        switch (direction) {
            case NORTH:
                return source;
            case SOUTH:
                return rotateBy(source, 180);
            case EAST:
                return rotateBy(source, 90);
            case WEST:
                return rotateBy(source, -90);
        }
        return null;
    }

    public static BufferedImage rotateBy(BufferedImage source, double degrees) {
        // The size of the original image
        int w = source.getWidth();
        int h = source.getHeight();
        // The angel of the rotation in radians
        double rads = Math.toRadians(degrees);
        // Some nice math which demonstrates I have no idea what I'm talking about
        // Okay, this calculates the amount of space the image will need in
        // order not be clipped when it's rotated
        double sin = Math.abs(Math.sin(rads));
        double cos = Math.abs(Math.cos(rads));
        int newWidth = (int) Math.floor(w * cos + h * sin);
        int newHeight = (int) Math.floor(h * cos + w * sin);

        // A new image, into which the original can be painted
        BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = rotated.createGraphics();
        // The transformation which will be used to actually rotate the image
        // The translation, actually makes sure that the image is positioned onto
        // the viewable area of the image
        AffineTransform at = new AffineTransform();
        at.translate((newWidth - w) / 2, (newHeight - h) / 2);

        // And we rotate about the center of the image...
        int x = w / 2;
        int y = h / 2;
        at.rotate(rads, x, y);
        g2d.setTransform(at);
        // And we paint the original image onto the new image
        g2d.drawImage(source, 0, 0, null);
        g2d.dispose();

        return rotated;
    }
}

(although I might use RIGHT, LEFT, UPSIDE or something, but that's me :P)

Runnable example...

enter image description here

import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class Main {
    public static void main(String[] args) {
        new Main();
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    JFrame frame = new JFrame();
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class TestPane extends JPanel {

        private BufferedImage masterImage;
        private BufferedImage northImage;
        private BufferedImage southImage;
        private BufferedImage eastImage;
        private BufferedImage westImage;

        public TestPane() throws IOException {
            masterImage = ImageIO.read(new File("/absolute/path/to/your/image.png"));

            northImage = ImageUtilities.rotateBy(masterImage, ImageUtilities.Direction.NORTH);
            southImage = ImageUtilities.rotateBy(masterImage, ImageUtilities.Direction.SOUTH);
            eastImage = ImageUtilities.rotateBy(masterImage, ImageUtilities.Direction.EAST);
            westImage = ImageUtilities.rotateBy(masterImage, ImageUtilities.Direction.WEST);

            setLayout(new GridLayout(3, 3));

            add(new JLabel(""));
            add(new JLabel(new ImageIcon(northImage)));
            add(new JLabel(""));

            add(new JLabel(new ImageIcon(westImage)));
            add(new JLabel(new ImageIcon(masterImage)));
            add(new JLabel(new ImageIcon(eastImage)));

            add(new JLabel(""));
            add(new JLabel(new ImageIcon(southImage)));
            add(new JLabel(""));
        }
    }

    public class ImageUtilities {

        public enum Direction {
            NORTH, SOUTH, EAST, WEST
        }

        public static BufferedImage rotateBy(BufferedImage source, Direction direction) {
            switch (direction) {
                case NORTH:
                    return source;
                case SOUTH:
                    return rotateBy(source, 180);
                case EAST:
                    return rotateBy(source, 90);
                case WEST:
                    return rotateBy(source, -90);
            }
            return null;
        }

        public static BufferedImage rotateBy(BufferedImage source, double degrees) {
            // The size of the original image
            int w = source.getWidth();
            int h = source.getHeight();
            // The angel of the rotation in radians
            double rads = Math.toRadians(degrees);
            // Some nice math which demonstrates I have no idea what I'm talking about
            // Okay, this calculates the amount of space the image will need in
            // order not be clipped when it's rotated
            double sin = Math.abs(Math.sin(rads));
            double cos = Math.abs(Math.cos(rads));
            int newWidth = (int) Math.floor(w * cos + h * sin);
            int newHeight = (int) Math.floor(h * cos + w * sin);

            // A new image, into which the original can be painted
            BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g2d = rotated.createGraphics();
            // The transformation which will be used to actually rotate the image
            // The translation, actually makes sure that the image is positioned onto
            // the viewable area of the image
            AffineTransform at = new AffineTransform();
            at.translate((newWidth - w) / 2, (newHeight - h) / 2);

            // And we rotate about the center of the image...
            int x = w / 2;
            int y = h / 2;
            at.rotate(rads, x, y);
            g2d.setTransform(at);
            // And we paint the original image onto the new image
            g2d.drawImage(source, 0, 0, null);
            g2d.dispose();

            return rotated;
        }
    }
}

But the image is rotating in the wrong direction!

Okay, so change the angle of rotation to meet your needs!

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Perfection thanks. graphics2d is where I got yesterday its running with no problems. had to deal with odd sized width and height but I simply padded it and used a subimagethen cropped and seems to be good. I marked the answer as correct above as he corrected the stupid bug creating a 1x1 pixel each iteration through the loop. It wont let me add two right answers or I would. Thanks again for your efforts and help. – Paul Hashmi Sep 03 '22 at 12:07
  • lol the cardinal directions are because the images are being passed into a game based on the direction the character is looking..or i might of used up, down, left, right, upside-down, right-side-up XD... – Paul Hashmi Sep 03 '22 at 12:12
0

As commented, the actual code is creating a new image for each pixel, that is, inside the inner loop. Only the image created in the last iteration is returned, containing just the last pixel.

The following code creates one single image - I tried to maintain the original code as much as possible:

private static BufferedImage rotateImg(BufferedImage image, String direction){

    int width = image.getWidth();
    int height = image.getHeight();
    BufferedImage rotated = null;


    switch(direction){
        case "SOUTH":
            rotated = new BufferedImage(width, height, image.getType());
            for(int y = 0; y < height; y++){
                for(int x = 0; x < width; x++){
                    rotated.setRGB((width - 1) - x, (height - 1) - y, image.getRGB(x,y));
                }
            }
            break;
        case "WEST":
            //ROTATE LEFT
            rotated = new BufferedImage(height, width, image.getType());
            for(int y = 0; y < height; y++){
                for(int x = 0; x < width; x++){
                    rotated.setRGB(y, (width - 1) - x, image.getRGB(x,y));
                }
            }
            break;
        case "EAST":
            //ROTATE RIGHT
            rotated = new BufferedImage(height, width, image.getType());
            for(int y = 0; y < height; y++){
                for(int x = 0; x < width; x++){
                    rotated.setRGB((height - 1) - y,  x, image.getRGB(x,y));
                }
            }
            break;
        default:
            return image;
    }
    return rotated;
}

To avoid having the loop code repeated (easier to maintain) we can use two functions - calX and calcY:

private static BufferedImage rotateImg(BufferedImage image, String direction){

    int width = image.getWidth();
    int height = image.getHeight();
    BufferedImage rotated = null;

    IntBinaryOperator calcX;
    IntBinaryOperator calcY;

    switch(direction){
        case "SOUTH":
            rotated = new BufferedImage(width, height, image.getType());
            calcX = (x, y) -> (width - 1) - x;
            calcY = (x, y) -> (height - 1) - y;
            break;
        case "WEST":
            //ROTATE LEFT
            rotated = new BufferedImage(height, width, image.getType());
            calcX = (x, y) -> y;
            calcY = (x, y) -> (width - 1) - x;
            break;
        case "EAST":
            //ROTATE RIGHT
            rotated = new BufferedImage(height, width, image.getType());
            calcX = (x, y) -> (height - 1) - y;
            calcY = (x, y) -> x;
            break;
        default:
            return image;
    }
    for(int y = 0; y < height; y++){
        for(int x = 0; x < width; x++){
            rotated.setRGB(calcX.applyAsInt(x, y), calcY.applyAsInt(x, y), image.getRGB(x,y));
        }
    }
    return rotated;
}
user16320675
  • 135
  • 1
  • 3
  • 9
  • Thanks for this. what a dumb mistake! to many hours infront of the computer should of just had a break. Marked correct as it fixed the bug in the code with a good explanation – Paul Hashmi Sep 03 '22 at 12:07