Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
c368173
initial commit
Feb 8, 2022
6e24762
finished test
Feb 9, 2022
b2e9240
8279614: The left line of the TitledBorder is not painted on 150 scal…
Feb 11, 2022
4c8d5bf
changed carriage return
Feb 14, 2022
f314462
changed carriage return
Feb 14, 2022
c0b3189
trailing whitespace, add extra newline at eof
Feb 14, 2022
47437d9
reverted old change, swapped order of painting to prevent overdrawing
Mar 3, 2022
88c1b1a
typo
Mar 3, 2022
6056faf
adjusted pixels to check for border
Mar 3, 2022
7b7732f
added functions for drawing border, fixed translate
Mar 14, 2022
06ac18d
changed approach to render border without transforms
Mar 17, 2022
3ecaf58
updated test
Mar 17, 2022
75ec24d
fixed apostrophe in comment of saveImage
Mar 17, 2022
9109b84
scale stroke width at higher scalings
Mar 23, 2022
5d4854d
fixed 1.25 scaling overdraw, fixed calcs for right and bottom side bo…
Apr 7, 2022
4dc1287
forgot to change starting x value of bottom line
Apr 20, 2022
6c2efaf
updated test
May 2, 2022
a793582
saved translation values in EtchedBorder, updated test
May 5, 2022
89e64e9
fixed bottom edge border starting x value, updated test
May 6, 2022
a50bda0
fixed coordinate checks
May 6, 2022
6767407
changed error message for gap between highlight and shadow
May 6, 2022
e7b2706
made changes according to comments
May 9, 2022
b84cf51
made suggested changes
May 9, 2022
e32e6c0
renamed test, renamed some methods, updated error messages, updated test
May 10, 2022
b17cdab
made suggested changes
May 20, 2022
65200a2
reverted copyright year
May 20, 2022
61fc0ae
updated test
alisenchung May 25, 2022
935ec43
changed test to headless
alisenchung May 25, 2022
cf91eb8
updated test
alisenchung May 31, 2022
1d6ac07
don't reset transform if it contains a non-scale transformation
alisenchung Jun 3, 2022
8f71aba
skip transform is m01 or m10 is nonzero
alisenchung Jun 6, 2022
adca6ef
made suggested changes
alisenchung Jun 8, 2022
24d7cee
fixed spacing
alisenchung Jun 8, 2022
325d09b
fixed declarations
alisenchung Jun 8, 2022
35e7d5a
removed rendering hints, changed condition to reapply old transform
alisenchung Jun 8, 2022
317fe35
removed initialization
alisenchung Jun 10, 2022
93f0b73
changed initializations to null
alisenchung Jun 10, 2022
610d8f8
fix typo
alisenchung Jun 10, 2022
d4ad493
removed unused import
alisenchung Jun 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 69 additions & 13 deletions src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2022, 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
Expand All @@ -24,11 +24,14 @@
*/
package javax.swing.border;

import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.Color;
import java.awt.Component;
import java.awt.Stroke;
import java.awt.geom.AffineTransform;
import java.beans.ConstructorProperties;

/**
Expand Down Expand Up @@ -119,6 +122,22 @@ public EtchedBorder(int etchType, Color highlight, Color shadow) {
this.shadow = shadow;
}

private void paintBorderHighlight(Graphics g, Color c, int w, int h, int stkWidth) {
g.setColor(c);
g.drawRect(stkWidth/2, stkWidth/2, w-(2*stkWidth), h-(2*stkWidth));
}

private void paintBorderShadow(Graphics g, Color c, int w, int h, int stkWidth) {
g.setColor(c);
g.drawLine(((3*stkWidth)/2), h-((3*stkWidth)/2), ((3*stkWidth)/2), ((3*stkWidth)/2)); // left line
g.drawLine(((3*stkWidth)/2), ((3*stkWidth)/2), w-((3*stkWidth)/2), ((3*stkWidth)/2)); // top line

g.drawLine((stkWidth/2), h-(stkWidth-stkWidth/2),
w-(stkWidth-stkWidth/2), h-(stkWidth-stkWidth/2)); // bottom line
g.drawLine(w-(stkWidth-stkWidth/2), h-(stkWidth-stkWidth/2),
w-(stkWidth-stkWidth/2), stkWidth/2); // right line
}

/**
* Paints the border for the specified component with the
* specified position and size.
Expand All @@ -131,22 +150,59 @@ public EtchedBorder(int etchType, Color highlight, Color shadow) {
* @param height the height of the painted border
*/
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
int w = width;
int h = 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));
}
}

g.translate(x, y);
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;
}

g.setColor(etchType == LOWERED? getShadowColor(c) : getHighlightColor(c));
g.drawRect(0, 0, w-2, h-2);
g.translate(xtranslation, ytranslation);

g.setColor(etchType == LOWERED? getHighlightColor(c) : getShadowColor(c));
g.drawLine(1, h-3, 1, 1);
g.drawLine(1, 1, w-3, 1);
paintBorderShadow(g, (etchType == LOWERED) ? getHighlightColor(c)
: getShadowColor(c),
w, h, stkWidth);
paintBorderHighlight(g, (etchType == LOWERED) ? getShadowColor(c)
: getHighlightColor(c),
w, h, stkWidth);

g.drawLine(0, h-1, w-1, h-1);
g.drawLine(w-1, h-1, w-1, 0);
g.translate(-xtranslation, -ytranslation);

g.translate(-x, -y);
// Set the transform we removed earlier
if (resetTransform) {
Graphics2D g2d = (Graphics2D) g;
g2d.setTransform(at);
g2d.setStroke(oldStk);
}
}

/**
Expand Down
232 changes: 232 additions & 0 deletions test/jdk/javax/swing/border/EtchedBorder/ScaledEtchedBorderTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,232 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.imageio.ImageIO;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

/*
* @test
* @bug 8279614
* @summary The left line of the TitledBorder is not painted on 150 scale factor
* @requires (os.family == "windows")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's still unclear to me whether we leave the requirement for Windows only or remove it. It came up a few times but no clear decision has been taken.

The change is not Windows-specific, the test is not Windows-specific, it can be run on other platforms. However, fractional UI scales aren't supported on other platforms but Windows; at the same time, applying fractional scales to Graphics when painting to a BufferedImage is supported.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it sufficient to make it Windows-specific at this time.
The BufferedImage case is extremely uncommon.
Mac is integer only and we don't have any timeline for supporting fractional scaling on Linux

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds reasonable. Let's leave it Windows-specific.

* @run main ScaledEtchedBorderTest
*/

