8282958: Rendering issues of borders, TextFields on Windows High-DPI systems
Co-authored-by: Alexey Ivanov <aivanov@openjdk.org> Reviewed-by: aivanov, honkar, kizune
This commit is contained in:
parent
2fb64a4a4f
commit
9911405e54
@ -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.
|
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||||
*
|
*
|
||||||
* This code is free software; you can redistribute it and/or modify it
|
* This code is free software; you can redistribute it and/or modify it
|
||||||
@ -34,6 +34,9 @@ import java.awt.geom.Path2D;
|
|||||||
import java.awt.geom.Rectangle2D;
|
import java.awt.geom.Rectangle2D;
|
||||||
import java.awt.geom.RoundRectangle2D;
|
import java.awt.geom.RoundRectangle2D;
|
||||||
import java.beans.ConstructorProperties;
|
import java.beans.ConstructorProperties;
|
||||||
|
import java.awt.geom.AffineTransform;
|
||||||
|
|
||||||
|
import static sun.java2d.pipe.Region.clipRound;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class which implements a line border of arbitrary thickness
|
* A class which implements a line border of arbitrary thickness
|
||||||
@ -144,28 +147,70 @@ public class LineBorder extends AbstractBorder
|
|||||||
if ((this.thickness > 0) && (g instanceof Graphics2D)) {
|
if ((this.thickness > 0) && (g instanceof Graphics2D)) {
|
||||||
Graphics2D g2d = (Graphics2D) g;
|
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();
|
Color oldColor = g2d.getColor();
|
||||||
g2d.setColor(this.lineColor);
|
g2d.setColor(this.lineColor);
|
||||||
|
|
||||||
Shape outer;
|
Shape outer;
|
||||||
Shape inner;
|
Shape inner;
|
||||||
|
|
||||||
int offs = this.thickness;
|
|
||||||
int size = offs + offs;
|
int size = offs + offs;
|
||||||
if (this.roundedCorners) {
|
if (this.roundedCorners) {
|
||||||
float arc = .2f * offs;
|
float arc = .2f * offs;
|
||||||
outer = new RoundRectangle2D.Float(x, y, width, height, offs, offs);
|
outer = new RoundRectangle2D.Float(0, 0, w, h, offs, offs);
|
||||||
inner = new RoundRectangle2D.Float(x + offs, y + offs, width - size, height - size, arc, arc);
|
inner = new RoundRectangle2D.Float(offs, offs, w - size, h - size, arc, arc);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
outer = new Rectangle2D.Float(x, y, width, height);
|
outer = new Rectangle2D.Float(0, 0, w, h);
|
||||||
inner = new Rectangle2D.Float(x + offs, y + offs, width - size, height - size);
|
inner = new Rectangle2D.Float(offs, offs, w - size, h - size);
|
||||||
}
|
}
|
||||||
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
|
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
|
||||||
path.append(outer, false);
|
path.append(outer, false);
|
||||||
path.append(inner, false);
|
path.append(inner, false);
|
||||||
g2d.fill(path);
|
g2d.fill(path);
|
||||||
g2d.setColor(oldColor);
|
g2d.setColor(oldColor);
|
||||||
|
|
||||||
|
g2d.translate(-xtranslation, -ytranslation);
|
||||||
|
|
||||||
|
if (resetTransform) {
|
||||||
|
g2d.setTransform(at);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
299
test/jdk/javax/swing/border/LineBorder/ScaledLineBorderTest.java
Normal file
299
test/jdk/javax/swing/border/LineBorder/ScaledLineBorderTest.java
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
/*
|
||||||
|
* 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 free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.BorderFactory;
|
||||||
|
import javax.swing.Box;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8282958
|
||||||
|
* @summary Verify LineBorder edges have the same width
|
||||||
|
* @requires (os.family == "windows")
|
||||||
|
* @run main ScaledLineBorderTest
|
||||||
|
*/
|
||||||
|
public class ScaledLineBorderTest {
|
||||||
|
private static final Dimension SIZE = new Dimension(120, 25);
|
||||||
|
|
||||||
|
private static final Color OUTER_COLOR = Color.BLACK;
|
||||||
|
private static final Color BORDER_COLOR = Color.RED;
|
||||||
|
private static final Color INSIDE_COLOR = Color.WHITE;
|
||||||
|
private static final Color TRANSPARENT_COLOR = new Color(0x00000000, true);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
Collection<String> params = Arrays.asList(args);
|
||||||
|
final boolean showFrame = params.contains("-show");
|
||||||
|
final boolean saveImages = params.contains("-save");
|
||||||
|
SwingUtilities.invokeAndWait(() -> testScaling(showFrame, saveImages));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testScaling(boolean showFrame, boolean saveImages) {
|
||||||
|
JComponent content = createUI();
|
||||||
|
if (showFrame) {
|
||||||
|
showFrame(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
paintToImages(content, saveImages);
|
||||||
|
verifyBorderRendering(saveImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyBorderRendering(final boolean saveImages) {
|
||||||
|
String errorMessage = null;
|
||||||
|
int errorCount = 0;
|
||||||
|
for (int i = 0; i < images.size(); i++) {
|
||||||
|
BufferedImage img = images.get(i);
|
||||||
|
double scaling = scales[i];
|
||||||
|
try {
|
||||||
|
int thickness = (int) Math.floor(scaling);
|
||||||
|
|
||||||
|
checkVerticalBorders(SIZE.width / 2, thickness, img);
|
||||||
|
|
||||||
|
for (Point p : panelLocations) {
|
||||||
|
int y = (int) (p.y * scaling) + SIZE.height / 2;
|
||||||
|
checkHorizontalBorder(y, thickness, img);
|
||||||
|
}
|
||||||
|
} catch (Error e) {
|
||||||
|
if (errorMessage == null) {
|
||||||
|
errorMessage = e.getMessage();
|
||||||
|
}
|
||||||
|
errorCount++;
|
||||||
|
|
||||||
|
System.err.printf("Scaling: %.2f\n", scaling);
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// Save the image if it wasn't already saved
|
||||||
|
if (!saveImages) {
|
||||||
|
saveImage(img, getImageFileName(scaling));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCount > 0) {
|
||||||
|
throw new Error("Test failed: "
|
||||||
|
+ errorCount + " error(s) detected - "
|
||||||
|
+ errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkVerticalBorders(final int x,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
checkBorder(x, 0,
|
||||||
|
0, 1,
|
||||||
|
thickness, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkHorizontalBorder(final int y,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
checkBorder(0, y,
|
||||||
|
1, 0,
|
||||||
|
thickness, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkBorder(final int xStart, final int yStart,
|
||||||
|
final int xStep, final int yStep,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
final int width = img.getWidth();
|
||||||
|
final int height = img.getHeight();
|
||||||
|
|
||||||
|
State state = State.BACKGROUND;
|
||||||
|
int borderThickness = 0;
|
||||||
|
|
||||||
|
int x = xStart;
|
||||||
|
int y = yStart;
|
||||||
|
do {
|
||||||
|
do {
|
||||||
|
final int color = img.getRGB(x, y);
|
||||||
|
switch (state) {
|
||||||
|
case BACKGROUND:
|
||||||
|
if (color == BORDER_COLOR.getRGB()) {
|
||||||
|
state = State.LEFT;
|
||||||
|
borderThickness = 1;
|
||||||
|
} else if (color != OUTER_COLOR.getRGB()
|
||||||
|
&& color != TRANSPARENT_COLOR.getRGB()) {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LEFT:
|
||||||
|
if (color == BORDER_COLOR.getRGB()) {
|
||||||
|
borderThickness++;
|
||||||
|
} else if (color == INSIDE_COLOR.getRGB()) {
|
||||||
|
if (borderThickness != thickness) {
|
||||||
|
throwWrongThickness(thickness, borderThickness, x, y);
|
||||||
|
}
|
||||||
|
state = State.INSIDE;
|
||||||
|
borderThickness = 0;
|
||||||
|
} else {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSIDE:
|
||||||
|
if (color == BORDER_COLOR.getRGB()) {
|
||||||
|
state = State.RIGHT;
|
||||||
|
borderThickness = 1;
|
||||||
|
} else if (color != INSIDE_COLOR.getRGB()) {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIGHT:
|
||||||
|
if (color == BORDER_COLOR.getRGB()) {
|
||||||
|
borderThickness++;
|
||||||
|
} else if (color == OUTER_COLOR.getRGB()) {
|
||||||
|
if (borderThickness != thickness) {
|
||||||
|
throwWrongThickness(thickness, borderThickness, x, y);
|
||||||
|
}
|
||||||
|
state = State.BACKGROUND;
|
||||||
|
borderThickness = 0;
|
||||||
|
} else {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (yStep > 0 && ((y += yStep) < height));
|
||||||
|
} while (xStep > 0 && ((x += xStep) < width));
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
BACKGROUND, LEFT, INSIDE, RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwWrongThickness(int thickness, int borderThickness,
|
||||||
|
int x, int y) {
|
||||||
|
throw new Error(
|
||||||
|
String.format("Wrong border thickness at %d, %d: %d vs %d",
|
||||||
|
x, y, borderThickness, thickness));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwUnexpectedColor(int x, int y, int color) {
|
||||||
|
throw new Error(
|
||||||
|
String.format("Unexpected color at %d, %d: %08x",
|
||||||
|
x, y, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JComponent createUI() {
|
||||||
|
Box contentPanel = Box.createVerticalBox();
|
||||||
|
contentPanel.setBackground(OUTER_COLOR);
|
||||||
|
|
||||||
|
Dimension childSize = null;
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
JComponent filler = new JPanel(null);
|
||||||
|
filler.setBackground(INSIDE_COLOR);
|
||||||
|
filler.setPreferredSize(SIZE);
|
||||||
|
filler.setBounds(i, 0, SIZE.width, SIZE.height);
|
||||||
|
filler.setBorder(BorderFactory.createLineBorder(BORDER_COLOR));
|
||||||
|
|
||||||
|
JPanel childPanel = new JPanel(new BorderLayout());
|
||||||
|
childPanel.setBorder(BorderFactory.createEmptyBorder(0, i, 4, 4));
|
||||||
|
childPanel.add(filler, BorderLayout.CENTER);
|
||||||
|
childPanel.setBackground(OUTER_COLOR);
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
// Save coordinates of the panels
|
||||||
|
for (Component comp : contentPanel.getComponents()) {
|
||||||
|
panelLocations.add(comp.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showFrame(JComponent content) {
|
||||||
|
JFrame frame = new JFrame("Scaled Line Border Test");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
|
frame.getContentPane().add(content, BorderLayout.CENTER);
|
||||||
|
frame.pack();
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void paintToImages(final JComponent content,
|
||||||
|
final boolean saveImages) {
|
||||||
|
for (double scaling : scales) {
|
||||||
|
BufferedImage image =
|
||||||
|
new BufferedImage((int) Math.ceil(content.getWidth() * scaling),
|
||||||
|
(int) Math.ceil(content.getHeight() * scaling),
|
||||||
|
BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
g2d.scale(scaling, scaling);
|
||||||
|
content.paint(g2d);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
if (saveImages) {
|
||||||
|
saveImage(image, getImageFileName(scaling));
|
||||||
|
}
|
||||||
|
images.add(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getImageFileName(final double scaling) {
|
||||||
|
return String.format("test%.2f.png", scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,313 @@
|
|||||||
|
/*
|
||||||
|
* 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 free software; you can redistribute it and/or modify it
|
||||||
|
* under the terms of the GNU General Public License version 2 only, as
|
||||||
|
* published by the Free Software Foundation.
|
||||||
|
*
|
||||||
|
* 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.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.imageio.ImageIO;
|
||||||
|
import javax.swing.Box;
|
||||||
|
import javax.swing.BoxLayout;
|
||||||
|
import javax.swing.JComponent;
|
||||||
|
import javax.swing.JFrame;
|
||||||
|
import javax.swing.JPanel;
|
||||||
|
import javax.swing.JTextField;
|
||||||
|
import javax.swing.SwingUtilities;
|
||||||
|
import javax.swing.border.LineBorder;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @test
|
||||||
|
* @bug 8282958
|
||||||
|
* @summary Verify all the borders are rendered consistently for a JTextField
|
||||||
|
* in Windows LaF which uses LineBorder
|
||||||
|
* @requires (os.family == "windows")
|
||||||
|
* @run main ScaledTextFieldBorderTest
|
||||||
|
*/
|
||||||
|
public class ScaledTextFieldBorderTest {
|
||||||
|
|
||||||
|
private static final double[] scales = {
|
||||||
|
1.00, 1.25, 1.50, 1.75,
|
||||||
|
2.00, 2.25, 2.50, 2.75,
|
||||||
|
3.00
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final List<BufferedImage> images =
|
||||||
|
new ArrayList<>(scales.length);
|
||||||
|
|
||||||
|
private static final List<Point> panelLocations =
|
||||||
|
new ArrayList<>(4);
|
||||||
|
|
||||||
|
private static Dimension textFieldSize;
|
||||||
|
|
||||||
|
public static void main(String[] args) throws Exception {
|
||||||
|
Collection<String> params = Arrays.asList(args);
|
||||||
|
final boolean showFrame = params.contains("-show");
|
||||||
|
final boolean saveImages = params.contains("-save");
|
||||||
|
SwingUtilities.invokeAndWait(() -> testScaling(showFrame, saveImages));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void testScaling(boolean showFrame, boolean saveImages) {
|
||||||
|
JComponent content = createUI();
|
||||||
|
if (showFrame) {
|
||||||
|
showFrame(content);
|
||||||
|
}
|
||||||
|
|
||||||
|
paintToImages(content, saveImages);
|
||||||
|
verifyBorderRendering(saveImages);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void verifyBorderRendering(final boolean saveImages) {
|
||||||
|
String errorMessage = null;
|
||||||
|
int errorCount = 0;
|
||||||
|
for (int i = 0; i < images.size(); i++) {
|
||||||
|
BufferedImage img = images.get(i);
|
||||||
|
double scaling = scales[i];
|
||||||
|
try {
|
||||||
|
int thickness = (int) Math.floor(scaling);
|
||||||
|
|
||||||
|
checkVerticalBorders(textFieldSize.width / 2, thickness, img);
|
||||||
|
|
||||||
|
for (Point p : panelLocations) {
|
||||||
|
int y = (int) (p.y * scaling) + textFieldSize.height / 2;
|
||||||
|
checkHorizontalBorder(y, thickness, img);
|
||||||
|
}
|
||||||
|
} catch (Error | Exception e) {
|
||||||
|
if (errorMessage == null) {
|
||||||
|
errorMessage = e.getMessage();
|
||||||
|
}
|
||||||
|
errorCount++;
|
||||||
|
|
||||||
|
System.err.printf("Scaling: %.2f\n", scaling);
|
||||||
|
e.printStackTrace();
|
||||||
|
|
||||||
|
// Save the image if it wasn't already saved
|
||||||
|
if (!saveImages) {
|
||||||
|
saveImage(img, getImageFileName(scaling));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorCount > 0) {
|
||||||
|
throw new Error("Test failed: "
|
||||||
|
+ errorCount + " error(s) detected - "
|
||||||
|
+ errorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkVerticalBorders(final int x,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
checkBorder(x, 0,
|
||||||
|
0, 1,
|
||||||
|
thickness, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void checkHorizontalBorder(final int y,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
checkBorder(0, y,
|
||||||
|
1, 0,
|
||||||
|
thickness, img);
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum State {
|
||||||
|
BACKGROUND, LEFT, INSIDE, RIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int transparentColor = 0x00000000;
|
||||||
|
private static int panelColor;
|
||||||
|
private static int borderColor;
|
||||||
|
private static int insideColor;
|
||||||
|
|
||||||
|
private static void checkBorder(final int xStart, final int yStart,
|
||||||
|
final int xStep, final int yStep,
|
||||||
|
final int thickness,
|
||||||
|
final BufferedImage img) {
|
||||||
|
final int width = img.getWidth();
|
||||||
|
final int height = img.getHeight();
|
||||||
|
|
||||||
|
State state = State.BACKGROUND;
|
||||||
|
int borderThickness = -1;
|
||||||
|
|
||||||
|
int x = xStart;
|
||||||
|
int y = yStart;
|
||||||
|
do {
|
||||||
|
do {
|
||||||
|
final int color = img.getRGB(x, y);
|
||||||
|
switch (state) {
|
||||||
|
case BACKGROUND:
|
||||||
|
if (color == borderColor) {
|
||||||
|
state = State.LEFT;
|
||||||
|
borderThickness = 1;
|
||||||
|
} else if (color != panelColor
|
||||||
|
&& color != transparentColor) {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case LEFT:
|
||||||
|
if (color == borderColor) {
|
||||||
|
borderThickness++;
|
||||||
|
} else if (color == insideColor) {
|
||||||
|
if (borderThickness != thickness) {
|
||||||
|
throwWrongThickness(thickness, borderThickness, x, y);
|
||||||
|
}
|
||||||
|
state = State.INSIDE;
|
||||||
|
borderThickness = 0;
|
||||||
|
} else {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case INSIDE:
|
||||||
|
if (color == borderColor) {
|
||||||
|
state = State.RIGHT;
|
||||||
|
borderThickness = 1;
|
||||||
|
} else if (color != insideColor) {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case RIGHT:
|
||||||
|
if (color == borderColor) {
|
||||||
|
borderThickness++;
|
||||||
|
} else if (color == panelColor) {
|
||||||
|
if (borderThickness != thickness) {
|
||||||
|
throwWrongThickness(thickness, borderThickness, x, y);
|
||||||
|
}
|
||||||
|
state = State.BACKGROUND;
|
||||||
|
borderThickness = 0;
|
||||||
|
} else {
|
||||||
|
throwUnexpectedColor(x, y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (yStep > 0 && ((y += yStep) < height));
|
||||||
|
} while (xStep > 0 && ((x += xStep) < width));
|
||||||
|
|
||||||
|
if (state != State.BACKGROUND) {
|
||||||
|
throw new Error(String.format("Border is not rendered correctly at %d, %d", x, y));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwWrongThickness(int thickness, int borderThickness, int x, int y) {
|
||||||
|
throw new Error(
|
||||||
|
String.format("Wrong border thickness at %d, %d: %d vs %d",
|
||||||
|
x, y, borderThickness, thickness));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void throwUnexpectedColor(int x, int y, int color) {
|
||||||
|
throw new Error(
|
||||||
|
String.format("Unexpected color at %d, %d: %08x",
|
||||||
|
x, y, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static JComponent createUI() {
|
||||||
|
JPanel contentPanel = new JPanel();
|
||||||
|
contentPanel.setLayout(new BoxLayout(contentPanel, BoxLayout.Y_AXIS));
|
||||||
|
|
||||||
|
final LineBorder tfBorder = new LineBorder(Color.RED);
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
JTextField textField = new JTextField(10);
|
||||||
|
textField.setBorder(tfBorder);
|
||||||
|
Box childPanel = Box.createHorizontalBox();
|
||||||
|
childPanel.add(Box.createHorizontalStrut(i));
|
||||||
|
childPanel.add(textField);
|
||||||
|
childPanel.add(Box.createHorizontalStrut(4));
|
||||||
|
|
||||||
|
contentPanel.add(childPanel);
|
||||||
|
if (textFieldSize == null) {
|
||||||
|
textFieldSize = textField.getPreferredSize();
|
||||||
|
borderColor = tfBorder.getLineColor().getRGB();
|
||||||
|
insideColor = textField.getBackground().getRGB();
|
||||||
|
}
|
||||||
|
textField.setBounds(i, 0, textFieldSize.width, textFieldSize.height);
|
||||||
|
childPanel.setBounds(0, (textFieldSize.height + 4) * i,
|
||||||
|
textFieldSize.width + i + 4, textFieldSize.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
contentPanel.setSize(textFieldSize.width + 4,
|
||||||
|
(textFieldSize.height + 4) * 4);
|
||||||
|
|
||||||
|
panelColor = contentPanel.getBackground().getRGB();
|
||||||
|
|
||||||
|
// Save coordinates of the panels
|
||||||
|
for (Component comp : contentPanel.getComponents()) {
|
||||||
|
panelLocations.add(comp.getLocation());
|
||||||
|
}
|
||||||
|
|
||||||
|
return contentPanel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void showFrame(JComponent content) {
|
||||||
|
JFrame frame = new JFrame("Text Field Border Test");
|
||||||
|
frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
|
||||||
|
frame.getContentPane().add(content, BorderLayout.CENTER);
|
||||||
|
frame.pack();
|
||||||
|
frame.setLocationRelativeTo(null);
|
||||||
|
frame.setVisible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void paintToImages(final JComponent content,
|
||||||
|
final boolean saveImages) {
|
||||||
|
for (double scaling : scales) {
|
||||||
|
BufferedImage image =
|
||||||
|
new BufferedImage((int) Math.ceil(content.getWidth() * scaling),
|
||||||
|
(int) Math.ceil(content.getHeight() * scaling),
|
||||||
|
BufferedImage.TYPE_INT_ARGB);
|
||||||
|
|
||||||
|
Graphics2D g2d = image.createGraphics();
|
||||||
|
g2d.scale(scaling, scaling);
|
||||||
|
content.paint(g2d);
|
||||||
|
g2d.dispose();
|
||||||
|
|
||||||
|
if (saveImages) {
|
||||||
|
saveImage(image, getImageFileName(scaling));
|
||||||
|
}
|
||||||
|
images.add(image);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getImageFileName(final double scaling) {
|
||||||
|
return String.format("test%.2f.png", scaling);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user