Gaz
Gaz

Reputation: 328

Applying colour to the transparent area of a JButton image - but not that of its container

I have a circular JButton constructed using a circular PNG image with a transparent area.

I want to fill the transparent area of the JButton image with a given colour - but something other than the opaque background colour of the JPanel which contains the JButton. I want to do this programmatically in Java rather than providing pre-coloured images from a graphics package.

I've got as far as the code below, which simply allows the orange background of the opaque panel to colour the transparent area. I can't figure out, though, how to keep the panel background as orange but fill the image transparency with, say, blue (or another colour for rollover and pressed effects).

import java.awt.Color;
import java.awt.Cursor;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;

import javax.imageio.ImageIO;
import javax.swing.DefaultButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class App extends JFrame implements ActionListener
{
    public App()
    {
       super();
       setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
       setContentPane(makeContentPane());
    }

    private final JPanel makeContentPane()
    {
        JPanel contentPane = new JPanel();
        BufferedImage bi = null;
        try
        {
            bi = ImageIO.read(new URL("http://features.advisorwebsites.com/sites/default/files/users/AdvisorWebsitesFeatures/icones/1384148625_twitter_circle_gray.png"));
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
        ImageIcon icon = new ImageIcon(bi);
        MyButton myButton = new MyButton(icon);
        myButton.addActionListener(this);
        contentPane.add(myButton);
        contentPane.setBackground(Color.ORANGE);
        contentPane.setOpaque(true);
        return contentPane;
    }

    public static void main(String[] args)
    {
        SwingUtilities.invokeLater(new Runnable()
        {
            public void run()
            {
                App app = new App();
                app.pack();
                app.setVisible(true);
            }
        });
    }

    class MyButton extends JButton
    {
        public MyButton(ImageIcon icon)
        {
            super(icon);
            setMargin(new Insets(0, 0, 0, 0));
            setFocusable(false);
            setContentAreaFilled(false);
            setBorderPainted(false);
            setModel(new DefaultButtonModel());
            setCursor(new Cursor(Cursor.HAND_CURSOR));
        }
    }

    @Override
    public void actionPerformed(ActionEvent arg0)
    {
        System.out.println("You clicked me");
    }
}

I'm guessing I might need to apply Graphics2D transformations to my transparent image to create a set of three new images (for the normal, rollover and pressed states of my JButton). Is this the appropriate way forward and, if so, can you provide me with a code example for the bit I'm missing ?

Thanks

Upvotes: 1

Views: 173

Answers (2)

MadProgrammer
MadProgrammer

Reputation: 347204

Okay, this is little more then you're asking for, but it demonstrates a means by which you can generate a shape which matches the shape of a image, based on its alpha, all through the magic of AlphaComposite...

So, basically, we're going to take the original image on the left and turn into the image on the right...

OriginalMasked

  • First, we load the original image
  • Next, we create a mask of the shape we want to achieve (a circle in this case)
  • Next, we mask the two images together, so that the original image is cropped into the mask (the second)
  • Then we create a new mask, which is filled with the color we want to be used as the outline
  • We then mask the first masked image with the "outline" image, this basically acts as a cookie cutter, cutting the shape of the first masked imaged with the "block" image.
  • We take this "outlined" shape and then scale it up slightly
  • And finally, we combine them.

This example generates two images, a "normal" and a "roll over" which is easily applied to a JButton, for example...

Normal...

Normal

Roll over...

Rollover

Now, if for some reason, you wanted to know when the button was "rolled over", you could simply add a ChangeListener to the ButtonModel, for example...

JButton btn = new JButton(new ImageIcon(normal));
btn.setRolloverIcon(new ImageIcon(rollOver));
btn.setRolloverEnabled(true);
btn.getModel().addChangeListener(new ChangeListener() {
    @Override
    public void stateChanged(ChangeEvent e) {
        ButtonModel model = (ButtonModel) e.getSource();
        System.out.println("Change: " + model.isRollover());
    }
});

And the runnable example...

import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.ButtonModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class JavaApplication24 {

    public static void main(String[] args) {
        new JavaApplication24();
    }

    public JavaApplication24() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (IOException ex) {
                    Logger.getLogger(JavaApplication24.class.getName()).log(Level.SEVERE, null, ex);
                }
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() throws IOException {
            setLayout(new GridBagLayout());
            BufferedImage source = ImageIO.read(...));

            // This the shape we want the source to be clipped to
            int size = Math.min(source.getWidth(), source.getHeight());
            BufferedImage mask = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB);
            Graphics2D maskg = mask.createGraphics();
            applyQualityRenderingHints(maskg);
            maskg.setColor(Color.WHITE);
            maskg.fillOval((source.getWidth() - size) / 2, (source.getHeight() - size) / 2, size, size);
            maskg.dispose();

            // This will mask the source to the shape we've defined
            BufferedImage masked = applyMask(source, mask, AlphaComposite.DST_ATOP);

            BufferedImage normal = makeOutline(masked, Color.BLACK);
            BufferedImage rollOver = makeOutline(masked, Color.RED);

            JButton btn = new JButton(new ImageIcon(normal));
            btn.setRolloverIcon(new ImageIcon(rollOver));
            btn.setRolloverEnabled(true);
            btn.getModel().addChangeListener(new ChangeListener() {
                @Override
                public void stateChanged(ChangeEvent e) {
                    ButtonModel model = (ButtonModel) e.getSource();
                    System.out.println("Change: " + model.isRollover());
                }
            });
            add(btn);
        }

        protected BufferedImage makeOutline(BufferedImage original, Color color) {

            // This generates a image which is completely filled with the provided color
            BufferedImage outline = new BufferedImage(original.getWidth(), original.getHeight(), BufferedImage.TYPE_INT_ARGB);
            Graphics2D outlineg = outline.createGraphics();
            applyQualityRenderingHints(outlineg);
            outlineg.setColor(color);
            outlineg.fillRect(0, 0, outline.getWidth(), outline.getHeight());
            outlineg.dispose();

            // This applies a AlphaComposite to mask the outline with the shape
            // of the original image
            outline = applyMask(original, outline, AlphaComposite.SRC_ATOP);

            // Now we make it slightly larger...
            double scale = 1.05;
            outline = getScaledInstanceToFit(outline, scale);

            // And we combine the images
            outlineg = outline.createGraphics();
            int x = (outline.getWidth() - original.getWidth()) / 2;
            int y = (outline.getHeight() - original.getHeight()) / 2;
            outlineg.drawImage(original, x, y, this);
            outlineg.dispose();

            return outline;

        }

        public BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage) {

            return applyMask(sourceImage, maskImage, AlphaComposite.DST_IN);

        }

        public BufferedImage applyMask(BufferedImage sourceImage, BufferedImage maskImage, int method) {

            BufferedImage maskedImage = null;
            if (sourceImage != null) {

                int width = maskImage.getWidth(null);
                int height = maskImage.getHeight(null);

                maskedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D mg = maskedImage.createGraphics();
                applyQualityRenderingHints(mg);

                int x = (width - sourceImage.getWidth(null)) / 2;
                int y = (height - sourceImage.getHeight(null)) / 2;

                mg.drawImage(sourceImage, x, y, null);
                mg.setComposite(AlphaComposite.getInstance(method));

                mg.drawImage(maskImage, 0, 0, null);

                mg.dispose();
            }

            return maskedImage;

        }

        public void applyQualityRenderingHints(Graphics2D g2d) {

            g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
            g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
            g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
            g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
//      g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        }

        public BufferedImage getScaledInstanceToFit(BufferedImage img, double scale) {
            int width = (int)(img.getWidth() * scale);
            int height = (int)(img.getHeight()* scale);
            return getScaledInstanceToFit(img, new Dimension(width, height));
        }

        public BufferedImage getScaledInstanceToFit(BufferedImage img, Dimension size) {

            double scaleFactor = getScaleFactorToFit(new Dimension(img.getWidth(), img.getHeight()), size);

            return getScaledInstance(img, scaleFactor);

        }

        public double getScaleFactorToFit(Dimension original, Dimension toFit) {

            double dScale = 1d;

            if (original != null && toFit != null) {

                double dScaleWidth = getScaleFactor(original.width, toFit.width);
                double dScaleHeight = getScaleFactor(original.height, toFit.height);

                dScale = Math.min(dScaleHeight, dScaleWidth);

            }

            return dScale;

        }

        public double getScaleFactor(int iMasterSize, int iTargetSize) {
            return (double) iTargetSize / (double) iMasterSize;
        }

        public BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor) {
            return getScaledInstance(img, dScaleFactor, RenderingHints.VALUE_INTERPOLATION_BILINEAR, true);
        }

        protected BufferedImage getScaledInstance(BufferedImage img, double dScaleFactor, Object hint, boolean bHighQuality) {

            BufferedImage imgScale = img;

            int iImageWidth = (int) Math.round(img.getWidth() * dScaleFactor);
            int iImageHeight = (int) Math.round(img.getHeight() * dScaleFactor);

            if (dScaleFactor <= 1.0d) {
                imgScale = getScaledDownInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
            } else {
                imgScale = getScaledUpInstance(img, iImageWidth, iImageHeight, hint, bHighQuality);
            }

            return imgScale;

        }

        protected BufferedImage getScaledDownInstance(BufferedImage img,
                        int targetWidth,
                        int targetHeight,
                        Object hint,
                        boolean higherQuality) {

            int type = (img.getTransparency() == Transparency.OPAQUE)
                            ? BufferedImage.TYPE_INT_RGB : BufferedImage.TYPE_INT_ARGB;

            BufferedImage ret = (BufferedImage) img;

            if (targetHeight > 0 || targetWidth > 0) {

                int w, h;
                if (higherQuality) {
                    w = img.getWidth();
                    h = img.getHeight();
                } else {
                    w = targetWidth;
                    h = targetHeight;
                }

                do {
                    if (higherQuality && w > targetWidth) {
                        w /= 2;
                        if (w < targetWidth) {
                            w = targetWidth;
                        }
                    }

                    if (higherQuality && h > targetHeight) {
                        h /= 2;
                        if (h < targetHeight) {
                            h = targetHeight;
                        }
                    }

                    BufferedImage tmp = new BufferedImage(Math.max(w, 1), Math.max(h, 1), type);
                    Graphics2D g2 = tmp.createGraphics();
                    g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
                    g2.drawImage(ret, 0, 0, w, h, null);
                    g2.dispose();

                    ret = tmp;

                } while (w != targetWidth || h != targetHeight);
            } else {
                ret = new BufferedImage(1, 1, type);
            }

            return ret;
        }

        protected BufferedImage getScaledUpInstance(BufferedImage img,
                        int targetWidth,
                        int targetHeight,
                        Object hint,
                        boolean higherQuality) {

            int type = BufferedImage.TYPE_INT_ARGB;

            BufferedImage ret = (BufferedImage) img;
            int w, h;
            if (higherQuality) {
                w = img.getWidth();
                h = img.getHeight();
            } else {
                w = targetWidth;
                h = targetHeight;
            }

            do {
                if (higherQuality && w < targetWidth) {
                    w *= 2;
                    if (w > targetWidth) {
                        w = targetWidth;
                    }
                }

                if (higherQuality && h < targetHeight) {
                    h *= 2;
                    if (h > targetHeight) {
                        h = targetHeight;
                    }
                }

                BufferedImage tmp = new BufferedImage(w, h, type);
                Graphics2D g2 = tmp.createGraphics();
                g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, hint);
                g2.drawImage(ret, 0, 0, w, h, null);
                g2.dispose();

                ret = tmp;
                tmp = null;

            } while (w != targetWidth || h != targetHeight);
            return ret;
        }
    }

}

