From 80ab50b3389cbdae6bced7cea3f3a84b94c5bb82 Mon Sep 17 00:00:00 2001 From: Harshitha Onkar Date: Thu, 19 Jan 2023 18:43:54 +0000 Subject: [PATCH] 8294680: Refactor scaled border rendering Co-authored-by: Alexey Ivanov Reviewed-by: rmahajan, achung, aivanov, jdv --- .../com/sun/java/swing/SwingUtilities3.java | 131 +++++++++++++++++- .../javax/swing/border/EtchedBorder.java | 61 ++------ .../javax/swing/border/LineBorder.java | 63 ++------- .../javax/swing/plaf/metal/MetalBorders.java | 68 ++------- 4 files changed, 168 insertions(+), 155 deletions(-) diff --git a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java index 199026aae3bf..8040120695d7 100644 --- a/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java +++ b/src/java.desktop/share/classes/com/sun/java/swing/SwingUtilities3.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2002, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -25,19 +25,26 @@ package com.sun.java.swing; -import sun.awt.AppContext; -import sun.awt.SunToolkit; - -import java.util.Collections; -import java.util.Map; -import java.util.WeakHashMap; import java.applet.Applet; import java.awt.Component; import java.awt.Container; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Stroke; import java.awt.Window; +import java.awt.geom.AffineTransform; +import java.util.Collections; +import java.util.Map; +import java.util.WeakHashMap; + import javax.swing.JComponent; import javax.swing.RepaintManager; +import sun.awt.AppContext; +import sun.awt.SunToolkit; + +import static sun.java2d.pipe.Region.clipRound; + /** * A collection of utility methods for Swing. *

@@ -135,4 +142,114 @@ public static RepaintManager getDelegateRepaintManager(Component } return delegate; } + + /** + * A task which paints an unscaled border after {@code Graphics} + * transforms are removed. It's used with the + * {@link #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter) + * SwingUtilities3.paintBorder} which manages changing the transforms and calculating + * the coordinates and size of the border. + */ + @FunctionalInterface + public interface UnscaledBorderPainter { + /** + * Paints the border for the specified component after the + * {@code Graphics} transforms are removed. + * + *

+ * The x and y of the painted border are zero. + * + * @param c the component for which this border is being painted + * @param g the paint graphics + * @param w the width of the painted border, in physical pixels + * @param h the height of the painted border, in physical pixels + * @param scaleFactor the scale that was in the {@code Graphics} + * + * @see #paintBorder(Component, Graphics, int, int, int, int, UnscaledBorderPainter) + * SwingUtilities3.paintBorder + * @see javax.swing.border.Border#paintBorder(Component, Graphics, int, int, int, int) + * Border.paintBorder + */ + void paintUnscaledBorder(Component c, Graphics g, + int w, int h, + double scaleFactor); + } + + /** + * Paints the border for a component ensuring its sides have consistent + * thickness at different scales. + *

+ * It performs the following steps: + *

    + *
  1. Reset the scale transform on the {@code Graphics},
  2. + *
  3. Call {@code painter} to paint the border,
  4. + *
  5. Restores the transform.
  6. + *
+ * + * @param c the component for which this border is being painted + * @param g the paint graphics + * @param x the x position of the painted border + * @param y the y position of the painted border + * @param w the width of the painted border + * @param h the height of the painted border + * @param painter the painter object which paints the border after + * the transform on the {@code Graphics} is reset + */ + public static void paintBorder(Component c, Graphics g, + int x, int y, + int w, int h, + UnscaledBorderPainter painter) { + + // Step 1: Reset Transform + AffineTransform at = null; + Stroke oldStroke = null; + boolean resetTransform = false; + double scaleFactor = 1; + + int xtranslation = x; + int ytranslation = y; + int width = w; + int height = h; + + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + at = g2d.getTransform(); + oldStroke = g2d.getStroke(); + scaleFactor = Math.min(at.getScaleX(), at.getScaleY()); + + // if m01 or m10 is non-zero, then there is a rotation or shear, + // or if scale=1, skip resetting the transform in these cases. + resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)) + && ((at.getScaleX() > 1) || (at.getScaleY() > 1)); + + if (resetTransform) { + /* Deactivate the HiDPI scaling transform, + * so we can do paint operations in the device + * pixel coordinate system instead of the logical coordinate system. + */ + g2d.setTransform(new AffineTransform()); + double xx = at.getScaleX() * x + at.getTranslateX(); + double yy = at.getScaleY() * y + at.getTranslateY(); + xtranslation = clipRound(xx); + ytranslation = clipRound(yy); + width = clipRound(at.getScaleX() * w + xx) - xtranslation; + height = clipRound(at.getScaleY() * h + yy) - ytranslation; + } + } + + g.translate(xtranslation, ytranslation); + + // Step 2: Call respective paintBorder with transformed values + painter.paintUnscaledBorder(c, g, width, height, scaleFactor); + + // Step 3: Restore previous stroke & transform + g.translate(-xtranslation, -ytranslation); + if (g instanceof Graphics2D) { + Graphics2D g2d = (Graphics2D) g; + g2d.setStroke(oldStroke); + if (resetTransform) { + g2d.setTransform(at); + } + } + } } diff --git a/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java b/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java index 95021b4b4bcb..cd2068b0afa0 100644 --- a/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java +++ b/src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -30,10 +30,10 @@ import java.awt.Insets; import java.awt.Color; import java.awt.Component; -import java.awt.Stroke; -import java.awt.geom.AffineTransform; import java.beans.ConstructorProperties; +import com.sun.java.swing.SwingUtilities3; + /** * A class which implements a simple etched border which can * either be etched-in or etched-out. If no highlight/shadow @@ -150,59 +150,26 @@ private void paintBorderShadow(Graphics g, Color c, int w, int h, int stkWidth) * @param height the height of the painted border */ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { - // We remove any initial transforms to prevent rounding errors - // when drawing in non-integer scales - AffineTransform at = null; - Stroke oldStk = null; - int stkWidth = 1; - boolean resetTransform = false; - if (g instanceof Graphics2D) { - Graphics2D g2d = (Graphics2D) g; - at = g2d.getTransform(); - oldStk = g2d.getStroke(); - // if m01 or m10 is non-zero, then there is a rotation or shear - // skip resetting the transform - resetTransform = (at.getShearX() == 0) && (at.getShearY() == 0); - if (resetTransform) { - g2d.setTransform(new AffineTransform()); - stkWidth = (int) Math.floor(Math.min(at.getScaleX(), at.getScaleY())); - g2d.setStroke(new BasicStroke((float) stkWidth)); - } - } + SwingUtilities3.paintBorder(c, g, + x, y, + width, height, + this::paintUnscaledBorder); + } - int w; - int h; - int xtranslation; - int ytranslation; - if (resetTransform) { - w = (int) Math.floor(at.getScaleX() * width - 1); - h = (int) Math.floor(at.getScaleY() * height - 1); - xtranslation = (int) Math.ceil(at.getScaleX()*x+at.getTranslateX()); - ytranslation = (int) Math.ceil(at.getScaleY()*y+at.getTranslateY()); - } else { - w = width; - h = height; - xtranslation = x; - ytranslation = y; + private void paintUnscaledBorder(Component c, Graphics g, + int w, int h, + double scaleFactor) { + int stkWidth = (int) Math.floor(scaleFactor); + if (g instanceof Graphics2D) { + ((Graphics2D) g).setStroke(new BasicStroke((float) stkWidth)); } - g.translate(xtranslation, ytranslation); - paintBorderShadow(g, (etchType == LOWERED) ? getHighlightColor(c) : getShadowColor(c), w, h, stkWidth); paintBorderHighlight(g, (etchType == LOWERED) ? getShadowColor(c) : getHighlightColor(c), w, h, stkWidth); - - g.translate(-xtranslation, -ytranslation); - - // Set the transform we removed earlier - if (resetTransform) { - Graphics2D g2d = (Graphics2D) g; - g2d.setTransform(at); - g2d.setStroke(oldStk); - } } /** diff --git a/src/java.desktop/share/classes/javax/swing/border/LineBorder.java b/src/java.desktop/share/classes/javax/swing/border/LineBorder.java index 39183afb2638..9116731eaf0e 100644 --- a/src/java.desktop/share/classes/javax/swing/border/LineBorder.java +++ b/src/java.desktop/share/classes/javax/swing/border/LineBorder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,9 +34,8 @@ import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.beans.ConstructorProperties; -import java.awt.geom.AffineTransform; -import static sun.java2d.pipe.Region.clipRound; +import com.sun.java.swing.SwingUtilities3; /** * A class which implements a line border of arbitrary thickness @@ -144,73 +143,41 @@ public LineBorder(Color color, int thickness, boolean roundedCorners) { * @param height the height of the painted border */ public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) { + SwingUtilities3.paintBorder(c, g, + x, y, + width, height, + this::paintUnscaledBorder); + } + + private void paintUnscaledBorder(Component c, Graphics g, + int w, int h, + double scaleFactor) { if ((this.thickness > 0) && (g instanceof Graphics2D)) { Graphics2D g2d = (Graphics2D) g; - AffineTransform at = g2d.getTransform(); - - // if m01 or m10 is non-zero, then there is a rotation or shear - // or if no Scaling enabled, - // skip resetting the transform - boolean resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)) && - ((at.getScaleX() > 1) || (at.getScaleY() > 1)); - - int xtranslation; - int ytranslation; - int w; - int h; - int offs; - - if (resetTransform) { - /* Deactivate the HiDPI scaling transform, - * so we can do paint operations in the device - * pixel coordinate system instead of the logical coordinate system. - */ - g2d.setTransform(new AffineTransform()); - double xx = at.getScaleX() * x + at.getTranslateX(); - double yy = at.getScaleY() * y + at.getTranslateY(); - xtranslation = clipRound(xx); - ytranslation = clipRound(yy); - w = clipRound(at.getScaleX() * width + xx) - xtranslation; - h = clipRound(at.getScaleY() * height + yy) - ytranslation; - offs = this.thickness * (int) at.getScaleX(); - } else { - w = width; - h = height; - xtranslation = x; - ytranslation = y; - offs = this.thickness; - } - - g2d.translate(xtranslation, ytranslation); - Color oldColor = g2d.getColor(); g2d.setColor(this.lineColor); Shape outer; Shape inner; + int offs = this.thickness * (int) scaleFactor; int size = offs + offs; if (this.roundedCorners) { float arc = .2f * offs; outer = new RoundRectangle2D.Float(0, 0, w, h, offs, offs); inner = new RoundRectangle2D.Float(offs, offs, w - size, h - size, arc, arc); - } - else { + } else { outer = new Rectangle2D.Float(0, 0, w, h); inner = new Rectangle2D.Float(offs, offs, w - size, h - size); } + Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD); path.append(outer, false); path.append(inner, false); g2d.fill(path); - g2d.setColor(oldColor); - - g2d.translate(-xtranslation, -ytranslation); - if (resetTransform) { - g2d.setTransform(at); - } + g2d.setColor(oldColor); } } diff --git a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java index 38b4a6923959..c7ebc9921662 100644 --- a/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java +++ b/src/java.desktop/share/classes/javax/swing/plaf/metal/MetalBorders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2022, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,9 +33,7 @@ import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Insets; -import java.awt.Stroke; import java.awt.Window; -import java.awt.geom.AffineTransform; import javax.swing.AbstractButton; import javax.swing.ButtonModel; @@ -62,6 +60,7 @@ import javax.swing.plaf.basic.BasicBorders; import javax.swing.text.JTextComponent; +import com.sun.java.swing.SwingUtilities3; import sun.swing.StringUIClientPropertyKey; import sun.swing.SwingUtilities2; @@ -250,6 +249,14 @@ public InternalFrameBorder() {} public void paintBorder(Component c, Graphics g, int x, int y, int w, int h) { + SwingUtilities3.paintBorder(c, g, + x, y, w, h, + this::paintUnscaledBorder); + } + + private void paintUnscaledBorder(Component c, Graphics g, + int width, int height, + double scaleFactor) { Color background; Color highlight; Color shadow; @@ -264,48 +271,6 @@ public void paintBorder(Component c, Graphics g, int x, int y, shadow = MetalLookAndFeel.getControlInfo(); } - AffineTransform at = null; - Stroke oldStk = null; - boolean resetTransform = false; - int stkWidth = 1; - double scaleFactor = 1; - - if (g instanceof Graphics2D g2d) { - at = g2d.getTransform(); - scaleFactor = at.getScaleX(); - oldStk = g2d.getStroke(); - - // if m01 or m10 is non-zero, then there is a rotation or shear - // skip resetting the transform - resetTransform = ((at.getShearX() == 0) && (at.getShearY() == 0)); - - if (resetTransform) { - g2d.setTransform(new AffineTransform()); - stkWidth = clipRound(Math.min(at.getScaleX(), at.getScaleY())); - g2d.setStroke(new BasicStroke((float) stkWidth)); - } - } - - int xtranslation; - int ytranslation; - int width; - int height; - - if (resetTransform) { - double xx = at.getScaleX() * x + at.getTranslateX(); - double yy = at.getScaleY() * y + at.getTranslateY(); - xtranslation = clipRound(xx); - ytranslation = clipRound(yy); - width = clipRound(at.getScaleX() * w + xx) - xtranslation; - height = clipRound(at.getScaleY() * h + yy) - ytranslation; - } else { - xtranslation = x; - ytranslation = y; - width = w; - height = h; - } - g.translate(xtranslation, ytranslation); - // scaled border int thickness = (int) Math.ceil(4 * scaleFactor); @@ -319,12 +284,17 @@ public void paintBorder(Component c, Graphics g, int x, int y, // midpoint at which highlight & shadow lines // are positioned on the border int midPoint = thickness / 2; + int stkWidth = clipRound(scaleFactor); int offset = (((scaleFactor - stkWidth) >= 0) && ((stkWidth % 2) != 0)) ? 1 : 0; int loc1 = thickness % 2 == 0 ? midPoint + stkWidth / 2 - stkWidth : midPoint; int loc2 = thickness % 2 == 0 ? midPoint + stkWidth / 2 : midPoint + stkWidth; // scaled corner int corner = (int) Math.round(CORNER * scaleFactor); + if (g instanceof Graphics2D) { + ((Graphics2D) g).setStroke(new BasicStroke((float) stkWidth)); + } + // Draw the Long highlight lines g.setColor(highlight); g.drawLine(corner + 1, loc2, width - corner, loc2); //top @@ -343,14 +313,6 @@ public void paintBorder(Component c, Graphics g, int x, int y, g.drawLine(corner, (height - offset) - loc2, width - corner - 1, (height - offset) - loc2); } - - // restore previous transform - g.translate(-xtranslation, -ytranslation); - if (resetTransform) { - Graphics2D g2d = (Graphics2D) g; - g2d.setTransform(at); - g2d.setStroke(oldStk); - } } public Insets getBorderInsets(Component c, Insets newInsets) {