在重写的paintComponent(...)方法中旋转图像

3

我想知道如何使用JLabel组件的paintComponent()方法旋转矩形图像并正确设置其新的宽度和高度?

我尝试过旋转(请参见附图),但图像的比例变大,而JLabel的比例保持不变,这使得图像超出了JLabel的边界或其他什么问题:S。所以我的问题是如何以更优化的方式动态地设置图像的新宽度和高度到组件中?

enter image description here


1
为了更快地获得帮助,请发布您当前尝试的SSCCE - Andrew Thompson
2
请查看转换形状、文本和图像 - nIcE cOw
1
通常我要花很久才能找到这个答案,不过您可以看一下这个回答 - MadProgrammer
@MadProgrammer +1 绝对是一个惊人的发现。 - David Kroukamp
3个回答

4

对MadProgrammers的评论和链接点赞。

使用链接中的方法(稍作修改,省略了对GraphicsConfigurationdrawRenderImage(..)的使用,并更改了变量名):

//https://dev59.com/SVHTa4cB1Zd3GeqPU9AL
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;
}

这是一个例子:

在此输入图片描述

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://dev59.com/SVHTa4cB1Zd3GeqPU9AL
    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;
    }
}

参考资料:


这是一个有趣的例子,谢谢。但我不想每次都使用"BufferedImage rotatedBI = new BufferedImage(...);",有没有避免在每次重绘时创建新对象的方法? - user592704
@user592704 我的例子并不是每次重绘都使用它,你应该转换一次并使用那个转换后的图像。如果你必须在每次旋转时进行转换,那么你别无选择,但在我看来,用另一种方式做这件事并不能节省更多的 CPU/GPU 时间。相反,最好学会使用 repaint(int x,int y,int w,int h) 来优化绘图过程,可以参考这里:Refining the Design - David Kroukamp
我能看到,但是用文本字段或其他东西来设置一次倾斜并不十分有趣。如果使用JSlider实时可视化旋转过程呢?实际上重写的paintComponent真的很有用:S。你展示的片段真的很好,稍后我会尝试一下,但是我仍然想避免在每次重新绘制时创建新对象,但是现在我不太确定如何以更优化的方式做到这一点 :) - user592704
我尝试了这段代码,看起来没问题,但为什么结果组件的宽度比图像宽度更宽呢?我的意思是图像位于左上角而不是组件中心? :S - user592704
+1 @David Kroukamp 谢谢 :) 这确实是一个很好的例子,但它并没有完全解决我正在寻找的问题。 - user592704

3

好的,所以您希望JLabel保持其尺寸并调整图像大小。

您可能已经在使用仿射变换,因此可以使用一些三角函数和min / max来查找旋转图像的AABB。适当缩放(我假设您已经使用仿射变换进行旋转)

编辑:

AABB = 轴对齐边界框,其边缘对应于旋转图像的最小/最大x,y坐标。只是更简洁地说事情。


不,我想找到一些标准的方法来解决这个问题,而不是重新发明轮子:S 因为如果你旋转一个矩形,旋转会改变图像的比例,导致图像超出组件边界。所以我想知道是否有一些标准的方法来避免这种“越界”效应? - user592704
让调整大小的标签不断改变其他组件的布局会很烦人,但使用FlowLayout + JScrollPane可以解决这个问题 :) - user592704
'AABB'? 可以给更多细节吗?我看到了 at.scale(...,...) 方法的使用,但它改变了图像的比例本身,我不确定它是否有助于重新缩放组件? :( - user592704

0

好的,我尝试了David Kroukamp给我的示例。谢谢你,David,你指引了我正确的方向。我认为这是一个非常好的片段可以借鉴。

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

例如,这里是我旋转45度角的图像版本。

snippet based result image

...从蓝色背景中可以看出,它的宽度并不完全适合周围的白色图像...实际上,我正在寻找某种标准的Java图像处理旋转解决方案,但我找不到 :(所以我必须深入挖掘问题,至少找出如何解决'更宽的效果'并避免每次重绘都创建新的BufferedImage对象...

好吧,经过我的研究,我尝试编写一些自己的旋转代码。在这里:

>已测试

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);


        }

...所以旋转-30度倾斜的结果图像看起来像这样

enter image description here

我非常希望这个技巧能够帮助你解决问题 :)


感谢大家的帮助


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接