Rotate image in overriden paintComponent(…) method

2019-05-16 11:42发布

问题:

I am just wondering how to rotate a rectangle image with paintComponent() method of JLabel component and set its new width and height correctly?

I tried to rotate (see attached image) and the image scale becomes bigger but the JLabel scale keeps the same what makes image be out of JLabel bounds or something :S So my question is how to set image new width and height to component dynamically in a more optimal way?

回答1:

+1 to MadProgrammers comment and link.

Using the method from link (edited slightly to omit use of GraphicsConfiguration, drawRenderImage(..) and changed variable names):

//https://stackoverflow.com/questions/4156518/rotate-an-image-in-java
public static BufferedImage createTransformedImage(BufferedImage image, double angle) {
    double sin = Math.abs(Math.sin(angle));
    double cos = Math.abs(Math.cos(angle));
    int originalWidth = image.getWidth();
    int originalHeight = image.getHeight();
    int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
    int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
    BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
    Graphics2D g2d = rotatedBI.createGraphics();
    g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
    g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
    g2d.drawImage(image, 0, 0, null);
    g2d.dispose();
    return rotatedBI;
}

Here is an example:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class RotateImage {

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame frame = new JFrame();
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.add(new MyRotatableImage(createImage(), -45));
                frame.pack();
                frame.setVisible(true);
            }
        });
    }

    public static BufferedImage createImage() {
        BufferedImage img = new BufferedImage(100, 50, BufferedImage.TRANSLUCENT);
        Graphics2D g2d = img.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
        g2d.setColor(Color.BLACK);
        g2d.setFont(new Font("Calibri", Font.BOLD, 20));
        FontMetrics fm = g2d.getFontMetrics();
        String text = "Hello world";
        int textWidth = fm.stringWidth(text);
        g2d.drawString(text, (img.getWidth() / 2) - textWidth / 2, img.getHeight() / 2);
        g2d.dispose();
        return img;
    }
}

class MyRotatableImage extends JPanel {

    private BufferedImage transformedImage;

    public MyRotatableImage(BufferedImage img, int angle) {
        transformedImage = createTransformedImage(img, angle);
    }

    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.drawImage(transformedImage, 0, 0, null);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(transformedImage.getWidth(), transformedImage.getHeight());
    }

    //https://stackoverflow.com/questions/4156518/rotate-an-image-in-java
    public static BufferedImage createTransformedImage(BufferedImage image, double angle) {
        double sin = Math.abs(Math.sin(angle));
        double cos = Math.abs(Math.cos(angle));
        int originalWidth = image.getWidth();
        int originalHeight = image.getHeight();
        int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
        int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
        BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
        Graphics2D g2d = rotatedBI.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
        g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
        g2d.drawImage(image, 0, 0, null);
        g2d.dispose();
        return rotatedBI;
    }
}

Reference:

  • Rotate an image in java


回答2:

OK, so you want the JLabel to keep its dimensions and resize the image instead.

You're probably already using an affine transform anyways, so you can use some trigonometry and min/max to find the AABB of the rotated image. Scale appropriately (I assume you're already using affine transforms to do the rotation)

Edit:

AABB = Axis Aligned Bounding Box, its sides correspond to the min/max x,y coords of the rotated image. Just a more concise way of saying things.



回答3:

OK I tried the sample David Kroukamp showed me. Thanks, David, you pointed me to right direction. I think it is a really good snippet to lean back on.

public static BufferedImage rotateImage(Image image, int angle)
    {
        double sin = Math.abs(Math.sin(angle));
        double cos = Math.abs(Math.cos(angle));
        int originalWidth = image.getWidth(null);
        int originalHeight = image.getHeight(null);
        int newWidth = (int) Math.floor(originalWidth * cos + originalHeight * sin);
        int newHeight = (int) Math.floor(originalHeight * cos + originalWidth * sin);
        BufferedImage rotatedBI = new BufferedImage(newWidth, newHeight, BufferedImage.TRANSLUCENT);
        Graphics2D g2d = rotatedBI.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.translate((newWidth - originalWidth) / 2, (newHeight - originalHeight) / 2);
        g2d.rotate(angle, originalWidth / 2, originalHeight / 2);
        g2d.drawImage(image, 0, 0, null);
        g2d.dispose();
        return rotatedBI;
    }

David Kroukamp's result image preview

I tested the snippet and the result image + its component's scale were really dynamic changed. But the result component width I've got has been always a little bit wider than it's inner image was :S

For example here is my version of an image rotated in 45 degrees angle

... As I could find (on blue background) its width is not totally fit to surround white image ... Actually I was looking for some kind of standard Java Image Processing rotate solution but I couldn't find it :( so I had to dig deeper to the problem and at least figure out how to solve the 'wider effect' and avoid create new BufferedImage object every re-paint...

OK, as a reault of my research, I tried to write some kind of my rotate code adaptation. Here it is :

>tested

public static void rotateImage(Graphics g, Image image,int tilt,JComponent component)
    {

        // create the transform, note that the transformations happen

                  // in reversed order (so check them backwards)
                  AffineTransform at = new AffineTransform();

                  //5. modify component scale ...

                  double sin = Math.abs(Math.sin(Math.toRadians(tilt)));
                  double cos = Math.abs(Math.cos(Math.toRadians(tilt)));

                  int w=image.getWidth(null);
                  int h=image.getHeight(null);
                  int newW=(int) Math.floor(w * cos + h * sin);
                  int newH=(int) Math.floor(h * cos + w * sin);

                  component.setSize(newW, newH);

                  int width=component.getWidth();
                  int height=component.getHeight();

                  // 4. translate it to the center of the component
                  at.translate(width / 2, height / 2);

                  // 3. do the actual rotation
                  at.rotate(Math.toRadians(tilt));

                  // 2. just a scale because this image is big
    //              at.scale(1, 1);


                  // 1. translate the object so that you rotate it around the
                  //    center (easier :))
                  at.translate(-image.getWidth(null)/2, -image.getHeight(null)/2);



                  // draw the image
                  ((Graphics2D) g).drawImage(image, at, null);


        }

... so the result image rotated on -30 degrees tilt looks like this

I dearly hope the tip saves ones day :)


Thank you all for help