I'm currently working on a personal use application in Java. I have started the design phase and I have a problem with adding an icon for my button. The image is always pixelated and I don't understand why.
Here is the code of the panel where the button is located:
BufferedImage lockIcon = ImageIO.read(new File(System.getProperty("user.dir")+"\\src\\main\\resources\\icon_lock.png"));
this.setSize(1280, 100);
GridBagLayout gridBagLayout = new GridBagLayout();
gridBagLayout.columnWidths = new int[]{100, 717, 0};
gridBagLayout.rowHeights = new int[]{100, 0};
gridBagLayout.columnWeights = new double[]{0.0, 0.0, Double.MIN_VALUE};
gridBagLayout.rowWeights = new double[]{0.0, Double.MIN_VALUE};
setLayout(gridBagLayout);
this.setBackground(new Color(142, 59, 70));
JButton lockingButton = new JButton(new ImageIcon(lockIcon));
lockingButton.setBorder(BorderFactory.createEmptyBorder());
lockingButton.setContentAreaFilled(false);
GridBagConstraints gbc_lockButton = new GridBagConstraints();
gbc_lockButton.fill = GridBagConstraints.BOTH;
gbc_lockButton.insets = new Insets(10, 5, 0, 5);
gbc_lockButton.gridx = 0;
gbc_lockButton.gridy = 0;
add(lockingButton, gbc_lockButton);
My icon is a .png with a size of 100x100 pixels and a resolution of 300 dpi and the size of my frame is 1280x720 pixels. I've already tried to change the image size and I also to modify the layout of my panel but it didn't change anything.
Here is a screenshot of the result:
So my question is: How can I solve this problem, what am I doing wrong?
I tested your code and to be honest I'm a bit confused...
JButton itself does not scale the image to fill, it just gets centered, so I don't see a reason for it to loose quality (tested it with my own icon and it looked fine).
In general I do not recommend scaling icons with the rest of the UI (dynamic size determined by the LayoutManager), so you should be able to scale the image before adding it to the button using following line:
image = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
If you still wish to scale the image with the UI you override the buttons getIcon() method and do the scaling there.
As stated in my comment, if you want to do really high end scaling you could try the options provided here: Java - resize image without losing quality
BONUS:
I'm not sure why it would pixelate in your case, but I can give you something that may help:
I created a Component specifically for Images and specialized on being used as buttons.
It will scale the image with the smooth option. It will also abide if the layout forces a size or you set one manually and scale the image (keeping the aspect ratio) instead of centering.
If no size is set, it will default to text height.
You may also call jImage.generateStateImages(); to let it automatically create clicked, hovered and disabled images (by altering brightness and gray-scaling).
As soon as you add an action listener it will start behaving (but not looking) like a JButton.
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Insets;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.awt.image.FilteredImageSource;
import java.awt.image.ImageFilter;
import java.awt.image.ImageProducer;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.swing.GrayFilter;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
public class JImage extends JLabel{
private static final long serialVersionUID = 1L;
private Image image;
private Image disabledImage;
private Image hoveredImage;
private Image clickedImage;
private final Map<Image, Image> scaledInstances = new HashMap<Image, Image>();
private boolean enabled = true;
private boolean hovered;
private boolean clicked;
private final List<ActionListener> actionListeners = new ArrayList<ActionListener>();
private boolean isCursorSet = false;
private boolean mouseListenersInitialized = false;
public JImage(Icon icon){
this(toImage(icon));
}
public JImage(Image image){
this.image = image;
addFocusListener(new FocusListener() {
#Override
public void focusLost(FocusEvent e) {
repaint();
}
#Override
public void focusGained(FocusEvent e) {
repaint();
}
});
addKeyListener(new KeyAdapter() {
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == KeyEvent.VK_ENTER){
if(!actionListeners.isEmpty()){
doClick();
}
}
}
});
}
private void initMouseListeners(){
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
clicked = false;
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
clicked = true;
repaint();
}
#Override
public void mouseExited(MouseEvent e) {
hovered = false;
repaint();
}
#Override
public void mouseEntered(MouseEvent e) {
hovered = true;
repaint();
}
#Override
public void mouseClicked(MouseEvent e) {
doClick(e.getButton());
}
});
mouseListenersInitialized = true;
}
public void doClick(){
doClick(MouseEvent.BUTTON1);
}
public void doClick(int mouseButton){
if(isEnabled() && mouseButton == MouseEvent.BUTTON1){
for(ActionListener actionListener : getActionListeners()) actionListener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "mouseClicked"));
}
}
#Override
public Dimension getPreferredSize(){
if(super.isPreferredSizeSet()){
return super.getPreferredSize();
}else if(getImage() != null){
int height = getFontMetrics(getFont()).getHeight();
double imageRatio = (double) getImage().getWidth(null) / getImage().getHeight(null);
int width = (int) (height * imageRatio);
return new Dimension(width, height);
}else return new Dimension(0, 0);
}
#Override
public Dimension getMinimumSize(){
return getPreferredSize();
}
#Override
protected void paintComponent(Graphics g){
super.setCursor(getCursor());
super.paintComponent(g);
Image image;
if(!isEnabled() && getDisabledImage() != null) image = getDisabledImage();
else if(clicked && getClickedImage() != null) image = getClickedImage();
else if((hovered || isFocusOwner()) && getHoveredImage() != null) image = getHoveredImage();
else image = this.getImage();
if(image != null){
if(image.getWidth(null) == 0 || image.getHeight(null) == 0) System.out.println("not yet loaded");
Insets insets;
if(getBorder() == null){
insets = new Insets(0, 0, 0, 0);
}else{
insets = getBorder().getBorderInsets(this);
}
int width = getWidth() - insets.right - insets.left;
int height = getHeight() - insets.bottom - insets.top;
double imageRatio = (double) image.getWidth(null) / image.getHeight(null);
double containerRatio = (double) width / height;
if(imageRatio > containerRatio){
height = (int) (width / imageRatio);
}else{
width = (int) (height * imageRatio);
}
int x = (getWidth() - width) / 2;
int y = (getHeight() - height) / 2;
Image scaled = scaledInstances.get(image);
if(scaled == null || scaled.getWidth(null) != width || scaled.getHeight(null) != height){
scaled = image.getScaledInstance(width, height, Image.SCALE_SMOOTH);
scaledInstances.put(image, scaled);
}
g.drawImage(scaled, x, y, null);
}
}
public void scalePreferredSize(float scale){
Dimension prefSize = getPreferredSize();
prefSize.width *= scale;
prefSize.height *= scale;
setPreferredSize(prefSize);
}
/**
* Convenience method to use JImages as Buttons
* <br/>Adds a click listener.
* <br/>Also changes the cursor to a hand cursor when on top of this component.
* <br/>To revert this, call {#link #setCursor(java.awt.Cursor)}
* #param actionListener the listener to be called on click
*/
public void addActionListener(final ActionListener actionListener) {
actionListeners.add(actionListener);
if(!mouseListenersInitialized) initMouseListeners();
setFocusable(true);
}
protected List<ActionListener> getActionListeners(){
return actionListeners;
}
public void generateStateImages(){
generateStateImages(true, true, true);
}
public void generateStateImages(boolean disabled, boolean hovered, boolean clicked){
if(image == null) throw new IllegalStateException("Cannot generate state images while default image is null.");
if(disabled){
this.disabledImage = grayScale(image);
}
if(hovered){
this.hoveredImage = changeBrightness(image, 1.2f);
}
if(clicked){
this.clickedImage = changeBrightness(image, 0.8f);
}
if(!mouseListenersInitialized) initMouseListeners();
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
repaint();
}
public Image getDisabledImage() {
return disabledImage;
}
public void setDisabledImage(Image disabledImage) {
this.disabledImage = disabledImage;
if(!mouseListenersInitialized && disabledImage != null) initMouseListeners();
repaint();
}
public Image getHoveredImage() {
return hoveredImage;
}
public void setHoveredImage(Image hoveredImage) {
this.hoveredImage = hoveredImage;
if(!mouseListenersInitialized && hoveredImage != null) initMouseListeners();
repaint();
}
public Image getClickedImage() {
return clickedImage;
}
public void setClickedImage(Image clickedImage) {
this.clickedImage = clickedImage;
if(!mouseListenersInitialized && clickedImage != null) initMouseListeners();
repaint();
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
repaint();
}
public boolean isHovered() {
return hovered;
}
public boolean isClicked() {
return clicked;
}
#Override
public Cursor getCursor(){
if(!isCursorSet && !actionListeners.isEmpty() && isEnabled()) return Cursor.getPredefinedCursor(isEnabled() ? Cursor.HAND_CURSOR : Cursor.DEFAULT_CURSOR);
else return super.getCursor();
}
#Override
public void setCursor(Cursor cursor){
super.setCursor(cursor);
isCursorSet = true;
}
/**
* #return a copy that will update its image when the original its updated.
*/
public JImage createDelegate(){
final JImage thisImage = this;
JImage delegate = new JImage(getImage()){
public boolean isEnabled() {
return thisImage.isEnabled();
}
public List<ActionListener> getActionListeners(){
List<ActionListener> actionListeners = new ArrayList<ActionListener>(thisImage.getActionListeners());
actionListeners.addAll(super.getActionListeners());
return actionListeners;
}
public Image getImage() {
return thisImage.getImage();
}
public Image getDisabledImage() {
return thisImage.getDisabledImage();
}
public Image getHoveredImage() {
return thisImage.getHoveredImage();
}
public Image getClickedImage() {
return thisImage.getClickedImage();
}
};
return delegate;
}
// Extracted from my image utilities
private static Image grayScale(Image image){
ImageFilter filter = new GrayFilter(true, 50);
ImageProducer producer = new FilteredImageSource(image.getSource(), filter);
return Toolkit.getDefaultToolkit().createImage(producer);
}
// Extracted from my image utilities
private static Image changeBrightness(Image image, float mult) {
BufferedImage newImage = new BufferedImage(image.getWidth(null), image.getHeight(null), BufferedImage.TYPE_INT_ARGB);
newImage.getGraphics().drawImage(image, 0, 0, null);
WritableRaster wr = newImage.getRaster();
int[] pixel = new int[4];
int[] overflows = new int[3];
for(int i = 0; i < wr.getWidth(); i++){
for(int j = 0; j < wr.getHeight(); j++){
wr.getPixel(i, j, pixel);
pixel[0] = (int) (pixel[0] * mult);
pixel[1] = (int) (pixel[1] * mult);
pixel[2] = (int) (pixel[2] * mult);
// if a value exceeds 255, the other color values will instead be raised to bleach the image
overflows[0] = 0;
overflows[1] = 0;
overflows[2] = 0;
if(pixel[0] > 255){
overflows[1] += pixel[0] - 255;
overflows[2] += pixel[0] - 255;
pixel[0] = 255;
}
if(pixel[1] > 255){
pixel[0] += pixel[1] - 255;
pixel[2] += pixel[1] - 255;
pixel[1] = 255;
}
if(pixel[2] > 255){
pixel[0] += pixel[2] - 255;
pixel[1] += pixel[2] - 255;
pixel[2] = 255;
}
pixel[0] = Math.min(255, pixel[0] + overflows[0]);
pixel[1] = Math.min(255, pixel[1] + overflows[1]);
pixel[2] = Math.min(255, pixel[2] + overflows[2]);
wr.setPixel(i, j, pixel);
}
}
return newImage;
}
// Extracted from my image utilities
private static Image toImage(Icon icon){
if (icon instanceof ImageIcon) {
return ((ImageIcon) icon).getImage();
} else {
int w = icon.getIconWidth();
int h = icon.getIconHeight();
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
icon.paintIcon(null, g, 0, 0);
g.dispose();
return image;
}
}
}
Related
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 7 years ago.
Improve this question
As i've declared in title of my question, I'm about to make a sort of editor of particular areas of a given png image to change colours pixel by pixel by clicking on it, maybe helping myself magnifying the area...
I'm mainly stuck because I don't know, ad I didn't find so far a solution to display a png which has a "grid" that divides every pixel.
I mean, a sort of thin line that like crosswords could "highlight" every pixel.
Pls point me in the right direction!
thanks!
Okay, so basically, what this is does is a very "simple" scaling process. Each pixel in the image is represented by a "cell" which has a size. Each cell is filled with the color of the pixel. A simple grid is then overlaid on top.
You can use the slider to change the scaling (making the grid larger or smaller).
The example also makes use of the tool tip support to show the pixel color
This example doesn't providing editing though. It would be a trival matter to add a MouseListener to the EditorPane and using the same algorithm as the getToolTipText method, find the pixel which needs to be updated.
My example was using a large sprite (177x345) and is intended to provide for a variable sized sprite. Smaller or fixed sized sprites will provide better performance.
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JSlider;
import javax.swing.JViewport;
import javax.swing.Scrollable;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
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 SpriteEditorSpane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
ex.printStackTrace();
}
}
});
}
public class SpriteEditorSpane extends JPanel {
private JLabel sprite;
private JSlider zoom;
private EditorPane editorPane;
public SpriteEditorSpane() throws IOException {
setLayout(new GridBagLayout());
BufferedImage source = ImageIO.read(new File("sprites/Doctor-01.png"));
sprite = new JLabel(new ImageIcon(source));
editorPane = new EditorPane();
editorPane.setSource(source);
zoom = new JSlider(2, 10);
zoom.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
editorPane.setGridSize(zoom.getValue());
}
});
zoom.setValue(2);
zoom.setPaintTicks(true);
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = 0;
gbc.gridy = 0;
gbc.gridheight = GridBagConstraints.REMAINDER;
add(sprite, gbc);
gbc.gridx++;
gbc.gridheight = 1;
gbc.fill = GridBagConstraints.BOTH;
gbc.weightx = 1;
gbc.weighty = 1;
add(new JScrollPane(editorPane), gbc);
gbc.gridy++;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.weightx = 1;
gbc.weighty = 0;
add(zoom, gbc);
}
}
public class EditorPane extends JPanel implements Scrollable {
private BufferedImage source;
private BufferedImage gridBuffer;
private int gridSize = 2;
private Color gridColor;
private Timer updateTimer;
public EditorPane() {
updateTimer = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
doBufferUpdate();
revalidate();
repaint();
}
});
updateTimer.setRepeats(false);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
updateBuffer();
}
});
setGridColor(new Color(128, 128, 128, 128));
setToolTipText("Sprite");
}
#Override
public Dimension getPreferredSize() {
return source == null ? new Dimension(200, 200)
: new Dimension(source.getWidth() * gridSize, source.getHeight() * gridSize);
}
public void setGridColor(Color color) {
if (color != gridColor) {
this.gridColor = color;
updateBuffer();
}
}
public Color getGridColor() {
return gridColor;
}
public void setSource(BufferedImage image) {
if (image != source) {
this.source = image;
updateBuffer();
}
}
public void setGridSize(int size) {
if (size != gridSize) {
this.gridSize = size;
updateBuffer();
}
}
public BufferedImage getSource() {
return source;
}
public int getGridSize() {
return gridSize;
}
#Override
public String getToolTipText(MouseEvent event) {
Point p = event.getPoint();
int x = p.x / getGridSize();
int y = p.y / getGridSize();
BufferedImage source = getSource();
String tip = null;
if (x < source.getWidth() && y < source.getHeight()) {
Color pixel = new Color(source.getRGB(x, y), true);
StringBuilder sb = new StringBuilder(128);
sb.append("<html><table><tr><td>");
sb.append("R:").append(pixel.getRed());
sb.append(" G:").append(pixel.getGreen());
sb.append(" B:").append(pixel.getBlue());
sb.append(" A:").append(pixel.getAlpha());
String hex = String.format("#%02x%02x%02x%02x", pixel.getRed(), pixel.getGreen(), pixel.getBlue(), pixel.getAlpha());
sb.append("</td></tr><tr><td bgcolor=").append(hex);
sb.append("width=20 height=20> </td></tr></table>");
tip = sb.toString();
}
return tip;
}
#Override
public Point getToolTipLocation(MouseEvent event) {
Point p = new Point(event.getPoint());
p.x += 8;
p.y += 8;
return p;
}
protected void doBufferUpdate() {
BufferedImage source = getSource();
int gridSize = getGridSize();
gridBuffer = null;
if (source != null) {
gridBuffer = new BufferedImage(source.getWidth() * gridSize, source.getHeight() * gridSize, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = gridBuffer.createGraphics();
for (int row = 0; row < source.getHeight(); row++) {
for (int col = 0; col < source.getWidth(); col++) {
int xPos = col * gridSize;
int yPos = row * gridSize;
Color pixel = new Color(source.getRGB(col, row), true);
g2d.setColor(pixel);
g2d.fillRect(xPos, yPos, gridSize, gridSize);
g2d.setColor(getGridColor());
g2d.drawRect(xPos, yPos, gridSize, gridSize);
}
}
g2d.dispose();
} else if (getWidth() > 0 && getHeight() > 0) {
gridBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = gridBuffer.createGraphics();
g2d.setColor(gridColor);
for (int xPos = 0; xPos < getWidth(); xPos += gridSize) {
g2d.drawLine(xPos, 0, xPos, getHeight());
}
for (int yPos = 0; yPos < getHeight(); yPos += gridSize) {
g2d.drawLine(0, yPos, getWidth(), yPos);
}
g2d.dispose();
}
}
protected void updateBuffer() {
updateTimer.restart();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (gridBuffer != null) {
g2d.drawImage(gridBuffer, 0, 0, this);
}
g2d.dispose();
}
#Override
public Dimension getPreferredScrollableViewportSize() {
return new Dimension(200, 200);
}
#Override
public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
#Override
public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) {
return 128;
}
#Override
public boolean getScrollableTracksViewportWidth() {
Container parent = getParent();
return parent instanceof JViewport
&& parent.getWidth() > getPreferredSize().width;
}
#Override
public boolean getScrollableTracksViewportHeight() {
Container parent = getParent();
return parent instanceof JViewport
&& parent.getHeight() > getPreferredSize().height;
}
}
}
The overall performance is pretty slow when generating the "grid", you might be able to use byte[] bytes = ((DataBufferByte)gridBuffer.getRaster().getDataBuffer()).getData() which will give you a byte array of the pixels, but in my testing, it didn't make that big a difference.
You might also like to have a look at Zoom box for area around mouse location on screen
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
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:
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);
}
}
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...
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...
Roll over...
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 ;)
I am trying to make a health bar, and as what might be original, it will start out green, and after losing health you find that it will turn yellow, then orange, then red.. or something relative to that.
I tried using the method provided in this link: https://stackoverflow.com/questions/19841477/java-smooth-color-transition
The result from that link was this code, just a test from value 100 to 0, but it ended in an IllegalArgumentException at normally Red and Green, and my guess for reason is it being over the value of 255.
Color to = Color.red;
Color base = Color.green;
int red = (int)Math.abs((100 * to.getRed()) + ((1 - 100) * base.getRed()));
int green = (int)Math.abs((100 * to.getGreen()) + ((1 - 100) * base.getGreen()));
int blue = (int)Math.abs((100 * to.getBlue()) + ((1 - 100) * base.getBlue()));
setForeground(new Color(red, green, blue));
It didn't really work, and I have absolutely no idea how I can get it to transition the way I wish it to.
So within my HealthBar class, I have an update() method
public void update() {
if (getValue() < 10) setForeground(Color.red);
else if (getValue() < 25) setForeground(Color.orange);
else if (getValue() < 60) setForeground(Color.yellow);
else setForeground(Color.green);
}
This code does a basic transition at certain points.
I need to create fields to use certain colors at certain values of the health bar, so now I have this..
if (getValue() < 10) {
Color to = Color.black;
// Color current = getForeground() ?
Color from = Color.red;
// ?
}
I am just going to use an example for the last bit.
So I know that I am going to have a color that I am going to, and a color that is a base. I am not so sure if I need a color for current. The problem that I see now is for the steps of the transition because each transition has a different amount of steps.
Summary and Question
I don't know how to achieve what I am attempting, all I know for sure is I need a to and base color, and I provided a link to an answer that I saw, but I couldn't really figure it out. With the information given, how could I get it to transition colors
?
I spent a lot of time trying to find/create a blending algorithm that worked for me, this is basically what I was able to hobble together.
I've used this approach to generate a gradient transition mixing multiple colors, as demonstrated here
Basically, this approach allows you to set up a series of colors and percentage marks so that you gain much greater control over which points the colors transition between.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.text.NumberFormat;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class ColorFading {
public static void main(String[] args) {
new ColorFading();
}
public ColorFading() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new FadePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class FadePane extends JPanel {
private final float[] fractions = new float[]{0f, 0.5f, 1f};
private final Color[] colors = new Color[]{Color.RED, Color.YELLOW, Color.GREEN};
private float progress = 1f;
private JSlider slider;
public FadePane() {
slider = new JSlider(0, 100);
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
progress = ((float)slider.getValue() / 100f);
repaint();
}
});
slider.setValue(100);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
int width = getWidth();
int height = getHeight();
Color startColor = blendColors(fractions, colors, progress);
g2d.setColor(startColor);
g2d.fillRect(0, 0, width, height);
g2d.dispose();
}
}
public static Color blendColors(float[] fractions, Color[] colors, float progress) {
Color color = null;
if (fractions != null) {
if (colors != null) {
if (fractions.length == colors.length) {
int[] indicies = getFractionIndicies(fractions, progress);
float[] range = new float[]{fractions[indicies[0]], fractions[indicies[1]]};
Color[] colorRange = new Color[]{colors[indicies[0]], colors[indicies[1]]};
float max = range[1] - range[0];
float value = progress - range[0];
float weight = value / max;
color = blend(colorRange[0], colorRange[1], 1f - weight);
} else {
throw new IllegalArgumentException("Fractions and colours must have equal number of elements");
}
} else {
throw new IllegalArgumentException("Colours can't be null");
}
} else {
throw new IllegalArgumentException("Fractions can't be null");
}
return color;
}
public static int[] getFractionIndicies(float[] fractions, float progress) {
int[] range = new int[2];
int startPoint = 0;
while (startPoint < fractions.length && fractions[startPoint] <= progress) {
startPoint++;
}
if (startPoint >= fractions.length) {
startPoint = fractions.length - 1;
}
range[0] = startPoint - 1;
range[1] = startPoint;
return range;
}
public static Color blend(Color color1, Color color2, double ratio) {
float r = (float) ratio;
float ir = (float) 1.0 - r;
float rgb1[] = new float[3];
float rgb2[] = new float[3];
color1.getColorComponents(rgb1);
color2.getColorComponents(rgb2);
float red = rgb1[0] * r + rgb2[0] * ir;
float green = rgb1[1] * r + rgb2[1] * ir;
float blue = rgb1[2] * r + rgb2[2] * ir;
if (red < 0) {
red = 0;
} else if (red > 255) {
red = 255;
}
if (green < 0) {
green = 0;
} else if (green > 255) {
green = 255;
}
if (blue < 0) {
blue = 0;
} else if (blue > 255) {
blue = 255;
}
Color color = null;
try {
color = new Color(red, green, blue);
} catch (IllegalArgumentException exp) {
NumberFormat nf = NumberFormat.getNumberInstance();
System.out.println(nf.format(red) + "; " + nf.format(green) + "; " + nf.format(blue));
exp.printStackTrace();
}
return color;
}
}
OK, before Mad posted his answer (and 1+ to it), I was working on this too, so I might as well post what I came up with....
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.EnumMap;
import java.util.Map;
import javax.swing.*;
import javax.swing.event.*;
#SuppressWarnings("serial")
public class ColorTransition extends JPanel {
private static final int TRANSITION_DELAY = 30;
private static final int PREF_W = 800;
private static final int PREF_H = 600;
private RgbSliderPanel rgbSliderPanel1 = new RgbSliderPanel("Color 1");
private RgbSliderPanel rgbSliderPanel2 = new RgbSliderPanel("Color 2");
private Color background1;
private Color background2;
private JButton button = new JButton(new ButtonAction("Push Me"));
public ColorTransition() {
setBackground(Color.black);
add(rgbSliderPanel1.getMainPanel());
add(rgbSliderPanel2.getMainPanel());
add(button);
rgbSliderPanel1.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent evt) {
if (RgbSliderPanel.COLOR.equals(evt.getPropertyName())) {
setBackground(rgbSliderPanel1.calculateColor());
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
button.setEnabled(enabled);
rgbSliderPanel1.setEnabled(enabled);
rgbSliderPanel2.setEnabled(enabled);
}
private class ButtonAction extends AbstractAction {
public ButtonAction(String name) {
super(name);
}
#Override
public void actionPerformed(ActionEvent e) {
ColorTransition.this.setEnabled(false);
background1 = rgbSliderPanel1.calculateColor();
background2 = rgbSliderPanel2.calculateColor();
setBackground(background1);
Timer timer = new Timer(TRANSITION_DELAY, new TransitionListener());
timer.start();
}
private class TransitionListener implements ActionListener {
private int index = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (index > 100) {
((Timer) e.getSource()).stop();
ColorTransition.this.setEnabled(true);
} else {
int r = (int) (background2.getRed() * index / 100.0 + background1
.getRed() * (100 - index) / 100.0);
int g = (int) (background2.getGreen() * index / 100.0 + background1
.getGreen() * (100 - index) / 100.0);
int b = (int) (background2.getBlue() * index / 100.0 + background1
.getBlue() * (100 - index) / 100.0);
setBackground(new Color(r, g, b));
}
index++;
}
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("ColorTransition");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ColorTransition());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Rgb {
RED("Red"), GREEN("Green"), BLUE("Blue");
private String name;
private Rgb(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
class RgbSliderPanel {
public static final String COLOR = "color";
private JPanel mainPanel = new JPanel();
private SwingPropertyChangeSupport propertyChangeSupport = new SwingPropertyChangeSupport(
this);
private Map<Rgb, JSlider> colorSliderMap = new EnumMap<>(Rgb.class);
private String name;
protected Color color;
public RgbSliderPanel(String name) {
this.name = name;
mainPanel.setBorder(BorderFactory.createTitledBorder(name));
//mainPanel.setOpaque(false);
mainPanel.setLayout(new GridLayout(0, 1));
for (Rgb rgb : Rgb.values()) {
JSlider colorSlider = new JSlider(0, 255, 0);
colorSliderMap.put(rgb, colorSlider);
mainPanel.add(colorSlider);
colorSlider.setBorder(BorderFactory.createTitledBorder(rgb.getName()));
colorSlider.setPaintTicks(true);
colorSlider.setPaintTrack(true);
colorSlider.setMajorTickSpacing(50);
colorSlider.setMinorTickSpacing(10);
colorSlider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
Color oldValue = color;
Color newValue = calculateColor();
color = newValue;
propertyChangeSupport.firePropertyChange(COLOR, oldValue,
newValue);
}
});
}
}
public JComponent getMainPanel() {
return mainPanel;
}
public void setEnabled(boolean enabled) {
for (JSlider slider : colorSliderMap.values()) {
slider.setEnabled(enabled);
}
}
public Color calculateColor() {
int r = colorSliderMap.get(Rgb.RED).getValue();
int g = colorSliderMap.get(Rgb.GREEN).getValue();
int b = colorSliderMap.get(Rgb.BLUE).getValue();
return new Color(r, g, b);
}
public String getName() {
return name;
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.addPropertyChangeListener(listener);
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
propertyChangeSupport.removePropertyChangeListener(listener);
}
}
I'm trying to improve my understanding of Java, particularly Java GUI, by making a puzzle program. Currently the user selects an image, which is cut up into a specified number of pieces. The pieces are drawn randomly to the screen but they seem to be covered by blank portions of other pieces, and not all of them show up, but I can print out all the coordinates. I am using absolute positioning because a LayoutManager didn't seem to work. I briefly tried layeredPanes but they confused me and didn't seem to solve the problem. I would really appreciate some help.
Here are the 2 relevant classes:
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
public class PuzzlePieceDriver extends JFrame
{
private static Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
private static final int HEIGHT = SCREENSIZE.height;
private static final int WIDTH = SCREENSIZE.width;
public static int MY_WIDTH;
public static int MY_HEIGHT;
private static BufferedImage image;
private int xPieces = PuzzleMagicDriver.getXPieces();
private int yPieces = PuzzleMagicDriver.getYPieces();
private PuzzlePiece[] puzzle = new PuzzlePiece[xPieces*yPieces];
public Container pane = this.getContentPane();
private JLayeredPane layeredPane = new JLayeredPane();
public PuzzlePieceDriver(ImageIcon myPuzzleImage)
{
MY_WIDTH = myPuzzleImage.getIconWidth()+(int)myPuzzleImage.getIconHeight()/2;
MY_HEIGHT = myPuzzleImage.getIconHeight()+(int)myPuzzleImage.getIconHeight()/2;
setTitle("Hot Puzz");
setSize(MY_WIDTH,MY_HEIGHT);
setLocationByPlatform(true);
pane.setLayout(null);
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip(image);
//pane.add(layeredPane);
setVisible(true);
}//end constructor
public static BufferedImage iconToImage(ImageIcon icon)
{
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max)
{
int temp =
min + (int)(Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip(BufferedImage passedImage)
{
int cw, ch;
int w,h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w/xPieces;
ch = h/yPieces;
int[] cells=new int[xPieces*yPieces];
int dx, dy;
BufferedImage clip = passedImage;
//layeredPane.setPreferredSize(new Dimension(w,h));
for (int x=0; x<xPieces; x++)
{
int sx = x*cw;
for (int y=0; y<yPieces; y++)
{
int sy = y*ch;
int cell = cells[x*xPieces+y];
dx = (cell / xPieces) * cw;
dy = (cell % yPieces) * ch;
clip= passedImage.getSubimage(sx, sy, cw, ch);
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
PuzzlePiece piece=new PuzzlePiece(clip,myX,myY);
puzzle[x*xPieces+y]=piece;
piece.setBounds(myX,myY,w,h);
//layeredPane.setBounds(myX,myY,w,h);
//layeredPane.add(piece,new Integer(x*xPieces+y));
pane.add(piece);
piece.repaint();
}//end nested for
}//end for
return puzzle;
}//end createClip
}//end class
Sorry if the spacing is a little messed up!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel
{
private Point imageCorner; //the image's top-left corner location
private Point prevPt; //mouse location for previous event
private Boolean insideImage =false;
private BufferedImage image;
public PuzzlePiece(BufferedImage clip, int x, int y)
{
image = clip;
imageCorner = new Point(x,y);
//repaint();
}//end constructor
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(image, (int)getImageCornerX(),(int)getImageCornerY(), this);
System.out.println("paint "+getImageCornerX()+" "+getImageCornerY());
//repaint();
//g.dispose();
}//end paintComponent
public Point getImageCorner()
{
return imageCorner;
}//end getImageCorner
public double getImageCornerY()
{
return imageCorner.getY();
}//end getImageCornerY
public double getImageCornerX()
{
return imageCorner.getX();
}//end getPoint
}//end class PuzzlePiece
Any help would be appreciated, I've gotten really stuck! Thanks!!
I was really intrigued by this idea, so I made another example, using a custom layout manager.
public class MyPuzzelBoard extends JPanel {
public static final int GRID_X = 4;
public static final int GRID_Y = 4;
private BufferedImage image;
public MyPuzzelBoard(BufferedImage image) {
setLayout(new VirtualLayoutManager());
setImage(image);
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (e.getClickCount() == 2) {
removeAll();
generatePuzzel();
} else {
Component comp = getComponentAt(e.getPoint());
if (comp != null && comp != MyPuzzelBoard.this) {
setComponentZOrder(comp, 0);
invalidate();
revalidate();
repaint();
}
}
}
});
}
public void setImage(BufferedImage value) {
if (value != image) {
image = value;
removeAll();
generatePuzzel();
}
}
public BufferedImage getImage() {
return image;
}
protected float generateRandomNumber() {
return (float) Math.random();
}
protected void generatePuzzel() {
BufferedImage image = getImage();
if (image != null) {
int imageWidth = image.getWidth();
int imageHeight = image.getHeight();
int clipWidth = imageWidth / GRID_X;
int clipHeight = imageHeight / GRID_Y;
for (int x = 0; x < GRID_X; x++) {
for (int y = 0; y < GRID_Y; y++) {
float xPos = generateRandomNumber();
float yPos = generateRandomNumber();
Rectangle bounds = new Rectangle((x * clipWidth), (y * clipHeight), clipWidth, clipHeight);
MyPiece piece = new MyPiece(image, bounds);
add(piece, new VirtualPoint(xPos, yPos));
}
}
}
invalidate();
revalidate();
repaint();
}
public class VirtualPoint {
private float x;
private float y;
public VirtualPoint(float x, float y) {
this.x = x;
this.y = y;
}
public float getX() {
return x;
}
public float getY() {
return y;
}
public void setX(float x) {
this.x = x;
}
public void setY(float y) {
this.y = y;
}
}
public class VirtualLayoutManager implements LayoutManager2 {
private Map<Component, VirtualPoint> mapConstraints;
public VirtualLayoutManager() {
mapConstraints = new WeakHashMap<>(25);
}
#Override
public void addLayoutComponent(Component comp, Object constraints) {
if (constraints instanceof VirtualPoint) {
mapConstraints.put(comp, (VirtualPoint) constraints);
}
}
#Override
public Dimension maximumLayoutSize(Container target) {
return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
}
#Override
public float getLayoutAlignmentX(Container target) {
return 0.5f;
}
#Override
public float getLayoutAlignmentY(Container target) {
return 0.5f;
}
#Override
public void invalidateLayout(Container target) {
}
#Override
public void addLayoutComponent(String name, Component comp) {
}
#Override
public void removeLayoutComponent(Component comp) {
mapConstraints.remove(comp);
}
#Override
public Dimension preferredLayoutSize(Container parent) {
return new Dimension(400, 400);
}
#Override
public Dimension minimumLayoutSize(Container parent) {
return preferredLayoutSize(parent);
}
#Override
public void layoutContainer(Container parent) {
int width = parent.getWidth();
int height = parent.getHeight();
for (Component comp : parent.getComponents()) {
VirtualPoint p = mapConstraints.get(comp);
if (p != null) {
int x = Math.round(width * p.getX());
int y = Math.round(height * p.getY());
Dimension size = comp.getPreferredSize();
x = Math.min(x, width - size.width);
y = Math.min(y, height - size.height);
comp.setBounds(x, y, size.width, size.height);
}
}
}
}
}
Basically, this uses a "virtual" coordinate system, where by rather then supply absolute x/y positions in pixels, you provide them as percentage of the parent container. Now, to be honest, it wouldn't take much to convert back to absolute positioning, just this way, you also get layout scaling.
The example also demonstrates Z-reording (just in case) and the double click simple re-randomizes the puzzel
Oh, I also made the piece transparent (opaque = false)
Oh, one thing I should mention, while going through this example, I found that it was possible to have pieces placed off screen (completely and partially).
You may want to check your positioning code to make sure that the images when they are laid out aren't been moved off screen ;)
Try using setBorder(new LineBorder(Color.RED)) in your puzzle piece constructor to see where the bounds of your puzzle pieces are. If they are where you'd expect them to be, it's likely that your positioning is wrong. Also make your puzzle pieces extend JComponent instead, or use setOpaque(false) if you're extending JPanel.
There are lots of suggestions I'd like to make, but first...
The way you choose a random position is off...
int myX = randomNumber(0,(int)w);
int myY = randomNumber(0,(int)h);
This allows duplicate position's to be generated (and overlaying cells)
UPDATES (using a layout manager)
Okay, so this is a slight shift in paradigm. Rather then producing a clip and passing it to the piece, I allowed the piece to make chooses about how it was going to render the the piece. Instead, I passed it the Rectangle it was responsible for.
This means, you could simply use something like setCell(Rectangle) to make a piece change (unless you're hell bent on drag'n'drop ;))
I ended up using Board panel due to some interesting behavior under Java 7, but that's another question ;)
package puzzel;
import java.awt.BorderLayout;
import java.awt.EventQueue;
import javax.swing.*;
public class PuzzlePieceDriver extends JFrame {
public PuzzlePieceDriver(ImageIcon myPuzzleImage) {
setTitle("Hot Puzz");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout());
add(new Board(myPuzzleImage));
pack();
setVisible(true);
}//end constructor
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException ex) {
} catch (InstantiationException ex) {
} catch (IllegalAccessException ex) {
} catch (UnsupportedLookAndFeelException ex) {
}
ImageIcon image = new ImageIcon(PuzzlePieceDriver.class.getResource("/issue459.jpg"));
PuzzlePieceDriver driver = new PuzzlePieceDriver(image);
driver.setLocationRelativeTo(null);
driver.setVisible(true);
}
});
}
}//end class
A piece panel...
The panel overrides the preferred and minimum size methods...while it works for this example, it's probably better to use setPreferredSize and setMiniumumSize instead ;)
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package puzzel;
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
public class PuzzlePiece extends JPanel {
private BufferedImage masterImage;
private Rectangle pieceBounds;
private BufferedImage clip;
public PuzzlePiece(BufferedImage image, Rectangle bounds) {
masterImage = image;
pieceBounds = bounds;
// Make sure the rectangle fits the image
int width = Math.min(pieceBounds.x + pieceBounds.width, image.getWidth() - pieceBounds.x);
int height = Math.min(pieceBounds.y + pieceBounds.height, image.getHeight() - pieceBounds.y);
clip = image.getSubimage(pieceBounds.x, pieceBounds.y, width, height);
}//end constructor
#Override
public Dimension getPreferredSize() {
return pieceBounds.getSize();
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
int x = 0;
int y = 0;
g.drawImage(clip, x, y, this);
g.setColor(Color.RED);
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
}//end paintComponent
}//end class PuzzlePiece
The board panel...used mostly because of some interesting issues I was having with Java 7...Implements a MouseListener, when you run the program, click the board, it's fun ;)
package puzzel;
import java.awt.Graphics;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
/**
*
* #author shane
*/
public class Board extends JPanel {
public static final int X_PIECES = 4;
public static final int Y_PIECES = 4;
private PuzzlePiece[] puzzle = new PuzzlePiece[X_PIECES * Y_PIECES];
private static BufferedImage image;
public Board(ImageIcon myPuzzleImage) {
image = iconToImage(myPuzzleImage); //pass image into bufferedImage form
puzzle = createClip();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
removeAll();
invalidate();
createClip();
// doLayout();
invalidate();
revalidate();
repaint();
}
});
}
public static BufferedImage iconToImage(ImageIcon icon) {
Image img = icon.getImage();
int w = img.getWidth(null);
int h = img.getHeight(null);
BufferedImage image = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
Graphics g = image.createGraphics();
// Paint the image onto the buffered image
g.drawImage(img, 0, 0, null);
g.dispose();
return image;
}//end BufferedImage
protected int randomNumber(int min, int max) {
int temp = min + (int) (Math.random() * ((max - min) + 1));
return temp;
}//end randomNumber
private PuzzlePiece[] createClip() {
int cw, ch;
int w, h;
w = image.getWidth(null);
h = image.getHeight(null);
cw = w / X_PIECES;
ch = h / Y_PIECES;
// Generate a list of cell bounds
List<Rectangle> lstBounds = new ArrayList<>(25);
for (int y = 0; y < h; y += ch) {
for (int x = 0; x < w; x += cw) {
lstBounds.add(new Rectangle(x, y, cw, ch));
}
}
BufferedImage clip = image;
setLayout(new GridBagLayout());
for (int x = 0; x < X_PIECES; x++) {
for (int y = 0; y < Y_PIECES; y++) {
// Get a random index
int index = randomNumber(0, lstBounds.size() - 1);
// Remove the bounds so we don't duplicate any positions
Rectangle bounds = lstBounds.remove(index);
PuzzlePiece piece = new PuzzlePiece(clip, bounds);
puzzle[x * X_PIECES + y] = piece;
GridBagConstraints gbc = new GridBagConstraints();
gbc.gridx = x;
gbc.gridy = y;
gbc.fill = GridBagConstraints.BOTH;
add(piece, gbc);
piece.invalidate();
piece.repaint();
}//end nested for
}//end for
invalidate();
repaint();
return puzzle;
}//end createClip
}
Now I know you eventually going to ask about how to move a piece, GridBagLayout has this wonderful method called getConstraints which allows you to retrieve the constraints used to layout the component in question. You could then modify the gridx and gridy values and use setConstraints to update it (don't forget to call invalidate and repaint ;))
I'd recommend having a read of How to Use GridBagLayout for more information ;)
Eventually, you'll end up with something like:
I would to rotate JXImagePanel. It should be possible - this is about JXImagePanel:
Swing :: JXImagePanel
While JLabel and JButton allow you to easily add images to your Swing applications,
the JXImagePanel makes it trivially easy to add any BufferedImage or Icon to your Swing applications.
If editable, it also provides a way for the user to change the image. In addition, the JXImagePanel provides many built in effects out-of-the-box,
including Tiling, Scaling, Rotating, Compositing, and more.
However, I cannot figure out how to do this. Currently my code snippet is:
bufferedImage = ImageIO.read(new File("image.png"));
image = new ImageIcon(bufferedImage).getImage();
tempImage = image.getScaledInstance(100, 150, Image.SCALE_FAST);
this.deskJXImagePanel.setImage(tempImage);
Now I would like to rotate it in 0-360 degrees. How it can be done?
JXImagePanel is deprecated (actually, made package private as of 1.6.2, because it's still used internally), so better not use is, will be removed soon.
Instead, use a JXPanel with an ImagePainter and an arbitrary transformOp applied to the painter, in code snippets something like:
JXPanel panel = new JXPanel();
ImagePainter image = new ImagePainter(myImage);
image.setFilters(
new AffineTransformOp(AffineTransform.getRotateInstance(-Math.PI * 2 / 8, 100, 100), null)
);
panel.setBackgroundPainter(image);
you'll probably have to play a bit to get the exact effects you want to achieve. On problems, you might want to try posting to the Swinglabs forum.
I don't know somethimg more about SwingX's JXImagePanel but for plain vanilla Swing there exists excelent workaround (by aephyr or tjacobs or ... I hate this endless-mess from old.forums.sun.com by Sn'Oracle eerrrght)
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.util.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
import javax.swing.filechooser.*;
public class RotatableImageComponent extends JComponent {
private static final long serialVersionUID = 1L;
private Image image;
private double angle = 0;
private MyObservable myObservable;
public RotatableImageComponent() {
myObservable = new MyObservable();
}
public RotatableImageComponent(Image image) {
this();
this.image = image;
}
public Image getImage() {
return image;
}
public void setImage(Image image) {
this.image = image;
}
public double getAngle() {
return angle;
}
public void setAngle(double angle) {
if (angle == this.angle) {
return;
}
this.angle = angle;
double circle = Math.PI * 2;
while (angle < 0) {
angle += circle;
}
while (angle > circle) {
angle -= circle;
}
if (myObservable != null) {
myObservable.setChanged();
myObservable.notifyObservers(this);
}
repaint();
}
/**
* In the rotation events sent to the listener(s), the second argument
* (the value) will be a reference to the RotatableImageComponent. One then
* should call getAngle() to get the new value.
* #param o
*/
public void addRotationListener(Observer o) {
myObservable.addObserver(o);
}
public void removeRotationListener(Observer o) {
myObservable.deleteObserver(o);
}
public void rotateClockwise(double rotation) {
setAngle(getAngle() + rotation);
}
public void rotateCounterClockwise(double rotation) {
//setAngle(getAngle() - rotation);
rotateClockwise(-rotation);
}
#Override
public void paintComponent(Graphics g) {
if (image == null) {
super.paintComponent(g);
return;
}
Graphics2D g2 = (Graphics2D) g;
AffineTransform trans = AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2);
trans.rotate(angle);
trans.translate(-image.getWidth(null) / 2, -image.getHeight(null) / 2);
g2.transform(trans);
g2.drawImage(image, 0, 0, null);
}
#Override
public Dimension getPreferredSize() {
if (image == null) {
return super.getPreferredSize();
}
int wid = image.getWidth(null);
int ht = image.getHeight(null);
int dist = (int) Math.ceil(Math.sqrt(wid * wid + ht * ht));
return new Dimension(dist, dist);
}
public static class TimedRotation {
private RotatableImageComponent comp;
private long totalTime, startTime;
private double toRotate, startRotation;
private int interval;
public Timer myTimer;
private myAction mAction;
public TimedRotation(RotatableImageComponent comp, double toRotate, long totalTime, int interval) {
//super(interval, new myAction());
this.comp = comp;
this.totalTime = totalTime;
this.toRotate = toRotate;
this.startRotation = comp.getAngle();
this.interval = interval;
}
public void start() {
if (mAction == null) {
mAction = new myAction();
}
if (myTimer == null) {
myTimer = new Timer(interval, new myAction());
myTimer.setRepeats(true);
} else {
myTimer.setDelay(interval);
}
myTimer.start();
startTime = System.currentTimeMillis();
}
public void stop() {
myTimer.stop();
}
private class myAction implements ActionListener {
#Override
public void actionPerformed(ActionEvent ae) {
long now = System.currentTimeMillis();
if (totalTime <= (now - startTime)) {
comp.setAngle(startRotation + toRotate);
stop();
return;
}
double percent = (double) (now - startTime) / totalTime;
double rotation = toRotate * percent;
comp.setAngle(startRotation + rotation);
}
}
}
private class MyObservable extends Observable {
#Override
protected void setChanged() {
super.setChanged();
}
}
public static class RotationKeys extends KeyAdapter {
private RotatableImageComponent comp;
private double rotationAmt;
public RotationKeys(RotatableImageComponent comp, double rotationAmt) {
this.comp = comp;
this.rotationAmt = rotationAmt;
}
public RotationKeys(RotatableImageComponent comp) {
this(comp, Math.PI / 90);
}
#Override
public void keyPressed(KeyEvent ke) {
if (ke.getKeyCode() == KeyEvent.VK_LEFT) {
comp.rotateCounterClockwise(rotationAmt);
} else if (ke.getKeyCode() == KeyEvent.VK_RIGHT) {
comp.rotateClockwise(rotationAmt);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {
FileFilter filter = new FileNameExtensionFilter("JPEG file", "jpg", "jpeg");
JFileChooser chooser = new JFileChooser();
chooser.addChoosableFileFilter(filter);
if (chooser.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
File f = chooser.getSelectedFile();
BufferedImage im = ImageIO.read(f);
final RotatableImageComponent c = new RotatableImageComponent(im);
c.addRotationListener(new Observer() {
#Override
public void update(Observable arg0, Object arg1) {
System.out.println("Angle changed: " + ((RotatableImageComponent) arg1).getAngle());
}
});
JPanel controls = new JPanel(new FlowLayout());
final JTextField rotation = new JTextField();
rotation.setText("30");
controls.add(new JLabel("Rotation(degrees)"));
controls.add(rotation);
final JTextField time = new JTextField();
time.setText("1000");
time.setColumns(6);
rotation.setColumns(7);
controls.add(new JLabel("Time(millis)"));
controls.add(time);
JButton go = new JButton("Go");
go.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
TimedRotation tr = new TimedRotation(c,
Double.parseDouble(rotation.getText()) / 180 * Math.PI,
Integer.parseInt(time.getText()), 50);
tr.start();
}
});
controls.add(go);
RotationKeys keys = new RotationKeys(c);
c.addKeyListener(keys);
c.setFocusable(true);
JFrame jf1 = new JFrame();
jf1.getContentPane().add(c);
JFrame jf2 = new JFrame();
jf2.getContentPane().add(controls);
jf1.pack();
jf2.pack();
jf1.setLocation(100, 100);
jf2.setLocation(400, 100);
jf1.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf2.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
jf1.setVisible(true);
jf2.setVisible(true);
}
} catch (Throwable t) {
t.printStackTrace();
}
}
});
}
}