Scale tiny low-resolution app to larger screen size

好久不见. 提交于 2019-12-04 19:15:56

One approach, suggested here, is to rely on drawImage() to scale an image of the content. Your game would render itself in the graphics context of a BufferedImage, rather than your implementation of paintComponent(). If the game includes mouse interaction, you'll have to scale the mouse coordinates as shown. In the variation below, I've given the CENTER panel a preferred size that is a multiple of SCALE = 8 and added the original as an icon in the WEST of a BorderLayout. As the default, CENTER, ignores a component's preferred size, you may want to add it to a (possibly nested) panel having FlowLayout. Resize the frame to see the effect.

f.setLayout(new FlowLayout());
f.add(new Grid(NAME));
//f.add(new JLabel(ICON), BorderLayout.WEST);

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;

/**
 * @see https://stackoverflow.com/a/44373975/230513
 * @see http://stackoverflow.com/questions/2900801
 */
public class Grid extends JPanel implements MouseMotionListener {

    private static final String NAME = "OptionPane.informationIcon";
    private static final Icon ICON = UIManager.getIcon(NAME);
    private static final int SCALE = 8;
    private final BufferedImage image;
    private int imgW, imgH, paneW, paneH;

    public Grid(String name) {
        super(true);
        imgW = ICON.getIconWidth();
        imgH = ICON.getIconHeight();
        image = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = (Graphics2D) image.getGraphics();
        ICON.paintIcon(null, g2d, 0, 0);
        g2d.dispose();
        this.addMouseMotionListener(this);
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(imgW * SCALE, imgH * SCALE);
    }

    @Override
    protected void paintComponent(Graphics g) {
        paneW = this.getWidth();
        paneH = this.getHeight();
        g.drawImage(image, 0, 0, paneW, paneH, null);
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        Point p = e.getPoint();
        int x = p.x * imgW / paneW;
        int y = p.y * imgH / paneH;
        int c = image.getRGB(x, y);
        this.setToolTipText(x + "," + y + ": "
            + String.format("%08X", c));
    }

    @Override
    public void mouseDragged(MouseEvent e) {
    }

    private static void create() {
        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.add(new Grid(NAME));
        f.add(new JLabel(ICON), BorderLayout.WEST);
        f.pack();
        f.setVisible(true);
    }

    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {

            @Override
            public void run() {
                create();
            }
        });
    }
}

One approach, suggested here, is to rely on the graphics context's scale() method and construct to an inverse transform to convert between mouse coordinates and image coordinates. In the example below, note how the original image is 256 x 256, while the displayed image is scaled by SCALE = 2.0. The mouse is hovering over the center the image; the tooltip shows an arbitrary point in the display and the center point (127, 127) in the original.

import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;

/** @see https://stackoverflow.com/a/2244285/230513 */
public class InverseTransform {

    private static final double SCALE = 2.0;

    public static void main(String[] args) {
        JFrame frame = new JFrame("Inverse Test");
        BufferedImage image = getImage(256, 'F');
        AffineTransform at = new AffineTransform();
        at.scale(SCALE, SCALE);
        frame.add(new ImageView(image, at));
        frame.pack();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setVisible(true);
    }

    private static BufferedImage getImage(int size, char c) {
        final Font font = new Font("Serif", Font.BOLD, size);
        BufferedImage bi = new BufferedImage(
            size, size, BufferedImage.TYPE_INT_ARGB);
        Graphics2D g2d = bi.createGraphics();
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
            RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setPaint(Color.white);
        g2d.fillRect(0, 0, size, size);
        g2d.setPaint(Color.blue);
        g2d.setFont(font);
        FontMetrics fm = g2d.getFontMetrics();
        int x = (size - fm.charWidth(c)) / 2;
        int y = fm.getAscent() + fm.getDescent() / 4;
        g2d.drawString(String.valueOf(c), x, y);
        g2d.setPaint(Color.black);
        g2d.drawLine(0, y, size, y);
        g2d.drawLine(x, 0, x, size);
        g2d.fillOval(x - 3, y - 3, 6, 6);
        g2d.drawRect(0, 0, size - 1, size - 1);
        g2d.dispose();
        return bi;
    }

