Question

How can I detect if a user has a retina display in Java? I am already aware of detecting the scale factor using Toolkit.getDefaultToolkit().getDesktopProperty("apple.awt.contentScaleFactor"), but java won't let me convert the returned value into an int. I'm wondering how I can convert that into an int, or another way to detect retina displays.

Was it helpful?

Solution

I would get the value this way -

public static boolean hasRetinaDisplay() {
  Object obj = Toolkit.getDefaultToolkit()
      .getDesktopProperty(
          "apple.awt.contentScaleFactor");
  if (obj instanceof Float) {
    Float f = (Float) obj;
    int scale = f.intValue();
    return (scale == 2); // 1 indicates a regular mac display.
  }
  return false;
}

OTHER TIPS

Beware that users may have multiple displays! What does “detect a Retina display” mean in this scenario?

For most purposes, you are interested in rendering an image onto a GUI component. You therefore need to detect what display the component is on.

Luckily java.awt.Component has a getGraphicsConfiguration method that gives us the necessary information.

However, Java 8 (and 7) and Java 9 require different handling: Java 9 exposes the necessary information directly via the graphics device’s default transform. Java 7 and 8 also expose this transformation, but it is always set to an identity transformation (i.e. no transformation), even for a Retina display.

For Java < 9, we need to use reflection to query macOS specific fields in the OpenJDK classes that implement graphics for Mac.

The following class implements the necessary checks for Retina displays and works for Java 8 as well as Java 9. Java 7 might also work with trivial changes but I didn’t test it.

package me.klmr.ui;

import java.awt.*;
import java.lang.reflect.Method;

public final class Device {
    private enum JavaVersion {
        V8,
        V9
    }

    private static final JavaVersion JAVA_VERSION = getJavaVersion();

    private static JavaVersion getJavaVersion() {
        final String versionString = System.getProperty("java.version");
        if (versionString.startsWith("1.8")) return JavaVersion.V8;
        if (versionString.startsWith("9.")) return JavaVersion.V9;
        throw new RuntimeException("Unsupported Java version");
    }

    public static GraphicsConfiguration getCurrentConfiguration(final Component component) {
        final GraphicsConfiguration graphicsConfiguration = component.getGraphicsConfiguration();
        if (graphicsConfiguration == null) {
            return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        } else {
            return graphicsConfiguration;
        }
    }

    public static GraphicsDevice getCurrentDevice(final Component component) {
        final GraphicsConfiguration graphicsConfiguration = component.getGraphicsConfiguration();
        if (graphicsConfiguration == null) {
            return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
        } else {
            return graphicsConfiguration.getDevice();
        }
    }

    public static boolean isOnRetinaDisplay(final Component component) {
        switch (JAVA_VERSION) {
            case V8: return isOnRetinaDisplayJava8(component);
            case V9: return isOnRetinaDisplayJava9(component);
            default: throw new AssertionError("Unreachable");
        }
    }

    public static double getDisplayScalingFactor(final Component component) {
        switch (JAVA_VERSION) {
            case V8: return getDisplayScalingFactorJava8(component);
            case V9: return getDisplayScalingFactorJava9(component);
            default: throw new AssertionError("Unreachable");
        }
    }

    private static boolean isOnRetinaDisplayJava8(final Component component) {
        final GraphicsDevice device = getCurrentDevice(component);
        try {
            final Method getScaleFactorMethod = device.getClass().getMethod("getScaleFactor");
            final Object scale = getScaleFactorMethod.invoke(device);
            return scale instanceof Integer && ((Integer) scale).intValue() == 2;
        } catch (ReflectiveOperationException e) {
            return false;
        }
    }

    private static boolean isOnRetinaDisplayJava9(final Component component) {
        return ! getCurrentConfiguration(component).getDefaultTransform().isIdentity();
    }

    private static double getDisplayScalingFactorJava8(final Component component) {
        return isOnRetinaDisplayJava8(component) ? 2.0 : 1.0;
    }

    private static double getDisplayScalingFactorJava9(final Component component) {
        return getCurrentConfiguration(component).getDefaultTransform().getScaleX();
    }
}

In practice, moving a dialog from one screen to another will cause components to re-render. If the component’s rendering code uses the above class to find out the correct resolution, they will render correctly regardless of which display they are currently on.

For Java 9, this also works:

    public static boolean isMacRetinaDisplay() {
        final GraphicsConfiguration gfxConfig = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
        final AffineTransform transform = gfxConfig.getDefaultTransform();
        return !transform.isIdentity();
    }

You could alternatively inspect the scale factor of the transform and check if it is equal to 2 and fall back to non-retina otherwise.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top