Swing and bitmaps on retina displays

喜你入骨 提交于 2019-11-28 10:19:43

Use IconLoader library. It supports HiDPI images http://bulenkov.com/iconloader/ It also provides a way to work with HiDPI images (drawing, etc)

On Apple's Java 6 you can provide multiple versions of the same image. Depending on the screen (retina or not), one or the other image is picked and drawn.

However, those images have to loaded in a special way:

Toolkit.getDefaultToolkit().getImage("NSImage://your_image_name_without_extension");

For example, if your (regular resolution) image is called: "scissor.png", you have to create a high resolution version "scissor@2x.png" (following the Apple naming conventions) and place both images in the Resources directory of your app bundle (yes, you need to bundle your app). Then call:

Image img = Toolkit.getDefaultToolkit().getImage("NSImage://scissor");

You can use the resulting image in your buttons and it will be drawn with the right resolution magically.

There are two other "tricks" you can use:

  1. Using an AffineTransform of (0.5, 0.5) on your Graphics2D object before drawing an Image. Also see this java-dev message
  2. Creating a high dpi version of your image programmatically using this hack

The first "trick" (0.5 scaling) by now also works on Oracle's Java 7/8. I.e. if you draw an image with 0.5 scaling directly to the component's Graphics object, it will be rendered in high resolution on Retina displays (and also with half its original size).

I can confirm that the scaling your images works with on Oracle Java 1.8. I cannot get the NSImage hack to work on java 1.7 or 1.8. I think this only works with Java 6 from Mac...

Unless someone else has a better solution, what I do is the following:

Create two sets of icons. If you have a 48pixel width icon create one 48px @normal DPI and another at 96px with 2x DPI. Rename the 2xDPI image as @2x.png to conform with apple naming standards.

Subclass ImageIcon and call it RetinaIcon or whatever. You can test for a Retina display as follows:

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;
}

Make sure to @Override the width and height of the new ImageIcon class as follows:

@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();
}

Once you have a test for the retina screen and your custom width/height methods overridden you can customise the painIcon method as follows:

@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();
}

I do not know how this will work with multiple screens though- is there anyone else that can help out with that???

Hope this code helps out anyway!

Jason Barraclough.

Here is an example of using the scaling as mentioned above: RetinaIcon is on the left. ImageIcon is on the right

Here is a solution, that works also when the icons are used in the apple menu. There the icon is automatically greyed. So I have implemented a class DenseIcon which paints densely:

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());
    }
}

How to hook into the greying I have not yet figured out. So as a kludge we return a low res image so that the menu can do its modifications:

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

You find the code of the full class here on gist. You need to instantiate the icon class with an URL to an image that is twice the size. Works for 2K displays.

This how icons look like on my retina macbook '12:

On the left side icons in IntelliJ IDEA 11 (swing app) and on the right side IDEA 12 which is claimed to be retinized. As you can see automatically resized icons (on the left) looks pretty ugly.

As far as I know, they, just like the guys from Chrome team, made it by providing double sized icons.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!