java - Windows 上 JavaFX 中 SwingNode 的模糊渲染

标签 java swing javafx flying-saucer

概述
在 JavaFX 应用程序中使用 FlyingSaucer,出于各种原因避免使用 WebView:

  • 不提供对其滚动条的直接 API 访问以实现同步行为;
  • 捆绑 JavaScript,这对我的用例来说是一个巨大的膨胀;和
  • 无法在 Windows 上运行。

  • FlyingSaucer 使用 Swing,这需要包裹它的 XHTMLPanel ( JPanel 的子类)在 SwingNode 中与 JavaFX 一起使用。一切正常,应用程序实时呈现 Markdown,并且响应迅速。这是一个 demo video在 Linux 上运行的应用程序。
    问题
    Windows 上的文本渲染是模糊的。在 JFrame 中运行时, 没有被 SwingNode 包裹,但仍是视频中显示的同一应用程序的一部分,文字质量完美无瑕。屏幕截图显示了应用程序的主窗口(底部),其中包括 SwingNode连同前面提到的 JFrame (最佳)。您可能需要放大“l”或“k”的直边才能看到为什么一个清晰而另一个模糊:
    Blurry SwingNode
    这只发生在 Windows 上。通过系统的字体预览程序在 Windows 上查看字体时,字体会使用 LCD 颜色进行抗锯齿处理。该应用程序使用灰度。我怀疑如果有办法强制渲染使用颜色进行抗锯齿而不是灰度,问题可能会消失。再说一次,在它自己的 JFrame 中运行时,没有问题,不使用 LCD 颜色。
    代码
    这是 JFrame 的代码有一个完美的渲染:
      private static class Flawless {
        private final XHTMLPanel panel = new XHTMLPanel();
        private final JFrame frame = new JFrame( "Single Page Demo" );
    
        private Flawless() {
          frame.getContentPane().add( new JScrollPane( panel ) );
          frame.pack();
          frame.setSize( 1024, 768 );
        }
    
        private void update( final org.w3c.dom.Document html ) {
          frame.setVisible( true );
    
          try {
            panel.setDocument( html );
          } catch( Exception ignored ) {
          }
        }
      }
    
    模糊代码 SwingNode有点复杂(参见 full listing ),但这里有一些相关的片段(注意 HTMLPanelXHTMLPanel 延伸只是为了在更新期间抑制一些不需要的自动滚动):
    private final HTMLPanel mHtmlRenderer = new HTMLPanel();
    private final SwingNode mSwingNode = new SwingNode();
    private final JScrollPane mScrollPane = new JScrollPane( mHtmlRenderer );
    
    // ...
    
    final var context = getSharedContext();
    final var textRenderer = context.getTextRenderer();
    textRenderer.setSmoothingThreshold( 0 );
    
    mSwingNode.setContent( mScrollPane );
    
    // ...
    
    // The "preview pane" contains the SwingNode.
    final SplitPane splitPane = new SplitPane(
        getDefinitionPane().getNode(),
        getFileEditorPane().getNode(),
        getPreviewPane().getNode() );
    
    最小工作示例
    这是一个相当小的自包含示例:
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.embed.swing.SwingNode;
    import javafx.scene.Scene;
    import javafx.scene.control.SplitPane;
    import javafx.stage.Stage;
    import org.jsoup.Jsoup;
    import org.jsoup.helper.W3CDom;
    import org.xhtmlrenderer.simple.XHTMLPanel;
    
    import javax.swing.*;
    
    import static javax.swing.SwingUtilities.invokeLater;
    import static javax.swing.UIManager.getSystemLookAndFeelClassName;
    import static javax.swing.UIManager.setLookAndFeel;
    
    public class FlyingSourceTest extends Application {
      private final static String HTML = "<!DOCTYPE html><html><head" +
          "><style type='text/css'>body{font-family:serif; background-color: " +
          "#fff; color:#454545;}</style></head><body><p style=\"font-size: " +
          "300px\">TEST</p></body></html>";
    
      public static void main( String[] args ) {
        Application.launch( args );
      }
    
      @Override
      public void start( Stage primaryStage ) {
        invokeLater( () -> {
          try {
            setLookAndFeel( getSystemLookAndFeelClassName() );
          } catch( Exception ignored ) {
          }
    
          primaryStage.setTitle( "Hello World!" );
    
          final var renderer = new XHTMLPanel();
          renderer.getSharedContext().getTextRenderer().setSmoothingThreshold( 0 );
          renderer.setDocument( new W3CDom().fromJsoup( Jsoup.parse( HTML ) ) );
    
          final var swingNode = new SwingNode();
          swingNode.setContent( new JScrollPane( renderer ) );
    
          final var root = new SplitPane( swingNode, swingNode );
    
          // ----------
          // Here be dragons? Using a StackPane, instead of a SplitPane, works.
          // ----------
          //StackPane root = new StackPane();
          //root.getChildren().add( mSwingNode );
    
          Platform.runLater( () -> {
            primaryStage.setScene( new Scene( root, 300, 250 ) );
            primaryStage.show();
          } );
        } );
      }
    }
    
    来自最小工作示例的模糊捕获;
    放大显示字母边缘被严重抗锯齿而不是鲜明的对比:
    Blurry
    使用 JLabel也表现出相同的模糊渲染:
      final var label = new JLabel( "TEST" );
      label.setFont( label.getFont().deriveFont( Font.BOLD, 128f ) );
    
      final var swingNode = new SwingNode();
      swingNode.setContent( label );
    
    尝试
    以下是我尝试消除模糊的大部分方法。
    java
    在 Java 方面,someone suggested使用以下方法运行应用程序:
    -Dawt.useSystemAAFontSettings=off
    -Dswing.aatext=false
    
    无文rendering hints有帮助。
    设置SwingNode的内容内SwingUtilities.invokeLater没有效果。
    JavaFX
    别人 mentioned关闭缓存有帮助,但这是针对 JavaFX ScrollPane , 不是 SwingNode 中的一个.它没有用。JScrollPane包含在 SwingNode将其对齐 X 和对齐 Y 分别设置为 0.5 和 0.5。确保半像素偏移为 recommended elsewhere .我无法想象设置 Scene使用 StrokeType.INSIDE会有所作为,尽管我确实尝试使用 1 的笔触宽度无济于事。
    飞碟
    飞碟有多个configuration options .各种设置组合包括:
    java -Dxr.text.fractional-font-metrics=true \
         -Dxr.text.aa-smoothing-level=0 \
         -Dxr.image.render-quality=java.awt.RenderingHints.VALUE_INTERPOLATION_BICUBIC
         -Dxr.image.scale=HIGH \
         -Dxr.text.aa-rendering-hint=VALUE_TEXT_ANTIALIAS_GASP -jar ...
    
    xr.image.设置只影响 FlyingSaucer 渲染的图像,而不影响 FlyingSaucer 的输出如何在 SwingNode 中由 JavaFX 渲染。 .
    CSS 使用磅来表示字体大小。
    研究
  • https://stackoverflow.com/a/26227562/59087 - 看起来一些解决方案可能会有所帮助。
  • https://bugs.openjdk.java.net/browse/JDK-8089499 -- 似乎不适用,因为这是使用 SwingNodeJScrollPane .
  • https://stackoverflow.com/a/24124020/59087 -- 可能不相关,因为没有使用 XML 场景构建器。
  • https://www.cs.mcgill.ca/media/tech_reports/42_Lessons_Learned_in_Migrating_from_Swing_to_JavaFX_LzXl9Xv.pdf -- 第 8 页描述了移动 0.5 像素,但如何移动?
  • https://dlsc.com/2014/07/17/javafx-tip-9-do-not-mix-swing-javafx/ -- 建议不要混合使用 JavaFX 和 Swing,但转向纯 Swing 不是一种选择:我会尽快用另一种语言重写应用程序。

  • 作为针对 OpenJDK/JavaFX 的错误被接受:
  • https://bugs.openjdk.java.net/browse/JDK-8252255

  • JDK 和 JRE
    使用 Bellsoft的 OpenJDK 与 JavaFX 捆绑在一起。据我所知,OpenJDK 支持 Freetype 已经有一段时间了。此外,该字体在 Linux 上看起来很棒,所以它可能不是 JDK。
    屏幕
    以下屏幕规范显示了这个问题,但其他人(毫无疑问,在不同的显示器和分辨率上观看)已经提到了这个问题。
  • 15.6"4:3 高清 (1366x768)
  • 全高清 (1920x1080)
  • 广视角LED背光
  • ASUS n56v


  • 为什么飞碟的XHTMLPanel当包裹在 SwingNode 内时在 Windows 上变得模糊,但仍显示相同的 XHTMLPanelJFrame在同一个 JavaFX 应用程序中运行看起来很清晰?如何解决问题?
    问题涉及SplitPane .

    最佳答案

    尽管我不得不承认我不了解 FlyingSaucer 及其 API,但您可以尝试一些选项。
    FlyingSaucer 有不同的渲染器。因此,可以通过使用此库来完全避免 Swing/AWT 渲染,以便直接在 JavaFX 中进行所有渲染。 https://github.com/jfree/fxgraphics2d
    另一种可能性是让 FlyingSaucer 渲染成可以通过直接缓冲区非常有效地在 JavaFX 中显示的图像。在此处查看我的存储库中的 AWTImage 代码:https://github.com/mipastgt/JFXToolsAndDemos

    关于java - Windows 上 JavaFX 中 SwingNode 的模糊渲染,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63444771/

    相关文章:

    java - Jslider 应该在停止时触发更改事件

    java - 重写 AbstractButton.getIcon 的问题

    java - 将 JProgressBar 与下载进程连接

    java - 自定义组件 JavaFX 的自定义操作发射器属性 - 如何?

    javafx - 向列表单元格添加投影后悬停和选择事件被窃听

    java - 在 IntelliJ 中导入之前组织静态导入

    java - 如何在 Restful Web 服务中发送数组中的参数列表

    java - 架构和项目组织

    java - 调用从 Java 返回字符串的 Haskell 函数

    java - ObservableList 中的结果集