Setting multiple icons on JButton - java

I have a JButton which I have set a custom icon on. Now I want it to display another icon ontop of the one that is already displayed when I drag my mouse cursor over it but I can't figure out how to do it because if I use button.setIcon(icon); it will replace the icon that already is displayed. How would I do this in an as easy way as possible?

I have a JButton which I have set a custom icon on. Now I want it to
display another icon ontop of the one that is already displayed when I
drag my mouse cursor over it but I can't figure out how to do it
because if I use button.setIcon(icon); it will replace the icon that
already is displayed. How would I do this in an as easy way as
possible
I think thats about JButton.setRolloverIcon(myIcon);
JButton has implemented those methods in API
JButton.setIcon(myIcon);
JButton.setRolloverIcon(myIcon);
JButton.setPressedIcon(myIcon);
JButton.setDisabledIcon(myIcon);
for example

If your icons are already transparent you can easily implement your own Icon to combine the two -
public class CombineIcon implements Icon {
private Icon top;
private Icon bottom;
public CombineIcon(Icon top, Icon bottom) {
this.top = top;
this.bottom = bottom;
}
public int getIconHeight() {
return Math.max(top.getIconHeight(), bottom.getIconHeight());
}
public int getIconWidth() {
return Math.max(top.getIconWidth(), bottom.getIconWidth());
}
public void paintIcon(Component c, Graphics g, int x, int y) {
bottom.paintIcon(c, g, x, y);
top.paintIcon(c, g, x, y);
}
}
You use setRolloverIcon(icon) to specify the icon you want to show when the mouse is over the button.

Create a second version of that button icon which contains the overlay. On mouse over switch to the image with the overlay.
Another approach could be to combine the icon with its overlay to a new icon in memory and place it as an icon on the button. This might be a good approach if your icons are frequently changing. If that's not the case I would definitely use the first approach.

I find this pretty easy.
import java.awt.*;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
class CombinedIconButton {
public static BufferedImage getCombinedImage(BufferedImage i1, BufferedImage i2) {
if (i1.getHeight() != i2.getHeight()
|| i1.getWidth() != i2.getWidth()) {
throw new IllegalArgumentException("Images are not the same size!");
}
BufferedImage bi = new BufferedImage(
i1.getHeight(),
i1.getWidth(),
BufferedImage.TYPE_INT_ARGB);
Graphics g = bi.getGraphics();
g.drawImage(i1,0,0,null);
g.drawImage(i2,0,0,null);
g.dispose();
return bi;
}
public static void main(String[] args) throws Exception {
URL url1 = new URL("http://i.stack.imgur.com/gJmeJ.png"); // blue circle
URL url2 = new URL("http://i.stack.imgur.com/5v2TX.png"); // red triangle
final BufferedImage bi1 = ImageIO.read(url1);
final BufferedImage bi2 = ImageIO.read(url2);
final BufferedImage biC = getCombinedImage(bi1,bi2);
Runnable r = new Runnable() {
#Override
public void run() {
JPanel gui = new JPanel(new BorderLayout());
JToggleButton b = new JToggleButton();
b.setIcon(new ImageIcon(bi1));
b.setRolloverIcon(new ImageIcon(biC));
b.setSelectedIcon(new ImageIcon(bi2));
gui.add(b);
JOptionPane.showMessageDialog(null, gui);
}
};
// Swing GUIs should be created and updated on the EDT
// http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
SwingUtilities.invokeLater(r);
}
}
Images borrowed from this answer.

One way to do that is:
Create an icon which you want to see when the pointer is hovered on top of the button by some image editing tool. And set that image once mousehover event occurs.
p.s. use any pic editing tool and you can easily create a overlay image.
I also saw now that there is a concept of roll over icon in AbsractButton class. You can use that as well.

Related

Defining a background color and a foreground icon for a JButton?

