Windows 上 JavaFX 中 SwingNode 的模糊渲染
Blurry render of SwingNode in JavaFX on Windows
概览
在 JavaFX 应用程序中使用 FlyingSaucer,出于各种原因避免 WebView:
- 不提供直接API访问其同步行为的滚动条;
- 捆绑Java脚本,这对我的用例来说是一个巨大的膨胀;和
- 运行 Windows 失败。
FlyingSaucer 使用 Swing,这需要将其 XHTMLPanel
(JPanel
的子类)包装在 SwingNode
中以与 JavaFX 一起使用。一切都很好,应用程序实时呈现 Markdown,并且响应迅速。这是应用程序 运行ning 在 Linux.
上的 demo video
问题
Windows 上的文本呈现模糊。当 运行 在 JFrame
中时,没有被 SwingNode
包裹,但仍然是视频中显示的同一应用程序的一部分,文本的质量是完美的。屏幕截图显示了应用程序的主要 window(底部),其中包括 SwingNode
以及前面提到的 JFrame
(顶部)。您可能需要放大“l”或“k”的直边才能看到为什么一个清晰而另一个模糊:
这只发生在 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),但这里有一些相关的片段(请注意 HTMLPanel
扩展自 XHTMLPanel
只是为了在更新期间抑制一些不需要的自动滚动):
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();
} );
} );
}
}
最小工作示例中的模糊捕获;
放大显示字母边缘严重抗锯齿而不是形成鲜明对比:
使用 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
None 的文字 rendering hints 有帮助。
在SwingUtilities.invokeLater
中设置SwingNode
的内容无效。
Java外汇
其他人 mentioned 认为关闭缓存有帮助,但那是针对 JavaFX ScrollPane
,而不是 SwingNode
中的一个。没用。
SwingNode
包含的 JScrollPane
的对齐 X 和对齐 Y 分别设置为 0.5 和 0.5。确保半像素偏移量为 recommended elsewhere。我无法想象将 Scene
设置为使用 StrokeType.INSIDE
会有什么不同,尽管我尝试使用 1 的笔画宽度无济于事。
飞碟
FlyingSaucer 有多个 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 渲染的图像,而不影响 JavaFX 在 SwingNode
.
中如何渲染 FlyingSaucer 的输出
CSS 使用 磅 字体大小。
研究
- -- 看起来一些解决方案可能会有帮助。
- https://bugs.openjdk.java.net/browse/JDK-8089499 -- 似乎不适用,因为这是使用
SwingNode
和 JScrollPane
.
- -- 可能不相关,因为没有 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 的错误:
JDK & JRE
使用 Bellsoft 的 OpenJDK 与 JavaFX 捆绑。据我所知,OpenJDK 支持 Freetype 已有一段时间了。此外,字体在 Linux 上看起来很棒,所以它可能不是 JDK.
屏幕
以下屏幕规格显示了这个问题,但其他人(无疑是在不同的显示器和分辨率上查看)提到了这个问题。
- 15.6" 4:3 高清 (1366x768)
- 全高清 (1920x1080)
- 宽视角 LED 背光灯
- ASUS n56v
问题
为什么 FlyingSaucer 的 XHTMLPanel
包裹在 SwingNode
上时在 Windows 上变得模糊,但在 JFrame
[=192] 上显示相同的 XHTMLPanel
=]ning 在同一个 JavaFX 应用程序中显得清晰?如何解决问题?
问题涉及SplitPane
.
尽管我不得不承认我不了解 FlyingSaucer 及其 API。
,但您可以尝试一些选项
FlyingSaucer 有不同的渲染器。因此,可以通过使用此库来完全避免 Swing/AWT 渲染,以便直接在 JavaFX 中进行所有渲染。 https://github.com/jfree/fxgraphics2d
另一种可能性是让 FlyingSaucer 渲染成可以通过直接缓冲区非常有效地在 JavaFX 中显示的图像。请在此处查看我存储库中的 AWTImage 代码:https://github.com/mipastgt/JFXToolsAndDemos
我自己无法重现该问题,因此您使用的 JDK/JavaFX 版本组合可能存在一些问题。也有可能只有显示尺寸和屏幕缩放比例的特定组合才会出现此问题。
我的设置如下:
- JavaFX 14
- OpenJDK 14
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
import org.xhtmlrenderer.simple.XHTMLPanel;
import javax.swing.*;
public class FlyingSourceTest extends Application {
private final static String HTML_PREFIX = "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<body>\n";
private static final String HTML_CONTENT =
"<p style=\"font-size:500px\">TEST</p>";
private final static String HTML_SUFFIX = "<p style='height=2em'> </p></body></html>";
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
primaryStage.setTitle("Hello World!");
XHTMLPanel mHtmlRenderer = new XHTMLPanel();
mHtmlRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);
SwingNode mSwingNode = new SwingNode();
JScrollPane mScrollPane = new JScrollPane(mHtmlRenderer);
String htmlContent = HTML_PREFIX + HTML_CONTENT + HTML_SUFFIX;
Document jsoupDoc = Jsoup.parse(htmlContent);
org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(jsoupDoc);
mHtmlRenderer.setDocument(w3cDoc);
mSwingNode.setContent(mScrollPane);
// AnchorPane anchorPane = new AnchorPane();
// anchorPane.getChildren().add(mSwingNode);
// AnchorPane.setTopAnchor(mSwingNode, 0.5);
// AnchorPane.setLeftAnchor(mSwingNode, 0.5);
// mSwingNode.setTranslateX(0.5);
// mSwingNode.setTranslateY(0.5);
StackPane root = new StackPane();
root.getChildren().add(mSwingNode);
Platform.runLater(() -> {
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
});
});
}
}
该问题已被接受为针对 OpenJDK/JavaFX 的错误:
Mipa 的两个建议在实践中都行不通。 FlyingSaucer 与 JScrollPane
紧密集成,排除了强制 FlyingSaucer 渲染到 JavaFX-based 面板的可能性。
另一种可能是反其道而行之:创建一个Swing应用程序并嵌入JavaFX控件,例如使用JFXPanel;然而,在 bug 被解决之前接受模糊的行为似乎更为谨慎。
概览
在 JavaFX 应用程序中使用 FlyingSaucer,出于各种原因避免 WebView:
- 不提供直接API访问其同步行为的滚动条;
- 捆绑Java脚本,这对我的用例来说是一个巨大的膨胀;和
- 运行 Windows 失败。
FlyingSaucer 使用 Swing,这需要将其 XHTMLPanel
(JPanel
的子类)包装在 SwingNode
中以与 JavaFX 一起使用。一切都很好,应用程序实时呈现 Markdown,并且响应迅速。这是应用程序 运行ning 在 Linux.
问题
Windows 上的文本呈现模糊。当 运行 在 JFrame
中时,没有被 SwingNode
包裹,但仍然是视频中显示的同一应用程序的一部分,文本的质量是完美的。屏幕截图显示了应用程序的主要 window(底部),其中包括 SwingNode
以及前面提到的 JFrame
(顶部)。您可能需要放大“l”或“k”的直边才能看到为什么一个清晰而另一个模糊:
这只发生在 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),但这里有一些相关的片段(请注意 HTMLPanel
扩展自 XHTMLPanel
只是为了在更新期间抑制一些不需要的自动滚动):
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();
} );
} );
}
}
最小工作示例中的模糊捕获; 放大显示字母边缘严重抗锯齿而不是形成鲜明对比:
使用 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
None 的文字 rendering hints 有帮助。
在SwingUtilities.invokeLater
中设置SwingNode
的内容无效。
Java外汇
其他人 mentioned 认为关闭缓存有帮助,但那是针对 JavaFX ScrollPane
,而不是 SwingNode
中的一个。没用。
SwingNode
包含的 JScrollPane
的对齐 X 和对齐 Y 分别设置为 0.5 和 0.5。确保半像素偏移量为 recommended elsewhere。我无法想象将 Scene
设置为使用 StrokeType.INSIDE
会有什么不同,尽管我尝试使用 1 的笔画宽度无济于事。
飞碟
FlyingSaucer 有多个 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 渲染的图像,而不影响 JavaFX 在 SwingNode
.
CSS 使用 磅 字体大小。
研究
- -- 看起来一些解决方案可能会有帮助。
- https://bugs.openjdk.java.net/browse/JDK-8089499 -- 似乎不适用,因为这是使用
SwingNode
和JScrollPane
. - -- 可能不相关,因为没有 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 的错误:
JDK & JRE
使用 Bellsoft 的 OpenJDK 与 JavaFX 捆绑。据我所知,OpenJDK 支持 Freetype 已有一段时间了。此外,字体在 Linux 上看起来很棒,所以它可能不是 JDK.
屏幕
以下屏幕规格显示了这个问题,但其他人(无疑是在不同的显示器和分辨率上查看)提到了这个问题。
- 15.6" 4:3 高清 (1366x768)
- 全高清 (1920x1080)
- 宽视角 LED 背光灯
- ASUS n56v
问题
为什么 FlyingSaucer 的 XHTMLPanel
包裹在 SwingNode
上时在 Windows 上变得模糊,但在 JFrame
[=192] 上显示相同的 XHTMLPanel
=]ning 在同一个 JavaFX 应用程序中显得清晰?如何解决问题?
问题涉及SplitPane
.
尽管我不得不承认我不了解 FlyingSaucer 及其 API。
,但您可以尝试一些选项FlyingSaucer 有不同的渲染器。因此,可以通过使用此库来完全避免 Swing/AWT 渲染,以便直接在 JavaFX 中进行所有渲染。 https://github.com/jfree/fxgraphics2d
另一种可能性是让 FlyingSaucer 渲染成可以通过直接缓冲区非常有效地在 JavaFX 中显示的图像。请在此处查看我存储库中的 AWTImage 代码:https://github.com/mipastgt/JFXToolsAndDemos
我自己无法重现该问题,因此您使用的 JDK/JavaFX 版本组合可能存在一些问题。也有可能只有显示尺寸和屏幕缩放比例的特定组合才会出现此问题。
我的设置如下:
- JavaFX 14
- OpenJDK 14
import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingNode;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;
import org.jsoup.Jsoup;
import org.jsoup.helper.W3CDom;
import org.jsoup.nodes.Document;
import org.xhtmlrenderer.simple.XHTMLPanel;
import javax.swing.*;
public class FlyingSourceTest extends Application {
private final static String HTML_PREFIX = "<!DOCTYPE html>\n"
+ "<html>\n"
+ "<body>\n";
private static final String HTML_CONTENT =
"<p style=\"font-size:500px\">TEST</p>";
private final static String HTML_SUFFIX = "<p style='height=2em'> </p></body></html>";
public static void main(String[] args) {
Application.launch(args);
}
@Override
public void start(Stage primaryStage) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException e) {
e.printStackTrace();
}
primaryStage.setTitle("Hello World!");
XHTMLPanel mHtmlRenderer = new XHTMLPanel();
mHtmlRenderer.getSharedContext().getTextRenderer().setSmoothingThreshold(0);
SwingNode mSwingNode = new SwingNode();
JScrollPane mScrollPane = new JScrollPane(mHtmlRenderer);
String htmlContent = HTML_PREFIX + HTML_CONTENT + HTML_SUFFIX;
Document jsoupDoc = Jsoup.parse(htmlContent);
org.w3c.dom.Document w3cDoc = new W3CDom().fromJsoup(jsoupDoc);
mHtmlRenderer.setDocument(w3cDoc);
mSwingNode.setContent(mScrollPane);
// AnchorPane anchorPane = new AnchorPane();
// anchorPane.getChildren().add(mSwingNode);
// AnchorPane.setTopAnchor(mSwingNode, 0.5);
// AnchorPane.setLeftAnchor(mSwingNode, 0.5);
// mSwingNode.setTranslateX(0.5);
// mSwingNode.setTranslateY(0.5);
StackPane root = new StackPane();
root.getChildren().add(mSwingNode);
Platform.runLater(() -> {
primaryStage.setScene(new Scene(root, 300, 250));
primaryStage.show();
});
});
}
}
该问题已被接受为针对 OpenJDK/JavaFX 的错误:
Mipa 的两个建议在实践中都行不通。 FlyingSaucer 与 JScrollPane
紧密集成,排除了强制 FlyingSaucer 渲染到 JavaFX-based 面板的可能性。
另一种可能是反其道而行之:创建一个Swing应用程序并嵌入JavaFX控件,例如使用JFXPanel;然而,在 bug 被解决之前接受模糊的行为似乎更为谨慎。