我正在编写一个扑克游戏,目前正在尝试编写一个生成给定手牌图像的类。我最初只是通过将 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.
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] 删除。
我来说两句