0

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).

camickr
  • 321,443
  • 19
  • 166
  • 288
AnjaM
  • 2,941
  • 8
  • 39
  • 62

1 Answers1

2

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);
camickr
  • 321,443
  • 19
  • 166
  • 288
  • Thanks for your reply and your comments. I added a screenshot as an example for what I see on my screen. As scaling of the shape is done with keeping a fixed aspect ratio, I would expect the rectangle to always fill either the entire width or the entire height of the window (or both if the aspect ratio of the frame is the same as the aspect ratio of the rectangle). However, there is always lots of free space to the right and to the bottom of the rectangle. – AnjaM May 03 '20 at 14:28
  • Unfortunately, removing the condition doesn't solve the problem, the free space at the right and at the bottom is still there. – AnjaM May 03 '20 at 18:37
  • @AnjaM, Well, I've tried it with your original code using a size of (2000, 500) which is wider than my display and with a size of (800, 500) which fits in my display. In all cases the black rectangle is always painted to one of the edges, and both edges with in the proper aspect ratio. Make sure all your old class files have been deleted before you compile. Post your updated code showing how you implemented my suggestions. – camickr May 03 '20 at 20:26
  • Added code and screenshots to my original post. I created a new project from scratch and copied this code into it to produce the screenshots. – AnjaM May 04 '20 at 10:48
  • The only difference I make to the code is that I move the main() method inside the DrawTest class, so I only have a single source file. I edited your code to post the code I compiled and tested. The new code still works fine for me. That is no matter how I resize the frame, at least one edge of the rectangle will be painted to the edge of the frame. I NEVER see a gap at the right and bottom edges at the same time. I am not sure why you see something different. See my edit of one more small change to your code that you can add. – camickr May 04 '20 at 14:52
  • Thanks a lot for all your suggestions and the time you spent figuring out the problem. I tried your tested code in Eclipse and still had the same problem. I then tried to compile and execute your code via the command prompt and the gap is gone. It seems that for whatever reason Eclipse caused this weird GUI behaviour. – AnjaM May 04 '20 at 16:03
  • @AnjaM *It seems that for whatever reason Eclipse caused this weird GUI behaviour.* - see edits 3 and 4. – camickr May 04 '20 at 17:23
  • Thanks, with your 4th edit, the scaling is fine in Eclipse. Could you please explain a bit more detailed why this problem occurred? When concatenating two scalings, I would expect that the resulting scaling will be incorrect because I am applying my scaling factor to an already scaled graphical object rather than to the original one. Why is this not the case? What happens with and without concatenation "behind the scenes"? – AnjaM May 04 '20 at 18:01
  • *I would expect that the resulting scaling will be incorrect* - I don't know what happens behind the scenes, buy I'm not sure why you think the scaling is incorrect. Do the math yourself. It doesn't matter the order you do multiplication. If you want to scale your rectangle by 10% you multiply by 1.1. If the entire application is scaled by 50% you multiply by 1.5. It doesn't matter if you do (800 x 1.1 x 1.5) or (800 x 1.5 x 1.1) the value is still 1320. If you think this is the wrong value, then what do you think the value should be? – camickr May 04 '20 at 18:50