你如何使用重新调整所有 Graphic2D

2022-01-24 00:00:00 render image-resizing java swing jframe

在 java 中,如何让游戏完全可实现!但是逻辑和图形可以使用它吗?我尝试过使用 SCALE 方法.但这并不能让每台计算机都能完美地全屏显示.所以我做了这个:

 public void resize(int WIDTH, int HEIGHT, boolean UNDECORATED) {frame.setPreferredSize(新维度(WIDTH, HEIGHT));frame.setMaximumSize(新维度(WIDTH, HEIGHT));frame.setMinimumSize(新维度(WIDTH, HEIGHT));这个.WIDTH = 宽度;this.HEIGHT = 高度;frame.setUndecorated(UNDECORATED);frame.setSize(宽度,高度);}

因此,您可以将屏幕尺寸设置为您想要的任何尺寸!它可以工作,但图形无法使用它?Graphics2D 中是否有办法拉伸所有图形以使其适合?例如,如果存在这样的方法:

 G2D.resize(WIDTH, HEIGHT, Image.NEAREST_PARENT_RESCALE);

有什么想法吗?

我尝试过的事情:

  • 将所有图形绘制到一个缓冲图像上,然后将该图像绘制到屏幕尺寸上.
  • 只需使用 SCALE 并执行 WIDTH * SCALE 等.
  • 大量数学

我不介意的事情

  • 如果您有 WIDE-SCREEN,它会将图形 2D 对象拉伸到相应大小.
  • 如果您有一个 SQUARE-SCREEN,它会将 graphics2D 对象压缩到相应大小.

那么,我怎样才能使用 Graphics2D、JFrame 制作一个完全可重新封装的游戏.

解决方案

用最通用的形式,可以认为这是一个经典的图形编程问题,即从世界坐标的变换屏幕坐标.您的世界坐标系中有一个大小为1.0 x 1.0"的对象(无论它具有哪个 unit).并且这个对象应该被绘制,使其在屏幕上具有例如600 像素 * 600 像素"的大小.

一般来说,在 Swing 中至少有三个选项可以实现这一点:

  • 您可以绘制到图像中,然后绘制图像的缩放版本
  • 您可以绘制成缩放的 Graphics2D 对象
  • 您可以绘制缩放的对象

每一个都有可能的优点和缺点,以及隐藏的警告.

绘制成图像,并绘制图像的缩放版本:

这可能看起来像一个简单的解决方案,但有一个潜在的缺点:图像本身具有一定的分辨率(大小).如果图像太小,并且您将其放大以填满屏幕,它可能会出现块状.如果图像太大,并且您将其缩小以适应屏幕,则图像的像素可能会丢失.

在这两种情况下,缩放图像的过程都有几个调整参数.事实上,缩放图像比乍一看要复杂得多.具体可参考文章:

