/*
* 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.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.geom.AffineTransform;
import java.awt.image.BaseMultiResolutionImage;
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.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import static java.awt.RenderingHints.KEY_TEXT_ANTIALIASING;
import static java.awt.RenderingHints.VALUE_TEXT_ANTIALIAS_ON;
import static java.awt.image.BufferedImage.TYPE_INT_RGB;
/**
* Test case for
* JDK-8265214:
* Blurry button icons with 125% monitor display scaling on Windows 10.
*
This app has several modes:
*
* - by default, the app generates the images on the fly and shows
* the GUI.
* -generate
: generates images for different scaling
* saves them to disk and exits;
* -load
: loads images from disk and shows the GUI;
*
*/
public final class HiDPIButtonImage {
/**
* Stores font size for text and base image size.
*/
private enum Size {
SIZE_36(36, 200, 60),
SIZE_72(72, 360, 100);
public final Font font;
public final int width;
public final int height;
Size(int fontSize, int width, int height) {
this.font = new Font("Dialog", Font.PLAIN, fontSize);
this.width = width;
this.height = height;
}
}
/**
* Stores the scale factor and text color.
*/
private enum Scale {
SCALE_100(100, Color.BLACK),
SCALE_125(125, Color.ORANGE),
SCALE_150(150, Color.MAGENTA),
SCALE_175(175, Color.GREEN),
SCALE_200(200, Color.RED);
public final int scale;
public final Color color;
Scale(int scale, Color color) {
this.scale = scale;
this.color = color;
}
public double getScale2D() {
return (double) scale / 100.0;
}
}
private static final String POINTS = "points";
public static void main(String[] args) throws IOException {
String param = args.length > 0 ? args[0] : "";
switch (param) {
case "-generate":
generateAndSaveImages();
break;
case "-load":
loadImagesAndShowGUI();
break;
default:
generateImagesAndShowGUI();
}
}
@FunctionalInterface
private interface ImageSupplier {
Image getImage(Size size, Scale scale) throws IOException;
}
private static Image getImages(final Size size,
final ImageSupplier supplier)
throws IOException {
List images = new ArrayList<>(Scale.values().length);
for (Scale scale : Scale.values()) {
images.add(supplier.getImage(size, scale));
}
return new BaseMultiResolutionImage(images.toArray(new Image[0]));
}
private static List getImages(final ImageSupplier supplier)
throws IOException {
List imageSizes = new ArrayList<>(Size.values().length);
for (Size size : Size.values()) {
imageSizes.add(getImages(size, supplier));
}
return imageSizes;
}
private static void generateImagesAndShowGUI() throws IOException {
showGUI(getImages(HiDPIButtonImage::createImage));
}
private static void loadImagesAndShowGUI() throws IOException {
showGUI(getImages(HiDPIButtonImage::loadImage));
}
private static void showGUI(final List imageSizes) {
SwingUtilities.invokeLater(() -> createGUI(imageSizes));
}
private static void createGUI(final List imageSizes) {
JPanel main = new JPanel();
for (Image image : imageSizes) {
main.add(new JButton(new ImageIcon(image)));
}
JPanel footer = new JPanel();
footer.setLayout(new BoxLayout(footer, BoxLayout.Y_AXIS));
footer.add(new JLabel("Java version: "
+ System.getProperty("java.runtime.version")));
footer.add(new JLabel(System.getProperty("os.name") + " v."
+ System.getProperty("os.version")));
JFrame frame = new JFrame("High DPI Button Image");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(main, BorderLayout.CENTER);
frame.getContentPane().add(footer, BorderLayout.SOUTH);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private static void generateAndSaveImages() throws IOException {
for (Size size : Size.values()) {
for (Scale scale : Scale.values()) {
BufferedImage image = createImage(size, scale);
saveImage(image, getImageFileName(size, scale));
}
}
}
private static BufferedImage createImage(final Size size,
final Scale scale) {
int width = (int) (size.width * scale.getScale2D());
int height = (int) (size.height * scale.getScale2D());
BufferedImage image = new BufferedImage(width, height, TYPE_INT_RGB);
Graphics2D g2d = image.createGraphics();
g2d.setColor(Color.WHITE);
g2d.fillRect(0, 0, width, height);
g2d.setFont(size.font);
g2d.setColor(scale.color);
g2d.setRenderingHint(KEY_TEXT_ANTIALIASING, VALUE_TEXT_ANTIALIAS_ON);
String str = size.font.getSize() + " " + POINTS;
Point txtLoc = getTextLocation(g2d, size, str);
AffineTransform at = new AffineTransform();
at.setToScale(scale.getScale2D(), scale.getScale2D());
g2d.setTransform(at);
g2d.drawString(str, txtLoc.x, txtLoc.y);
g2d.dispose();
return image;
}
private static Image loadImage(final Size size,
final Scale scale) throws IOException {
return ImageIO.read(new File(getImageFileName(size, scale)));
}
private static Point getTextLocation(final Graphics2D g2d,
final Size size,
final String str) {
FontMetrics fm = g2d.getFontMetrics();
int strWidth = fm.stringWidth(str);
int strHeight = fm.getHeight();
return new Point((size.width - strWidth) / 2,
(size.height + strHeight) / 2 - fm.getDescent());
}
private static void saveImage(final BufferedImage image,
final String fileName) throws IOException {
ImageIO.write(image, "png", new File(fileName));
}
private static String getImageFileName(final Size size,
final Scale scale) {
return size.font.getSize() + POINTS + "-" + scale.scale + ".png";
}
}