JDK-8289125 : Printing JavaFX node with complex clip is pixelated.
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: openjfx18
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: os_x
  • CPU: aarch64
  • Submitted: 2022-06-23
  • Updated: 2022-07-22
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
Mac OS Big Sur

A DESCRIPTION OF THE PROBLEM :
On macOS Big Sur, running JDK 17 and JavaFX 18, when you have an irregular clipping region, the clipped image ends up badly pixelated when printed.

The pixelation happens both on actual paper (on my Epson printer), as well as when saved to a PDF (using macOSs innate "Save to PDF" feature).

If you have a clip region with a Rectangle, there is no pixelation, everything looks smooth and crisp.

Note, that it must be a Rectangle, a rectangular Polygon has problems as well.

Seems to me that its rendering the node in to the "PDF resolution", which is 72 DPI (not actual printer resolution), and then masking the region using a bitmap operation rather than actually clipping the content to the irregular border. But it special cases the Rectangle and clips to that.

I have a post on StackOverflow with some images.

https://stackoverflow.com/questions/72723418/printing-pixelates-when-clipped



STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Run the attached program. Click the Print button. Either Save to PDF, or Print to paper and observe the results.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
I was expecting to see the text and lines in the circle region to be in the same resolution as the text and lines in the rectangle region.
ACTUAL -
The text and lines within the circle region are at a lower resolution than the text and lines in the rectangular region.

---------- BEGIN SOURCE ----------
package pkg;

import javafx.application.Application;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Line;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;

public class App extends Application {

    Pane printPane;

    @Override
    public void start(Stage stage) {
        var scene = new Scene(getView(), 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

    private Pane getView() {
        var flowPane = new FlowPane(getCirclePane(), getRectPane());
        var b = new Button("Print");
        printPane = flowPane;
        b.setOnAction((t) -> {
            var job = PrinterJob.createPrinterJob();

            if (job != null && job.showPrintDialog(null)) {
                if (job.printPage(printPane)) {
                    job.endJob();
                }
            }

        });
        var bp = new BorderPane();
        bp.setCenter(flowPane);
        bp.setBottom(b);

        return bp;
    }

    private Pane getCirclePane() {
        var pane = new Pane();
        var circle = new Circle(100, 100, 45);
        pane.setClip(circle);
        var text = new Text(75, 100, "Circle");
        pane.getChildren().add(text);
        for (int i = 0; i < 10; i++) {
            double ox = i * 25;
            pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
            pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
        }

        return pane;
    }

    private Pane getRectPane() {
        var pane = new Pane();
        var rect = new Rectangle(50, 50, 200, 125);
        pane.setClip(rect);
        var text = new Text(125, 100, "Rectangle");
        pane.getChildren().add(text);
        for (int i = 0; i < 10; i++) {
            double ox = i * 25;
            pane.getChildren().add(new Line(ox, 200, ox + 25, 0));
            pane.getChildren().add(new Line(ox, 0, ox + 25, 200));
        }

        return pane;
    }

}

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

FREQUENCY : always



Comments
This has been true all the way back to JDK 8 (as I verified after getting rid of all the var usages). It is printed as an image as seen below because when we get into and then uses PrEffectHelper.render with a Blend operation to draw the clipped rendering. What we probably need to do - so long as it is a 2D transform - and we detect printer graphics is get the clip shape out of the clip node and set that on the printer graphics and then render. But this will require some work because the PrismGraphics doesn't work like that. So J2DPrismGraphics only has rect support. And on top of that I am not sure what the Java 2D's QuartzRenderer does with a complex clip anyway .. that code came from Apple. java.lang.Exception: Stack trace at java.base/java.lang.Thread.dumpStack(Thread.java:2282) at java.desktop/sun.java2d.OSXSurfaceData.blitImage(OSXSurfaceData.java:1108) at java.desktop/sun.java2d.CRenderer.blitImage(CRenderer.java:461) at java.desktop/sun.java2d.CRenderer.copyImage(CRenderer.java:479) at java.desktop/sun.java2d.CRenderer.copyImage(CRenderer.java:545) at java.desktop/sun.java2d.pipe.ValidatePipe.copyImage(ValidatePipe.java:196) at java.desktop/sun.java2d.SunGraphics2D.copyImage(SunGraphics2D.java:3336) at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3507) at java.desktop/sun.java2d.SunGraphics2D.drawImage(SunGraphics2D.java:3453) at java.desktop/sun.print.ProxyGraphics2D.drawImage(ProxyGraphics2D.java:1020) at javafx.graphics@20-internal/com.sun.prism.j2d.J2DPrismGraphics.drawTexture(J2DPrismGraphics.java:977) at javafx.graphics@20-internal/com.sun.scenario.effect.impl.prism.PrEffectHelper.render(PrEffectHelper.java:176) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGNode.renderClip(NGNode.java:2302) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2066) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGGroup.renderContent(NGGroup.java:270) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGRegion.renderContent(NGRegion.java:579) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGNode.doRender(NGNode.java:2072) at javafx.graphics@20-internal/com.sun.javafx.sg.prism.NGNode.render(NGNode.java:1964) at javafx.graphics@20-internal/com.sun.prism.j2d.print.J2DPrinterJob$J2DPageable.printNode(J2DPrinterJob.java:1156) at javafx.graphics@20-internal/com.sun.prism.j2d.print.J2DPrinterJob$J2DPageable.print(J2DPrinterJob.java:1146) at java.desktop/sun.lwawt.macosx.CPrinterJob$4.run(CPrinterJob.java:735) at java.desktop/sun.lwawt.macosx.CPrinterJob.printToPathGraphics(CPrinterJob.java:749) at java.desktop/sun.lwawt.macosx.CPrinterJob.printLoop(Native Method) at java.desktop/sun.lwawt.macosx.CPrinterJob.print(CPrinterJob.java:367) at javafx.graphics@20-internal/com.sun.prism.j2d.print.J2DPrinterJob$PrintJobRunnable.run(J2DPrinterJob.java:880) at java.base/java.lang.Thread.run(Thread.java:1589) Java_sun_java2d_CRenderer_doImage
22-07-2022

In cases where FX caches some rendering for the on-screen rendering context and we detect that the current graphics is a PrinterGraphics, then we invalidate/ignore that cached rendering and render for the printer. This sounds like such a case (still to be proven) If so, we either we missed this case (always) or some recent optimisation for on-screen did not add that check.
24-06-2022