导入java.awt.Color;导入 java.awt.Dimension;导入 java.awt.Graphics;导入 java.awt.Graphics2D;导入 java.awt.GridLayout;导入 java.awt.RenderingHints;导入 java.awt.Shape;导入 java.awt.geom.AffineTransform;导入 java.awt.geom.Ellipse2D;导入 java.awt.geom.Line2D;导入 java.awt.geom.Rectangle2D;导入 java.awt.image.BufferedImage;导入 java.util.ArrayList;导入 java.util.List;导入 javax.swing.BorderFactory;导入 javax.swing.JFrame;导入 javax.swing.JPanel;导入 javax.swing.SwingUtilities;公共类 ScalingMethodComparison{公共静态无效主要(字符串 [] 参数){SwingUtilities.invokeLater(new Runnable(){@覆盖公共无效运行(){createAndShowGUI();}});}私有静态无效 createAndShowGUI(){JFrame f = 新的 JFrame();f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);f.getContentPane().setLayout(new GridLayout(0,1));尺寸更大 = 新尺寸(190,190);尺寸更小=新尺寸(60,60);f.getContentPane().add(createPanel(larger, false));f.getContentPane().add(createPanel(larger, true));f.getContentPane().add(createPanel(smaller, false));f.getContentPane().add(createPanel(smaller, true));f.pack();f.setLocationRelativeTo(null);f.setVisible(true);}私有静态 JPanel createPanel(Dimension d, boolean highQuality){JPanel p = new JPanel(new GridLayout(1,3));对于 (ScalingMethodComparisonPanel.ScalingMethod scalingMethod :ScalingMethodComparisonPanel.ScalingMethod.values()){p.add(createPanel(d, scalingMethod, highQuality));}返回 p;}私有静态JPanel createPanel(维度 d,ScalingMethodComparisonPanel.ScalingMethod scalingMethod,布尔高品质){JPanel p = new JPanel(new GridLayout(1,1));p.setBorder(BorderFactory.createTitledBorder(scalingMethod.toString()+(highQuality?" (HQ)":"")));JPanel scalingMethodComparisonPanel =新的 ScalingMethodComparisonPanel(createObjects(), d, scalingMethod, highQuality);p.add(scalingMethodComparisonPanel);返回 p;}//返回应该绘制的对象列表,//在世界坐标中占据一个 100x100 的矩形私有静态列表<形状>创建对象(){列出<形状>对象 = 新的 ArrayList<形状>();objects.add(new Ellipse2D.Double(10,10,80,80));objects.add(new Rectangle2D.Double(20,20,60,60));objects.add(new Line2D.Double(30,30,70,70));返回对象;}}类 ScalingMethodComparisonPanel 扩展 JPanel{私有静态最终颜色颜色 [] = {颜色.红色,颜色.绿色,颜色.蓝色,};枚举缩放方法{缩放图像,SCALING_GRAPHICS,缩放形状,}私有最终列表<形状>物体;private final ScalingMethod scalingMethod;私人最终布尔高质量;私有最终维度 originalSize = new Dimension(100,100);私有最终维度 scaledSize;私有 BufferedImage 图像;公共 ScalingMethodComparisonPanel(列出<形状>物体,尺寸缩放尺寸,缩放方法缩放方法,布尔高品质){this.objects = 对象;this.scaledSize = new Dimension(scaledSize);this.scalingMethod = scalingMethod;this.highQuality = 高品质;}@覆盖公共维度 getPreferredSize(){返回新维度(scaledSize);}@覆盖受保护的无效paintComponent(图形gr){super.paintComponent(gr);Graphics2D g = (Graphics2D)gr;g.setColor(Color.WHITE);g.fillRect(0,0,getWidth(), getHeight());如果(高质量){g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);g.setRenderingHint(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);}if (scalingMethod == ScalingMethod.SCALING_IMAGE){paintByScalingImage(g);}else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS){paintByScalingGraphics(g);}else if (scalingMethod == ScalingMethod.SCALING_SHAPES){PaintByScalingShapes(g);}}私人无效paintByScalingImage(Graphics2D g){如果(图像 == 空){图像 = 新的缓冲图像(originalSize.width, originalSize.height,BufferedImage.TYPE_INT_ARGB);}Graphics2D ig = image.createGraphics();油漆对象(i​​g,空);ig.dispose();g.drawImage(图像, 0, 0, scaledSize.width, scaledSize.height, null);}私人无效paintByScalingGraphics(Graphics2D g){仿射变换 oldAT = g.getTransform();双 scaleX = (double)scaledSize.width/originalSize.width;双 scaleY = (double)scaledSize.height/originalSize.height;g.scale(scaleX, scaleY);油漆对象(g,空);g.setTransform(旧AT);}私人无效paintByScalingShapes(Graphics2D g){双 scaleX = (double)scaledSize.width/originalSize.width;双 scaleY = (double)scaledSize.height/originalSize.height;仿射变换在 =AffineTransform.getScaleInstance(scaleX, scaleY);油漆对象(g,在);}私人无效paintObjects(Graphics2D g,AffineTransform at){for (int i=0; i

