3

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?

enter image description here

user592704
  • 3,674
  • 11
  • 70
  • 107

3 Answers3

4

+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:

enter image description here

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:

Community
  • 1
  • 1
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138
  • It is interesting example thank you, but I was about not to use BufferedImage rotatedBI = new BufferedImage(...); every time is there a way to avoid new object creation on each repaint? – user592704 Jan 14 '13 at 16:41
  • @user592704 my example does not use it every repaint you should transform it once and use that transformed image if you must transform on each rotate than you have no choice but to do that well IMO its not going to save you much more CPU/GPU time to do it another way. Rather learn to use `repaint(int x,int y,int w,int h)` to refine the drawing process as seen here: [Refining the Design](http://docs.oracle.com/javase/tutorial/uiswing/painting/refining.html) – David Kroukamp Jan 14 '13 at 16:59
  • I can see that but it is not quite interesting to set tilt once with a text field or something what if to use JSlider to visualize rotate process it in real time for example? Actually the overriden paintComponent is really used for :S The snippet you show is really good I'll try it a little bit later but still I'd like to avoid creating new object each repaint, anyway, but for now I am not pretty sure how to do that in a more optimal way :) – user592704 Jan 14 '13 at 19:47
  • I tried the code and it seems fine but why the result component width is wider than the image width? I mean the image is located in left corner instead of component center? :S – user592704 Jan 15 '13 at 16:34
  • +1 @David Kroukamp Thank you :) It is really a good example but it is not exactly solves the thing I was looking for – user592704 Jan 16 '13 at 02:05
3

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.

user1354999
  • 163
  • 6
  • No, I want to find some standard way to do so not to re-make a bike or something :S There must be something helpful to rotate because if you rotate a rectangle the rotation, of course, will change image scale and the image comes out a component bounds. So I thought is there some standard way to avoid that 'out of bounds' effect? – user592704 Jan 13 '13 at 04:56
  • 'Having the resizing label constantly changing the layout of other components would be annoying' oh its fine if to use FlowLayout + JScrollPane as for this point :) – user592704 Jan 13 '13 at 05:01
  • 'AABB'? Could you give more details? I saw the usage of at.scale(...,...) method but it changed image scale itself I am not sure will it help the component to be re-scaled ? :( – user592704 Jan 13 '13 at 05:07
0

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

snippet based result image

... 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

enter image description here

I dearly hope the tip saves ones day :)


Thank you all for help

Community
  • 1
  • 1
user592704
  • 3,674
  • 11
  • 70
  • 107