8151303: [macosx] [hidpi] JButton's low-res. icon is visible when clicking on it

8156182: [macosx] HiDPI/Retina icons do not work for disabled JButton/JMenuItem etc

Reviewed-by: flar, prr
This commit is contained in:
Alexander Scherbatiy 2016-08-19 16:48:53 +04:00
parent 5390af7c2b
commit 966cbcfce3
6 changed files with 335 additions and 17 deletions

View File

@ -105,38 +105,44 @@ final class AquaUtils {
}
static Image generateSelectedDarkImage(final Image image) {
final ImageProducer prod = new FilteredImageSource(image.getSource(), new IconImageFilter() {
final ImageFilter filter = new IconImageFilter() {
@Override
int getGreyFor(final int gray) {
return gray * 75 / 100;
}
});
return Toolkit.getDefaultToolkit().createImage(prod);
};
return map(image, filter);
}
static Image generateDisabledImage(final Image image) {
final ImageProducer prod = new FilteredImageSource(image.getSource(), new IconImageFilter() {
final ImageFilter filter = new IconImageFilter() {
@Override
int getGreyFor(final int gray) {
return 255 - ((255 - gray) * 65 / 100);
}
});
return Toolkit.getDefaultToolkit().createImage(prod);
};
return map(image, filter);
}
static Image generateLightenedImage(final Image image, final int percent) {
final GrayFilter filter = new GrayFilter(true, percent);
return (image instanceof MultiResolutionCachedImage)
? ((MultiResolutionCachedImage) image).map(
rv -> generateLightenedImage(rv, filter))
: generateLightenedImage(image, filter);
return map(image, filter);
}
static Image generateLightenedImage(Image image, ImageFilter filter) {
static Image generateFilteredImage(Image image, ImageFilter filter) {
final ImageProducer prod = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(prod);
}
private static Image map(Image image, ImageFilter filter) {
if (image instanceof MultiResolutionImage) {
return MultiResolutionCachedImage
.map((MultiResolutionImage) image,
(img) -> generateFilteredImage(img, filter));
}
return generateFilteredImage(image, filter);
}
private abstract static class IconImageFilter extends RGBImageFilter {
IconImageFilter() {
super();

View File

@ -26,6 +26,7 @@ package javax.swing;
import java.awt.*;
import java.awt.image.*;
import sun.awt.image.MultiResolutionCachedImage;
/**
* An image filter that "disables" an image by turning
@ -48,7 +49,16 @@ public class GrayFilter extends RGBImageFilter {
* @param i an {@code Image} to be created as disabled
* @return the new grayscale image created from {@code i}
*/
public static Image createDisabledImage (Image i) {
public static Image createDisabledImage(Image i) {
if (i instanceof MultiResolutionImage) {
return MultiResolutionCachedImage
.map((MultiResolutionImage) i,
(img) -> createDisabledImageImpl(img));
}
return createDisabledImageImpl(i);
}
private static Image createDisabledImageImpl(Image i) {
GrayFilter filter = new GrayFilter(true, 50);
ImageProducer prod = new FilteredImageSource(i.getSource(), filter);
Image grayImage = Toolkit.getDefaultToolkit().createImage(prod);

View File

@ -33,6 +33,7 @@ import java.util.List;
import java.util.function.Function;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import java.awt.image.MultiResolutionImage;
import java.awt.image.AbstractMultiResolutionImage;
public class MultiResolutionCachedImage extends AbstractMultiResolutionImage {
@ -44,17 +45,30 @@ public class MultiResolutionCachedImage extends AbstractMultiResolutionImage {
private int availableInfo;
public MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
BiFunction<Integer, Integer, Image> mapper) {
this(baseImageWidth, baseImageHeight, new Dimension[]{new Dimension(
baseImageWidth, baseImageHeight)
BiFunction<Integer, Integer, Image> mapper)
{
this(baseImageWidth, baseImageHeight,
new Dimension[]{new Dimension( baseImageWidth, baseImageHeight)
}, mapper);
}
public MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
Dimension2D[] sizes, BiFunction<Integer, Integer, Image> mapper) {
Dimension2D[] sizes,
BiFunction<Integer, Integer, Image> mapper)
{
this(baseImageWidth, baseImageHeight, sizes, mapper, true);
}
private MultiResolutionCachedImage(int baseImageWidth, int baseImageHeight,
Dimension2D[] sizes,
BiFunction<Integer, Integer, Image> mapper,
boolean copySizes)
{
this.baseImageWidth = baseImageWidth;
this.baseImageHeight = baseImageHeight;
this.sizes = (sizes == null) ? null : Arrays.copyOf(sizes, sizes.length);
this.sizes = (copySizes && sizes != null)
? Arrays.copyOf(sizes, sizes.length)
: sizes;
this.mapper = mapper;
}
@ -99,6 +113,35 @@ public class MultiResolutionCachedImage extends AbstractMultiResolutionImage {
mapper.apply(getResolutionVariant(width, height)));
}
public static Image map(MultiResolutionImage mrImage,
Function<Image, Image> mapper) {
if (mrImage instanceof MultiResolutionToolkitImage) {
MultiResolutionToolkitImage mrtImage =
(MultiResolutionToolkitImage) mrImage;
return MultiResolutionToolkitImage.map(mrtImage, mapper);
}
BiFunction<Integer, Integer, Image> sizeMapper
= (w, h) -> mapper.apply(mrImage.getResolutionVariant(w, h));
if (mrImage instanceof MultiResolutionCachedImage) {
MultiResolutionCachedImage mrcImage
= (MultiResolutionCachedImage) mrImage;
return new MultiResolutionCachedImage(mrcImage.baseImageWidth,
mrcImage.baseImageHeight,
mrcImage.sizes,
sizeMapper,
false);
}
Image image = (Image) mrImage;
int width = image.getWidth(null);
int height = image.getHeight(null);
return new MultiResolutionCachedImage(width, height, sizeMapper);
}
@Override
public int getWidth(ImageObserver observer) {
updateInfo(observer, ImageObserver.WIDTH);

View File

@ -29,6 +29,7 @@ import java.awt.image.ImageObserver;
import java.awt.image.MultiResolutionImage;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import sun.awt.SoftCache;
public class MultiResolutionToolkitImage extends ToolkitImage implements MultiResolutionImage {
@ -47,6 +48,13 @@ public class MultiResolutionToolkitImage extends ToolkitImage implements MultiRe
? this : resolutionVariant;
}
public static Image map(MultiResolutionToolkitImage mrImage,
Function<Image, Image> mapper) {
Image baseImage = mapper.apply(mrImage);
Image rvImage = mapper.apply(mrImage.resolutionVariant);
return new MultiResolutionToolkitImage(baseImage, rvImage);
}
private static void checkSize(double width, double height) {
if (width <= 0 || height <= 0) {
throw new IllegalArgumentException(String.format(

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2016, 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.Color;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.MediaTracker;
import java.awt.Toolkit;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
import java.io.File;
import javax.imageio.ImageIO;
import javax.swing.GrayFilter;
import java.awt.image.MultiResolutionImage;
import javax.swing.JLabel;
/**
* @test
* @bug 8156182
* @summary [macosx] HiDPI/Retina icons do not work for disabled
* JButton/JMenuItem etc.
* @run main/othervm -Dsun.java2d.uiScale=2 MultiResolutionDisabledImageTest
*/
public class MultiResolutionDisabledImageTest {
private static final String IMAGE_NAME_1X = "image.png";
private static final String IMAGE_NAME_2X = "image@2x.png";
private static final int IMAGE_SIZE = 100;
private static final Color COLOR_1X = Color.GREEN;
private static final Color COLOR_2X = Color.BLUE;
public static void main(String[] args) throws Exception {
Image baseMRImage = new BaseMultiResolutionImage(createImage(1),
createImage(2));
testMRDisabledImage(baseMRImage);
saveImages();
Image toolkitMRImage = Toolkit.getDefaultToolkit().getImage(IMAGE_NAME_1X);
if (toolkitMRImage instanceof MultiResolutionImage) {
testMRDisabledImage(toolkitMRImage);
}
}
private static void testMRDisabledImage(Image image) throws Exception {
Image disabledImage = GrayFilter.createDisabledImage(image);
MediaTracker mediaTracker = new MediaTracker(new JLabel());
mediaTracker.addImage(disabledImage, 0);
mediaTracker.waitForID(0);
BufferedImage buffImage = new BufferedImage(IMAGE_SIZE,
IMAGE_SIZE,
BufferedImage.TYPE_INT_RGB);
int x = IMAGE_SIZE / 2;
int y = IMAGE_SIZE / 2;
Graphics2D g = buffImage.createGraphics();
g.scale(1, 1);
g.drawImage(disabledImage, 0, 0, null);
int rgb1x = buffImage.getRGB(x, y);
g.scale(2, 2);
g.drawImage(disabledImage, 0, 0, null);
int rgb2x = buffImage.getRGB(x, y);
g.dispose();
if (rgb1x == rgb2x) {
throw new RuntimeException("Disabled image is the same for the base"
+ "image and the resolution variant");
}
}
private static BufferedImage createImage(int scale) throws Exception {
BufferedImage image = new BufferedImage(scale * 200, scale * 300,
BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
g.setColor(scale == 1 ? COLOR_1X : COLOR_2X);
g.fillRect(0, 0, scale * 200, scale * 300);
g.dispose();
return image;
}
private static void saveImages() throws Exception {
saveImage(createImage(1), IMAGE_NAME_1X);
saveImage(createImage(2), IMAGE_NAME_2X);
}
private static void saveImage(BufferedImage image, String name) throws Exception {
ImageIO.write(image, "png", new File(name));
}
}

View File

@ -0,0 +1,132 @@
/*
* Copyright (c) 2016, 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.Dimension;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.Robot;
import java.awt.event.InputEvent;
import java.awt.image.BaseMultiResolutionImage;
import java.awt.image.BufferedImage;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
/**
* @test
* @bug 8151303
* @summary [macosx] [hidpi] JButton's low-res. icon is visible when clicking on it
* @run main/othervm PressedIconTest
* @run main/othervm -Dsun.java2d.uiScale=2 PressedIconTest
*/
public class PressedIconTest {
private final static int IMAGE_SIZE = 300;
private final static Color COLOR_1X = Color.RED;
private final static Color COLOR_2X = Color.BLUE;
private static JFrame frame;
private static volatile double scale = -1;
private static volatile int centerX;
private static volatile int centerY;
public static void main(String[] args) throws Exception {
Robot robot = new Robot();
robot.setAutoDelay(50);
SwingUtilities.invokeAndWait(() -> createAndShowGUI());
robot.waitForIdle();
SwingUtilities.invokeAndWait(() -> {
scale = frame.getGraphicsConfiguration().getDefaultTransform()
.getScaleX();
Point location = frame.getLocation();
Dimension size = frame.getSize();
centerX = location.x + size.width / 2;
centerY = location.y + size.height / 2;
});
robot.waitForIdle();
robot.mouseMove(centerX, centerY);
robot.mousePress(InputEvent.BUTTON1_MASK);
robot.waitForIdle();
Thread.sleep(100);
Color color = robot.getPixelColor(centerX, centerY);
robot.mouseRelease(InputEvent.BUTTON1_MASK);
SwingUtilities.invokeAndWait(() -> frame.dispose());
if ((scale == 1 && !similar(color, COLOR_1X))
|| (scale == 2 && !similar(color, COLOR_2X))) {
throw new RuntimeException("Colors are different!");
}
}
private static void createAndShowGUI() {
frame = new JFrame();
frame.setSize(IMAGE_SIZE, IMAGE_SIZE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new BorderLayout());
BufferedImage img1x = generateImage(1, COLOR_1X);
BufferedImage img2x = generateImage(2, COLOR_2X);
BaseMultiResolutionImage mri = new BaseMultiResolutionImage(
new BufferedImage[]{img1x, img2x});
Icon mrIcon = new ImageIcon(mri);
JToggleButton button = new JToggleButton();
button.setIcon(mrIcon);
panel.add(button, BorderLayout.CENTER);
frame.getContentPane().add(panel);
frame.setVisible(true);
}
private static boolean similar(Color c1, Color c2) {
return similar(c1.getRed(), c2.getRed())
&& similar(c1.getGreen(), c2.getGreen())
&& similar(c1.getBlue(), c2.getBlue());
}
private static boolean similar(int n, int m) {
return Math.abs(n - m) <= 50;
}
private static BufferedImage generateImage(int scale, Color c) {
int size = IMAGE_SIZE * scale;
BufferedImage img = new BufferedImage(size, size, BufferedImage.TYPE_INT_RGB);
Graphics g = img.createGraphics();
g.setColor(c);
g.fillRect(0, 0, size, size);
g.dispose();
return img;
}
}