Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8222209

JavaFX is rendered blurry on systems with monitors in different configuration

XMLWordPrintable

    • b06
    • x86_64
    • windows_10

      ADDITIONAL SYSTEM INFORMATION :
      Windows 10, OpenJDK 11.01, OpenJFX11

      A DESCRIPTION OF THE PROBLEM :
      Error receipe:
      * Two monitors in different scales (175% 125% (Main-Monitor))
      * Use the JavaFX Panel

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      Error receipe:
      * Two monitors in different scales (175% 125% (Main-Monitor))
      * Use the JavaFX Panel inside a JFrame
      * Move the JFrame from one monitor to the other -> the rendering inside the JFXPanel becomes blurry

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Clean rendering like in Swing
      ACTUAL -
      The Label in the JFXPanel is rendered blurry

      ---------- BEGIN SOURCE ----------
      package test;

      import java.awt.FlowLayout;

      import javax.swing.JFrame;
      import javax.swing.JLabel;
      import javax.swing.JPanel;

      import javafx.application.Platform;
      import javafx.embed.swing.JFXPanel;
      import javafx.scene.Scene;
      import javafx.scene.control.Label;

      public class UnsharpJFXPanel
      {
         public static void main(String... args)
         {
            JFrame frame = new JFrame("Unsharp JFXPanel");
            frame.setSize(550, 100);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

            JFXPanel jfxPanel = new JFXPanel();
            jfxPanel.setSize(500, 30);
            Label label1 = new Label("JavaFX: An preost wes on leoden, Laȝamon was ihoten- He wes Leovenaðes sone");
            label1.setPrefWidth(500);

            JPanel jPanel = new JPanel(new FlowLayout());
            jPanel.add(jfxPanel);
            jPanel.add(new JLabel("Swing: An preost wes on leoden, Laȝamon was ihoten- He wes Leovenaðes sone"));
            frame.setContentPane(jPanel);

            frame.setVisible(true);
            Platform.runLater(() -> {
               jfxPanel.setScene(new Scene(label1));
            });
         }
      }

      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      The newScaleFactorX and newScaleFactorY in updateComponentSize() must not be the one of the DefaultScreenDevice, but the one that is currently used for display.

      We use for updateComponentSize() and paintComponent(Graphics g):
      AffineTransform defaultTransform = GUIHelper.getGraphicsConfiguration(this, false).getDefaultTransform();
      final double newScaleFactorX = defaultTransform.getScaleX();
      final double newScaleFactorY = defaultTransform.getScaleY();

      With GUIHelper
      /**
          * Nudges a point onto a given screen.
          *
          * The given point is translated by at most 1 px per direction if that makes it
          * lie inside the bounds of the given screen. This is necessary if a highly scaled
          * screen (>= 200%) is used and a particular correct logical bound of that screen
          * differs from a part of the location of a component that is completely on that screen.
          *
          * @param point a point
          * @param configuration a screen
          * @return the same point again, but potentially modified
          */
         public static Point nudgeLocationOntoPreferredDevice(Point point, GraphicsConfiguration configuration)
         {
            if ((point == null) || (configuration == null))
            {
               return point;
            }

            Rectangle bounds = configuration.getBounds();

            int x = point.x;
            if (x == bounds.x - 1)
            {
               x++;
            }
            else if (x == bounds.x + bounds.width)
            {
               x--;
            }

            int y = point.y;
            if (y == bounds.y - 1)
            {
               y++;
            }
            else if (y == bounds.y + bounds.height)
            {
               y--;
            }

            if (bounds.contains(x, y))
            {
               point.move(x, y);
            }

            return point;
         }

         /**
          * Determines a screen for a given component, or the primary screen if there is no proper one.
          *
          * If {@code checkBounds} is {@code true}, the screen's bounds are checked against the
          * location of the component. Other than usual, do not start with screen 0 but consider the
          * associated screen first. This is necessary if multiple screen have different scalings, because
          * the downscaled logical pixels can overlap, leading to the impression that a pixel actually
          * belongs to another screen.
          *
          * @param component the component to consider
          * @param checkBounds whether we need to check whether the location on screen is part of
          * the returned screen
          */
         @NotNull
         public static GraphicsConfiguration getGraphicsConfiguration(Component component, boolean checkBounds)
         {
            if (component != null)
            {
               GraphicsConfiguration configuration = component.getGraphicsConfiguration();
               if ((configuration != null) && !checkBounds)
               {
                  return configuration;
               }
               else if (checkBounds && component.isShowing())
               {
                  Point location = nudgeLocationOntoPreferredDevice(component.getLocationOnScreen(), configuration);
                  if ((configuration != null) && configuration.getBounds().contains(location))
                  {
                     // Prefer the associated device
                     return configuration;
                  }
                  else
                  {
                     for (GraphicsDevice device : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
                     {
                        if (device.getType() == GraphicsDevice.TYPE_RASTER_SCREEN)
                        {
                           configuration = device.getDefaultConfiguration();
                           if (configuration.getBounds().contains(location))
                           {
                              return configuration;
                           }
                        }
                     }
                  }
               }
            }
            return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
         }

            psadhukhan Prasanta Sadhukhan
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            5 Start watching this issue

              Created:
              Updated:
              Resolved: