Scaling shape with frame size

Tags: ,



I’m new to Java and I’m playing around with a simple GUI example. After having received a good answer to my last question, I tried to take it one step further and to scale the shape with the frame size (with fixed aspect ratio). Indeed, when I change the window size, the shape scales with it. However, the shape never fills the frame to a maximum, there is always quite a lot of space at the right and bottom margins. Here’s the code:

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;

public class DrawTest2 {

    class DrawingPanel extends JPanel {

        private Rectangle2D shape;
        private JFrame frame;

        public DrawingPanel(Rectangle2D shape, JFrame frame) {
            this.shape = shape;
            this.frame = frame;
        }

        public void paintComponent(Graphics g) {

            Graphics2D g2D = (Graphics2D) g;                
            double scaleFactor = calcScaleFactor();

            AffineTransform oldAt = g2D.getTransform();
            AffineTransform tx1 = new AffineTransform();
            tx1.scale(scaleFactor, scaleFactor);

            super.paintComponent(g2D);  

            g2D.setTransform(tx1);
            g2D.transform(AffineTransform.getTranslateInstance(3, 3)); 

            g2D.setColor(new Color(31, 21, 1));
            g2D.fill(shape);
            g2D.draw(shape);
            g2D.setTransform(oldAt);

        }

        @Override
        public Dimension getPreferredSize() {
            Dimension dimension = new Dimension((int)shape.getWidth(), (int)shape.getHeight());
            System.out.println("Dimension: " + dimension); // 2000 x 500
            return dimension;
        }

        private double calcScaleFactor() {

            double maxX = shape.getMaxX();
            double maxY = shape.getMaxY();

            double widthPanel = frame.getContentPane().getSize().getWidth();
            double heightPanel = frame.getContentPane().getSize().getHeight();
            double minFactor;

            if (widthPanel < maxX || heightPanel < maxY) {
                double factorX = widthPanel/maxX;
                double factorY = heightPanel/maxY;
                minFactor = factorX < factorY ? factorX : factorY;
            } else {
                minFactor = 1.0;
            }

            System.out.println("widthPanel: " + widthPanel); // 1283
            System.out.println("heightPanel: " + heightPanel); // 500
            System.out.println("widthFrame: " + frame.getSize().getWidth()); // 1297
            System.out.println("heightFrame: " + frame.getSize().getHeight()); // 537
            System.out.println("maxX: " + maxX); // 2000
            System.out.println("maxY: " + maxY); // 500
            System.out.println("minFactor:" + minFactor); // 0.6415

            return minFactor; 
        }
    }


    public void draw() {
        JFrame frame = new JFrame();
        Rectangle2D shape = new Rectangle2D.Float();
        shape.setRect(0, 0, 2000, 500);
        final DrawingPanel drawing = new DrawingPanel(shape, frame);
        frame.getContentPane().add(drawing, BorderLayout.CENTER);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);  

    }
}

public class DrawMain {
    public static void main(String[] args) {
        DrawTest2 test = new DrawTest2();
        test.draw();

    }
}

The scaling factor seems to be ok (2000 * 0.6415 = 1283). My guess is that although the calculation itself is right, I apply the scaling factor at the wrong place. Any help with this will be appreciated.

EDIT: Here is a screenshot of the window that I get to make the problem clearer:

shape with large margins at the right and at the bottom

EDIT2: Here is the updated code (slightly renamed):

import java.awt.*;
import java.awt.geom.*;   
import javax.swing.*;

public class DrawTest2 {

class DrawingPanel extends JPanel {

        private Rectangle2D shape;

        public DrawingPanel(Rectangle2D shape) {
            this.shape = shape;
        }

        public void paintComponent(Graphics g) {

            Graphics2D g2D = (Graphics2D) g;                
            double scaleFactor = calcScaleFactor();

            AffineTransform oldAt = g2D.getTransform();
            AffineTransform tx1 = new AffineTransform();
            tx1.scale(scaleFactor, scaleFactor);

            super.paintComponent(g2D);  

            g2D.setTransform(tx1);

            g2D.setColor(new Color(31, 21, 1));
            g2D.fill(shape);
            g2D.draw(shape);
            g2D.setTransform(oldAt);

        }

        @Override
        public Dimension getPreferredSize() {
            Dimension dimension = new Dimension((int)shape.getWidth(), (int)shape.getHeight());
            return dimension;
        }

        private double calcScaleFactor() {

            double maxX = shape.getMaxX();
            double maxY = shape.getMaxY();

            double widthPanel = getWidth();
            double heightPanel = getHeight();
            double minFactor;

            double factorX = widthPanel/maxX;
            double factorY = heightPanel/maxY;
            minFactor = factorX < factorY ? factorX : factorY;

            return minFactor; 
        }
    }


