Bad performance with Canvas and extensive clipping
Tom Schindl
tom.schindl at bestsolution.at
Sat May 24 13:54:36 UTC 2014
Hi,
another big difference when using the a BufferedImage is to that the
font rendering is catastrophic, hope to offend nobody. I'm not very good
a AWT maybe I made a dumb mistake?
See http://downloads.efxclipse.org/font_j2d_fx.png - the j2d font looks
completely blurred in contrast to the sharp JavaFX Canvas version in the
foreground.
Similar blurring happens when makeing screenshots of a canvas - I've
written a small sample application showing problems I am seeing which
gets me to an image as in this link
http://downloads.efxclipse.org/screen_compare.png.
Could I somehow use the javafx font-rendering push it to a bitmap and
draw it on the buffered image?
Anyways those are all only workarounds for javafx canvas inefficiencies
that e.g. awt does not have.
> package application;
>
> import java.awt.Graphics2D;
> import java.awt.RenderingHints;
> import java.awt.image.BufferedImage;
>
> import javafx.application.Application;
> import javafx.embed.swing.SwingFXUtils;
> import javafx.geometry.VPos;
> import javafx.scene.Node;
> import javafx.scene.Scene;
> import javafx.scene.SnapshotParameters;
> import javafx.scene.canvas.Canvas;
> import javafx.scene.control.Label;
> import javafx.scene.image.ImageView;
> import javafx.scene.image.WritableImage;
> import javafx.scene.layout.HBox;
> import javafx.scene.layout.VBox;
> import javafx.scene.paint.Color;
> import javafx.scene.text.Font;
> import javafx.stage.Stage;
>
>
> public class Main extends Application {
> private WritableImage img;
>
> @Override
> public void start(Stage primaryStage) {
> try {
> HBox root = new HBox();
> Scene scene = new Scene(root,400,400);
> scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
>
> root.getChildren().add(createCanvas());
> root.getChildren().add(createBufferedCanvas());
> root.getChildren().add(new VBox(new ImageView(img), new Label("Snapshot")));
>
> primaryStage.setScene(scene);
> primaryStage.show();
> } catch(Exception e) {
> e.printStackTrace();
> }
> }
>
> private Node createBufferedCanvas() {
> VBox b = new VBox();
> b.setStyle("-fx-border-style: solid; -fx-border-width: 2px;");
> Canvas c = new Canvas(150, 150);
> BufferedImage img = new BufferedImage(150, 150, BufferedImage.TYPE_INT_ARGB);
> Graphics2D graphics = img.createGraphics();
> graphics.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,RenderingHints.VALUE_TEXT_ANTIALIAS_LCD_HRGB);
> graphics.setColor(java.awt.Color.BLACK);
> graphics.setFont(new java.awt.Font(Font.getDefault().getName(), java.awt.Font.PLAIN, 20));
> graphics.drawString("Hello World!", 0, 20);
> img.flush();
> c.getGraphicsContext2D().drawImage(SwingFXUtils.toFXImage(img, null),10,10);
>
> b.getChildren().add(c);
> b.getChildren().add(new Label("Buffered-Canvas"));
>
> return b;
> }
>
> private Node createCanvas() {
> VBox b = new VBox();
> b.setStyle("-fx-border-style: solid; -fx-border-width: 2px;");
> Canvas c = new Canvas(150, 150);
> c.getGraphicsContext2D().setFont(Font.font(Font.getDefault().getName(),20));
> c.getGraphicsContext2D().setTextBaseline(VPos.TOP);
> c.getGraphicsContext2D().fillText("Hello World", 10, 10);
>
> SnapshotParameters parameters = new SnapshotParameters();
> parameters.setFill(Color.TRANSPARENT);
> img = c.snapshot(parameters,null);
>
> b.getChildren().add(c);
> b.getChildren().add(new Label("FX-Canvas"));
> return b;
> }
>
> public static void main(String[] args) {
> launch(args);
> }
> }
Tom
On 24.05.14 02:46, Tom Schindl wrote:
> Hi,
>
> As an experiment I've now written a SWT-GC implementation using a
> BufferedImage & Graphics2D and transfering the pixels over to JavaFX and
> the performance is as it is with native SWT.
>
> I always thought Canvas works similar to Image and one only draws pixels
> - looks like that is not the case, having a dep in my application
> java.awt is not what I'm aiming at but without acceptable performance in
> conjunction with clipping it looks like i have to go this route :-(
>
> Tom
>
> On 23.05.14 23:57, Tom Schindl wrote:
>> In the current usecase it is a rect all time but that's just in this special use case.
>>
>> I guess that rect clipping is the most common one so having an optimization for rects and a slow path for none rects might help.
>>
>> Tom
>>
>> Von meinem iPhone gesendet
>>
>>> Am 23.05.2014 um 23:35 schrieb Jim Graham <james.graham at oracle.com>:
>>>
>>> Are you clipping to an arbitrary path in all cases or just a rectangle? Unfortunately we only offer the arbitrary clip-to-current-path method that isn't optimized for basic rectangular clipping and it implements soft clipping.
>>>
>>> There is an outstanding tweak that we added faster clipping support for WebNode and we need to start using it for Node.setClipNode(non-rectangle) and Canvas, but we haven't implemented that yet. (https://javafx-jira.kenai.com/browse/RT-30107) It basically is a direct "render this texture through that other texture as a clip" operation instead of the current code that runs it through some Blend effect filters. It would definitely improve your run times, but I'm not sure how much.
>>>
>>> Even more savings could be had for rectangular clips if we provided some way to communicate them to the GC...
>>>
>>> ...jim
>>>
>>>> On 5/23/14 11:47 AM, Tom Schindl wrote:
>>>> Hi,
>>>>
>>>> Maybe as some of you might know I've been working since sometime on SWT
>>>> on JavaFX and to implement direct drawing operations we use JavaFX-Canvas.
>>>>
>>>> I've today tried to run a heavy direct drawing grid implementation and
>>>> it performed very bad because it makes heavy use of clipping.
>>>>
>>>> For a grid I've counted ~1500 clipping operations the library works
>>>> something like this:
>>>>
>>>> boolean activeClip;
>>>> Canvas canvas = new Canvas();
>>>>
>>>> public void setClipping(PathIterator pathIterator) {
>>>> GraphicsContext gc = canvas.getGraphicsContext2D();
>>>> if(activeClip) {
>>>> gc.restore();
>>>> activeClip= false;
>>>> }
>>>>
>>>> if( pathIterator == null ) {
>>>> return;
>>>> }
>>>>
>>>> activeClip = true;
>>>> float coords[] = new float[6];
>>>> gc.save();
>>>> gc.beginPath();
>>>>
>>>> float x = 0;
>>>> float y = 0;
>>>>
>>>>
>>>> gc.moveTo(0, 0);
>>>>
>>>> while( ! pathIterator.isDone() ) {
>>>> switch (pathIterator.currentSegment(coords)) {
>>>> case PathIterator.SEG_CLOSE:
>>>> gc.lineTo(x, y);
>>>> break;
>>>> case PathIterator.SEG_CUBICTO:
>>>> gc.bezierCurveTo(coords[0], coords[1], coords[2], coords[3],
>>>> coords[4], coords[5]);
>>>> break;
>>>> case PathIterator.SEG_LINETO:
>>>> gc.lineTo(coords[0], coords[1]);
>>>> break;
>>>> case PathIterator.SEG_MOVETO:
>>>> gc.moveTo(coords[0], coords[1]);
>>>> x = coords[0];
>>>> y = coords[1];
>>>> break;
>>>> case PathIterator.SEG_QUADTO:
>>>> gc.quadraticCurveTo(coords[0], coords[1], coords[2], coords[3]);
>>>> break;
>>>> default:
>>>> break;
>>>> }
>>>> pathIterator.next();
>>>> }
>>>>
>>>> gc.clip();
>>>> gc.closePath();
>>>> }
>>>>
>>>> Am I doing something ultimately wrong, totally wrong? Has anyone an idea
>>>> how I would work around the problem?
>>>>
>>>> Tom
>>>>
>
More information about the openjfx-dev
mailing list