JavaFX DPI Scaling

Important (2015-06-30): Starting with Java 8u60, JavaFX provides WPF-like DPI scaling on Windows by implicitly scaling all coordinates and image sizes to the current system DPI setting. So the manual scaling workaround described below is now only required for previous versions. The original post follows but now applies only to Java 8u45 and older. End of addendum

JavaFX is one of the GUI frameworks covered in my overview, DPI Scaling in Windows GUIs. As noted there, JavaFX 2.2 is partly but not fully suited for high DPI scaling on Windows. The default font and all standard controls are automatically scaled whereas explicitly specified sizes are not, with the exception of (some?) CSS em sizes. This post shows how Oracle’s own Windows samples break in high DPI mode, and how to work around this defect until perhaps one day JavaFX scales its coordinate system.

Oracle’s Layout Samples

Oracle’s official JavaFX documentation includes Joni Gordon’s three-part tutorial, Working With Layouts in JavaFX. The code showcased there is typical for JavaFX sample code you can find on the web: explicit sizes in Java or CSS are mixed freely (and thoughtlessly) with default-sized fonts and self-sized controls. We’ll pick the second and third tutorial to demonstrate why this is a bad idea.

Procedure. The following is based on the June 2013 revision of Oracle’s tutorials. I downloaded the ZIP archives for each tutorial and recompiled the source code in JavaFX 2.2 without changes, except to complete various tutorial steps. Then I ran each program at four different DPI settings on my Windows 8 system and took the screenshots you see below. The DPI settings are all standard ones:

  • 100% (96 DPI) — same as Joni Gordon’s, except for different window decoration since she was using Windows 7. This verifies that I’m indeed running the same programs with the same results, so any layout errors are due to DPI scaling.
  • 125% (120 DPI) — approximately the physical dot pitch of my Dell 2711 desktop monitor, and already a very common setting today.
  • 150% (144 DPI) — what I’m actually using on my monitor for comfortable viewing at my preferred distance, and also the default setting for Microsoft’s Surface Pro tablet. Necessary to accommodate modern high-resolution tablet screens.
  • 200% (192 DPI) — trumpeted for the upcoming Windows 8.1 but actually available since Windows 7. Expect people to use this setting on high-resolution tablets, too.

All JavaFX applications register themselves as DPI-aware on Windows, so they always run with XP style scaling and never use Vista DPI virtualization, regardless of whether XP style scaling is forced or not. Please read High DPI Settings in Windows if you don’t understand the last sentence. The bottom line is that there are no Windows settings to work around JavaFX layout failures at high DPI settings. The developers must fix their programs.

First Sample. The first sample is taken from the second tutorial, Tips for Sizing and Aligning Nodes. Here all controls are sized in Java code, with some inline CSS for button fonts. The unchanged sample code produces “Figure 2-2 Desired Sizes” on the tutorial page. Observe how the 96 DPI version looks great, the 120 DPI version starts misarranging buttons, the 144 DPI version has ugly overlaps, and the 192 DPI version is entirely unusable.

What happened? The Scene size was explicitly set to 300×400 pixels. While the buttons scaled with the Windows default font, the Scene and its nested containers could not grow to accommodate the growing buttons. Also note that the “Exit” label was explicitly set to 15pt – intended as 25% bigger than the default font, but actually much smaller at high DPI.

Sizing & Aligning

Sizing & Aligning (click for original size)

Second Sample. The second sample is taken from the third tutorial, Styling Layout Panes with CSS. I completed all suggested changes to the CSS stylesheet to produce “Figure 3-3 Styled with Images,” the last picture on the tutorial page. Once again, the 96 DPI version looks great and then everything falls apart.

  • The fixed width of the two top buttons eventually cuts off their labels to the point of becoming incomprehensible.
  • The text on the picture buttons to the right starts out barely smaller than Windows-rendered text, but ends up downright microscopic. If the regular button text is close to the smallest acceptable size, the picture button text will be unreadable.
  • The cloud pattern behind the two rotated buttons at the bottom was originally sized to exactly fit them. As the buttons grow larger, they protrude beyond the pattern and clouds appear between the buttons rather than around them.
CSS Layout Sample

CSS Layout Sample (click for original size)

Scalable JavaFX Layout

To reiterate, all of the above layout failures are the result of mixing explicit sizes with default sizes. JavaFX automatically scales its default font and standard controls, but not explicitly specified positions and sizes. That means you must scale them manually, based on the JavaFX default font size. The idea is similar to the concept of root em sizing in HTML/CSS. To obtain a scalable page layout, you define all positions and sizes in terms of “root em” (rem), the em size of the default font. Obtaining a JavaFX application’s root em is easy:

