Java 视网膜显示器上的Swing和位图

Java 视网膜显示器上的Swing和位图,java,swing,icons,pixel,retina-display,Java,Swing,Icons,Pixel,Retina Display,我有一个Java桌面应用程序,可以在OSX上运行 现在新的MacBookPro有了一个视网膜显示屏,我很担心:关于Swing,它将如何工作 如果Java应用程序同时使用Swing组件和一些位图图形(如自定义图标/ImageIcon),该怎么办 是否所有桌面Java应用程序都会自动调整大小(例如,通过将每个像素增加四倍),还是我需要创建两个版本的图标集(例如,一个具有24x24图标,另一个具有96x96图标)并以某种方式确定应用程序正在视网膜显示器上运行?这是我的视网膜macbook'12上的图标

我有一个Java桌面应用程序,可以在OSX上运行

现在新的MacBookPro有了一个视网膜显示屏,我很担心:关于Swing,它将如何工作

如果Java应用程序同时使用Swing组件和一些位图图形(如自定义图标/ImageIcon),该怎么办


是否所有桌面Java应用程序都会自动调整大小(例如,通过将每个像素增加四倍),还是我需要创建两个版本的图标集(例如,一个具有24x24图标,另一个具有96x96图标)并以某种方式确定应用程序正在视网膜显示器上运行?

这是我的视网膜macbook'12上的图标的外观:

在IntelliJ IDEA 11(swing应用程序)的左侧图标上,在IDEA 12的右侧图标上,该图标被重新命名。正如您所看到的,自动调整大小的图标(在左侧)看起来非常难看


据我所知,他们通过提供两倍大小的图标而成功。

在苹果的Java 6上,您可以提供同一图像的多个版本。根据屏幕(视网膜与否),选择并绘制一幅或另一幅图像

但是,必须以特殊方式加载这些图像:

Toolkit.getDefaultToolkit().getImage("NSImage://your_image_name_without_extension");
例如,如果您的(常规分辨率)图像名为:“剪刀.png”,则必须创建高分辨率版本”scissor@2x.png“(遵循)并将这两个图像放置在应用程序包的
参考资料
目录中(是的,您需要)。 然后打电话:

Image img = Toolkit.getDefaultToolkit().getImage("NSImage://scissor");
您可以在按钮中使用生成的图像,它将以正确的分辨率神奇地绘制出来

