Verschwommenes Rendern von SwingNode in JavaFX unter Windows
Überblick
Verwenden von FlyingSaucer in einer JavaFX-Anwendung, um WebView aus verschiedenen Gründen zu vermeiden:
- bietet keinen direkten API-Zugriff auf die Bildlaufleisten für synchrones Verhalten;
- bündelt JavaScript, was für meinen Anwendungsfall eine große Aufblähung darstellt; und
- konnte unter Windows nicht ausgeführt werden.
FlyingSaucer verwendet Swing, für das es erforderlich ist, seine XHTMLPanel(eine Unterklasse von JPanel) in a SwingNodezu verpacken , um sie zusammen mit JavaFX zu verwenden. Alles funktioniert hervorragend, die Anwendung rendert Markdown in Echtzeit und reagiert. Hier ist ein Demo-Video der Anwendung, die unter Linux ausgeführt wird.
Problem
Die Textwiedergabe unter Windows ist verschwommen. Wenn Sie in einer JFrameDatei arbeiten, die nicht von einer Datei umschlossen ist SwingNode, aber dennoch Teil derselben im Video gezeigten Anwendung ist, ist die Qualität des Textes einwandfrei. Die Bildschirmaufnahme zeigt das Hauptfenster der Anwendung (unten), das das SwingNodezusammen mit dem oben genannten JFrame(oben) enthält. Möglicherweise müssen Sie in die gerade Kante des "l" oder "k" zoomen, um zu sehen, warum das eine scharf und das andere verschwommen ist:
Dies geschieht nur unter Windows. Wenn Sie die Schriftart unter Windows über das Schriftartenvorschau-Programm des Systems anzeigen, werden die Schriftarten mithilfe von LCD-Farben antialiasiert. Die Anwendung verwendet Graustufen. Ich vermute, dass das Problem möglicherweise verschwindet, wenn es eine Möglichkeit gibt, das Rendern zu zwingen, Farbe für Antialiasing anstelle von Graustufen zu verwenden. Andererseits JFramegibt es kein Problem , wenn es alleine läuft , und es werden keine LCD-Farben verwendet.
Code
Hier ist der Code für den JFrame, der ein perfektes Rendering hat:
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 ) {
}
}
}
Der Code für die Unschärfe SwingNodeist etwas komplizierter (siehe vollständige Auflistung ), aber hier sind einige relevante Ausschnitte (Hinweis, der HTMLPanelsich XHTMLPanelnur erstreckt , um unerwünschtes Autoscrolling während Updates zu unterdrücken):
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() );
Minimales Arbeitsbeispiel
Hier ist ein ziemlich minimales, in sich geschlossenes Beispiel:
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();
} );
} );
}
}
Verschwommene Erfassung anhand des minimalen Arbeitsbeispiels; Beim Vergrößern werden die Buchstabenkanten stärker antialiasiert als scharfe Kontraste:
Die Verwendung von a JLabelzeigt auch das gleiche Fuzzy-Rendering:
final var label = new JLabel( "TEST" );
label.setFont( label.getFont().deriveFont( Font.BOLD, 128f ) );
final var swingNode = new SwingNode();
swingNode.setContent( label );
Versuche
Hier sind die meisten Möglichkeiten, wie ich versucht habe, die Unschärfe zu entfernen.
Java
Auf der Java-Seite schlug jemand vor , die Anwendung folgendermaßen auszuführen:
-Dawt.useSystemAAFontSettings=off
-Dswing.aatext=false
Keiner der Hinweise zum Rendern von Text hat geholfen.
Das Einstellen des Inhalts des SwingNodeInneren SwingUtilities.invokeLaterhat keine Auswirkung.
JavaFX
Jemand anderes erwähnte, dass das Deaktivieren des Cachings hilfreich war, aber das war für ein JavaFX ScrollPane, nicht für eines innerhalb eines SwingNode. Es hat nicht funktioniert.
Die JScrollPaneenthaltenen durch das SwingNodehat seine Ausrichtung X und Y Ausrichtung auf 0,5 bzw. 0,5. An anderer Stelle wird empfohlen, einen Versatz von einem halben Pixel sicherzustellen . Ich kann mir nicht vorstellen, dass das Einstellen der Scenezu verwendenden Funktion StrokeType.INSIDEeinen Unterschied machen würde, obwohl ich vergeblich versucht habe, eine Strichbreite von 1 zu verwenden.
Fliegende Untertasse
FlyingSaucer bietet eine Reihe von Konfigurationsoptionen . Verschiedene Kombinationen von Einstellungen umfassen:
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 ...
Die xr.image.Einstellungen wirken sich nur auf Bilder aus, die von FlyingSaucer gerendert werden, und nicht darauf, wie die Ausgabe von FlyingSaucer von JavaFX innerhalb von FlyingSaucer gerendert wird SwingNode.
Das CSS verwendet Punkte für die Schriftgrößen.
Forschung
- https://stackoverflow.com/a/26227562/59087 - Es sieht so aus, als ob einige Lösungen hilfreich sein könnten.
- https://bugs.openjdk.java.net/browse/JDK-8089499- scheint nicht zuzutreffen, da dies
SwingNodeund verwendetJScrollPane. - https://stackoverflow.com/a/24124020/59087 - wahrscheinlich nicht relevant, da kein XML-Szenen-Builder verwendet wird.
- https://www.cs.mcgill.ca/media/tech_reports/42_Lessons_Learned_in_Migrating_from_Swing_to_JavaFX_LzXl9Xv.pdf - Seite 8 beschreibt die Verschiebung um 0,5 Pixel, aber wie?
- https://dlsc.com/2014/07/17/javafx-tip-9-do-not-mix-swing-javafx/ - schlägt vor, JavaFX und Swing nicht zu mischen, aber die Umstellung auf reines Swing ist keine Option: Ich würde die App eher in einer anderen Sprache umschreiben.
Als Fehler gegen OpenJDK / JavaFX akzeptiert:
- https://bugs.openjdk.java.net/browse/JDK-8252255
JDK & JRE
Verwenden von Bellsofts OpenJDK mit JavaFX-Bundle. Meines Wissens hat das OpenJDK seit einiger Zeit Freetype-Unterstützung. Außerdem sieht die Schriftart unter Linux großartig aus, sodass es sich wahrscheinlich nicht um das JDK handelt.
Bildschirm
Die folgenden Bildschirmspezifikationen zeigen das Problem, aber andere Personen (zweifellos auf verschiedenen Monitoren und Auflösungen) haben das Problem erwähnt.
- 15,6 "4: 3 HD (1366 x 768)
- Full HD (1920 x 1080)
- Weitwinkel-LED-Hintergrundbeleuchtung
- ASUS n56v
Frage
Warum funktioniert FlyingSaucer die , XHTMLPanelwenn sie innerhalb gewickelt SwingNodewerden verschwommen auf Windows, und doch Anzeige desselben XHTMLPanelin ein JFramein der gleichen JavaFX laufenden Anwendung erscheint knackig? Wie kann das Problem behoben werden?
Das Problem betrifft SplitPane.
Antworten
Es gibt einige Optionen, die Sie ausprobieren könnten, obwohl ich zugeben muss, dass ich FlyingSaucer und seine API nicht kenne.
FlyingSaucer hat verschiedene Renderer. Daher ist es möglicherweise möglich, das Swing / AWT-Rendering vollständig zu vermeiden, indem Sie stattdessen diese Bibliothek verwenden, um das gesamte Rendering direkt in JavaFX durchzuführen.https://github.com/jfree/fxgraphics2d
Eine andere Möglichkeit besteht darin, FlyingSaucer in ein Bild rendern zu lassen, das über direkte Puffer sehr effizient in JavaFX angezeigt werden kann. Den AWTImage-Code in meinem Repository finden Sie hier:https://github.com/mipastgt/JFXToolsAndDemos
Ich konnte das Problem nicht selbst reproduzieren, daher liegt möglicherweise ein Problem in der Kombination der von Ihnen verwendeten JDK / JavaFX-Version vor. Es ist auch möglich, dass das Problem nur bei einer bestimmten Kombination aus Anzeigegröße und Bildschirmskalierung auftritt.
Mein Setup ist das folgende:
- 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();
});
});
}
}
Das Problem wurde als Fehler gegen OpenJDK / JavaFX akzeptiert:
- https://bugs.openjdk.java.net/browse/JDK-8252255
Keiner der Vorschläge von Mipa würde in der Praxis funktionieren. FlyingSaucer ist eng in a integriert JScrollPane, was die Möglichkeit ausschließt, FlyingSaucer zum Rendern auf einem JavaFX-basierten Panel zu zwingen.
Eine andere Möglichkeit besteht darin, in die entgegengesetzte Richtung zu gehen: Erstellen Sie eine Swing-Anwendung und binden Sie JavaFX-Steuerelemente ein, z. B. mithilfe eines JFXPanel . Es erscheint jedoch vorsichtiger, das verschwommene Verhalten zu akzeptieren, bis der Fehler behoben ist.