final double rem = javafx.scene.text.Font.getDefault().getSize();

That’s the default font size in typographical points (1/72″, CSS pt). You can obtain this value whenever you need it, or cache it since it won’t change while the application is running. (That is, until JavaFX supports the new dynamic per-monitor scaling in Windows 8.1.) Now, instead of supplying explicit pixel measures to positioning and sizing methods, you specify fractions or multiples of rem, like so:

// scalable version of 300x400 scene, assuming rem=12 at 100%
Scene scene = new Scene(tabs, 25 * rem, 33.33 * rem);

2013-09-08: As Cay Horstmann suggests below, it’s probably smarter to define rem in terms of pixels rather than points. This requires some additional rounding to obtain integral sizes on Windows, however. The alternative pixel-based definition looks like this:

final double rem = Math.rint(new Text("").getLayoutBounds().getHeight());

CSS and FXML

What about CSS? JavaFX exposes only a fraction of its sizing & positioning properties in CSS, and pixel or point sizes (px/pt) are never scaled anyway. Em sizes do appear to scale to the current default font size, but Oracle makes no guarantees to this effect. I recall em sometimes producing a hard-coded 12pt rather than the current default font size – possibly the behavior depends on when the style is loaded or applied. The safest route is to either specify no sizes in CSS, or else to calculate the necessary styles at runtime using Node.setStyle with the current root em. Alternatively, you could prepare multiple stylesheets at different scales and select one at runtime, Android-style.

What about FXML? No help there either, as any coordinates are simply passed through to the corresponding Java methods, meaning they are interpreted as unscaled pixels. Moreover, FXML does not support computational coordinate scaling or em equivalents. You could create multiple FXML files at different scales and select one at runtime – or avoid specifying any layout measures in FXML.

2013-08-17: As John Smith points out below, you can use computed expressions for FXML coordinates! See his example in the OpenJDK discussion thread FXML and high dpi screens. So you should be able to scale FXML layouts by root em without too much hassle.

The Road Ahead?

Although JavaFX is certainly better suited to scalable GUIs than Swing, thanks to its scaled default font and intelligently self-sizing controls, it’s still rather lagging behind the state of the scaling art. Microsoft’s Windows Presentation Foundation (WPF) scales all coordinates, whether in code or XAML, and the new WinRT API maps different pixel densities to a standardized virtual screen size. Apple OS-X and Android both offer dual APIs so that developers can use either scaled coordinates for portable layout or unscaled coordinates for precise drawing, at their discretion. Apple had customized Java 1.6 rendering to provide high DPI support in Swing, but JavaFX 2.2 still offers nothing comparable.

Planned for Spring 2014, JavaFX 8 and its new Modena theme will finally support Apple’s “retina” displays. Android support is currently being prototyped at Oracle but there’s no release date that I’m aware of. Back in December 2012, Richard Bair promised that JavaFX will “in the future” replicate Apple’s dual API so that developers can opt into or out of coordinate scaling as necessary. I certainly hope that this feature will be available for all platforms, including Windows, and sooner rather than later. To put it bluntly, a single unscaled coordinate system is not good enough for a GUI framework in 2013.

2013-09-11: Looks like Christmas came early for Apple users. The current Java SE 7u40 claims to already support Apple Retina displays in both Swing/AWT and JavaFX.

