1

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.

3 Answers3

2

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

Andrew Thompson
  • 168,117
  • 40
  • 217
  • 433
mKorbel
  • 109,525
  • 20
  • 134
  • 319
  • I use no Labels in the code, and i also override Paintcomponents and PaintAll the same way, it does work partly. The only problems is that parts of the Layers that i added to the JPanel are cut off before the scaling takes place. – anubisviech Dec 06 '11 at 18:58
  • @anubisviech without joking, for real help to your issue you have to edit your question and post here code in http://sscce.org/ form, – mKorbel Dec 06 '11 at 19:05
  • thanks for the hint, ill try to post a reduced compileable sample tomorrow – anubisviech Dec 06 '11 at 19:19
1

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.

Community
  • 1
  • 1
trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • the ScaledView may be a solution, as i dont want to scale every single layer, but instead want to draw the whole scene and scale it in the final pass when everything is drawn. – anubisviech Dec 06 '11 at 19:03
0

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.