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

科布

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

在此处输入图片说明

然后我决定最好将牌叠放在一起并散开来展示手牌,就像一个人拿着一手牌一样。

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

在此处输入图片说明

如您所见,最后三张牌看起来应该是这样,但前三张牌在第三张牌的左侧被切断。

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

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

编辑

Just to make things clearer, here is the result when the loop is only executed first (only the first card is drawn) and the background is coloured to show the size of the entire image.

在此处输入图片说明

Marco13

The reason for the behavior that you are observing is stated in the 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.

And when you print the bounds of each card with a statement like

System.out.println("Bounds: "+transformOp.getBounds2D(cardImage));

you will see that the bounds are negative (as one might expect when rotating the cards to the left).

This can be avoided by adjusting the AffineTransform to always result in positive bounds, and calling the filter method with a non-null destination image (in your case: with the image that will contain the hand - i.e. all card images).


(This ^ was the actual answer to the question. The remaining part may be ignored, or considered as evidence that I have too much free time)


That being said, I'd like to suggest a different solution, because there are some issues with the current approach, on different levels.


On the highest level: Why do you want to create this image at all? I guess you're implementing a card-playing game. And during such a game, you will have possibly hundreds of different "hands". Why do you want to create a new image for each hand?

Instead of creating an image for each hand, you can simply draw the rotated images directly. Roughly speaking: Instead of painting the images into the Graphics of a new image, you can simply draw them into the Graphics of your JPanel where you are acutally painting the hands.

But considering that the difference is only the Graphics object that you are painting into, this is something that can easily be changed later (if implemented accordingly), and maybe there is an actual reason for you to create these images.


On the lowest level: the function degreesToRadian should entirely be replaced with Math.toRadians.


Below is an example, implemented as a MCVE. (This means that it does not use the HandOfCards and PlayingCards classes. Instead, it operates on a list of BufferedImage objects. These images are actually downloaded from wikipedia, at runtime).

The core of this example is the RotatedPlayingCardsPainter. It allows you to paint the (rotated) cards into a Graphics2D. One advantage of this approach may become obvious when you try it out: You can use the slider to dynamically change the angle between the cards. (Imagine some sort of fancy animation for your game here...)

(But if you wish, it also contains a createImage method that allows you to create an image, as originally done in the question)

When you read the code, you will see that the AffineTransform instance for each card is created in the createTransform method. There, I added some arbitrary, magic factor to slighly shift the cards against each other, and give them a more "fan-like" appearance.

Compare this image, without the magic factor

旋转卡没有魔法

具有魔法因子的那个:

旋转的纸牌

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


另一个注意事项:直接绘制图像(与该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;
    }

}

本文收集自互联网,转载请注明来源。

如有侵权,请联系[email protected] 删除。

编辑于
0

我来说两句

0条评论
登录后参与评论

相关文章

来自分类Dev

将图像分组为一张图像

来自分类Dev

将多张图像与一张图像进行比较

来自分类Dev

超时后如何更改下一张图像

来自分类Dev

幻灯片在上一张图像后停止

来自分类Dev

WPF-将许多图像一张一张地加载到ObservableCollection中

来自分类Dev

一张一张地将图像上传到服务器-Android

来自分类Dev

DIV中有多个背景图像时,如何只旋转一张图像?

来自分类Dev

滚动旋转图像只使用javascript处理一张图像

来自分类Dev

图像的垂直面板,要在上一张图像后重新启动图像

来自分类Dev

如何将多张图像合并为一张?

来自分类Dev

自动将一张png图像放入花药中

来自分类Dev

wxpython一张一张显示gif图像

来自分类Dev

一张一张地打印图像

来自分类Dev

将四张图像合并为一张图像以供下载

来自分类Dev

再次单击后,单击的图像不会回到上一张图像

来自分类Dev

一张一张一张地滑动图像

来自分类Dev

单击另一张图像时如何隐藏另一张图像顶部的图像

来自分类Dev

在一张图像中叠加图片

来自分类Dev

多次更改一张图像的边框

来自分类Dev

如何将几张图像作为一张图像快速保存到照片库中?

来自分类Dev

如何影响图像悬停时的上一张和下一张图像

来自分类Dev

从图库中获取下一张图像和上一张图像

来自分类Dev

如何影响图像悬停时的上一张和下一张图像

来自分类Dev

缩放一张图像会影响另一张图像

来自分类Dev

在iPhone中将多张图像合并为一张图像

来自分类Dev

如何隐藏多张图像并仅显示一张图像?

来自分类Dev

在图像动画的末尾显示最后一张图像

来自分类Dev

选择第一张图像,而不是随机图像

来自分类Dev

在dockerfile中完成打包后如何删除第一张图像?

Related 相关文章

热门标签

归档