简体   繁体   中英

java JFrame scaled UI display with virtual drawing space

i have an application that has graphics which are thought to be displayed at 1024x768.

I want to make the application flexible in size without rewriting all drawing code, position calculation etc..

To achieve that my attempt was overriding the paint method of the JFrame container in the following way:

@Override
public void paint(Graphics g)
{
    BufferedImage img = new BufferedImage(this.desiredWidth, this.desiredHeight, BufferedImage.TYPE_INT_ARGB);

    Graphics2D gi = (Graphics2D) img.getGraphics();
    gi.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB);
    gi.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
    gi.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);

    super.paint(gi);
    gi.dispose();

    ((Graphics2D) g).drawImage(img, screenScale, null);

}

while screenScale is an AffineTransform Object i created in the constructor which does the appropriate scaling according to the target size.

The problem now is: My child components get drawn and scaled, but with the limitations of the parent JFrame. So if my parent frame has the dimension 640x480 the child layers that i have added to it can only draw inside a 640x480 fraction of the 1024x768 BufferedImage that it is painting on. I guess in some place the child components use getPreferredSize of the JFrame parent, because the child always has this values as bounds. So in the end my scaling strategy is in conflict with the painting behavior of the childs, because they fully ignore the bounds of the graphics object they get delivered for drawing on.

In the end, what ever i do, my child layers (derived from jpanel if that matters) get cut off when the target size is smaller than my "virtual" screen size.

Can anyone provide a better solution or hints how i can circumvent the strange behavior that the graphics bounds are ignored?

Edit: updated outcome of above code with unscaled output, expectet output and resulting output

expected output resulted output

update: working test code

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.print.attribute.standard.OrientationRequested;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class AffineTransformTest
{

private static TransformingFrame    canvas;
private static JButton button;
private static TestLayer layer;

public static void main(String[] args)
{
    canvas = new TransformingFrame();
    canvas.addMouseWheelListener(new ScaleHandler());

    layer=new TestLayer(canvas.originalSize);
    canvas.getContentPane().add(layer);
    layer.setVisible(true);
    button = new JButton("asdf");
    canvas.setUndecorated(true);
    button.setVisible(true);
    canvas.getContentPane().add(button);


    canvas.pack();



    canvas.setLayout(new BorderLayout());
    canvas.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
    canvas.setPreferredSize(canvas.originalSize);
    canvas.setSize(canvas.originalSize);
    canvas.setLayout(null);

    canvas.setVisible(true);
    canvas.validate();
}

@SuppressWarnings("serial")
private static class TransformingFrame extends JFrame
{
    private double  scale;
    private final Dimension originalSize;
    private AffineTransform tx = new AffineTransform();

    TransformingFrame()
    {
        originalSize=new Dimension(800,600);
        scale = 1;
    }

    @Override
    public void paint(Graphics g)
    {
        BufferedImage offscreenBuffer=new BufferedImage(originalSize.width,originalSize.height, BufferedImage.TYPE_INT_ARGB);
        Graphics bufferGraphics=offscreenBuffer.getGraphics();
        super.paint(bufferGraphics);
        bufferGraphics.dispose();

        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setColor(Color.black);
        g.fillRect(0, 0, getWidth(), getHeight());
        ((Graphics2D) g).drawImage(offscreenBuffer, tx,null);
    }
    @Override
    public void paintComponents(Graphics g)
    {
        BufferedImage offscreenBuffer=new BufferedImage(originalSize.width,originalSize.height, BufferedImage.TYPE_INT_ARGB);
        Graphics bufferGraphics=offscreenBuffer.getGraphics();
        super.paintComponents(bufferGraphics);
        bufferGraphics.dispose();

        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setColor(Color.black);
        g.fillRect(0, 0, getWidth(), getHeight());
        ((Graphics2D) g).drawImage(offscreenBuffer, tx,null);

    }
    @Override
    public void paintAll(Graphics g)
    {
        BufferedImage offscreenBuffer=new BufferedImage(originalSize.width,originalSize.height, BufferedImage.TYPE_INT_ARGB);
        Graphics bufferGraphics=offscreenBuffer.getGraphics();
        super.paintAll(bufferGraphics);
        bufferGraphics.dispose();

        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_VRGB);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
        ((Graphics2D) g).setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
        g.setColor(Color.black);
        g.fillRect(0, 0, getWidth(), getHeight());
        ((Graphics2D) g).drawImage(offscreenBuffer, tx,null);
}

}