17 thoughts on “JavaFX DPI Scaling

  1. Pingback: JavaFX links of the week, August 11 // JavaFX News, Demos and Insight // FX Experience

  2. Pingback: Java desktop links of the week, August 11 « Jonathan Giles

  3. John Smith

    Very nice article on DPI scaling in JavaFX.

    Scaling possiblities with JavaFX are improving with Java 8 (and, I believe, the very soon to be released Java 7u40). As you point out, support will not reach state-of-the-art related to DPI handling at the time Java 8 is released. I’m confident that HiDPI support on OS X will work well with Java 8, but top quality support for Windows and other platforms will likely lag until at least a JavaFX 8.1 release.

    Hopefully continual progress can be made in a reasonably timely manner to make handling such concepts standard practice in a way that is simple and intuitive for developers – allowing developers to more easily build applications that scale correctly.

    I had some related ideas on providing scaling in FXML. FXML expression binding may be used (for example prefWidth=”${35*u.em}” prefHeight=”${25*u.em}”). FXML expression binding is currently available in JavaFX 2.2. Full example at: http://mail.openjdk.java.net/pipermail/openjfx-dev/2013-May/007738.html. The expressions also work in SceneBuilder (Oracle’s FXML based visual UI designer). It’s not an ideal solution, but may provide an interim process to aid in developing DPI scaling aware apps for UIs defined in FXML.

    Reply
    1. Christoph Nahr Post author

      Thanks for pointing out this discussion and your solution! I was totally unaware that you could already use computed expressions in FXML. Oracle’s own FXML reference says that “boolean or other operators may be added in the future,” so I thought that was still the status quo. Is there an up-to-date FXML reference available somewhere?

      Reply
  4. jewelsea

    As of today, the FXML reference Christoph refers to is the most accurate and up to date information on FXML that is available from Oracle.

    Reply
  5. Nicolas Lorain

    Can you file one or more issues in Jira (https://javafx-jira.kenai.com/) so we can keep track of them? Hi-DPI support in JavaFX 8 was meant to be optimized for Mac OS X based Retina displays, which explains why it lags on Windows. It also looks like you should file a feature request for the FXML documentation ;-)

    Reply
    1. Christoph Nahr Post author

      I think there are a couple of related Jira requests already, but I’ll make one for comprehensive coordinate scaling on Windows if none exists. And one for a revised FXML reference, too!

      Reply
  6. Anil

    I developed my program on Windows XP. The font was set to 12 point for most fields. The size was readable and adequate. When I tried running on Windows 7, the font size on screen is tiny. A 12 size font now appears like it is size 6! However, this is not uniform. Text that is set on some widgets – like on Table column headers and Choice boxes, appear large – 12 point size font like before. But now on the scaled down gui widgets, they are cut off. The display resolution is defaulted to 1920 x 1080. I am at a loss where the problem lies – is it JavaFX, or the display, or something else? Most importantly, what do I do to fix it?
    Specifically, how to reduce the font size of the table column headers and the choice boxes?

    http://stackoverflow.com/questions/18495823/javafx-text-fonts-are-tiny-on-windows-7

    Reply
    1. Christoph Nahr Post author

      Most likely, your Windows 7 system uses high DPI mode at 150%. Please familiarize yourself with High DPI Settings in Windows and check your system’s display scaling. If it’s indeed set to higher than minimum, you’ll have to rework your entire GUI and manually scale all sizes to fit the current Windows system font size, as described in this post here. There is no easy fix in JavaFX 2.2.

      Reply
  7. cayhorstmann

    I am not sure why it’s such a good idea to compute the root em from the point size of the default font. That’s in points, not pixels, so it’s not giving us an accurate conversion between pixels and points.
    Instead, why not measure some actual text in pixels, e.g.

    final double rem = new Text(“”).getLayoutBounds().getHeight();

    Reply
    1. Christoph Nahr Post author

      Good point, I had not thought of this alternative. It’s certainly viable and avoids confusion (or any potential scaling discrepancy) between point and pixel measures.

      However, there’s one problem regarding layout precision. Font.getDefault().getSize() returns integral values (12, 15, 18, 24) for all predefined Windows DPI steps. So if you can define your (pixel) layout in whole multiples or well-behaved fractions of that (point) size, you’ll get accurate pixel measures.

      But that’s not true for new Text(“”).getLayoutBounds().getHeight(). For the same predefined DPI steps, its results should be integral but aren’t. On my 144 DPI screen, the result is 23.941406 instead of 24 (= 18 x 96/72). For example, 9 rem would produce 215.47… pixels which would get rounded down to 215 as opposed to the correct 216.

      So you’ll need to first round the getHeight result before calculating any layout measures. Math.rint is probably better than Math.ceil, so as to avoid adding a whole pixel per rem when the result slightly exceeds the exact value. I’ll amend the post accordingly, thanks for your suggestion!

      Reply
    2. Christoph Nahr Post author

      PS: To verify my assertion that the 23.941406 value is an internal JavaFX rounding error and not the actual (though inconvenient) root em we should use, I measured the string “\n” in the same fashion, i.e. two lines. The result was now an integral 48. Three lines produced an integral 72. So I think it’s a safe guess that the layout height for the single line just trips over the point-to-pixel factor.

      Reply
  8. Andrew Shay

    Great article and information! It helped a lot! One issue I’m having is with images. On high dpi screens (200% in my case) images are not correctly located on their X,Y axis. Even putting the images in Labels or Buttons didn’t help. Any advice on how to overcome this issue?

    Reply

Leave a Reply