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

JLayer draws borders of non-opaque child, breaking translucent borders

XMLWordPrintable

    • Icon: Bug Bug
    • Resolution: Unresolved
    • Icon: P3 P3
    • tbd
    • 11, 17, 21, 22, 23
    • client-libs
    • Oracle JDK 8u - Issue not Reproducible

       

      Affected Version : JDK 11, 17, 21, 22 and 23ea both Linux and Windows Enivirionment

      ADDITIONAL SYSTEM INFORMATION :
      Java >8

      Tested with
      Java 8 / Linux
      Java 11 / Windows, Linux
      Java 17 / Linux
      Java 23 / Linux


      A DESCRIPTION OF THE PROBLEM :
      JLayer delegates its setBorder() and getBorder() method calls to its view. As a side effect, its (JComponent.)paintBorder() paints the border of that child. This is because paintBorder() calls getBorder() instead of using this.border directly like most other places in JComponent does (there's two calls of getBorder() in JComponent compared to 13 uses of this.border). Then the child paints the border again in paintChildren().

      Originally JLayers didn't have borders: calling setBorder() threw an exception and getBorder() was not defined. This was changed as a result of fix to bug 8054543 <https://github.com/openjdk/jdk/commit/08fb482e4122fcd5d14a9a14f5570df86a21960e> sometime during Java 9. In that commit the current implementation of those methods was introduced.

      When the child component is opaque there's no visible issue, but with a non-opaque object whose border is translucent there is. The issue appears when the border is directly drawn with translucent color (alpha < 1) or if it is drawn with opaque color but composited with a translucent AlphaComposite. In both cases the result is darker than expected because JLayer already drew the border.

      I have tested the example code with Java 8 (draws correctly) and with Java 11, 17, and 23, all of which do not work.

      Maybe either replace getBorder() with this.border in JComponent, or implement a no-op paintBorder() for JLayer. There might be other inconsistencies in JLayer between getBorder() returning non-null and other methods working as if there no border, e.g. getInsets().


      ---------- BEGIN SOURCE ----------
      import java.awt.AlphaComposite;
      import java.awt.BorderLayout;
      import java.awt.Color;
      import java.awt.Composite;
      import java.awt.Container;
      import java.awt.Dimension;
      import java.awt.Graphics;
      import java.awt.Graphics2D;
      import javax.swing.BorderFactory;
      import javax.swing.JFrame;
      import javax.swing.JLayer;
      import javax.swing.JPanel;
      import javax.swing.SwingUtilities;
      import javax.swing.plaf.LayerUI;

      /**
       * Demonstrate how JLayer's border drawing behavior breaks translucent borders.
       *
       * <p>The windows created by this program have a JLayer containing a bordered
       * JPanel ("outer") that contains another bordered JPanel ("inner"),
       * demonstrating how the outer border is drawn differently than the inner one
       * with versions later than Java 8. Java 8 draws them similarly. The details
       * of these three windows are:</p>
       *
       * <dl>
       * <dt>Border</dt>
       * <dd>Draws the borders with 0.5f alpha.</dd>
       * <dt>Paint</dt>
       * <dd>Uses AlphaComposite to draw both panels.</dd>
       * <dt>Workaround</dt>
       * <dd>Wraps the outer panel in a third panel ("borderless") with an empty
       * border.</dd>
       * </dl>
       */
      public class JLayerBug {
          public static void main(String[] args) {
              JLayerBug program = new JLayerBug();
              SwingUtilities.invokeLater(program::gui);
          }

          public void gui() {
              Window border = new Window(Kind.BORDER_ALPHA);
              border.pack();
              border.setVisible(true);

              Window paint = new Window(Kind.PAINT_ALPHA);
              paint.pack();
              paint.setVisible(true);

              Window workaround = new Window(Kind.WORKAROUND);
              workaround.pack();
              workaround.setVisible(true);

          }

          enum Kind {
              BORDER_ALPHA, PAINT_ALPHA, WORKAROUND
          }

          private class Window extends JFrame {
              private final Kind kind;

              private Window(Kind kind) {
                  this.kind = kind;
                  setTitle(title());

                  Container c = getContentPane();
                  c.setLayout(new BorderLayout());
                  LayerUI<JPanel> layerUi = new LayerUI<>();
                  JLayer<JPanel> layer = new JLayer<>(contentPanel(), layerUi);
                  c.add(layer);

                  setDefaultCloseOperation(EXIT_ON_CLOSE);
              }

              private String title() {
                  switch (this.kind) {
                      case BORDER_ALPHA: return "Border";
                      case PAINT_ALPHA: return "Paint";
                      case WORKAROUND: return "Workaround";
                  }
                  return "wot";
              }

              private JPanel contentPanel() {
                  Color borderColor = kind != Kind.PAINT_ALPHA
                          ? new Color(0, 0, 0, 0.5f)
                          : Color.BLACK;

                  JPanel outer = new JPanel() {
                      @Override
                      public void paint(Graphics g) {
                          if (kind != Kind.PAINT_ALPHA) {
                              super.paint(g);
                              return;
                          }

                          Graphics2D g2d = (Graphics2D) g;
                          Composite c = g2d.getComposite();
                          AlphaComposite a = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.25f);
                          g2d.setComposite(a);
                          super.paint(g2d);
                          g2d.setComposite(c);
                      }
                  };
                  outer.setOpaque(false);
                  outer.setLayout(new BorderLayout());
                  outer.setBorder(BorderFactory.createLineBorder(borderColor, 5));
                  outer.setPreferredSize(new Dimension(400, 300));

                  JPanel inner = new JPanel();
                  inner.setOpaque(false);
                  inner.setBorder(BorderFactory.createLineBorder(borderColor, 5));
                  outer.add(inner);

                  if (kind == Kind.WORKAROUND) {
                      JPanel borderless = new JPanel();
                      borderless.setLayout(new BorderLayout());
                      borderless.add(outer);
                      return borderless;
                  } else {
                      return outer;
                  }
              }
          }
      }

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

      CUSTOMER SUBMITTED WORKAROUND :
      Add an extra borderless panel to wrap the child.


        1. JLayerBug.java
          4 kB
        2. Capture_jdk11_17_21_23_Ubuntu.png
          Capture_jdk11_17_21_23_Ubuntu.png
          13 kB
        3. Capture_jdk11_17_21_23_Windows.png
          Capture_jdk11_17_21_23_Windows.png
          32 kB
        4. Capture_8u401_Ubuntu.png
          Capture_8u401_Ubuntu.png
          16 kB
        5. Capture_8u401_Windows.png
          Capture_8u401_Windows.png
          27 kB
        6. macOS_JDK23.png
          macOS_JDK23.png
          155 kB
        7. macOS_JDK8u401.png
          macOS_JDK8u401.png
          180 kB

            honkar Harshitha Onkar
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            4 Start watching this issue

              Created:
              Updated: