Skip to content

Commit b42c1ad

Browse files
Alisen Chungaivanov-jdk
andcommitted
8279614: The left line of the TitledBorder is not painted on 150 scale factor
Co-authored-by: Alexey Ivanov <[email protected]> Reviewed-by: kizune, aivanov, prr
1 parent 9b6d0a7 commit b42c1ad

File tree

2 files changed

+301
-13
lines changed

2 files changed

+301
-13
lines changed

src/java.desktop/share/classes/javax/swing/border/EtchedBorder.java

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1997, 2015, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -24,11 +24,14 @@
2424
*/
2525
package javax.swing.border;
2626

27+
import java.awt.BasicStroke;
2728
import java.awt.Graphics;
29+
import java.awt.Graphics2D;
2830
import java.awt.Insets;
29-
import java.awt.Rectangle;
3031
import java.awt.Color;
3132
import java.awt.Component;
33+
import java.awt.Stroke;
34+
import java.awt.geom.AffineTransform;
3235
import java.beans.ConstructorProperties;
3336

3437
/**
@@ -119,6 +122,22 @@ public EtchedBorder(int etchType, Color highlight, Color shadow) {
119122
this.shadow = shadow;
120123
}
121124

125+
private void paintBorderHighlight(Graphics g, Color c, int w, int h, int stkWidth) {
126+
g.setColor(c);
127+
g.drawRect(stkWidth/2, stkWidth/2, w-(2*stkWidth), h-(2*stkWidth));
128+
}
129+
130+
private void paintBorderShadow(Graphics g, Color c, int w, int h, int stkWidth) {
131+
g.setColor(c);
132+
g.drawLine(((3*stkWidth)/2), h-((3*stkWidth)/2), ((3*stkWidth)/2), ((3*stkWidth)/2)); // left line
133+
g.drawLine(((3*stkWidth)/2), ((3*stkWidth)/2), w-((3*stkWidth)/2), ((3*stkWidth)/2)); // top line
134+
135+
g.drawLine((stkWidth/2), h-(stkWidth-stkWidth/2),
136+
w-(stkWidth-stkWidth/2), h-(stkWidth-stkWidth/2)); // bottom line
137+
g.drawLine(w-(stkWidth-stkWidth/2), h-(stkWidth-stkWidth/2),
138+
w-(stkWidth-stkWidth/2), stkWidth/2); // right line
139+
}
140+
122141
/**
123142
* Paints the border for the specified component with the
124143
* specified position and size.
@@ -131,22 +150,59 @@ public EtchedBorder(int etchType, Color highlight, Color shadow) {
131150
* @param height the height of the painted border
132151
*/
133152
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
134-
int w = width;
135-
int h = height;
153+
// We remove any initial transforms to prevent rounding errors
154+
// when drawing in non-integer scales
155+
AffineTransform at = null;
156+
Stroke oldStk = null;
157+
int stkWidth = 1;
158+
boolean resetTransform = false;
159+
if (g instanceof Graphics2D) {
160+
Graphics2D g2d = (Graphics2D) g;
161+
at = g2d.getTransform();
162+
oldStk = g2d.getStroke();
163+
// if m01 or m10 is non-zero, then there is a rotation or shear
164+
// skip resetting the transform
165+
resetTransform = (at.getShearX() == 0) && (at.getShearY() == 0);
166+
if (resetTransform) {
167+
g2d.setTransform(new AffineTransform());
168+
stkWidth = (int) Math.floor(Math.min(at.getScaleX(), at.getScaleY()));
169+
g2d.setStroke(new BasicStroke((float) stkWidth));
170+
}
171+
}
136172

137-
g.translate(x, y);
173+
int w;
174+
int h;
175+
int xtranslation;
176+
int ytranslation;
177+
if (resetTransform) {
178+
w = (int) Math.floor(at.getScaleX() * width - 1);
179+
h = (int) Math.floor(at.getScaleY() * height - 1);
180+
xtranslation = (int) Math.ceil(at.getScaleX()*x+at.getTranslateX());
181+
ytranslation = (int) Math.ceil(at.getScaleY()*y+at.getTranslateY());
182+
} else {
183+
w = width;
184+
h = height;
185+
xtranslation = x;
186+
ytranslation = y;
187+
}
138188

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

142-
g.setColor(etchType == LOWERED? getHighlightColor(c) : getShadowColor(c));
143-
g.drawLine(1, h-3, 1, 1);
144-
g.drawLine(1, 1, w-3, 1);
191+
paintBorderShadow(g, (etchType == LOWERED) ? getHighlightColor(c)
192+
: getShadowColor(c),
193+
w, h, stkWidth);
194+
paintBorderHighlight(g, (etchType == LOWERED) ? getShadowColor(c)
195+
: getHighlightColor(c),
196+
w, h, stkWidth);
145197