This code borrows heavily from my personal library code, so it might be a little convoluted ;)

Upvotes: 1

vlatkozelka
vlatkozelka

Reputation: 999

try to override paintComponent() in your custom JButton

Here's what I tried

class MyButton extends JButton {

        public MyButton(ImageIcon icon) {
            super(icon);
            setMargin(new Insets(0, 0, 0, 0));
            setFocusable(false);
            setContentAreaFilled(false);
            setBorderPainted(false);
            setModel(new DefaultButtonModel());
            setCursor(new Cursor(Cursor.HAND_CURSOR));

        }

        public void paintComponent(Graphics g) {
            g.setColor(Color.green);
            g.fillOval(0, 0, this.getWidth(), this.getHeight());
            super.paintComponent(g);

        }

    }

And here's the result:

enter image description here

EDIT:

To get it to change color depending on mouse movement, you need to add a MouseListener to the JButton and add a Color attribute to the class, when a MouseEvent is fired you change the color. Also don't forget to set the graphics to that color in paintComponent()

class MyButton extends JButton {

        Color color = Color.GREEN;

        public MyButton(ImageIcon icon) {
            super(icon);
            setMargin(new Insets(0, 0, 0, 0));
            setFocusable(false);
            setContentAreaFilled(false);
            setBorderPainted(false);
            setModel(new DefaultButtonModel());
            setCursor(new Cursor(Cursor.HAND_CURSOR));
            this.addMouseListener(new MouseListener() {

                @Override
                public void mouseClicked(MouseEvent e) {

                }

                @Override
                public void mousePressed(MouseEvent e) {
                    color=Color.RED;
                }

                @Override
                public void mouseReleased(MouseEvent e) {
                    color=Color.BLUE;
                }

                @Override
                public void mouseEntered(MouseEvent e) {
                    color=Color.BLUE;
                }

                @Override
                public void mouseExited(MouseEvent e) {
                    color=Color.GREEN;
                }
            });

        }

        @Override
        public void paintComponent(Graphics g) {
            g.setColor(color);
            g.fillOval(0, 0, this.getWidth(), this.getHeight());
            super.paintComponent(g);

        }

    }

Upvotes: 2

Related Questions