In java how can you make a game fully realizable! But so logic and graphics can work with it? I have tried using SCALE methods. But this doesn't allow perfect full-screen for every computer. So I made this:

    public void resize(int WIDTH, int HEIGHT, boolean UNDECORATED) {

          frame.setPreferredSize(new Dimension(WIDTH, HEIGHT));
          frame.setMaximumSize(new Dimension(WIDTH, HEIGHT));
          frame.setMinimumSize(new Dimension(WIDTH, HEIGHT));

          this.WIDTH = WIDTH;
          this.HEIGHT = HEIGHT;
          frame.setUndecorated(UNDECORATED);
          frame.setSize(WIDTH, HEIGHT);
      }

So you can set your screen size to whatever you want! It works but the graphics will not work with it? Is there a way in Graphics2D to stretch all the graphics so it fits? For example if there was a method that existed like:

            G2D.resize(WIDTH, HEIGHT, Image.NEAREST_PARENT_RESCALE);

Any idea?

Things I have tried:

  • Drawing all graphics to a Buffered-image then drawing that Image onto the screen size.
  • Just using SCALE and doing WIDTH * SCALE etc.
  • Lots of math

Things I do not mind

  • If you have a WIDE-SCREEN it stretches graphic2D objects to the size.
  • If you have a SQUARE-SCREEN it squishes graphics2D objects to the size.

So how can I make a perfectly resealable game using Graphics2D, JFrame.

解决方案

In the most generic form, one can consider this as a classical problem of graphics programming, namely, as the transformation from world coordinates to screen coordinates. You have an object that has a size of "1.0 x 1.0" in your world coordinate system (regardless of which unit this has). And this object should be painted so that it has a size of, for example, "600 pixels * 600 pixels" on the screen.

Broadly speaking, there are at least three options to achieve this in Swing:

  • You can draw into an image, and then draw a scaled version of the image
  • You can draw into a scaled Graphics2D object
  • You can draw scaled objects

Each of this has possible advantages and disadvantages, and hidden caveats.

Drawing into an image, and drawing a scaled version of the image:

This might look like a simple solution, but has a potential drawback: The image itself has a certain resolution (size). If the image is too small, and you are scaling it up to fill the screen, it may appear blocky. If the image is too large, and you are scaling it down to fit into the screen, pixels of the image may be lost.

In both cases, there are several tuning parameters for the process of scaling the image. In fact, scaling an image is far more tricky than it looks at the first glance. For details, one may refer to the article The Perils of Image.getScaledInstance() by Chris Campbell.

Drawing into a scaled Graphics2D object

The Graphics2D class already offers the full functionality that is necessary to create the transformation between the world coordinate system and the screen coordinate system. This is accomplished by the Graphics2D class by internally storing an AffineTransform, which describes this transformation. This AffineTransform may be modified directly via the Graphics2D object:

void paintSomething(Graphics2D g) {
    ...
    g.draw(someShape);

    // Everything that is painted after this line will
    // be painted 3 times as large: 
    g.scale(3.0, 3.0);

    g.draw(someShape); // Will be drawn larger 
}

Some care has to be taken to properly manage the transform that is stored in the Graphics2D object. In general, one should create a backup of the original AffineTransform before applying additional transformations, and restore this original transform afterwards:

// Create a backup of the original transform
AffineTransform oldAT = g.getTransform();

// Apply some transformations
g.scale(3.0, 4.0);
g.translate(10.0, 20.0);

// Do custom painting the the transformed graphics
paintSomething(g):

// Restore the original transformation
g.setTransform(oldAT);