I'm working on a Fire Emblem mock up using Swing (which I'm just getting the hang of, I used to work mostly on console programs). To those who are not familiar with the game, it's a tile based strategy game where you move units on a grid (akin to chess).
I'm thinking about using JButtons for the grid, so that the player is able to click on the unit he wants to move, and click on his destination. As you can see on the picture, the tile color behind the unit can vary (red means the unit can attack the unit on that tile, blue means a tile where the selected unit can move). I don't want to have 15 different tile designs for each unit (UnitX with blue background, UnitX with red background, UnitX with green background, etc.) so is there a way to use "layers" with JButtons? Draw a blue tile and draw the correct character over it?
Draw a blue tile and draw the correct character over it?
Use the setBackground(...) method to set the background
color.
Use the setIcon(...) method to set the character.
The answer from camickr is correct (assuming that I've understood your question).
The following code demonstrates how you can use setBackground and setIcon on a JButton. It shows one button with a background color and an icon. The button changes its background color when it's clicked:
import javax.swing.*;
import java.awt.*;
import java.net.*;
import java.util.*;
import java.util.List;
public class ButtonBackgroundAndIcon {
private static final List<Color> BACKGROUND_COLORS = Arrays.asList(
new Color(229, 119, 120),
Color.BLUE,
Color.CYAN,
Color.GREEN,
Color.YELLOW,
Color.RED
);
private int backgroundIndex;
public static void main(String[] arguments) {
SwingUtilities.invokeLater(new ButtonBackgroundAndIcon()::run);
}
private void run() {
JFrame frame = new JFrame("Stack Overflow");
frame.setBounds(100, 100, 800, 600);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
JButton tileButton = new JButton();
tileButton.setBackground(BACKGROUND_COLORS.get(0));
Icon icon = getRoyIcon();
if (icon != null) {
tileButton.setIcon(icon);
}
tileButton.addActionListener(actionEvent -> {
backgroundIndex = (backgroundIndex + 1) % BACKGROUND_COLORS.size();
tileButton.setBackground(BACKGROUND_COLORS.get(backgroundIndex));
});
frame.getContentPane().add(tileButton);
frame.setVisible(true);
}
private ImageIcon getRoyIcon() {
ImageIcon imageIcon;
try {
String iconLocation = "http://orig06.deviantart.net/fd0e/f/2008"
+ "/060/d/1/roy_sprite_by_chstuba007.gif";
imageIcon = new ImageIcon(new URL(iconLocation));
} catch (MalformedURLException e) {
imageIcon = null;
}
return imageIcon;
}
}
Assuming if you have a transparent image you can use this class to accomplish what you desire.
public class ColorBackgroundIcon implements Icon {
private ImageIcon image;
private Color background;
public ColorBackgroundIcon(ImageIcon image,
Color background) {
this.image = image;
this.background = background;
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
Graphics2D g2d = (Graphics2D) g;
g.setColor(background);
g.fillRect(x, y, getIconWidth(), getIconHeight());
g2d.drawImage(image.getImage(), x, y, null);
}
public void setColor(Color bgColor) {
this.background = bgColor;
}
#Override
public int getIconWidth() {
return image.getIconWidth();
}
#Override
public int getIconHeight() {
return image.getIconHeight();
}
}
Then just setIcon() using a instance created above. Works great for me since I have four states a certain icon can represent. Instead of creating four different icons I have one transparent icon and I change the background with the code provided.
Hope that helps for anyone,
DK

Formatting JPanel to Include ArrayList<JComponent> and Split View depending on view mode clicked

I'm trying to create this basic photo editing app via java swing. I have my code to work somewhat for when 1 picture is imported; I have 3 views - photo view which displays the photo only, thumbnail view which should display thumbnails of the pictures and split view which should be a combination of photo in BorderLayout.CENTER and thumbnail in BorderLayout.SOUTH. I've put in images and code excerpts as to why this isn't working the way it should. I can't upload any images but hopefully the excerpts provide much detail.
Split View Related Excerpts:
public void changeMode(boolean p, boolean b, boolean s){
/*
* Photo View will display a single PhotoComponent2 in a large area.
*/
isPhoto = p;
if (isPhoto){
//have a child JPanel set as CENTER component of BorderLayout of the JScrollPane (scroll)
childPhoto = new JPanel();
childPhoto.add(displayPhotos.get(getCurrentPhoto()), BorderLayout.CENTER);
System.out.println("in lc photo view class");
}
/*
* Browser View will hold all the images.
*/
isBrowse = b;
if(isBrowse){
//have a child JPanel set as CENTER component of BorderLayout to hold grid of thumbnails within
// JScrollPanel (scroll)
this.removeAll();
childBrowse = new JPanel();
tc2 = new ThumbnailComponent(displayPhotos.get(getCurrentPhoto()));
childBrowse.setLayout(new WrapLayout());
childBrowse.add(tc2);
// for(int i = 0; i < displayThumbs.size(); i++){
// childBrowse.add(displayThumbs.get(i));
// System.out.println(displayThumbs.get(i));
// }
System.out.println("in lc browser view class");
}
/*
* Split View is a combination of Photo and Browser View in that the top half
* is Photo View and the bottom half is Browser View.
*/
isSplit = s;
if(isSplit){
//have a child JPanel in CENTER to hold PhotoComponent plus a child JPanel in SOUTH to hold thumbnails
containsAll = new JPanel();
containsAll.setLayout(new BorderLayout());
containsAll.add(childPhoto, BorderLayout.CENTER);
containsAll.add(childBrowse, BorderLayout.SOUTH);
System.out.println("in lc split view class");
}
}
This is basically all of my problems right now. Currently the thumbnails that are being created are based on the current image. If I go to photo view and and change the image being displayed with my forward/backward buttons and then go back to browser view I get the thumbnail of the respective image. However, I want to be able to create thumbnails for all images being imported and then display it. I tried the for loop (which I've commented out) and that didn't help either. The other code associated with this are my main class that creates the JFrame and the buttons,etc., the photoComponent that has my paintComponent method I use in my thumbnails and lightComponent classes.
MyPhotos3:
view = new JMenu("View");
mbar.add(view);
pv = new JRadioButton("Photo Viewer");
pv.addActionListener(new ActionListener(){ //change status
public void actionPerformed(ActionEvent e){
sbar.setText("Status: By clicking this, you'll be able to view your photos one at a time");
boolean p = true;
boolean b = false;
boolean s = false;
lc.changeMode(p,b,s);
scroll.add(lc.childPhoto);
scroll.setViewportView(lc.childPhoto);
scroll.getViewport().setBackground(Color.BLUE);
scroll.revalidate();
scroll.repaint();
}
});
b = new JRadioButton("Browser");
b.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){ //change status
sbar.setText("Status: By clicking this, you'll be able to view all your photos as thumbnails");
boolean p = false;
boolean b = true;
boolean s = false;
lc.changeMode(p,b,s);
scroll.setViewportView(lc.childBrowse);
scroll.getViewport().setBackground(Color.BLUE);
scroll.revalidate();
scroll.repaint();
}
});
sm = new JRadioButton("Split Mode");
sm.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){ //change status
sbar.setText("Status: By clicking this, you'll be able to view a single photo with a film strip dimensional view");
boolean p = false;
boolean b = false;
boolean s = true;
lc.changeMode(p,b,s);
scroll.setViewportView(lc.containsAll);
scroll.getViewport().setBackground(Color.BLUE);
scroll.revalidate();
scroll.repaint();
}
});
PhotoComponent:
public PhotoComponent2(boolean f, Image img){
isFlip = f;
init = img;
x = init.getWidth(null);
y = init.getHeight(null);
setPreferredSize(new Dimension (x,y));
bi = new BufferedImage(init.getWidth(null),init.getHeight(null),BufferedImage.TYPE_INT_ARGB);
newIcon = new ImageIcon(bi);
img1 = new JLabel("", newIcon, JLabel.CENTER);
image = img1;
this.add(img1);
this.addKeyListener(this);
this.setFocusable(true);
this.requestFocus(true);
//System.out.println("In constructor");
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(init, 0, 0, null);
this.addMouseListener(this);
this.addMouseMotionListener(this);
//System.out.println("In paintComponent");
Thumbnails:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import javax.swing.JComponent;
public class ThumbnailComponent extends JComponent{
/**
* ThumbnailComponent class is a way to create smaller versions of each photo passed in.
*
* #author Puja Sheth
* #version 1.0 10/16/2014
*/
private static final long serialVersionUID = 1L;
PhotoComponent2 pc;
Image img;
double x;
double y;
int newX;
int newY;
public ThumbnailComponent(PhotoComponent2 input){
pc = input;
img = pc.init;
x = (img.getWidth(null))/(.5);
y = (img.getHeight(null))/(.5);
newX = (int)x;
newY = (int)y;
setPreferredSize(new Dimension (newX,newY));
// add(input);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics gCopy = g.create();
Graphics2D g2d = (Graphics2D)gCopy;
g2d.scale(.5,.5);
pc.paintComponent(g2d);
}
}
Any help would be greatly appreciated!!!!
Won't solve your problem, but a few general comments about your code:
scroll.add(lc.childPhoto);
scroll.setViewportView(lc.childPhoto);
scroll.revalidate();
scroll.repaint();
You should never add a component to a scrollpane. The only line of code you need is:
scroll.setViewportView(lc.childPhoto);
The scrollpane will automatically revalidate() and repaint() itself when the viewport view is changed.
super.paintComponent(g);
g.drawImage(init, 0, 0, null);
this.addMouseListener(this);
this.addMouseMotionListener(this);
Never add listener to a component is a painting method. Painting methods are for painting only. A listener has nothing to do with painting. Also, the painting methods are invoked whenever Swing determines the component needs to be repainted so you will be adding multiple listeners to the component.

JFileChooser and Next - Previous Button

First of all, I'd like to inform you all that I'm just a beginner and I've tried many thing without any success.
I've managed to make a JFrame then a JPanel and inside that JPanel, a JLabel with icon. I can retrieve the picture with the button JFileChooser, the picture is resized to fit the JLabel.
My project has exactly 5 files :
ImageFilter: used by LoadFiles
ImagePreview: used by LoadFiles
LoadFiles: JFileChooser class
NewJFrame
Utils: used by LoadFiles
I am using NetBeans IDE and the GUI Builder, I made my class LoadFiles(JFileChooser) so that I just have to drop the class on NewJFrame and the button appears on my JFrame, so that my program can be easily modified (Every class is a module, but yet only one which is LoadFiles) and there is nothing except the variable declared in my NewJFrame.
Here is how my program looks like:
And I want to add two buttons, Next and Previous to navigate between the pictures I already opened in my JLabel.
Class LoadFiles
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
public class LoadFiles extends JButton implements ActionListener {
JFileChooser jfc;
public LoadFiles() {
super("Load");
addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
if (jfc == null) {
jfc = new JFileChooser(".");
jfc.addChoosableFileFilter(new ImageFilter());
jfc.setAcceptAllFileFilterUsed(false);
jfc.setAccessory(new ImagePreview(jfc));
}
jfc.setDialogTitle("test");
int result = jfc.showOpenDialog(null);
if(result == JFileChooser.APPROVE_OPTION) {
File file = jfc.getSelectedFile();
System.out.println(file.getParent());
System.out.println(file.getName());
// Read image and place new file icon into preferred locations
BufferedImage newImage = null;
try {
newImage = ImageIO.read(file);
} catch(IOException ex) {
System.out.println("red error: " + ex.getMessage());
}
ImageIcon backgdIcon = new ImageIcon(newImage);
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
} else if (result == JFileChooser.CANCEL_OPTION) {
System.out.println(JFileChooser.CANCEL_OPTION);
}
}
private Image getScaledImage(Image srcImg, int w, int h){
BufferedImage resizedImg = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = resizedImg.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2.drawImage(srcImg, 0, 0, w, h, null);
g2.dispose();
return resizedImg;
} }
You can find here my program:
http://www.speedyshare.com/k6e5r/javaapplication29.7z
Please, could you help me?
Edit 1 - Thank you for your comments, I couldn't answer sooner. I will try what you are talking about both MadProgrammer and Andrew Thompson. I will get back to you later hoping I'll make it work.
I'll also remember for my next posts to make it better (MCVE).
Joop Eggen - I am not trying to make a preview icon in JFileChooser, I already have that. I want to be able to retrieve the last image i added to the JLabel with the Previous Button (not Preview). Thank you anyway for your reply.
Edit 2 - Thank's for your advices mbw, what I had in mind when I did that was thatI could use that classe in any application by just dropping it on a GUI and it could work everywhere by just chaning variable's name but you are right, it is not easy to communicate with the other conponent.
I wanted to do the less code possible in the JFrame, so it can be the most modular possible.
I will probably do like you're saying.
Edit 3 -
Finaly I succeeded,
I declare this in the class :
List<BufferedImage> images;
int currentImage = 0;
Then after I put the image in the BufferedImage
//bufferedimage dans la arraylist
if(images == null)
images = new ArrayList<BufferedImage>();
images.add(newImage);
currentImage = images.size() - 1;
And I did two methods
public void nextImage() {
if(images != null && images.get(currentImage + 1) != null )
{
ImageIcon backgdIcon = new ImageIcon(images.get(currentImage + 1));
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);//taille en pixels
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
}
}
public void prevImage() {
if(images != null && images.get(currentImage - 1) != null )
{
ImageIcon backgdIcon = new ImageIcon(images.get(currentImage - 1));
Image zoom = getScaledImage(backgdIcon.getImage(), 471, 189);//taille en pixels
Icon iconScaled = new ImageIcon(zoom);
NewJFrame.jLabel1.setIcon(iconScaled);
}
}
Thank you everyone for all your advices. Have a good day.
There are some good ideas in the comments, but I think it will be easier if you change the structure of your code.
If I were you, I would not create extend JButton and create a custom class for each button. It really complicates things, and makes it harder to pass information around the gui to different components that need it.
Instead, to make the code easier to read, I would create three JButtons in your JFrame with the netbeans gui builder: Load, next and previous. Then you can easily add an action event listener for each button and do the all the work in the JFrame. This also makes it really easy to keep a reference to all the loaded pictures for the next and previous button to use.

