Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

With JDK 9 my swing app works well on Windows with 4k highdpi and normal 1080p normal dpi. Labels, Comboboxes etc. all look nice and are scaled up on the 4k screen. But so is my JPanel where i draw custom images. Can i disable the scaling for this one JPanel to handle drawing myself? I am using apache-commons bicubic interpolation to draw more details on the higher unscaled resolution, but as it is scaled out of the box, i just have the "normal" dimensions to draw.

Kind regards

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
428 views
Welcome To Ask or Share your Answers For Others

1 Answer

(EDIT: Pasted new version from my library in the form that resulted after about 2 years of use. Comments may not be up to date, but is more production-code worthy now.)

The scaling in Java 9 seems to work like this: Your paint(Component)() methods receive a Graphics2D object which is already scaled. Additionally, the component sizes (e.g. myJFrame.setSize(), myJPanel.getWidth()) are scaled invisibly to the program, meaning that when you say setSize(800,600) on a 200% desktop, the component will be 1600x1200 but getWidth/getHeight will return 800/600.

Can i disable the scaling for this one JPanel to handle drawing myself?

To "reset" your Graphics object to scaling 1, do this:

final Graphics2D g = (Graphics2D) graphics;
final AffineTransform t = g.getTransform();
final double scaling = t.getScaleX(); // Assuming square pixels :P
t.setToScale(1, 1);
g.setTransform(t);

To get the correct dimensions, e.g. for filling the whole background with blackness before drawing:

final int w = (int) Math.round(getWidth() * scaling);

If you do it like this, you should get the desired result on Java 9 and Java 8.


I just created a class for Java devs who strive for a more custom Component design and/or raw drawing, where the system's display scaling should be known and manual scaling is often necessary. It should solve all scaling problems on Java 8 and Java 9. Here it is:

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




/**
 * TL;DR:
 * <p>
 * Call GUIScaling.initialize() at application start on the Swing thread.
 * <p>
 * If you set your own Component font sizes or border sizes or window sizes, multiply them by
 * GUIScaling.GUISCALINGFACTOR_COMPONENTSANDFONTS and/or use the helper methods newDimension() and scaleForComponent().
 * Works on Java 8 and 9.
 * <p>
 * If you do your own custom graphics and want to have control down to the actual pixel, create an instance of
 * GUIScalingCustomGraphics to obtain your Graphics2D at scaling 1 and your component's true physical width and height
 * (Which Java 9 reports differently!), and scale all your graphics using GUIScaling.GUISCALINGFACTOR_CUSTOMGRAPHICS
 * and/or use the helper method scaleForCustom(). The helper method scaleForRealComponentSize() can transform your mouse
 * coordinates to the real physical coordinate, which Java 9 reports differently!
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * <p>
 * GUIScaling class v[___3___, 2019-04-23 07!00 UTC] by dreamspace-president.com
 * <p>
 * This Swing class detects the system's display scaling setting, which is important to make your GUI and custom
 * graphics scale properly like the user wants it. On a 4K display, for example, you'd probably set 200% in your
 * system.
 * <p>
 * Not tested with Java less than 8!
 * <p>
 * On Java 8 (and with most but not all (e.g. no the default) LooksAndFeels), component sizes (e.g. JButton) and their
 * font sizes will scale automatically, but if you have a certain border width in mind, or decided for a certain min and
 * default window size or a certain font size, you have to upscale those on a non-100%-system. With this class, just
 * multiply the values with GUISCALINGFACTOR_COMPONENTSANDFONTS. Done. newDimension() and scaleForComponent() help with
 * that.
 * <p>
 * On Java 9, component sizes and their font sizes DO NOT SCALE from the perspective of the application, but in reality
 * they are scaled: A window set to 800x600 size will really be 1600x1200, but it will still report half this size when
 * asked. A border of 50 pixels will really be 100 pixels. A Graphics2D object (paint method etc.) will have a scaling
 * of 2! (Not if you just create a BufferedImage object and do createGraphics(), the scale here will be 1.) So, you
 * don't have to bother with GUI scaling here at all. YOU CAN STILL USE GUISCALINGFACTOR_COMPONENTSANDFONTS, because
 * this class will set it to 1 on Java 9. This is detected by indeed checking the scaling of a Graphics2D object. So,
 * your Java 8 and 9 component/font code will be exactly the same in regards to scaling.
 * <p>
 * CUSTOM GRAPHICS: If you do your own painting and want to insist on true physical pixels (in which case obviously
 * you'd have to scale your fonts with GUISCALINGFACTOR_CUSTOMGRAPHICS instead of GUISCALINGFACTOR_COMPONENTSANDFONTS),
 * on Java 9 you have to reset the scaling of the Graphics2D object the paint(Component)() method gives you from 2 to 1,
 * and (also Java 9) you have to adjust the width/height reported by your component. Both is done by making an instance
 * of GUIScalingCustomGraphics. You can do this blindly on Java 8 and 9, your code will stay the same. And then, apply
 * this class' GUISCALINGFACTOR_CUSTOMGRAPHICS to scale everything according to system settings. Or, instead of
 * insisting on true physical pixels, you could trust Java 9 and not mess with the initial scaling - but then you'd have
 * to distinguish whether you're dealing with Java 8 or 9, because on 8, you'd still have to scale your custom graphics.
 * In case you decide for this, use GUISCALINGFACTOR_COMPONENTSANDFONTS for your custom graphics instead of
 * GUISCALINGFACTOR_CUSTOMGRAPHICS because the former will be ***1*** on Java 9 but will be proper (e.g. 2.0 for a 200%
 * system) on Java 8.
 * <p>
 * A weird problem that comes with Java 9: If you use the mouse coordinates as reported by the system (instead of, say,
 * quasi-fix the physical mouse pointer invisibly at the screen center and make your own pointer based on coordinate
 * differences), you will have HALF THE USUAL RESOLUTION. On Java 8, a 3840x2160 screen will give you according mouse
 * coordinates, but on Java 9, you get half these coordinates (if the system is set to scaling 200%). While
 * scaleForRealComponentSize() helps correct this, a custom drawn mouse pointer will now step in 2 pixel distances, it
 * can not reach every individual pixel any longer. I wish they had updated the MouseEvent class accordingly with
 * additional float methods.
 */
