我正在编写一个扑克游戏,目前正在尝试编写一个生成给定手牌图像的类。我最初只是通过将 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;
}
编辑
只是为了让事情更清楚,这里是循环仅首先执行(仅绘制第一张卡片)并且背景着色以显示整个图像的大小的结果。
最佳答案
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 . (这意味着它不使用
HandOfCards
和 PlayingCards
类。相反,它对 BufferedImage
对象列表进行操作。这些图像实际上是在运行时从维基百科下载的)。这个例子的核心是
RotatedPlayingCardsPainter
.它允许您将(旋转的)卡片绘制成 Graphics2D
.当您尝试时,这种方法的一个优势可能会变得很明显:您可以使用 slider 来动态更改卡片之间的角度。 (在这里为您的游戏想象一些花哨的动画......)(但如果您愿意,它还包含一个
createImage
方法,允许您创建图像,如问题中最初所做的那样)当您阅读代码时,您会看到
AffineTransform
每张卡的实例都在 createTransform
中创建。方法。在那里,我添加了一些任意的、神奇的因素来稍微改变卡片彼此的位置,并让它们看起来更像“粉丝”。比较这张图片,没有 神奇的因素
给一个与 神奇的因素:
我认为后者看起来更“现实”,但这可能是一个品味问题。
另一个注意事项:直接绘制图像的一个缺点(与
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/