Java Swing - place image in JLabel to/above second JLabel (map, player indicator)

I have a JFrame window with GridBagLayout(). There is a JLabel with Icon - it's a map. Now I need to show another picture (player position indicator) above map.
Is it possible, how?
Thank you very much!
Subclass JLabel and override the paint method.
public void paintComponent(Graphics g) {
super.paintComponent(g);
// paint player location on map
}
Handle your conversion from in game coordinates to coordinates on the map.
As an alternative to paintComponent(), you might also want to look at OverlayLayout, seen here. For some reason it's not included in the gallery, but it may be of use.
Couldn't resist to take this question as an opportunity to play with new jdk7 api :-)
Override paintComponent definitely is a good solution. Nevertheless, it requires subclassing just to paint something at a given location on top of something else. The ol' way doing so would be a JLayeredPane (which is a pain to handle). The new jdk has a JLayer component which allows to do that (and a lot more) by simple decoration of the component. Here's a snippet which:
has a label with the base image and decorates it with the default JLayer
adds the "player" image to the layer's glassPane
uses Rob's DragLayout (set on the glassPane) to move the player around, bare bones ui with two sliders
Working code (just some outer boiler plate missing, you all have your templates to stick that in :)
// to please Andrew :)
JLabel fooLabel = new JLabel(new ImageIcon(ImageIO.read(
new URL("http://pscode.org/media/stromlo2.jpg"))));
final JLayer<JLabel> layer = new JLayer<JLabel>(fooLabel);
final JLabel player = new JLabel(new CursorIcon(30));
// to please myself (never-ever use a null layout :)
layer.getGlassPane().setLayout(new DragLayout());
layer.getGlassPane().add(player);
layer.getGlassPane().setVisible(true);
final JSlider horizontal = new JSlider(0, fooLabel.getPreferredSize().width, 0);
final JSlider vertical = new JSlider(JSlider.VERTICAL, 0, fooLabel.getPreferredSize().height, 0);
vertical.setInverted(true);
ChangeListener l = new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
player.setLocation(horizontal.getValue(), vertical.getValue());
layer.revalidate();
}
};
horizontal.addChangeListener(l);
vertical.addChangeListener(l);
Quick icon:
public static class CursorIcon implements Icon {
private int size;
public CursorIcon(int size) {
this.size = size;
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(Color.red);
g.drawLine(size/2,y,size/2,size);
g.drawLine(x,size/2,size,size/2);
}
#Override
public int getIconWidth() {
return size;
}
#Override
public int getIconHeight() {
return size;
}
}
For earlier jdk versions, the SwingLabs subproject JXLayer is available