final public class GUIScaling { // INITIAL TOUCHING of this class MUST be on Swing thread!


    /**
     * Call this at the start of your application ON THE SWING THREAD. This initializes the class and hence its values.
     */
    public static void initialize() {

        System.err.print("");
    }


    public static void setLookAndFeelDefault() {
        // The last three (Nimbus etc.) DO NOT automatically scale their font sizes with the system's GUI scaling,
        // so using the font size in those cases to derive the scaling WILL FAIL.
        // Btw., the JButton font size at 100% Windows 10 system scaling is 11.0 in all cases but the last three.
        GUIScaling.setLookAndFeel("Windows",
                                  UIManager.getSystemLookAndFeelClassName(),
                                  UIManager.getCrossPlatformLookAndFeelClassName(),
                                  "Windows Classic",
                                  "Nimbus",
                                  "Metal",
                                  "CDE/Motif");
    }


    /**
     * By calling this, you ALSO initialize the class, so you don't HAVE TO use initialize() in that case (but it really
     * doesn't matter). And you can indeed set a LookAndFeel of your choice, even though initialization of this class
     * also sets AND TEMPORARILY USES a LookAndFeel.
     *
     * @param intendedLAFIs ANYTHING, but ideally a LookAndFeel name or several. The first value that equalsIgnoreCase
     *                      an installed LookAndFeelInfo.getName() will be used.
     */
    public static void setLookAndFeel(final String... intendedLAFIs) {

        if (intendedLAFIs != null && intendedLAFIs.length > 0) {
            final UIManager.LookAndFeelInfo[] installedLAFIs = UIManager.getInstalledLookAndFeels();
            LAFILOOP:
            for (String intendedLAFI : intendedLAFIs) {
                for (final UIManager.LookAndFeelInfo lafi : UIManager.getInstalledLookAndFeels()) {
                    if (lafi.getName().equalsIgnoreCase(intendedLAFI)) {
                        try {
                            UIManager.setLookAndFeel(lafi.getClassName());
                            break LAFILOOP;
                        } catch (Exception e) {
                            continue LAFILOOP;
                        }
                    }
                }
            }
        } else {
            throw new IllegalArgumentException("intendedLAFIs is null or empty.");
        }
    }


    /**
     * Convenience method, compatible with Java 8 and 9.
     */
    public static Dimension newDimension(final int w,
                                         final int h) {

        return new Dimension(scaleForComponent(w), scaleForComponent(h));
    }


    /**
     * @param v E.g. the width of a component, or the size of a border.
     * @return v scaled by the necessary display scaling factor for components and fonts, compatible with Java 8 and 9.
     */
    public static int scaleForComponent(final double v) {

        return (int) Math.round(v * GUISCALINGFACTOR_COMPONENTSANDFONTS);
    }


    /**
     * @param v E.g. the width of a rectangle being drawn in a paint() or paintComponent() override.
     * @return v scaled by the necessary display scaling factor for custom graphics, compatible with Java 8 and 9.
     */
    public static int scaleForCustom(final double v) {

        return (int) Math.round(v * GUISCALINGFACTOR_CUSTOMGRAPHICS);
    }


    /**
     * @param v E.g. the width as reported by a component. (Java 9 on 200% desktop reports e.g. 200, but the physical
     *          size is actually 400. This method returns 400 then.)
     * @return v scaled so that it represents real physical pixels, compatible with Java 8 and 9.
     */
    public static int scaleForRealComponentSize(final double v) {

        return (int) Math.round(v * GUISCALINGFACTOR_REALCOMPONENTSIZE);
    }


    /**
     * @param font A font instance (Or null. Returns null.) whose size has been derived kinda like this: "new
     *             JLabel().getFont().getSize()" So it will look correct when used in components, no matter the current
     *             Java version. ......... WAIT WTF why does that look correct on Java 8 ?!??!?!?!?!?!?!?! Anyway ...
     *             when you want to use THAT font in custom drawing, you'll have a bad time once you get on Java 9.
     *             Because components will have SMALLER font sizes than on Java 8 on a 200% desktop because their
     *             Graphics objects are scaled. But if you use custom drawing, you'll use the class
     *             GUIScalingCustomGraphics below, which reset the scaling to 1. But then the font is too small. THIS
     *             METHOD RETURNS THE SCALED FONT independent of the Java version.
     * @return
     */
    public static Font scaleFontForCustom(final Font font) {

        if (font != null) {
            return font.deriveFont(font.getSize2D() * (float) GUISCALINGFACTOR_REALCOMPONENTSIZE);
        }
        return null;
    }


    /**
     * For Java 9, but can blindly be used in Java 8, too. Ensures that the scaling of a paint(Component)()'s Graphics2D
     * object is 1. Conveniently does the usual casting, too.
     * <p>
     * Also calculates the physical pixel width/height of the component, which is reported differently on Java 9 if 

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...