Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8094374

Rendering Artifacts with Groups and Transformations

    Details

    • Type: Bug
    • Status: Closed
    • Priority: P2
    • Resolution: Fixed
    • Affects Version/s: 7u25, 7u40, 8
    • Fix Version/s: 8
    • Component/s: javafx
    • Labels:
      None

      Backports

        Description

        tested with JDK 1.7_15 and 1.7_25. This does not appear in JavaFX 8 (lombard) so far. This bug is related to RT-23351, however, the fix for this issue did not fix it and the workaround does seem to fix it in our application code, but not in the SSCCE that follows. This should have been fixed by T-24091 and needs to be backported to JavaFX 2.x

        consider the following application:

        == RenderingArtifactTest ==

        import javafx.application.Application;
        import javafx.beans.value.ChangeListener;
        import javafx.beans.value.ObservableValue;
        import javafx.event.ActionEvent;
        import javafx.event.EventHandler;
        import javafx.geometry.Bounds;
        import javafx.scene.Group;
        import javafx.scene.Scene;
        import javafx.scene.control.Button;
        import javafx.scene.layout.HBox;
        import javafx.scene.paint.Color;
        import javafx.scene.shape.Rectangle;
        import javafx.scene.transform.Scale;
        import javafx.scene.transform.Transform;
        import javafx.stage.Stage;

        public class RenderingArtifactTest extends Application {

          int step = 0;

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

          @Override
          public void start(Stage primaryStage) {

            Group root = new Group();
            final Group body = new Group();

            // rectangle that is bound to the bounds in parent of the body rectangle (similar to what ScenicView overlay does)
            final Rectangle bipRectangle = new Rectangle(500, 500, 90, 90);
            bipRectangle.setOpacity(0.5);
            bipRectangle.setFill(Color.YELLOW);

            // this is necessary to reproduce the bug. setting the bipRectangle bounds without a listener, for example in the buttons onActions, won't do it.
            body.boundsInParentProperty().addListener(new ChangeListener<Bounds>() {
              @Override
              public void changed(ObservableValue<? extends Bounds> observableValue, Bounds bounds, Bounds bounds2) {
                bipRectangle.setX(bounds2.getMinX());
                bipRectangle.setY(bounds2.getMinY());
                bipRectangle.setWidth(bounds2.getWidth());
                bipRectangle.setHeight(bounds2.getHeight());
              }
            });

            root.getChildren().addAll(body, bipRectangle);

            int rectWidth = 40;
            int rectheight = 40;

            int offset = 500;

            int rectLowerX = offset;
            int rectLowerY = offset;
            int rectUpperX = offset + 50;
            int rectUpperY = offset + 50;

            // 2 rectangles are the contents of the body-group
            final Rectangle breaker1 = new Rectangle(rectLowerX, rectLowerY, rectWidth, rectheight);
            final Transform translateTransformation1 = Transform.affine(1, 0, 0, 1, -70, -70);
            final Transform translateTransformation1Inv = Transform.affine(1, 0, 0, 1, 70, 70);

            final Rectangle breaker2 = new Rectangle(rectUpperX, rectUpperY, rectWidth, rectheight);
            final Transform translateTransformation4 = Transform.affine(1, 0, 0, 1, 70, 70);
            final Transform translateTransformation4Inv = Transform.affine(1, 0, 0, 1, -70, -70);

            body.getChildren().addAll(breaker1, breaker2);

            HBox buttons = new HBox(10);
            Button stepButton = new Button("step");
            Button addScaleDownTransform = new Button("scale down");
            Button addScaleUpTransform = new Button("scale up");
            Button moveAwayButton = new Button("move away");
            Button moveCloserButton = new Button("move closer");
            buttons.getChildren().addAll(stepButton, addScaleDownTransform, addScaleUpTransform, moveAwayButton, moveCloserButton);
            root.getChildren().add(buttons);

            /*
             button that performs the necessary steps to reproduce the bug.
             to reproduce the bug by the other buttons manually:
             1) scale down
             2) move away (make the group bigger)
             3) scale down again
              -> rendering artifacts appear
             */
            stepButton.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent actionEvent) {
                switch (step) {
                  case 0:
                    body.getTransforms().add(new Scale(.5, .5));
                    break;
                  case 1:
                    breaker1.getTransforms().add(translateTransformation1);
                    breaker1.getTransforms().add(translateTransformation1);
                    breaker2.getTransforms().add(translateTransformation4);
                    breaker2.getTransforms().add(translateTransformation4);
                    break;
                  case 2:
                    body.getTransforms().add(new Scale(.5, .5));
                    break;
                }
                step++;
              }
            });

            // adds a scale transform to the group the makes it smaller
            addScaleDownTransform.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent actionEvent) {
                body.getTransforms().add(new Scale(.5, .5));
              }
            });

            // adds the inverse transform of the scaling transform to the group
            addScaleUpTransform.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent actionEvent) {
                body.getTransforms().add(new Scale(2, 2));
              }
            });

            // adds a translate transform to the rectangles the moves them away from each other (thus making the group bigger)
            moveAwayButton.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent actionEvent) {

                breaker1.getTransforms().add(translateTransformation1);
                breaker2.getTransforms().add(translateTransformation4);
              }
            });

            // adds a transform to the rectangles that is the inverse to the other translate transform (thus making the group smaller)
            moveCloserButton.setOnAction(new EventHandler<ActionEvent>() {
              @Override
              public void handle(ActionEvent actionEvent) {

                breaker1.getTransforms().add(translateTransformation1Inv);
                breaker2.getTransforms().add(translateTransformation4Inv);
              }
            });

            Scene scene = new Scene(root, 600, 600, Color.WHITE);
            primaryStage.setScene(scene);
            primaryStage.show();
          }
        }


        This demo shows that, when transformations are applied to a group and another is applied to its children, this leads to weird behavior with stuff that is bound to the bounds of the group.
        The demo actually does what ScenicView does: It displays a rectangle in front of a group that is bound to the groups bounds.

        To reproduce the rendering artifacts, hit the scale down button once, then the move away button a few times, and then scale down again. the rectangles will still be visible until a repaint is forced, for example, when the window is resized. Alternativeley the step button reproduces the bug by itself.

        Note that this doesn't stop only when trying to implement something like zoom, but also when stuff is displayed this way and only translate transformations are applied, like in our application.

          Attachments

            Issue Links

              Activity

                People

                • Assignee:
                  lnerad Ľubomír Nerád
                  Reporter:
                  srheinnecjfx Sebastian Rheinnecker (Inactive)
                • Votes:
                  0 Vote for this issue
                  Watchers:
                  5 Start watching this issue

                  Dates

                  • Created:
                    Updated:
                    Resolved:
                    Imported: