(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