@SuppressWarnings("serial")
private static class TestLayer extends JPanel{

public TestLayer(Dimension originalSize)
{
    this.setPreferredSize(originalSize);
    this.setSize(originalSize);
    setOpaque(false);
    setDoubleBuffered(false);
}

@Override
public void paint(Graphics g)
{
    Graphics2D ourGraphics = (Graphics2D) g;
    super.paint(ourGraphics);
    ourGraphics.setColor(Color.green);
    ourGraphics.fillRect(0, 0, getWidth(), getHeight());
    ourGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    ourGraphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
    ourGraphics.setColor(Color.BLACK);

    ourGraphics.drawRect(50, 50, 50, 50);
    ourGraphics.fillOval(100, 100, 100, 100);
    ourGraphics.drawString("Test Affine Transform", 50, 30);
    ourGraphics.drawString(canvas.tx.toString(), 50, 250);
}

}

private static class ScaleHandler implements MouseWheelListener
{
    public void mouseWheelMoved(MouseWheelEvent e)
    {
        if (e.getScrollType() == MouseWheelEvent.WHEEL_UNIT_SCROLL)
        {

            // make it a reasonable amount of zoom
            // .1 gives a nice slow transition
            canvas.scale += (.1 * e.getWheelRotation());
            // don't cross negative threshold.
            // also, setting scale to 0 has bad effects
            canvas.scale = Math.max(0.00001, canvas.scale);
            canvas.tx.setTransform(new AffineTransform());
            canvas.tx.scale(canvas.scale, canvas.scale);
            canvas.setPreferredSize(new Dimension((int)(canvas.originalSize.width*canvas.scale),(int)(canvas.originalSize.height*canvas.scale)));
            canvas.setSize(new Dimension((int)(canvas.originalSize.width*canvas.scale),(int)(canvas.originalSize.height*canvas.scale)));
            canvas.validate();
            canvas.repaint();
        }
    }
}

}

for some reason this code is working (except the button disappearing).. maybe my error is somewhere else in the child layers.. i'll go investigate that

Okay after some hours fiddling around with it, i came to the conclusion that the drawing limitations that the child panels get in their paint(Graphics g) method don't allow painting more than the parent's size. In the example it works but in the full application not. Seems some settings force that behaviour on my application, but not the demo app.

So if my parent frame has the dimension 640x480 the child layers that i have added to it can only draw inside a 640x480 fraction of the 1024x768

create JFrame --> put there JScrollPane --> to the JScrollPane put :

1) JPanel or JComponent with override paintComponentn(Graphics g) not paint(Graphics g)

2) you wrote about BufferedImage , then better way is put BufferedImage as Icon to the JLabel

As you've observed, a component can be rendered in a scaled graphics context, but the result is effectively useless: the UI delegate has no knowledge of the altered geometry. As @mKorbel suggests, a JScrollPane is the traditional alternative.

You might also look at the scheme used in this game or the technique used in this scalable label . If you are willing to make your own components, you may be able to adapt the approach shown in this ScaledView .

My problem got completely solved after asking some ppl about this. The solution was: 1. Create a new Class which you can draw on and make the manipulation there, example:

private class ScaledPane extends JPanel
{
    public ScaledPane(Window parent)
    {
        super();
        setPreferredSize(new Dimension(parent.getDesiredWidth(), parent.getDesiredHeight()));
        setSize(this.getPreferredSize());
    }

    @Override
    public void paint(Graphics g)
    {
        Graphics2D g2 = (Graphics2D) g.create(0, 0, getWidth(), getHeight());
        g2.setTransform(screenScale);
        g2.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); //
        System.out.println(g2.getClip());
        super.paint(g2);
    }
}

after that set an instance of that class to your contentpane:

setScreenScale(AffineTransform.getScaleInstance((double) width / (double) desiredWidth, (double) height / (double) desiredHeight));
    setContentPane(new ScaledPane(this));

after doing that everything just went fine, as the components of the window us the contentpanes paint method to draw themselves with the new graphics object that is set there

With that done i can scale my window to any desired size without manipulation of the movement formulas or positions of any child.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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