public class ScaledEtchedBorderTest {

public static final Dimension SIZE = new Dimension(120, 20);

public static Color highlight = Color.RED;
public static Color shadow = Color.BLUE;

private static final double[] scales =
{1.00, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00};

private static final List<BufferedImage> images =
new ArrayList<>(scales.length);

private static final List<Point> panelLocations =
new ArrayList<>(4);

public static void main(String[] args) throws Exception {
boolean showFrame = args.length > 0 && "-show".equals(args[0]);
SwingUtilities.invokeAndWait(() -> testScaling(showFrame));
}

private static void testScaling(boolean show) {
createGUI(show);

for (int i = 0; i < scales.length; i++) {
BufferedImage img = images.get(i);
double scaling = scales[i];
System.out.println("Testing scaling: " + scaling);


// checking vertical border
int x = SIZE.width / 2;
checkVerticalBorder(x, img, scaling);

for (Point p : panelLocations) {
int y = (int) (p.y * scaling) + SIZE.height / 2;
checkHorizontalBorder(y, img, scaling);
}
}
}

private static void checkHorizontalBorder(int y, BufferedImage img, double scaling) {
int thickness = 0;
boolean checkShadow = false;
boolean checkHighlight = false;
for (int x = 0; x < img.getWidth(); x++) {
int color = img.getRGB(x, y);
if (!checkHighlight && !checkShadow) {
if (color == shadow.getRGB()) {
checkHighlight = true;
thickness++;
} else if (color == highlight.getRGB()) {
throw new RuntimeException("Horizontal Border was clipped or overdrawn.");
}
} else if (checkHighlight) {
if (color == shadow.getRGB()) {
thickness++;
} else if (color == highlight.getRGB()) {
verifyThickness(x, y, thickness, scaling, "Horizontal");
checkHighlight = false;
checkShadow = true;
thickness = 1;
} else {
throw new RuntimeException("Horizontal Border has empty space between highlight and shadow.");
}
} else {
if (color == shadow.getRGB()) {
throw new RuntimeException("Border colors reversed.");
} else if (color == highlight.getRGB()) {
thickness++;
} else {
verifyThickness(x, y, thickness, scaling, "Horizontal");
checkShadow = false;
thickness = 0;
}
}
}
}

private static void verifyThickness(int x, int y, int thickness, double scaling, String orientation) {
int expected = (int) Math.floor(scaling);
if (thickness != expected) {
throw new RuntimeException("Unexpected " + orientation + " Border thickness at x:"
+ x + " y: " + y + ". Expected: " + expected + " Actual: " + thickness);
}
}

private static void checkVerticalBorder(int x, BufferedImage img, double scaling) {
int thickness = 0;
boolean checkShadow = false;
boolean checkHighlight = false;
for (int y = 0; y < img.getHeight(); y++) {
int color = img.getRGB(x, y);
if (!checkHighlight && !checkShadow) {
if (color == shadow.getRGB()) {
checkHighlight = true;
thickness++;
} else if (color == highlight.getRGB()) {
throw new RuntimeException("Vertical Border was clipped or overdrawn.");
}
} else if (checkHighlight) {
if (color == shadow.getRGB()) {
thickness++;
} else if (color == highlight.getRGB()) {
verifyThickness(x, y, thickness, scaling, "Vertical");
checkHighlight = false;
checkShadow = true;
thickness = 1;
} else {
throw new RuntimeException("Vertical Border has empty space between highlight and shadow.");
}
} else {
if (color == shadow.getRGB()) {
throw new RuntimeException("Border colors reversed.");
} else if (color == highlight.getRGB()) {
thickness++;
} else {
verifyThickness(x, y, thickness, scaling, "Vertical");
checkShadow = false;
thickness = 0;
}
}
}
}

private static void createGUI(boolean show) {
// Render content panel
JPanel contentPanel = new JPanel();
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JPanel contentPanel = new JPanel();


Dimension childSize = null;
for (int i = 0; i < 4; i++) {
JPanel childPanel = new JPanel(new BorderLayout());
childPanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createEmptyBorder(0, i, 4, 4),
BorderFactory.createEtchedBorder(highlight, shadow)));
childPanel.add(Box.createRigidArea(SIZE), BorderLayout.CENTER);

contentPanel.add(childPanel);
if (childSize == null) {
childSize = childPanel.getPreferredSize();
}
childPanel.setBounds(0, childSize.height * i, childSize.width, childSize.height);
}

contentPanel.setSize(childSize.width, childSize.height * 4);

for (double scaling : scales) {
// Create BufferedImage
BufferedImage buff = new BufferedImage((int) Math.ceil(contentPanel.getWidth() * scaling),
(int) Math.ceil(contentPanel.getHeight() * scaling),
BufferedImage.TYPE_INT_ARGB);
Graphics2D graph = buff.createGraphics();
graph.scale(scaling, scaling);
// Painting panel onto BufferedImage
contentPanel.paint(graph);
graph.dispose();
// Save each image ? -- Here it's useful for debugging
saveImage(buff, String.format("test%.2f.png", scaling));
images.add(buff);
}
// Save coordinates of the panels
for (Component comp : contentPanel.getComponents()) {
panelLocations.add(comp.getLocation());
}

if (show) {
JFrame frame = new JFrame("Swing Test");
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
frame.getContentPane().add(contentPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}

private static void saveImage(BufferedImage image, String filename) {
try {
ImageIO.write(image, "png", new File(filename));
} catch (IOException e) {
// Don't propagate the exception
e.printStackTrace();
}
}
}