JDK-8228698 : Incorrectly calculated bounds for Arc shape with stroke width 5
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: openjfx11
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2019-07-28
  • Updated: 2019-08-05
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
Description
ADDITIONAL SYSTEM INFORMATION :
Windows 10, Java 11.0.4

A DESCRIPTION OF THE PROBLEM :
An arc, with radius x/y 22, and a stroke width of 5, should always fit in a 50x50 pixel box.  However, quite often its total width/height slightly exceeds this value (seen with getBoundsInLocal).  Sometimes however it exceeds this value by a lot (sometimes reaching 58 pixels high or wide), as demonstrated by the attached test case. 

Sample values this test prints:

arc: Arc[centerX=0.0, centerY=0.0, radiusX=22.0, radiusY=22.0, startAngle=90.0, length=-182.3427, type=OPEN, fill=0x00000000, stroke=0x7dff7d80, strokeWidth=5.0]

arc: BoundingBox [minX:-3.9993820190429688, minY:-33.319679260253906, minZ:0.0, width:28.9993896484375, height:58.31974792480469, depth:0.0, maxX:25.00000762939453, maxY:25.00006866455078, maxZ:0.0]

The height of 58.3 is completely unexplainable given the parameters the Arc has.

This happens infrequently, but usually within 60 seconds of running the test case, which simply animates the arc until the bug occurs.

Tracing the problem ends up somewhere deep in the Shape class (The Arc and Arc2D class seem to not cause this issue).  I suspect the issue may be in "Toolkit.getToolkit().accumulateStrokeBounds()" somewhere and could be time sensitive.

The problem does not occur with a stroke-width of 1 or 2 (there is no visible "jumping" of the circle while animated.  It starts occuring at width 3, and more frequently at 4 and 5.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1) Run attached program
2) Observe slight jumps in the circle positioning, indicative of the problem
3) Wait until the animation is halted and debug prints appear on the console.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
No positioning jumps should occur, the local bounds calculation should return predictable results.
ACTUAL -
Positioning of the arc fluctuates, often slightly, sometimes by multiple pixels.  The local bounds calculation returns obviously incorrect bounds when a bad position is shown on screen.

---------- BEGIN SOURCE ----------
package hs.javafxbug;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.geometry.Bounds;
import javafx.geometry.Pos;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.control.ContentDisplay;
import javafx.scene.control.Label;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Arc;
import javafx.scene.shape.ArcType;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import javafx.util.Duration;

public class JavaFXBug extends Application {

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

  @Override
  public void start(Stage primaryStage) throws Exception {
    MediaStatusIndicatorPane indicator = new MediaStatusIndicatorPane();

    primaryStage.setScene(new Scene(indicator));
    primaryStage.show();

    Timeline tl = new Timeline(
      new KeyFrame(Duration.ZERO, new KeyValue(indicator.value, 0.1)),
      new KeyFrame(Duration.seconds(2), new KeyValue(indicator.value, 0.99))
    );

    tl.setAutoReverse(true);
    tl.setCycleCount(Timeline.INDEFINITE);
    tl.play();

    //
    // Background thread which frequently checks bounds of the arc group,
    // and stops the animation if the bounds are wrong.  Bounds height
    // should never exceed 50 (but it often does so slightly).  Sometimes
    // however it exceeds it by multiple pixels, causing jumps...
    //
    // The Width of the bounds also has this problem.
    //
    Executors.newSingleThreadScheduledExecutor().scheduleWithFixedDelay(
      () -> Platform.runLater(() -> {
        Bounds boundsInLocal = indicator.group.getBoundsInLocal();

        if(boundsInLocal.getHeight() > 54) {
          System.out.println(boundsInLocal);
          System.out.println("arc: " + indicator.arc);
          System.out.println("arc: " + indicator.arc.getBoundsInLocal());
          tl.stop();
        }
      }),
      0,
      37,
      TimeUnit.MILLISECONDS
    );
  }

}

class MediaStatusIndicatorPane extends StackPane {
  public final DoubleProperty value = new SimpleDoubleProperty(0.0);

  public Group group;
  public Arc arc;

  public MediaStatusIndicatorPane() {
    value.addListener(obs -> {
      double percentage = value.get();

      List<Node> children = new ArrayList<>();
      Label bg = new Label();

      bg.setStyle("-fx-background-color: rgba(0,0,0,0.6); -fx-scale-x: 1.1; -fx-scale-y: 1.1; -fx-min-width: 50; -fx-min-height: 50;");

      children.add(bg);

      arc = new Arc(0, 0, 22, 22, 90, -360 * percentage);

      arc.setStyle("-fx-stroke-width: 5; -fx-fill: transparent; -fx-stroke: rgba(125, 255, 125, 0.5)");
      arc.setType(ArcType.OPEN);

      Rectangle rectangle = new Rectangle(-25, -25, 50, 50);

      rectangle.setFill(new Color(1, 0, 0, 0.3));

      Label arcGraphic = new Label();

      group = new Group(rectangle, arc);
      arcGraphic.setGraphic(group);
      arcGraphic.setAlignment(Pos.TOP_CENTER);
      arcGraphic.setContentDisplay(ContentDisplay.GRAPHIC_ONLY);

      children.add(arcGraphic);

      getChildren().setAll(children);
    });
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
None that I found, apart from rendering my own arc or setting stroke-width to 1.

FREQUENCY : always