Reputation: 142
I recently made the decision to switch over to a newer version of the JDK from the one I was using. (Specifically from jdk1.8.0_261
to jdk-14.0.2
). This upgrade when pretty smoothly, as most features work the way I expected them and I was able to find how to adjust to anything that was not the same.
However, as shown in the title, I came across an issue with a specific application I am writing that uses Swing. My main monitor is a 4k monitor, and I have set the windows system scaling for that monitor to 200%. While every other Swing application being scaled to match that DPI setting is nice, for this specific application I want to disable that scaling and have pixels be drawn 1:1, especially when this Application is distributed.
Is there a way to disable this automatic scaling with a VM argument? Or is there a way to disable the scaling at runtime before any calls are made to the swing library? (Similar to how any changes to the look and feel must be done before any other calls to the library.)
For reference:
Here is the code I use to create my JFrame and to create the graphics object I draw everything onto, which happens in a timed loop outside of this class and the scope of this question.
public class ScreenManager {
private GraphicsDevice device;
private final JFrame frame;
private static ScreenManager instance;
public ScreenManager() {
instance = this;
GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment();
this.device = env.getDefaultScreenDevice();
this.frame = new JFrame(game.getGame().gameName());
this.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.frame.setUndecorated(true);
this.frame.setIgnoreRepaint(true);
this.frame.setResizable(false);
this.device.setFullScreenWindow(frame);
this.device.setDisplayMode(device.getDisplayMode());
this.frame.createBufferStrategy(2);
}
public Graphics2D getRenderGraphics() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
Graphics2D g = (Graphics2D) strategy.getDrawGraphics();
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
return g;
}
return null;
}
public void updateDisplay() {
Window window = device.getFullScreenWindow();
if (window != null) {
BufferStrategy strategy = window.getBufferStrategy();
if (!strategy.contentsLost()) {
strategy.show();
}
}
Toolkit.getDefaultToolkit().sync();
}
// other methods go here
}
Using AffineTransforms would, while welcome advice, not truly help solve this issue, as I need to be able to display at the native resolution of any monitor and already extensively use AffineTransforms on the Graphics object.
EDIT: I have tried System.setProperty("sun.java2d.dpiaware", "false");
as the first line in main with no success. Is the property wrong?
EDIT 2: Addition of more clarity:
My render method looks like this:
private void render(Graphics2D g) {
// Black out the screen to prevent old stuff from showing
g.setColor(Color.BLACK);
g.fillRect(0, 0, ScreenManager.getScreenWidth(), ScreenManager.getScreenHeight());
// Set the transform for zoom to draw the zoomed stuffs
AffineTransform saveState = g.getTransform();
AffineTransform cameraTransform = ScreenManager.getInstance().getCamera().getCurrentTransform();
g.transform(cameraTransform);
// RENDER UPDATEABLE
this.game.getController().renderGame(g);
// REDNER STATIC GUI
g.setTransform(saveState);
// draw mouse cords
Point mouse = this.mouseHandler.getFrameMousePoint();
if (mouse != null) {
Font f = new Font("Consolas", 0, 40);
Point2D scaledPos;
try {
scaledPos = cameraTransform.inverseTransform(mouse, null);
} catch (NoninvertibleTransformException e) {
scaledPos = mouse;
e.printStackTrace();
}
String s1 = mouse.toString();
String s2 = scaledPos.toString();
Rectangle2D r = f.getMaxCharBounds(g.getFontRenderContext());
int yOff = (int) r.getHeight();
Color c = new Color(100, 100, 100, 191);
g.setColor(c);
g.fillRect(mouse.x, mouse.y, (int) (r.getWidth() * (s1.length() > s2.length() ? s1.length() : s2.length())), yOff + yOff + yOff);
g.setColor(Color.WHITE);
g.setFont(f);
g.drawString(s1, mouse.x, mouse.y + yOff);
g.drawString(s2, mouse.x, mouse.y + yOff + yOff);
}
}
And this method is called here:
Graphics2D g = screenManager.getRenderGraphics();
render(g);
g.dispose();
screenManager.updateDisplay(); // SYNCS SCREEN WITH VSync
I have printed out the AffineTransform that is on the graphics object when it is first created, and that prints like this AffineTransform[[2.0, 0.0, 0.0], [0.0, 2.0, 0.0]]
which does lead me to conclude that the scaling is done in code. However, the issue is, the things that are rendered with my custom transform ScreenManager.getInstance().getCamera().getCurrentTransform();
are also scaled up by 2, even though I do not contact my transform and set it to mine. (Actually never mind, I realized that I do g.transform on my transform not g.setTransform. I am still including this in the write-up though).
A workaround solution to my rendering problem is to do g.setTransform(AffineTransform.getScaleInstance(1, 1);
However, this is more than I want to do and it (after trying it on my code) does not fix the issue with the awt coordinate space. Meaning, The rendering works as desired, but the mouse position does not. When the mouse is in the bottom right corner of my screen, it will give a position that is half of the native resolution of my monitor. This solution does not work for me. Processing time is valuable and the more calculations that must be done to UNDO this feature addition. Here is a link discussing how this would be implemented. I link this to point out the section where it mentions that the developer could do it, but it would take a lot of work on the developers part.
I would still like to know if there is a way to either:
Upvotes: 4
Views: 2767
Reputation: 142
After coming back to this question after a while of working on something else, I have come across the solution I was looking for, mostly by accident.
The solution I was looking for was a Java system property or JVM argument that would disable the DPI awareness of Swing components, meaning that on a system with 200% scaling, the user input space and component rendering would match the native resolution of the screen and not the 200% scaled resolution (eg expecting to read mouse input from and render to a 3840x2160 screen rather than what was happening, where mouse input was capped at 1920x1080).
This solution was discussed in this answer to a similar question. I will restate it here for sake of being complete.
The solution is to pass -Dsun.java2d.uiScale=1
into the VM as a VM argument. This forces the UI scale to be 1, ignoring any system scaling and rendering and gathering mouse input at native resolution. I can also confirm that calling System.setProperty("sun.java2d.uiScale", "1");
before calling any swing class will also produce the result I was looking for.
Upvotes: 10