    private static class ImageView extends JPanel {

        private BufferedImage image;
        private AffineTransform at;
        private AffineTransform inverse;
        private Graphics2D canvas;
        private Point oldPt = new Point();
        private Point newPt;

        @Override
        public Dimension getPreferredSize() {
            return new Dimension( // arbitrary multiple of SCALE
                (int)(image.getWidth()  * SCALE * 1.25),
                (int)(image.getHeight() * SCALE * 1.25));
        }

        @Override
        public void paintComponent(Graphics g) {
            super.paintComponent(g);
            Graphics2D g2d = (Graphics2D) g;
            try {
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
                inverse = g2d.getTransform();
                inverse.invert();
                g2d.translate(this.getWidth() / 2, this.getHeight() / 2);
                g2d.transform(at);
                g2d.translate(-image.getWidth() / 2, -image.getHeight() / 2);
                inverse.concatenate(g2d.getTransform());
                g2d.drawImage(image, 0, 0, this);
            } catch (NoninvertibleTransformException ex) {
                ex.printStackTrace(System.err);
            }
        }

        ImageView(final BufferedImage image, final AffineTransform at) {
            this.setBackground(Color.lightGray);
            this.image = image;
            this.at = at;
            this.canvas = image.createGraphics();
            this.canvas.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
            this.canvas.setColor(Color.BLACK);
            this.addMouseMotionListener(new MouseMotionAdapter() {

                @Override
                public void mouseMoved(MouseEvent e) {
                    Point m = e.getPoint();
                    Point i = e.getPoint();
                    try {
                        inverse.inverseTransform(m, i);
                        setToolTipText("<html>Mouse: " + m.x + "," + m.y
                            + "<br>Inverse: " + i.x + "," + i.y + "</html>");
                    } catch (NoninvertibleTransformException ex) {
                        ex.printStackTrace();
                    }
                }
            });
        }
    }
}

Thanks to trashgod for pointing me in the right direction with his two answers. I was able to combine elements of both answers to arrive at something that works for what I need to do.

So first, my goal was to scale up an entire UI rather than scaling up a single icon or other simple component. By "an entire UI" I specifically mean a JPanel containing multiple custom child components laid out using a BorderLayout. There are no JButtons or any other interactive Swing components, and no mouse input (it's all keyboard-based input), so really I just need to scale a 304 x 256 JPanel up by a factor of 3 or 4.

Here's what I did:

package bb.view;

import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;

import static bb.BBConfig.SCREEN_WIDTH_PX;   // 304
import static bb.BBConfig.SCREEN_HEIGHT_PX;  // 256

public class Resizer extends JPanel {
    private static final int K = 3;
    private static final Dimension PREF_SIZE =
        new Dimension(K * SCREEN_WIDTH_PX, K * SCREEN_HEIGHT_PX);
    private static final AffineTransform SCALE_XFORM =
        AffineTransform.getScaleInstance(K, K);

    public Resizer(JComponent component) {
        setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
        add(component);
    }

    @Override
    public Dimension getPreferredSize() {
        return PREF_SIZE;
    }

    @Override
    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setTransform(SCALE_XFORM);
        super.paint(g2);
    }
}

Some important elements of the solution:

  • Using a FlowLayout here shrinkwraps the child component, which is what I want. (Thanks trashgod for that.) That is, I don't want the child to expand to fill the Resizer preferred size, because that wrecks the child component's layout. Specifically it was creating this huge gap between the child component's CENTER and SOUTH regions.
  • I configured the FlowLayout with left alignment and hgap, vgap = 0. That way my scale transform would have the scaled up version anchored in the upper left corner too.
  • I used an AffineTransform to accomplish the scaling. (Again thanks trashgod.)
  • I used paint() instead of paintComponent() because the Resizer is simply a wrapper. I don't want to paint a border. I basically want to intercept the paint() call, inserting the scale transform and then letting the JPanel.paint() do whatever it would normally do.
  • I didn't end up needing to render anything in a separate BufferedImage.

The end result is that the UI is large, but the all the code other than this Resizer thinks the UI is 304 x 256.

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!