Creating custom JButton from images containing transparent pixels

Read edit 2 for what I'm actually missing to make it work
I'm currently trying to create some custom JButtons using images created in photoshop that have an alpha parameter.
So far, overriding the paint() method to draw the image has worked in the sense that the button is drawn showing the correct image. I'd like to improve it, though, by making its shape (clickable area) the same as the visible pixels on the image (right now if I draw the button's border, it's a square).
Is there an easy way to do that or do I have to parse the image and find the alpha pixels to make a custom border?
Which methods would I have to override to make it work the way I want?
Also, another question I'm going to have later: would it be better to use some kind of algorithm to change the images' colors to make it seem like it is being clicked when people click on it or am I better off creating a second image and drawing that one while the button is active?
Edit: I just read on some other question that I should redefine paintComponent() instead of paint(), I'd like to know why since redefining paint() works fine?
Edit 2: I changed everything to make sure my JButtons are created using the default constructor with an icon. What I'm trying to do is get the X and Y position of where the click was registered and grab the icon's pixel at that position and check its alpha channel to see if it is 0 (if it is, do nothing, else do the action it is supposed to do).
The thing is, the alpha channel always returns 255 (and blue, red and green are at 238 on transparent pixels). On other pixels, everything returns the value it should be returning.
Here's an example (try it with another image if you want) that recreates my problem:
public class TestAlphaPixels extends JFrame
{
private final File FILECLOSEBUTTON = new File("img\\boutonrondX.png"); //My round button with transparent corners
private JButton closeButton = new JButton(); //Creating it empty to be able to place it and resize the image after the button size is known
public TestAlphaPixels() throws IOException
{
setLayout(null);
setSize(150, 150);
closeButton.setSize(100, 100);
closeButton.setContentAreaFilled(false);
closeButton.setBorderPainted(false);
add(closeButton);
closeButton.addMouseListener(new MouseListener()
{
public void mouseClicked(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
System.out.println("Alpha value of pixel (" + e.getX() + ", " + e.getY() + ") is: " + clickAlphaValue(closeButton.getIcon(), e.getX(), e.getY()));
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
});
Image imgCloseButton = ImageIO.read(FILECLOSEBUTTON);
//Resize the image to fit the button
Image newImg = imgCloseButton.getScaledInstance((int)closeButton.getSize().getWidth(), (int)closeButton.getSize().getHeight(), java.awt.Image.SCALE_SMOOTH);
closeButton.setIcon(new ImageIcon(newImg));
}
private int clickAlphaValue(Icon icon, int posX, int posY)
{
int width = icon.getIconWidth();
int height = icon.getIconHeight();
BufferedImage tempImage = (BufferedImage)createImage(width, height);
Graphics2D g = tempImage.createGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
int alpha = (tempImage.getRGB(posX, posY) >> 24) & 0x000000FF;
return alpha;
}
public static void main(String[] args)
{
try
{
TestAlphaPixels testAlphaPixels = new TestAlphaPixels();
testAlphaPixels.setVisible(true);
testAlphaPixels.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
catch(IOException ioe)
{
ioe.printStackTrace();
}
}
}
This is just a wild guess, but is it possible that when my image gets cast to an Icon, it loses its Alpha property and thus doesn't return the correct value? Anyway, I'd really appreciate it if someone could actually help me out and tell me what I should be changing to get the correct value.
I'm guessing that because when I try it with the original image, the alpha channel's value is fine, but I can't actually use that BufferedImage because I resize it, so I actually get the channel values of the image with the original size...
I think you are on the wrong way. You do not have to override neither paint() nor paintComponent() methods. JButton already "knows" to be shown with image only:
ImageIcon cup = new ImageIcon("images/cup.gif");
JButton button2 = new JButton(cup);
See the following tutorial for example: http://www.apl.jhu.edu/~hall/java/Swing-Tutorial/Swing-Tutorial-JButton.html
Moreover swing is fully customized. You can control opacity, border, color etc. You probably should override some mentioned methods to change functionality. But in most cases there is better and simpler solution.
Since there were good elements in multiple answers, but none of the answers were complete on their own, I'll answer my own question so other people that have the same problem can try something similar.
I created my buttons using a new class which extends JButton, with a new constructor that takes a BufferedImage as parameter instead of an icon. The reason for that is that when I did something like myButton.getIcon(), it would return an Icon, then I'd have to make various manipulations on it to make it a BufferedImage of the right size, and it ended up not working anyway because it seems like the first cast to Icon made it lose the alpha data in the pixels, so I couldn't check to see if the user was clicking on transparent pixels or not.
So I did something like this for the constructor:
public class MyButton extends JButton
{
private BufferedImage bufImg;
public MyButton(BufferedImage bufImg)
{
super(new ImageIcon(bufImg));
this.bufImg = bufImg;
}
}
Then I created an accessor for my bufImg that resized the image to fit the JButton using the getSize() method and then returned an image resized at the right size. I do the transformations in the getBufImg() accessor because the image size might change when the window gets resized. When you call the getBufImg(), it's usually because you clicked on the button and thus you're not currently resizing the window.
Something a little bit like this will return the image at the right size:
public BufferedImage getBufImg()
{
BufferedImage newImg = new BufferedImage(getSize().getWidth(), getSize().getHeight(), BufferedImage.TYPE_INT_ARGB); //Create a new buffered image the right size
Graphics2D g2d = newImg.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.drawImage(bufImg, 0, 0, getSize().getWidth(), getSize().getHeight(), null);
g2d.dispose();
return newImg;
}
With that buffered image, you can then code a method like this:
private int clickAlphaValue(BufferedImage bufImg, int posX, int posY)
{
int alpha;
alpha = (bufImg.getRGB(posX, posY) >>24) & 0x000000FF; //Gets the bit that contains alpha information
return alpha;
}
That you call on the button that implements a MouseListener, like this:
myButton.addMouseListener(new MouseListener()
{
public void mouseClicked(MouseEvent e)
{
}
public void mousePressed(MouseEvent e)
{
}
public void mouseReleased(MouseEvent e)
{
if(clickAlphaValue(((myButton)e.getSource()).getBufImg(), e.getX(), e.getY()) != 0) //If alpha is not set to 0
System.exit(0); //Or other things you want your button to do
}
public void mouseEntered(MouseEvent e)
{
}
public void mouseExited(MouseEvent e)
{
}
});
And voila! The button will only do the action if you clicked on non-transparent pixels.
Thanks for the help everyone, I couldn't have come up with this solutions on my own.
If you want to have shape-specific click points, you're better off using Shape and their contains method. If you want, you can create a shape when creating your custom button class as part of it, and implement a contains method by wrapping around the shape's contains method.
As for the custom JButton, create a class that extends JButton, like this:
import java.awt.*;
import javax.swing.*;
public class CustomButton extends JButton{
/** Filename of the image to be used as the button's icon. */
private String fileName;
/** The width of the button */
private int width;
/** The height of the button. */
private int height;
public CustomButton(String fileName, int width, int height){
this.fileName = fileName;
this.width = width;
this.height = height;
createButton();
}
/**
* Creates the button according to the fields set by the constructor.
*/
private void createButton(){
this.setIcon(getImageIcon(filename));
this.setPreferredSize(new Dimension(width, height));
this.setMaximumSize(new Dimension(width, height));
this.setFocusPainted(false);
this.setRolloverEnabled(false);
this.setOpaque(false);
this.setContentAreaFilled(false);
this.setBorderPainted(false);
this.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
}
}
Here's how you can load the ImageIcon, if you want to do it like this.
public ImageIcon getImageIcon(String fileName){
String imageDirectory = "images/"; //relative to classpath
URL imgURL = getClass().getResource(imageDirectory + fileName);
return new ImageIcon(imgURL);
}
This will give you a button that will at least look like your image.
I asked a similar question regarding Image-based events on click, and Shapes helped wonders.
I guess it comes down to how complex your button images are.
Here's reference anyway:
How can you detect a mouse-click event on an Image object in Java?
PS: Maybe look into generating shapes from images, that go around all the pixels that aren't transparent. No idea if this is possible, but it would mean that a button would only be "pressed" if the user clicks on the image part of it. Just a thought.
If you want your button layout to be that of the non-transparent pixels in your image, then you should redefine the paintComponent() method. It is the most correct way of doing it (overriding paint() worked in old times but is now discouraged).
However I think it is not exactly what you want: you want a click on the button to be detected only if it is on a non-transparent pixel, right? In that case you have to parse your image and when clicked compare mouse coordinates to the pixel alpha channel of your image as JButton does not have such a feature.
If you have a round button, this is exactly what you need:
public class RoundButton extends JButton {
public RoundButton() {
this(null, null);
}
public RoundButton(Icon icon) {
this(null, icon);
}
public RoundButton(String text) {
this(text, null);
}
public RoundButton(Action a) {
this();
setAction(a);
}
public RoundButton(String text, Icon icon) {
setModel(new DefaultButtonModel());
init(text, icon);
if(icon==null) return;
setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
setContentAreaFilled(false);
setFocusPainted(false);
initShape();
}
protected Shape shape, base;
protected void initShape() {
if(!getBounds().equals(base)) {
Dimension s = getPreferredSize();
base = getBounds();
shape = new Ellipse2D.Float(0, 0, s.width, s.height);
}
}
#Override public Dimension getPreferredSize() {
Icon icon = getIcon();
Insets i = getInsets();
int iw = Math.max(icon.getIconWidth(), icon.getIconHeight());
return new Dimension(iw+i.right+i.left, iw+i.top+i.bottom);
}
#Override public boolean contains(int x, int y) {
initShape();
return shape.contains(x, y);
//or return super.contains(x, y) && ((image.getRGB(x, y) >> 24) & 0xff) > 0;
}
}
JButton has a contains() method. Override it and call it on mouseReleased();
paintComponent() instead of paint() depends if you paint() inside XxxButtonUI or just override paintComponent(), but there exists the option JButton#setIcon.

Categories