java - 将旋转的图像堆叠成一张组合图像

标签 java image graphics bufferedimage affinetransform

我正在编写一个扑克游戏,目前正在尝试编写一个生成给定手牌图像的类。我最初只是通过将 5 张卡片中的每一张的图像并排组合来制作一张图像。结果是这样的:

enter image description here

然后,我决定将手牌堆叠在一起并呈扇形展开会更好,因为一个人会拿着一手牌。

这是迄今为止我能做到的最好的:

enter image description here

正如你所看到的,最后三张牌看起来应该是这样,但前三张牌在第三张牌的左侧被切掉了。

这是我现在的代码(它不是最干净的,因为我一直试图让它工作,不管它需要什么)

private static final int CARD_WIDTH = 500;
private static final int CARD_HEIGHT = 726;
private static final double ROTATION = 20.0;

public void createImage(HandOfCards hand) throws IOException {
    int handImageWidth = (int) (2 * (Math.sin(degreesToRadian(ROTATION)) * CARD_HEIGHT + Math.cos(degreesToRadian(ROTATION)) * CARD_WIDTH)- CARD_WIDTH);
    int handImageHeight = (int) (CARD_HEIGHT + Math.sin(degreesToRadian(ROTATION)) * CARD_WIDTH); 

    BufferedImage handImage = new BufferedImage(handImageWidth, handImageHeight, BufferedImage.TYPE_INT_ARGB);
    Graphics2D graphics = (Graphics2D) handImage.getGraphics();

    int xPos = handImageWidth / 2 - CARD_WIDTH / 2;
    int yPos = 0;
    int xAnchor = CARD_WIDTH; 
    int yAnchor = CARD_HEIGHT;

    double rotation = -ROTATION;        
    for (int i = 0; i < HandOfCards.HAND_SIZE; i++) {
        if (i == 3) xAnchor = 0;

        PlayingCard card = hand.getCard(i);
        BufferedImage cardImage = ImageIO.read(new File("cardImages/" + card + ".png"));

        AffineTransform transform = new AffineTransform();
        transform.rotate(degreesToRadian(rotation), xAnchor, yAnchor);
        AffineTransformOp transformOp = new AffineTransformOp(transform, AffineTransformOp.TYPE_BILINEAR);
        cardImage = transformOp.filter(cardImage, null);

        graphics.drawImage(cardImage, xPos, yPos, null);
        rotation += ROTATION / 2;
    }

private double degreesToRadian(double degrees) {
    return (degrees * Math.PI) / 180.0;
}

编辑

只是为了让事情更清楚,这里是循环仅首先执行(仅绘制第一张卡片)并且背景着色以显示整个图像的大小的结果。

enter image description here

最佳答案

documentation of AffineTransformOp#filter 中说明了您观察到的行为的原因。 :

The coordinates of the rectangle returned by getBounds2D(BufferedImage) are not necessarily the same as the coordinates of the BufferedImage returned by this method. If the upper-left corner coordinates of the rectangle are negative then this part of the rectangle is not drawn.



当您使用以下语句打印每张卡片的边界时
System.out.println("Bounds: "+transformOp.getBounds2D(cardImage));

你会看到边界是负数(就像人们在向左旋转卡片时所期望的那样)。

这可以通过调整 AffineTransform 来避免。总是导致 界限,并调用 filter使用非 null 的方法目标图像(在您的情况下:包含手的图像 - 即所有卡片图像)。

(这个^是问题的实际答案。剩下的部分可能会被忽略,或者被认为是我有太多空闲时间的证据)

话虽如此,我想提出一个不同的解决方案,因为当前的方法在不同的层面上存在一些问题。

最高层:为什么你想创建这个图像完全 ?我猜你正在实现一个纸牌游戏。而在这样的游戏中,你可能会有数百个不同的“手”。为什么要为每只手创建一个新图像?

无需为每只手创建图像,您只需 直接旋转图像。粗略地说:而不是将图像绘制到 Graphics新图像,您可以简单地将它们绘制到 Graphics您的JPanel你在哪里画手。

但考虑到区别只是Graphics您正在绘制的对象,这可以在以后轻松更改(如果相应地实现),并且您可能有实际原因创建这些图像。

在最低级别:函数degreesToRadian应完全替换为 Math.toRadians .

下面是一个示例,实现为 MCVE . (这意味着它不使用 HandOfCardsPlayingCards 类。相反,它对 BufferedImage 对象列表进行操作。这些图像实际上是在运行时从维基百科下载的)。

这个例子的核心是 RotatedPlayingCardsPainter .它允许您将(旋转的)卡片绘制成 Graphics2D .当您尝试时,这种方法的一个优势可能会变得很明显:您可以使用 slider 来动态更改卡片之间的角度。 (在这里为您的游戏想象一些花哨的动画......)

(但如果您愿意,它还包含一个 createImage 方法,允许您创建图像,如问题中最初所做的那样)

当您阅读代码时,您会看到 AffineTransform每张卡的实例都在 createTransform 中创建。方法。在那里,我添加了一些任意的、神奇的因素来稍微改变卡片彼此的位置,并让它们看起来更像“粉丝”。

比较这张图片,没有 神奇的因素

Rotated cards no magic

给一个神奇的因素:

Rotated playing cards

我认为后者看起来更“现实”,但这可能是一个品味问题。

另一个注意事项:直接绘制图像的一个缺点(与 AffineTransformOp 方法相比)是图像的边界可能看起来参差不齐,无论过滤和抗锯齿设置如何。这是因为在图像的边界处没有什么可以插值的。在给定的程序中,这被 addBorder 规避了。方法,它为图像添加一个 1 像素的透明边框,以确保图像在旋转时看起来不错并且边框看起来平滑。

这是代码:
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.SwingUtilities;

public class RotatedPlayingCards
{
    public static void main(String[] args)
    {
        try
        {
            List<BufferedImage> images = loadTestImages();
            SwingUtilities.invokeLater(() -> createAndShowGui(images));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }

    }

    private static void createAndShowGui(List<BufferedImage> images)
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        RotatedPlayingCardsPanel cardsPanel = 
            new RotatedPlayingCardsPanel(images);

        JSlider angleDegSlider = new JSlider(0, 20, 10);
        angleDegSlider.addChangeListener(e -> {
            double rotationAngleRad = Math.toRadians(angleDegSlider.getValue());
            cardsPanel.setRotationAngleRad(rotationAngleRad);
        });
        JPanel controlPanel = new JPanel();
        controlPanel.add(angleDegSlider);
        f.getContentPane().add(controlPanel, BorderLayout.NORTH);

        f.getContentPane().add(cardsPanel, BorderLayout.CENTER);
        f.setSize(500,500);
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static List<BufferedImage> loadTestImages() throws IOException
    {
        String basePath = "https://upload.wikimedia.org/wikipedia/commons/thumb/";
        List<String> subPaths = Arrays.asList(
          "3/36/Playing_card_club_A.svg/480px-Playing_card_club_A.svg.png",
          "2/20/Playing_card_diamond_4.svg/480px-Playing_card_diamond_4.svg.png",
          "9/94/Playing_card_heart_7.svg/480px-Playing_card_heart_7.svg.png",
          "2/21/Playing_card_spade_8.svg/480px-Playing_card_spade_8.svg.png",
          "b/bd/Playing_card_spade_J.svg/480px-Playing_card_spade_J.svg.png",
          "0/0b/Playing_card_diamond_Q.svg/480px-Playing_card_diamond_Q.svg.png",
          "2/25/Playing_card_spade_A.svg/480px-Playing_card_spade_A.svg.png"
        );
        List<BufferedImage> result = new ArrayList<BufferedImage>();
        for (String subPath : subPaths)
        {
            String path = basePath + subPath;
            System.out.println("Loading "+path);
            BufferedImage image = ImageIO.read(new URL(path));

            image = scale(image, 0.3);
            image = addBorder(image);
            result.add(image);
        }
        return result;
    }

    // Scale the given image by the given factor
    private static BufferedImage scale(
        BufferedImage image, double factor) 
    {
        int w = (int)(image.getWidth() * factor);
        int h = (int)(image.getHeight() * factor);
        BufferedImage scaledImage = new BufferedImage(w, h, image.getType());
        Graphics2D g = scaledImage.createGraphics();
        g.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        g.drawImage(image, 0, 0, w, h, null);
        g.dispose();
        return scaledImage;
    }

    // Add a 1-pixel transparent border to the given image, to avoid
    // aliasing artifacts when the image is rotated
    private static BufferedImage addBorder(
        BufferedImage image) 
    {
        int w = image.getWidth();
        int h = image.getHeight();
        BufferedImage result = new BufferedImage(w + 2, h + 2, image.getType());
        Graphics2D g = result.createGraphics();
        g.setColor(new Color(0,0,0,0));
        g.fillRect(0, 0, w + 2, h + 2);
        g.drawImage(image, 1, 1, w, h, null);
        g.dispose();
        return result;
    }    

}

class RotatedPlayingCardsPanel extends JPanel
{
    private List<BufferedImage> images;
    private double rotationAngleRad;

    public RotatedPlayingCardsPanel(List<BufferedImage> images)
    {
        this.images = images;
        this.rotationAngleRad = Math.toRadians(10);
    }

    public void setRotationAngleRad(double rotationAngleRad)
    {
        this.rotationAngleRad = rotationAngleRad;
        repaint();
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);

        g.translate(200, 100);
        RotatedPlayingCardsPainter.drawImages(
            g, images, rotationAngleRad);
    }
}