(Another advice for the last method: The Graphics2D#setTransform method should never be used to apply a new coordinate transform on top of an existing transform. It is solely intended for restoring an "old" transform, as shown in this example (and in the documentation of this method)).

One potential drawback of scaling with the Graphics2D class is that afterwards, everything will be scaled. Particularly, this scaling will also affect line widths (that is, the width of the Stroke). For example, consider a sequence of calls like this one:

// By default, this will paint a line with a width (stroke) of 1.0:
g.draw(someLine);

// Apply some scaling...
g.scale(10.0, 10.0);

// Now, this will paint the same line, but with a width of 10. 
g.draw(someLine);

The second call will cause a line to be drawn that is 10 pixels wide. This may not be desired in many cases. This effect can be avoided with the third alternative:

Drawing scaled objects

The transformation between the world coordinate system and the screen coordinate system can also be maintained manually. It is convenient to represent this as an AffineTransform. The AffineTransform class can be used to create transformed versions of Shape object, that can then be drawn directly into an (un-transformed) Graphics2D object. This is accomplished with the AffineTransform#createTransformedShape method:

void paintSomething(Graphics2D g) {
    ...
    // Draw some shape in its normal size
    g.draw(someShape);

    // Create a scaling transform
    AffineTransform at = AffineTransform.getScaleInstance(3.0, 3.0);

    // Create a scaled version of the shape
    Shape transformedShape = at.createTransformedShape(someShape);

    // Draw the scaled shape
    g.draw(transformedShape);
}

This is probably the most versatile approach. The only potential drawback is that, when many small, simple shapes are drawn, this will cause many, small temporary transformed shapes to be created, which may cause reduced performance. (There are ways to alleviate this problem, but detailed performance considerations and optimizations are beyond the scope of this answer).


Summary

The follwing image shows the comparison of all approaches. Some example objects (represented as Shape objects) are drawn. Each row compares the three different scaling methods mentioned above. With their "default" size, the objects fill a rectangle in world coordinates that has a size of 100x100. In the first two rows, they are scaled up to fill an area on the screen of 190x190 pixels. In the last two rows, they are scaled down to fill an area on the screen of 60x60 pixels. (These sizes have been chosen in order to have some "odd" scaling factors of 1.9 and 0.6. Certain effects (artifacts) may not appear when the scaling factors are whole numbers, or exactly 0.5, for example).

For the upscaling and the downscaling, there additionally is a comparison between the "standard" way of painting, and "high quality" painting (indicated by the "(HQ)" in the title of each panel). The "high quality" here simply means that the rendering hints

KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY

have been set:

Here is the corresponding program, as an MCVE:

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ScalingMethodComparison
{
    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            @Override
            public void run()
            {
                createAndShowGUI();
            }
        });
    }
    private static void createAndShowGUI()
    {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().setLayout(new GridLayout(0,1));

        Dimension larger = new Dimension(190,190);
        Dimension smaller = new Dimension(60,60);

        f.getContentPane().add(createPanel(larger, false));
        f.getContentPane().add(createPanel(larger, true));
        f.getContentPane().add(createPanel(smaller, false));
        f.getContentPane().add(createPanel(smaller, true));

        f.pack();
        f.setLocationRelativeTo(null);
        f.setVisible(true);
    }

    private static JPanel createPanel(Dimension d, boolean highQuality)
    {
        JPanel p = new JPanel(new GridLayout(1,3));
        for (ScalingMethodComparisonPanel.ScalingMethod scalingMethod : 
            ScalingMethodComparisonPanel.ScalingMethod.values())
        {
            p.add(createPanel(d, scalingMethod, highQuality));
        }
        return p;
    }

    private static JPanel createPanel(
        Dimension d, ScalingMethodComparisonPanel.ScalingMethod scalingMethod, 
        boolean highQuality)
    {
        JPanel p = new JPanel(new GridLayout(1,1));
        p.setBorder(BorderFactory.createTitledBorder(
            scalingMethod.toString()+(highQuality?" (HQ)":"")));
        JPanel scalingMethodComparisonPanel = 
            new ScalingMethodComparisonPanel(
                createObjects(), d, scalingMethod, highQuality);
        p.add(scalingMethodComparisonPanel);
        return p;
    }

    // Returns a list of objects that should be drawn, 
    // occupying a rectangle of 100x100 in WORLD COORDINATES
    private static List<Shape> createObjects()
    {
        List<Shape> objects = new ArrayList<Shape>();
        objects.add(new Ellipse2D.Double(10,10,80,80));
        objects.add(new Rectangle2D.Double(20,20,60,60));
        objects.add(new Line2D.Double(30,30,70,70));
        return objects;
    }
}


