Search code examples
javaswingjbuttonimageicon

JButton's icon becomes pixelated on hovering


I'm making a Java Swing app and I came across a problem. I want to make a button with an icon on it. To make it, I first make a rescaled ImageIcon:

studentIcon = new ImageIcon(new ImageIcon( 
"D:\\Programming\\Java\\ELearningDesktop\\src\\com\\core\\student.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));

Then I make a button and set the icon on it:

JButton studentButton = new JButton();
studentButton.setIcon(studentIcon);
studentButton.setFocusable(false);

It works fine, but for some reason the icon on button becomes pixelated every time I hover mouse onto it. After hovering it never becomes smooth unless I rescale the JFrame, so that it probably calls repaint() somewhere and it repaints it.

I use a downloaded look-and-feel but the problem remains if I use the default look-and-feel. Using ImageIcon the same way, but without rescaling does not help - pixilation still appears. What could be the solution?

Starting point of the program

public class Starter {
    public static void main(String[] args) {
        FlatLightLaf.install();
        EventQueue.invokeLater(()->{
            AuthFrame frame = new AuthFrame();
            frame.setVisible(true);
        });
    }
}

AuthFrame

public class AuthFrame extends JFrame {

    private JPanel mainPanel;
    private LoginPanel loginPanel;
    private ImageIcon studentIcon;
    private ImageIcon teacherIcon;

    public AuthFrame() {
        setLayout(new GridBagLayout());
        ImageIcon imageIcon = new ImageIcon(new ImageIcon(
                "D:\\Programming\\Java\\ELearningDesktop\\src\\com\\core\\tileBackground.jpg")
                .getImage().getScaledInstance(321, 333, Image.SCALE_SMOOTH));
        mainPanel = new BackgroundPanel(imageIcon.getImage(), BackgroundPanel.TILED,
                0f, 0.5f);

        add(mainPanel, new GBC(0, 0).setFill(BOTH).setWeights(1, 1));

        mainPanel.setLayout(new GridBagLayout());

        studentIcon = new ImageIcon(new ImageIcon(
                "D:\\Programming\\Java\\ELearningDesktop\\src\\com\\core\\student.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));
        teacherIcon = new ImageIcon(new ImageIcon(
                "D:\\Programming\\Java\\ELearningDesktop\\src\\com\\core\\teacher.png")
                .getImage().getScaledInstance(64, 64, Image.SCALE_SMOOTH));

        loginPanel = new LoginPanel();
        mainPanel.add(loginPanel);

        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setSize(FRAME_WIDTH, FRAME_HEIGHT);
        setExtendedState(Frame.MAXIMIZED_BOTH);
    }

LoginPanel - a private inner class that is used in the AuthFrame

 private class LoginPanel extends JPanel {
        private JTextField usernameField;
        private JPasswordField passwordField;
        private JLabel errorLabel;
        private JButton studentButton;
        private JButton teacherButton;
        private JLabel titleLabel;
        private boolean forStudent = true;

        public LoginPanel() {
            setLayout(new GridBagLayout());
            setBackground(new Color(255, 255, 255, 181));

            studentButton = new JButton();
            studentButton.setIcon(studentIcon);
            studentButton.setFocusable(false);
            //add(studentButton, new GBC(0, 0).setAnchor(GBC.WEST).setInsets(10));

            teacherButton = new JButton(teacherIcon);
            teacherButton.setFocusable(false);
            add(teacherButton, new GBC(0, 0).setAnchor(GBC.WEST).setInsets(10));

            titleLabel = new JLabel("<html>Signing in as <b>student</b></html>");
            Utils.deriveFontForTo(titleLabel, 24f);
            add(titleLabel, new GBC(1, 0).setAnchor(GBC.EAST).setInsets(10));
            titleLabel.setVerticalAlignment(JLabel.BOTTOM);

            JLabel usernameLabel = new JLabel("Username");
            Utils.deriveFontForTo(usernameLabel, 24f);
            add(usernameLabel, new GBC(0, 1).setAnchor(GBC.WEST).setInsets(10));
            usernameLabel.setHorizontalAlignment(LEFT);

            JLabel passwordLabel = new JLabel("Password");
            Utils.deriveFontForTo(passwordLabel, 24f);
            add(passwordLabel, new GBC(0, 2).setAnchor(GBC.WEST).setInsets(10));

            usernameField = new JTextField(15);
            Utils.deriveFontForTo(usernameField, 24f);
            add(usernameField, new GBC(1, 1).setInsets(10));

            passwordField = new JPasswordField(15);
            Utils.deriveFontForTo(passwordField, 24f);
            add(passwordField, new GBC(1, 2).setInsets(10));

            errorLabel = new JLabel();
            Utils.deriveFontForTo(errorLabel, 16f);
            errorLabel.setForeground(Color.RED);
            add(errorLabel, new GBC(1, 3).setAnchor(GBC.WEST).setInsets(2));
            errorLabel.setHorizontalAlignment(LEFT);

            JButton loginButton = new JButton("Log in");
            loginButton.setFocusable(false);
            Utils.deriveFontForTo(loginButton, 24f);
            add(loginButton, new GBC(1, 4, 1, 1)
                    .setFill(GridBagConstraints.HORIZONTAL).setInsets(10));

         
            JButton registerButton = new JButton("Sign Up");
            loginButton.setFocusable(false);
            Utils.deriveFontForTo(registerButton, 24f);
            add(registerButton, new GBC(1, 5, 1, 1)
                    .setInsets(10));
        }
    }

GBC - a covenience class to use GridBagLayout

package com.core.helpers.graphics;

import java.awt.*;

public class GBC extends GridBagConstraints {


    public GBC(int gridX, int gridY){
        super.gridx = gridX;
        super.gridy = gridY;
    }

    public GBC(int gridX, int gridY, int gridWidth, int gridHeight){
        super.gridx = gridX;
        super.gridy = gridY;
        super.gridwidth = gridWidth;
        super.gridheight = gridHeight;
    }

    public GBC setAnchor(int anchor){
        super.anchor = anchor;
        return this;
    }

    public GBC setWeights(double weightX, double weightY){
        super.weightx = weightX;
        super.weighty = weightY;
        return this;
    }

    public GBC setFill(int fill){
        super.fill = fill;
        return this;
    }

    public GBC setInsets(int k){
        this.insets = new Insets(k,k,k,k);
        return this;
    }

}

Thanks

Screenshots:

first - two buttons are smooth
first - two buttons are smooth

second - hovered button becomes pixilated
second - hovered button becomes pixilated

third - button becomes smooth again after resizing frame
third - button becomes smooth again after resizing frame


Solution

  • Edit:

    Looks like my problem was connected to windows scaling, as the marked answer here Java Swing, all images appear pixelated suggests. Using the marked answer from this link helped me. Here it is: https://stackoverflow.com/a/50566705/12538636

    Brute force approach :

    By repainting the button's panel every time there's a change in button makes it look smooth all the time. Here's code fragment:

    teacherButton.getModel().addChangeListener(new ChangeListener() {
         @Override
         public void stateChanged(ChangeEvent e) {
             mainPanel.repaint();
         }
    });
    
    studentButton.getModel().addChangeListener(new ChangeListener() {
         @Override
         public void stateChanged(ChangeEvent e) {
             mainPanel.repaint();
         }
    });
    

    The only little issue remained is that now there's a tiny gap between hovering and button's respond to hovering (changing a color slightly to denote it's hovered).