3

I have png icons and use them as icons in JButton / JLabel.
The problem is that the image displayed at runtime is larger than the original icon, and because of this resizing, it's super ugly.

Here is an example:
Original icon (left) and how it's rendered in the JButton (right)

original icon (left) and how it's rendered in the JButton (right)

The source code for this minimal example is simply:

public class Main {

    public static void main(String... args) {

        JFrame frame = new JFrame("Test");
        frame.setBounds(0, 0, 120, 80);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().setLayout(new FlowLayout());

        ImageIcon icon = new ImageIcon("icon.png");
        frame.getContentPane().add(new JButton("Test", icon));
        frame.setVisible(true);
    }
}

Is this expected? If not, how can I avoid this? I tried many things around forcing the size of the image, the button, etc. but could not get a proper image displayed.

I have tested with icons of various sizes: 16x16, 17x17, 18x18, 19x19, 20x20, and each time the icon displayed on the JButton is a bit larger than the original which makes it look ugly:

original icons sizes vs icons on JButton

Thank you!

Cheers.

Abra
  • 19,142
  • 7
  • 29
  • 41
Hubb
  • 51
  • 4
  • Any chance you could post a link to `icon.png` so that I can try to reproduce what you claim to be seeing? – Abra Oct 29 '20 at 06:37
  • Have you tried scaling the image yourself like [shown in this answer](https://stackoverflow.com/a/18335435/11441011)? – maloomeister Oct 29 '20 at 07:49
  • Thanks Abra. The same happens with absolutely any icon. Tried the same code using an icon from the following URL: ImageIcon icon = new ImageIcon(new URL("https://cdn4.iconfinder.com/data/icons/6x16-free-application-icons/16/Help.png")); The same issue occurred: icon is slightly bigger and all ugly. – Hubb Oct 29 '20 at 07:52
  • Thanks Maloomeister: yes I tried. The icon is 16x16 and I actually want it to be displayed with its original size of 16x16. I tried resizing to 16x16 (although it should not be required), and the icon is still displayed slightly bigger. – Hubb Oct 29 '20 at 07:55

3 Answers3

4

This is because you are using Windows scaling. The entire component is scaled, both the icon and the text.

You could turn the scaling of the Icon off by using a wrapper Icon:

import java.awt.*;
import java.awt.geom.AffineTransform;
import javax.swing.*;


public class NoScalingIcon implements Icon
{
    private Icon icon;

    public NoScalingIcon(Icon icon)
    {
        this.icon = icon;
    }

    public int getIconWidth()
    {
        return icon.getIconWidth();
    }

    public int getIconHeight()
    {
        return icon.getIconHeight();
    }

    public void paintIcon(Component c, Graphics g, int x, int y)
    {
        Graphics2D g2d = (Graphics2D)g.create();

        AffineTransform at = g2d.getTransform();

        int scaleX = (int)(x * at.getScaleX());
        int scaleY = (int)(y * at.getScaleY());

        int offsetX = (int)(icon.getIconWidth() * (at.getScaleX() - 1) / 2);
        int offsetY = (int)(icon.getIconHeight() * (at.getScaleY() - 1) / 2);

        int locationX = scaleX + offsetX;
        int locationY = scaleY + offsetY;

        AffineTransform scaled = AffineTransform.getScaleInstance(1.0 / at.getScaleX(), 1.0 / at.getScaleY());
        at.concatenate( scaled );
        g2d.setTransform( at );

        icon.paintIcon(c, g2d, locationX, locationY);

        g2d.dispose();
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                createAndShowGUI();
            }
        });
    }

    public static void createAndShowGUI()
    {
        JButton button = new JButton( "Button" );
        NoScalingIcon icon = new NoScalingIcon( new ImageIcon("box.jpg") );
        button.setIcon( icon );

        JPanel panel = new JPanel( );
        panel.add( button );

        JFrame f = new JFrame();
        f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        f.getContentPane().add(panel);
        f.setSize(200, 200);
        f.setLocationRelativeTo( null );
        f.setVisible(true);
    }
}
  1. The scaling adjustment will position the Icon at the top/left of the button area.

  2. The offset adjustment will then attempt to center the Icon in the scaled icon painting area.

  3. Using the default transform will have a scaling factor of 0 for the Icon.

camickr
  • 321,443
  • 19
  • 166
  • 288
2

Thank you all. The problem was the default scaling factor (which was 1.25). As I want to be fully in control of the size independently from DPI, I solved my issue by forcing the scaling factor to 1.0.

This answer was helpful

So, either pass to the command line

-Dsun.java2d.uiScale=1.0, 

or set it programmatically

System.setProperty("sun.java2d.uiScale", "1.0")
camickr
  • 321,443
  • 19
  • 166
  • 288
Hubb
  • 51
  • 4
0

Look at the source code for the constructor of class ImageIcon that takes a string parameter. It uses class java.awt.Toolkit to create the image from the file. This made me think that it must be doing some scaling. So I thought of creating the icon differently. ImageIcon has another constructor that takes an Image parameter. So I created a BufferedImage from the file and then used that image to create an ImageIcon. The BufferedImage is not scaled.

Note that your link to the icon file didn't work for me so I just downloaded a different 16x16 icon.

java.awt.image.BufferedImage img = javax.imageio.ImageIO.read(new java.io.File("cellphon.png"));
javax.swing.Icon ico = new javax.swing.ImageIcon(img);
javax.swing.JButton button = new javax.swing.JButton("Test", ico);
Abra
  • 19,142
  • 7
  • 29
  • 41