class ScalingMethodComparisonPanel extends JPanel
{
    private static final Color COLORS[] = {
        Color.RED, Color.GREEN, Color.BLUE,
    };

    enum ScalingMethod
    {
        SCALING_IMAGE,
        SCALING_GRAPHICS,
        SCALING_SHAPES,
    }

    private final List<Shape> objects;
    private final ScalingMethod scalingMethod;
    private final boolean highQuality;

    private final Dimension originalSize = new Dimension(100,100);
    private final Dimension scaledSize;

    private BufferedImage image;

    public ScalingMethodComparisonPanel(
        List<Shape> objects,
        Dimension scaledSize,
        ScalingMethod scalingMethod,
        boolean highQuality)
    {
        this.objects = objects;
        this.scaledSize = new Dimension(scaledSize);
        this.scalingMethod = scalingMethod;
        this.highQuality = highQuality;
    }

    @Override
    public Dimension getPreferredSize()
    {
        return new Dimension(scaledSize);
    }

    @Override
    protected void paintComponent(Graphics gr)
    {
        super.paintComponent(gr);
        Graphics2D g = (Graphics2D)gr;
        g.setColor(Color.WHITE);
        g.fillRect(0,0,getWidth(), getHeight());

        if (highQuality)
        {
            g.setRenderingHint( 
                RenderingHints.KEY_ANTIALIASING, 
                RenderingHints.VALUE_ANTIALIAS_ON);
            g.setRenderingHint(
                RenderingHints.KEY_RENDERING, 
                RenderingHints.VALUE_RENDER_QUALITY);
        }

        if (scalingMethod == ScalingMethod.SCALING_IMAGE)
        {
            paintByScalingImage(g);
        }
        else if (scalingMethod == ScalingMethod.SCALING_GRAPHICS)
        {
            paintByScalingGraphics(g);
        }
        else if (scalingMethod == ScalingMethod.SCALING_SHAPES)
        {
            paintByScalingShapes(g);
        }
    }

    private void paintByScalingImage(Graphics2D g)
    {
        if (image == null)
        {
            image = new BufferedImage(
                originalSize.width, originalSize.height,
                BufferedImage.TYPE_INT_ARGB);
        }
        Graphics2D ig = image.createGraphics();
        paintObjects(ig, null);
        ig.dispose();

        g.drawImage(image, 0, 0, scaledSize.width, scaledSize.height, null);
    }

    private void paintByScalingGraphics(Graphics2D g)
    {
        AffineTransform oldAT = g.getTransform();
        double scaleX = (double)scaledSize.width / originalSize.width;
        double scaleY = (double)scaledSize.height / originalSize.height;
        g.scale(scaleX, scaleY);
        paintObjects(g, null);
        g.setTransform(oldAT);
    }

    private void paintByScalingShapes(Graphics2D g)
    {
        double scaleX = (double)scaledSize.width / originalSize.width;
        double scaleY = (double)scaledSize.height / originalSize.height;
        AffineTransform at = 
            AffineTransform.getScaleInstance(scaleX, scaleY);
        paintObjects(g, at);
    }



    private void paintObjects(Graphics2D g, AffineTransform at)
    {
        for (int i=0; i<objects.size(); i++)
        {
            Shape shape = objects.get(i);
            g.setColor(COLORS[i%COLORS.length]);
            if (at == null)
            {
                g.draw(shape);
            }
            else
            {
                g.draw(at.createTransformedShape(shape));
            }
        }
    }
}

相关文章