你如何使用调整大小所有Graphics2D

时间:2014-12-12 07:58:51

标签: java swing jframe render image-resizing

在java中如何让游戏完全实现!但是逻辑和图形可以用它吗?我尝试过使用SCALE方法。但这并不能让每台电脑完全全屏。所以我做到了:

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

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

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

有什么想法吗?

我尝试过的事情:

  • 将所有图形绘制到缓冲图像,然后将该图像绘制到屏幕尺寸上。
  • 只使用SCALE并进行WIDTH * SCALE等。
  • 很多数学

我不介意的事情

  • 如果你有一个WIDE-SCREEN,它会将graphic2D对象拉伸到这个大小。
  • 如果您有SQUARE-SCREEN,它会将graphics2D对象大小调整。

那么如何使用Graphics2D,JFrame制作完全可重复密封的游戏。

3 个答案:

答案 0 :(得分:12)

在最通用的形式中,人们可以将其视为图形编程的经典问题,即从世界坐标屏幕坐标的转换。您的世界坐标系中有一个大小为“1.0 x 1.0”的对象(无论具有哪个单位)。并且应该对该对象进行绘制,使其在屏幕上具有例如“600像素×600像素”的大小。

从广义上讲,在Swing中至少有三种方法可以实现这一目标:

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

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

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

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

在这两种情况下,缩放图像的过程都有几个调整参数。实际上,缩放图像远比第一眼看上去要复杂得多。有关详细信息,请参阅Chris Campbell撰写的文章The Perils of Image.getScaledInstance()

绘制为缩放的Graphics2D对象

Graphics2D class已经提供了在世界坐标系屏幕坐标系之间创建转换所需的全部功能。这是由Graphics2D类通过内部存储AffineTransform来完成的,它描述了这种转换。可以通过AffineTransform对象直接修改此Graphics2D

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 
}

必须注意妥善管理存储在Graphics2D对象中的转换。通常,应该在应用其他转换之前创建原始AffineTransform的备份,然后恢复此原始转换:

// 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);

(对最后一种方法的另一个建议:Graphics2D#setTransform方法应该永远用于在现有转换之上应用新的坐标转换。它仅用于恢复“旧的“转换,如本例所示(以及此方法的文档)”。

使用Graphics2D类进行缩放的一个潜在缺点是,之后所有内容将被缩放。特别是,此缩放将影响线宽(即Stroke的宽度)。例如,考虑一系列类似的调用:

// 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);

第二次调用将导致绘制一条宽10像素的线条。在许多情况下可能不需要这样做。第三种选择可以避免这种影响:

绘制缩放对象

世界坐标系屏幕坐标系之间的转换也可以手动维护。将其表示为AffineTransform很方便。 AffineTransform类可用于创建Shape对象的转换版本,然后可以直接将其绘制到( un - 转换的)Graphics2D对象中。这是通过AffineTransform#createTransformedShape方法完成的:

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

这可能是最通用的方法。唯一可能的缺点是,当许多小的简单形状被绘制时,这将导致创建许多小的临时变换形状,这可能导致性能降低。 (有一些方法可以缓解这个问题,但详细的性能考虑和优化超出了这个答案的范围)。


摘要

下图显示了所有方法的比较。绘制了一些示例对象(表示为Shape个对象)。每行比较上面提到的三种不同的缩放方法。使用“默认”大小,对象将填充大小为100x100的世界坐标中的矩形。在前两行中,它们按比例放大以填充屏幕上190x190像素的区域。在最后两行中,它们按比例缩小以填充屏幕上60x60像素的区域。 (选择这些尺寸是为了使一些“奇数”缩放因子为1.9和0.6。当缩放因子是整数时,某些效果(伪像)可能不会出现,或者恰好为0.5)。

对于升级和降尺度,还有“标准”绘画方式和“高质量”绘画之间的比较(在每个小组的标题中用“(HQ)”表示)。这里的“高质量”仅仅意味着渲染提示

KEY_ANTIALIAS = VALUE_ANTIALIAS_ON
KEY_RENDERING = VALUE_RENDER_QUALITY

已经设定:

ScalingMethods

以下是相应的程序,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));
            }
        }
    }
}

答案 1 :(得分:1)

这在Java中实际上相当容易。在Graphics2d环境中,屏幕上的逻辑坐标系(您在绘图程序中使用的坐标)和物理坐标系(它们出现的坐标)完全不相关。每次绘制到Graphics2d对象时,逻辑坐标首先由AffineTransform对象转换为物理坐标,并且可以修改此AffineTransform对象。为此,您可以使用Graphics2D.scale(double,double)Graphics2D.rotate(double)Graphics2D.translate(double,double)Graphics2D.shear(double,double)方法。

所以如果你先打电话

g2d.scale(2.0,2.0);

然后您随后绘制的所有图形将在两个方向上都是两倍大。

答案 2 :(得分:0)

如果我理解正确你想要的是在不删除或添加任何内容的情况下以不同的分辨率绘制图形。

其中一个“你尝试过的东西”可以做到这一点。

绘制到固定大小BufferedImage将确保所有组件在BufferedImage内可见(假设您正确绘制它们并且相对于它的固定大小),那么您只需将图像绘制到灵活的尺寸屏幕。

这是一个完整的可运行代码示例:

import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;

public class Test extends Canvas implements Runnable {

    // fixed size for the image
    private static final int WIDTH = 640;
    private static final int HEIGHT = 480;

    private BufferedImage image;
    private boolean running;
    private Thread t;

    public Test(Dimension dims) {
        super();
        setPreferredSize(dims); // actual screen size
        image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        running = false;
    }

    public synchronized void start() {
        if (running)
            return;
        t = new Thread(this);
        running = true;
        t.start();
    }

    public synchronized void stop() {
        if (!running)
            return;
        running = false;
        boolean retry = true;
        while (retry) {
            try {
                t.join();
                retry = false;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void render() {
        // draw to your image
        Graphics2D g2d = (Graphics2D) image.getGraphics().create();
        g2d.fillRect((WIDTH / 2) - 25, (HEIGHT / 2) - 25, 50, 50);
        g2d.dispose();

        // draw the image to your screen
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }
        g2d = (Graphics2D) bs.getDrawGraphics().create();
        g2d.drawImage(image, 0, 0, getWidth(), getHeight(), null);
        g2d.dispose();
        bs.show();
    }

    public void run() {
        // approximately sync rendering to 60 FPS don't use it as it is.
        // there are much better ways to do this.
        long startTime = System.currentTimeMillis();
        long frameTime = 1000 / 60;
        long tick = 0;
        while (running) {
            while ((System.currentTimeMillis() - startTime) > tick) {
                render();
                tick += frameTime;
            }
            try {
                Thread.sleep(20);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Test test = new Test(new Dimension(800, 600));

        JFrame frame = new JFrame("Fit to screen");
        frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        frame.addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                test.stop();
                frame.dispose();
                super.windowClosing(e);
            }
        });
        frame.getContentPane().add(test);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setResizable(false);
        frame.setVisible(true);
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                test.start();
            }
        });
    }

}

这只是一个快速实现,有些事情可以通过代码更好地完成。希望这会有所帮助。