    public void draw() {
        JFrame frame = new JFrame();
        Rectangle2D shape = new Rectangle2D.Float();
        shape.setRect(0, 0, 800, 500);
        final DrawingPanel drawing = new DrawingPanel(shape);
        frame.getContentPane().add(drawing, BorderLayout.CENTER);
        frame.pack();
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);

    }

    public static void main(String[] args) {
        DrawTest2 test = new DrawTest2();
        test.draw();

    }
}

This is what the result looks like with shape.setRect(0, 0, 2000, 500):

Shape size 2000x500

This is what the result looks like with shape.setRect(0, 0, 800, 500):

Shape size 2000x800

If I replace return minFactor by return 1.0 (and stay with shape.setRect(0, 0, 2000, 500)), I get this result:

Shape size 2000x500, scaling factor 1.0

If I replace return minFactor by return 0.9 (and stay with shape.setRect(0, 0, 2000, 500)), I get this result:

enter image description here

If I replace return minFactor by return 0.9 and change to shape.setRect(0, 0, 3000, 500), I get the same visible result as with return 1.0 and shape.setRect(0, 0, 2000, 500).

Answer

there is always quite a lot of space at the right and bottom margins.

Works fine for me.

That is there is not extra space as long as the aspect ration of the frame is the same as that of the rectangle.

If you want the rectangle to always fill the space available, then you would need a separate scaling factor for the width and the height.

Other comments:

g2D.transform(AffineTransform.getTranslateInstance(3, 3)); 

I’m not sure why you are using a translate, but if you do then your preferred size calculation is wrong. You would need to add 3 to both the width and height, otherwise 3 pixels will be cut off when you paint the component at it preferred size.

double widthPanel = frame.getContentPane().getSize().getWidth();
double heightPanel = frame.getContentPane().getSize().getHeight();

Don’t reference the content pane or the frame. Custom painting for the panel is based on the size of the panel. Also, the panel may not always be added directly to the content pane.

So the code should be:

double widthPanel = getWidth();
double heightPanel = getHeight();

Also since you don’t need the frame the constructor for your panel should be:

//public DrawingPanel(Rectangle2D shape, JFrame frame) {
public DrawingPanel(Rectangle2D shape) {

In your application if you ever do need access to the frame you can use:

Window window = SwingUtilities.windowForComponent(…);

Edit:

You conditionally set the “minFactor”:

if (widthPanel < maxX || heightPanel < maxY) {
    double factorX = widthPanel/maxX;
    double factorY = heightPanel/maxY;
    minFactor = factorX < factorY ? factorX : factorY;
} else {
    minFactor = 1.0;

If you always want the aspect ratio preserved then do it unconditionally:

double factorX = widthPanel/maxX;
double factorY = heightPanel/maxY;
minFactor = factorX < factorY ? factorX : factorY;

Edit2:

Graphics2D g2D = (Graphics2D) g;
double scaleFactor = calcScaleFactor();
AffineTransform oldAt = g2D.getTransform();
AffineTransform tx1 = new AffineTransform();
tx1.scale(scaleFactor, scaleFactor);
super.paintComponent(g2D);

The purpose of invoking super.paintComponent() is to clear the background before doing custom painting to make sure you have no painting artifacts. Typically this is done as the first statement in the method. I’m not sure why you are applying a transform to the Graphics before invoking the method.

So I would suggest the code should be:

    public void paintComponent(Graphics g) {

        super.paintComponent(g);

        Graphics2D g2D = (Graphics2D) g;
        double scaleFactor = calcScaleFactor();

        AffineTransform oldAt = g2D.getTransform();
        AffineTransform tx1 = new AffineTransform();
        tx1.scale(scaleFactor, scaleFactor);

Edit 3:

I then tried to compile and execute your code via the command prompt and the gap is gone.

I also use the command line to compile/test which got me thinking about another question I comment on recently: How to make a picture not blurry in Java

Sure enough this is a related problem.

When I use:

java DrawTest2

I see the desired behaviour. However when I use:

java -Dsun.java2d.uiScale=1.5 DrawTest2

I see the behaviour you describe. So this would lead me to believe that Eclipse is doing something similar.

However, I don’t know how to resolve the problem so that the painting is done correctly whether scaling is used or not.

Edit 4:

Just found the solution. The Graphics object already contains the scaling factor for the application and you are resetting the factor. Instead you need to adjust the factor:

    tx1.concatenate( g2D.getTransform() );
    tx1.scale(scaleFactor, scaleFactor);


Source: stackoverflow