146-
g.drawLine(0, h-1, w-1, h-1);
147-
g.drawLine(w-1, h-1, w-1, 0);
198+
g.translate(-xtranslation, -ytranslation);
148199

149-
g.translate(-x, -y);
200+
// Set the transform we removed earlier
201+
if (resetTransform) {
202+
Graphics2D g2d = (Graphics2D) g;
203+
g2d.setTransform(at);
204+
g2d.setStroke(oldStk);
205+
}
150206
}
151207

152208
/**
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
3+
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4+
*
5+
* This code is distributed in the hope that it will be useful, but WITHOUT
6+
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
7+
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
8+
* version 2 for more details (a copy is included in the LICENSE file that
9+
* accompanied this code).
10+
*
11+
* You should have received a copy of the GNU General Public License version
12+
* 2 along with this work; if not, write to the Free Software Foundation,
13+
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
14+
*
15+
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
16+
* or visit www.oracle.com if you need additional information or have any
17+
* questions.
18+
*/
19+
20+
import java.awt.BorderLayout;
21+
import java.awt.Color;
22+
import java.awt.Component;
23+
import java.awt.Dimension;
24+
import java.awt.Graphics2D;
25+
import java.awt.Point;
26+
import java.awt.image.BufferedImage;
27+
import java.io.File;
28+
import java.io.IOException;
29+
import java.util.ArrayList;
30+
import java.util.List;
31+
32+
import javax.imageio.ImageIO;
33+
import javax.swing.BorderFactory;
34+
import javax.swing.Box;
35+
import javax.swing.BoxLayout;
36+
import javax.swing.JFrame;
37+
import javax.swing.JPanel;
38+
import javax.swing.SwingUtilities;
39+
40+
/*
41+
* @test
42+
* @bug 8279614
43+
* @summary The left line of the TitledBorder is not painted on 150 scale factor
44+
* @requires (os.family == "windows")
45+
* @run main ScaledEtchedBorderTest
46+
*/
47+
48+
public class ScaledEtchedBorderTest {
49+
50+
public static final Dimension SIZE = new Dimension(120, 20);
51+
52+
public static Color highlight = Color.RED;
53+
public static Color shadow = Color.BLUE;
54+
55+
private static final double[] scales =
56+
{1.00, 1.25, 1.50, 1.75, 2.00, 2.50, 3.00};
57+
58+
private static final List<BufferedImage> images =
59+
new ArrayList<>(scales.length);
60+
61+
private static final List<Point> panelLocations =
62+
new ArrayList<>(4);
63+
64+
public static void main(String[] args) throws Exception {
65+
boolean showFrame = args.length > 0 && "-show".equals(args[0]);
66+
SwingUtilities.invokeAndWait(() -> testScaling(showFrame));
67+
}
68+
69+
private static void testScaling(boolean show) {
70+
createGUI(show);
71+
72+
for (int i = 0; i < scales.length; i++) {
73+
BufferedImage img = images.get(i);
74+
double scaling = scales[i];
75+
System.out.println("Testing scaling: " + scaling);
76+
77+
78+
// checking vertical border
79+
int x = SIZE.width / 2;
80+
checkVerticalBorder(x, img, scaling);
81+
82+
for (Point p : panelLocations) {
83+
int y = (int) (p.y * scaling) + SIZE.height / 2;
84+
checkHorizontalBorder(y, img, scaling);
85+
}
86+
}
87+
}
88+
89+
private static void checkHorizontalBorder(int y, BufferedImage img, double scaling) {
90+
int thickness = 0;
91+
boolean checkShadow = false;
92+
boolean checkHighlight = false;
93+
for (int x = 0; x < img.getWidth(); x++) {
94+
int color = img.getRGB(x, y);
95+
if (!checkHighlight && !checkShadow) {
96+
if (color == shadow.getRGB()) {
97+
checkHighlight = true;
98+
thickness++;
99+
} else if (color == highlight.getRGB()) {
100+
throw new RuntimeException("Horizontal Border was clipped or overdrawn.");
101+
}
102+
} else if (checkHighlight) {
103+
if (color == shadow.getRGB()) {
104+
thickness++;
105+
} else if (color == highlight.getRGB()) {
106+
verifyThickness(x, y, thickness, scaling, "Horizontal");
107+
checkHighlight = false;
108+
checkShadow = true;
109+
thickness = 1;
110+
} else {
111+
throw new RuntimeException("Horizontal Border has empty space between highlight and shadow.");
112+
}
113+
} else {
114+
if (color == shadow.getRGB()) {
115+
throw new RuntimeException("Border colors reversed.");
116+
} else if (color == highlight.getRGB()) {
117+
thickness++;
118+
} else {
119+
verifyThickness(x, y, thickness, scaling, "Horizontal");
120+
checkShadow = false;
121+
thickness = 0;
122+
}
123+
}
124+
}
125+
}
126+
127+
private static void verifyThickness(int x, int y, int thickness, double scaling, String orientation) {
128+
int expected = (int) Math.floor(scaling);
129+
if (thickness != expected) {
130+
throw new RuntimeException("Unexpected " + orientation + " Border thickness at x:"
131+
+ x + " y: " + y + ". Expected: " + expected + " Actual: " + thickness);
132+
}
133+
}
134+
135+
private static void checkVerticalBorder(int x, BufferedImage img, double scaling) {
136+
int thickness = 0;
137+
boolean checkShadow = false;
138+
boolean checkHighlight = false;
139+
for (int y = 0; y < img.getHeight(); y++) {
140+
int color = img.getRGB(x, y);
141+
if (!checkHighlight && !checkShadow) {
142+
if (color == shadow.getRGB()) {
143+
checkHighlight = true;
144+
thickness++;
145+
} else if (color == highlight.getRGB()) {
146+
throw new RuntimeException("Vertical Border was clipped or overdrawn.");
147+
}
148+
} else if (checkHighlight) {
149+
if (color == shadow.getRGB()) {
150+
thickness++;
151+
} else if (color == highlight.getRGB()) {
152+
verifyThickness(x, y, thickness, scaling, "Vertical");
153+
checkHighlight = false;
154+
checkShadow = true;
155+
thickness = 1;
156+
} else {
157+
throw new RuntimeException("Vertical Border has empty space between highlight and shadow.");
158+
}
159+
} else {
160+
if (color == shadow.getRGB()) {
161+
throw new RuntimeException("Border colors reversed.");
162+
} else if (color == highlight.getRGB()) {
163+
thickness++;
164+
} else {
165+
verifyThickness(x, y, thickness, scaling, "Vertical");
166+
checkShadow = false;
167+
thickness = 0;
168+
}
169+
}
170+
}
171+
}
172+
173+
private static void createGUI(boolean show) {
174+
// Render content panel
175+
JPanel contentPanel = new JPanel();
176+
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
177+
178+
Dimension childSize = null;
179+
for (int i = 0; i < 4; i++) {
180+
JPanel childPanel = new JPanel(new BorderLayout());
181+
childPanel.setBorder(BorderFactory.createCompoundBorder(
182+
BorderFactory.createEmptyBorder(0, i, 4, 4),
183+
BorderFactory.createEtchedBorder(highlight, shadow)));
184+
childPanel.add(Box.createRigidArea(SIZE), BorderLayout.CENTER);
185+
186+
contentPanel.add(childPanel);
187+
if (childSize == null) {
188+
childSize = childPanel.getPreferredSize();
189+
}
190+
childPanel.setBounds(0, childSize.height * i, childSize.width, childSize.height);
191+
}
192+
193+
contentPanel.setSize(childSize.width, childSize.height * 4);
194+
195+
for (double scaling : scales) {
196+
// Create BufferedImage
197+
BufferedImage buff = new BufferedImage((int) Math.ceil(contentPanel.getWidth() * scaling),
198+
(int) Math.ceil(contentPanel.getHeight() * scaling),
199+
BufferedImage.TYPE_INT_ARGB);
200+
Graphics2D graph = buff.createGraphics();
201+
graph.scale(scaling, scaling);
202+
// Painting panel onto BufferedImage
203+
contentPanel.paint(graph);
204+
graph.dispose();
205+
// Save each image ? -- Here it's useful for debugging
206+
saveImage(buff, String.format("test%.2f.png", scaling));
207+
images.add(buff);
208+
}
209+
// Save coordinates of the panels
210+
for (Component comp : contentPanel.getComponents()) {
211+
panelLocations.add(comp.getLocation());
212+
}
213+
214+
if (show) {
215+
JFrame frame = new JFrame("Swing Test");
216+
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
217+
frame.getContentPane().add(contentPanel, BorderLayout.CENTER);
218+
frame.pack();
219+
frame.setLocationRelativeTo(null);
220+
frame.setVisible(true);
221+
}
222+
}
223+
224+
private static void saveImage(BufferedImage image, String filename) {
225+
try {
226+
ImageIO.write(image, "png", new File(filename));
227+
} catch (IOException e) {
228+
// Don't propagate the exception
229+
e.printStackTrace();
230+
}
231+
}
232+
}

0 commit comments

Comments
 (0)