繁体   English   中英

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

[英]How do you use re-size all Graphic2D

在 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,它会将图形 2D 对象拉伸到相应大小。
  • 如果您有一个 SQUARE-SCREEN,它会将 graphics2D 对象压缩到相应大小。

那么如何使用 Graphics2D、JFrame 制作完美的可重新封装游戏。

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

从广义上讲,在 Swing 中至少有三个选项可以实现这一点:

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

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

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

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

在这两种情况下,缩放图像的过程都有几个调整参数。 事实上,缩放图像比乍一看要复杂得多。 详情可参考 Chris Campbell 的文章The Perils of Image.getScaledInstance()

绘制成缩放的Graphics2D对象

Graphics2D已经提供了创建世界坐标系屏幕坐标系之间转换所需的全部功能。 这是由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对象的转换版本,然后可以将其直接绘制到(转换的) 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

已设置:

缩放方法

这是相应的程序,作为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));
            }
        }
    }
}

这在 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);

那么您随后绘制的所有图形在两个方向上都将是原来的两倍。

如果我理解正确,您想要的只是以不同的分辨率绘制图形,而无需删除或添加任何内容。

好吧,“您尝试过的事情”之一可以做到这一点。

绘制到固定大小的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();
            }
        });
    }

}

这只是一个快速的实现,有一些事情可以在该代码中做得更好,但你得到了图片。 希望这可以帮助。

也许这会有所帮助:

缩放包含基本形状的 graphics2D 有一个缺点:如果比例加倍,线条的粗细会加倍,这是实现缩放功能的应用程序中的问题......我发现的唯一方法是使容器的首选尺寸更大并且然后,绘制形状。

这是使用鼠标滚轮的缩放功能,鼠标指向的对象的像素停留在鼠标指针下方。 我花了很长时间才弄清楚如何正确地做到这一点,但我终于发现......(应用程序是一个星盘,我想放大和缩小)

graphics2D 属于一个 JPanel,它包含在 JSplitPane 的底部:

public void mouseWheelMoved(MouseWheelEvent e) {

Dimension dim = new Dimension(), oldDim = this.getPreferredSize();
double newX, newY;
Rectangle rect, oldRect;
    
if(this.mousewheel >= 0){
  this.mousewheel += -e.getWheelRotation() * this.mousewheelSensibility;
}
else {
  this.mousewheel = 0;
}

dim.setSize(this.astro.splitBottomDimension.getWidth() + this.mousewheel, this.astro.splitBottomDimension.getHeight() + this.mousewheel);

oldRect = this.getVisibleRect();
    
this.mouseX = e.getX();
this.mouseY = e.getY();
this.setPreferredSize(dim);

newX = this.mouseX / oldDim.getWidth() * dim.getWidth();
newY = this.mouseY / oldDim.getHeight() * dim.getHeight();
rect = new Rectangle((int)newX - (this.mouseX - oldRect.x), (int)newY - (this.mouseY - oldRect.y), oldRect.width, oldRect.height);

this.scrollRectToVisible(rect); 
    
this.revalidate();

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM