8151303: [macosx] [hidpi] JButton's low-res. icon is visible when clicking on it
authoralexsch
Fri, 19 Aug 2016 16:48:53 +0400
changeset 40707 8b0d53520355
parent 40706 19e735c76e8c
child 40708 2516b180bfc0
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
jdk/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java
jdk/src/java.desktop/share/classes/javax/swing/GrayFilter.java
jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionCachedImage.java
jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionToolkitImage.java
jdk/test/java/awt/image/MultiResolutionImage/MultiResolutionDisabledImageTest.java
jdk/test/javax/swing/JButton/8151303/PressedIconTest.java
--- a/jdk/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java	Fri Aug 19 12:22:23 2016 +0530
+++ b/jdk/src/java.desktop/macosx/classes/com/apple/laf/AquaUtils.java	Fri Aug 19 16:48:53 2016 +0400
@@ -105,36 +105,42 @@
     }
 
     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 generateFilteredImage(Image image, ImageFilter filter) {
+        final ImageProducer prod = new FilteredImageSource(image.getSource(), filter);
+        return Toolkit.getDefaultToolkit().createImage(prod);
     }
 
-    static Image generateLightenedImage(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 {
--- a/jdk/src/java.desktop/share/classes/javax/swing/GrayFilter.java	Fri Aug 19 12:22:23 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/javax/swing/GrayFilter.java	Fri Aug 19 16:48:53 2016 +0400
@@ -26,6 +26,7 @@
 
 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 @@
      * @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);
--- a/jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionCachedImage.java	Fri Aug 19 12:22:23 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionCachedImage.java	Fri Aug 19 16:48:53 2016 +0400
@@ -33,6 +33,7 @@
 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 @@
     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 @@
                         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);
--- a/jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionToolkitImage.java	Fri Aug 19 12:22:23 2016 +0530
+++ b/jdk/src/java.desktop/share/classes/sun/awt/image/MultiResolutionToolkitImage.java	Fri Aug 19 16:48:53 2016 +0400
@@ -29,6 +29,7 @@
 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 @@
                 ? 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(
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/java/awt/image/MultiResolutionImage/MultiResolutionDisabledImageTest.java	Fri Aug 19 16:48:53 2016 +0400
@@ -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));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/test/javax/swing/JButton/8151303/PressedIconTest.java	Fri Aug 19 16:48:53 2016 +0400
@@ -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;
+    }
+}