class RotatedPlayingCardsPainter
{
    public static BufferedImage createImage(
        List<? extends BufferedImage> images, double rotationAngleRad)
    {
        Rectangle2D bounds = computeBounds(images, rotationAngleRad);
        BufferedImage image = new BufferedImage(
            (int)bounds.getWidth(), (int)bounds.getHeight(), 
            BufferedImage.TYPE_INT_ARGB);
        Graphics2D graphics = image.createGraphics();
        graphics.setRenderingHint(
            RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        graphics.setRenderingHint(
            RenderingHints.KEY_INTERPOLATION,
            RenderingHints.VALUE_INTERPOLATION_BILINEAR);
        graphics.translate(-bounds.getX(), -bounds.getY());
        drawImages(graphics, images, rotationAngleRad);
        graphics.dispose();
        return image;
    }

    public static Rectangle2D computeBounds(
        List<? extends BufferedImage> images, double rotationAngleRad)
    {
        Rectangle2D totalBounds = null;
        for (int i=0; i<images.size(); i++)
        {
            BufferedImage image = images.get(i);
            AffineTransform transform = createTransform(
                i, images.size(), image.getWidth(), image.getHeight(), 
                rotationAngleRad);
            Rectangle2D imageBounds = new Rectangle2D.Double(0.0, 0.0, 
                image.getWidth(), image.getHeight());
            Rectangle2D transformedBounds = 
                transform.createTransformedShape(imageBounds).getBounds();
            if (totalBounds == null)
            {
                totalBounds = transformedBounds;
            }
            else
            {
                Rectangle.union(transformedBounds, totalBounds, totalBounds);
            }
        }
        return totalBounds;
    }

    public static void drawImages(Graphics2D g, 
        List<? extends BufferedImage> images, double rotationAngleRad)
    {
        for (int i=0; i<images.size(); i++)
        {
            AffineTransform oldAt = g.getTransform();
            BufferedImage image = images.get(i);
            AffineTransform transform = createTransform(
                i, images.size(), image.getWidth(), image.getHeight(), 
                rotationAngleRad);
            g.transform(transform);
            g.drawImage(image, 0, 0, null);
            g.setTransform(oldAt);
        }
    }

    private static AffineTransform createTransform(
        int index, int total, double width, double height, 
        double rotationAngleRad)
    {
        double startAngleRad = (total - 1) * 0.5 * rotationAngleRad;
        double angleRad = index * rotationAngleRad - startAngleRad;
        AffineTransform transform = new AffineTransform();

        // A magic factor to shift the images slightly, to give 
        // them a more fan-like appearance. Just set it to 0.0
        // or remove it if you don't like it.
        double magicFactor = 0.2;

        double magicOffsetFactor = 
            (1.0 - index) * magicFactor * rotationAngleRad;
        double magicOffsetX = -width * magicOffsetFactor;
        double magicOffsetY = height * magicOffsetFactor;
        transform.translate(magicOffsetX, height + magicOffsetY);
        transform.rotate(angleRad);
        transform.translate(0, -height);
        return transform;
    }

}

关于java - 将旋转的图像堆叠成一张组合图像,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43689572/

相关文章:

java - 使用数组还是字符效率更高?

java - Spark-提交错误行 71 :/Library/Java/JavaVirtualMachines/jdk1. 8.0_192.jdk/Contents/Home/bin/java: Mac 中没有这样的文件或目录

python - 将 Numpy 数组保存为图像(说明)

JavaScript 错误 : Cannot read property 'width' of null

java - 在圆圈内绘制文本的交叉区域

java.net.URL 读取流到字节 []

java - 如何将带有空格的 double 字字符串更改为我可以使用的仅由 double 字组成的数组

html - 社交媒体图标——图像还是 CSS 背景图像?

C# Graphics.DrawString 不渲染特殊字符?

r - 使用 ggplot2 显示堆叠直方图中 bin 元素的总数