您还可以使用另外两个“技巧”:

  • 在绘制图像之前,在Graphics2D对象上使用(0.5,0.5)的仿射变换。也看到
  • 使用编程方式创建图像的高dpi版本
  • 到目前为止,第一个“技巧”(0.5扩展)也适用于Oracle的Java 7/8。
    也就是说,如果您以0.5的比例直接绘制一幅图像到组件的图形对象,它将在视网膜显示器上以高分辨率呈现(也将以其原始大小的一半呈现)。

    使用IconLoader库。它支持HiDPI图像它还提供了一种处理HiDPI图像(绘图等)的方法。

    我可以确认,在Oracle Java 1.8上,图像的缩放功能是有效的。我无法让
    NSImage
    hack在Java1.7或1.8上工作。我认为这只适用于Mac的Java6

    除非其他人有更好的解决方案,否则我所做的是:

    创建两组图标。 如果您有一个
    48像素
    宽度图标,请创建一个
    48px
    @normal
    DPI
    ,另一个位于
    96px
    2xdpi
    。将
    2xDPI
    图像重命名为
    @2x.png
    ,以符合苹果的命名标准

    子类
    ImageIcon
    并将其命名为
    RetinaIcon
    或其他任何名称。 您可以按如下方式测试视网膜显示:

    public static boolean isRetina() {
    
    boolean isRetina = false;
    GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    
    try {
                  Field field = graphicsDevice.getClass().getDeclaredField("scale");
            if (field != null) {
                field.setAccessible(true);
                Object scale = field.get(graphicsDevice);
                if(scale instanceof Integer && ((Integer) scale).intValue() == 2) {
                    isRetina = true;
                }
            }
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
        return isRetina;
    }
    
    @Override
    public int getIconWidth()
    {
        if(isRetina())
        {
            return super.getIconWidth()/2;
        }
        return super.getIconWidth();
    }
    
    @Override
    public int getIconHeight()
    {
        if(isRetina())
        {
            return super.getIconHeight()/2;
        }
        return super.getIconHeight();
    }
    
    @Override
    public synchronized void paintIcon(Component c, Graphics g, int x, int y) 
    {
        ImageObserver observer = getImageObserver();
    
        if (observer == null) 
        {
            observer = c;
        }
    
        Image image = getImage();
        int width = image.getWidth(observer);
        int height = image.getHeight(observer);
        final Graphics2D g2d = (Graphics2D)g.create(x, y, width, height);
    
        if(isRetina())
        {
            g2d.scale(0.5, 0.5);
        }
        else
        {
    
        }
        g2d.drawImage(image, 0, 0, observer);
        g2d.scale(1, 1);
        g2d.dispose();
    }
    
    确保
    @覆盖新
    图像图标的宽度和高度,如下所示:

    public static boolean isRetina() {
    
    boolean isRetina = false;
    GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    
    try {
                  Field field = graphicsDevice.getClass().getDeclaredField("scale");
            if (field != null) {
                field.setAccessible(true);
                Object scale = field.get(graphicsDevice);
                if(scale instanceof Integer && ((Integer) scale).intValue() == 2) {
                    isRetina = true;
                }
            }
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
        return isRetina;
    }
    
    @Override
    public int getIconWidth()
    {
        if(isRetina())
        {
            return super.getIconWidth()/2;
        }
        return super.getIconWidth();
    }
    
    @Override
    public int getIconHeight()
    {
        if(isRetina())
        {
            return super.getIconHeight()/2;
        }
        return super.getIconHeight();
    }
    
    @Override
    public synchronized void paintIcon(Component c, Graphics g, int x, int y) 
    {
        ImageObserver observer = getImageObserver();
    
        if (observer == null) 
        {
            observer = c;
        }
    
        Image image = getImage();
        int width = image.getWidth(observer);
        int height = image.getHeight(observer);
        final Graphics2D g2d = (Graphics2D)g.create(x, y, width, height);
    
        if(isRetina())
        {
            g2d.scale(0.5, 0.5);
        }
        else
        {
    
        }
        g2d.drawImage(image, 0, 0, observer);
        g2d.scale(1, 1);
        g2d.dispose();
    }
    
    一旦您对视网膜屏幕进行了测试,并且覆盖了自定义宽度/高度方法,您可以按如下方式自定义
    painIcon
    方法:

    public static boolean isRetina() {
    
    boolean isRetina = false;
    GraphicsDevice graphicsDevice = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
    
    try {
                  Field field = graphicsDevice.getClass().getDeclaredField("scale");
            if (field != null) {
                field.setAccessible(true);
                Object scale = field.get(graphicsDevice);
                if(scale instanceof Integer && ((Integer) scale).intValue() == 2) {
                    isRetina = true;
                }
            }
        } 
        catch (Exception e) {
            e.printStackTrace();
        }
        return isRetina;
    }
    
    @Override
    public int getIconWidth()
    {
        if(isRetina())
        {
            return super.getIconWidth()/2;
        }
        return super.getIconWidth();
    }
    
    @Override
    public int getIconHeight()
    {
        if(isRetina())
        {
            return super.getIconHeight()/2;
        }
        return super.getIconHeight();
    }
    
    @Override
    public synchronized void paintIcon(Component c, Graphics g, int x, int y) 
    {
        ImageObserver observer = getImageObserver();
    
        if (observer == null) 
        {
            observer = c;
        }
    
        Image image = getImage();
        int width = image.getWidth(observer);
        int height = image.getHeight(observer);
        final Graphics2D g2d = (Graphics2D)g.create(x, y, width, height);
    
        if(isRetina())
        {
            g2d.scale(0.5, 0.5);
        }
        else
        {
    
        }
        g2d.drawImage(image, 0, 0, observer);
        g2d.scale(1, 1);
        g2d.dispose();
    }
    
    我不知道这将如何与多个屏幕,但-有没有其他人可以帮助呢

    希望这段代码能有所帮助

    杰森·巴拉克劳夫

    下面是使用上述缩放的示例:

    这里有一个解决方案,当在apple菜单中使用图标时也可以使用。在那里,图标会自动变为灰色。因此,我实现了一个类DenseIcon,它可以密集地绘制:

    public synchronized void paintIcon(Component c, Graphics g, int x, int y) {
        if(getImageObserver() == null) {
            g.drawImage(getImage0(), x, y, getIconWidth(), getIconHeight(), c);
        } else {
            g.drawImage(getImage0(), x, y, getIconWidth(), getIconHeight(), getImageObserver());
        }
    }
    
    我还没有弄明白如何融入灰色的世界。因此,我们返回一个低分辨率图像,以便菜单可以进行修改:

    public Image getImage() {
        Image image = getImage0().getScaledInstance(
                getIconWidth(),
                getIconHeight(),
                Image.SCALE_SMOOTH);
        ImageIcon icon = new ImageIcon(image, getDescription());
        return icon.getImage();
    }
    

    您可以在此处找到完整类的代码。您需要使用两倍大小的图像的URL实例化icon类。适用于2K显示器。

    请编辑您的问题,包括一个说明您关注领域的问题;新的MacBook Pro视网膜显示器所有者可能会发布一个。顺便说一句,您的24x24图标只需要变成48x48图标。视网膜将每个维度的分辨率提高一倍,将每个像素转换为四个像素。请回答者以外的其他人对此进行投票或接受,如果它确实有效,则在完成此操作之前,无法关闭此问题的副本。