I have a class extending JButton that I am trying to apply a .png image to.
The image is irregular in shape, and is surrounded by transparent pixels. I have overridden the paintComponent() method in the JButton to apply my buffered image to the button. Right now, the image is the only thing being drawn, which is what I want.
However, the button is still detecting events in the rectangle around it. Is there a way to limit detection to only the area containing opaque pixels (or rather to not detect events on the transparent pixels)?
Code for button class is below.
public class DrawButton extends JButton{
private BufferedImage bi;
public DrawButton(BufferedImage bi){
setPreferredSize(new Dimension(bi.getWidth(), bi.getHeight()));
this.bi = bi;
}
#Override
protected void paintComponent(Graphics g){
g.drawImage(bi, 0, 0, null);
g.dispose();
}
}
Well I would suggest using a MouseAdapter, and override mouseClicked(..). In mouseClicked check if pixel is alpha at point of click if it is do nothing, if not do something.
Always call super.paintComponent(..) as first call in overriden paintComponent method, but because, especially with buttons, this will redraw the JButton background call setContentAreaFilled(false) on JButton instance to stop this. You may also want setBorderPainted(false) too.
Here is a small example I made (adapted from here):
if click on smiley:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
public class TransparentButton {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
MyButton button = null;
try {
button = new MyButton(scaleImage(100, 100, ImageIO.read(new URL("http://2.bp.blogspot.com/-eRryNji1gQU/UCIPw0tY5bI/AAAAAAAASt0/qAvERbom5N4/s1600/original_smiley_face.png"))));
} catch (Exception ex) {
ex.printStackTrace();
}
button.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent me) {
super.mouseClicked(me);
MyButton mb = ((MyButton) me.getSource());
if (!isAlpha(mb.getIconImage(), me.getX(), me.getY())) {
JOptionPane.showMessageDialog(frame, "You clicked the smiley");
}
}
private boolean isAlpha(BufferedImage bufImg, int posX, int posY) {
int alpha = (bufImg.getRGB(posX, posY) >> 24) & 0xFF;
return alpha == 0;
}
});
frame.add(button);
frame.pack();
frame.setVisible(true);
}
});
}
public static BufferedImage scaleImage(int w, int h, Image img) throws Exception {
BufferedImage bi;
bi = new BufferedImage(w, h, BufferedImage.TRANSLUCENT);
Graphics2D g2d = (Graphics2D) bi.createGraphics();
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.addRenderingHints(new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY));
g2d.drawImage(img, 0, 0, w, h, null);
return bi;
}
}
class MyButton extends JButton {
BufferedImage icon;
MyButton(BufferedImage bi) {
this.icon = ((BufferedImage) bi);
setContentAreaFilled(false);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(icon.getWidth(), icon.getHeight());
}
public BufferedImage getIconImage() {
return icon;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(icon, 0, 0, this);
}
}
Related
I have a 3d-object composed of multiple polygons that I draw using graphics2D. When I rotate it it, it seems as if it has not enough time to draw the entire object at every frame since at some frames, some of the polygons are simply missing(not drawn). I don't understand how that can be since I in paintComponent first draw all the polygons onto the bufferedImage myImg, and then draw the finished image onto the screen. When I remove clearRect, this issue is resolved but then of course it doesn't remove the last frame's drawing before drawing the next.
Note: I'm an amateur but I've tried really hard understanding and so this is my last resort and would be really glad to get some help. The code (with unnecessary code removed is as follows) :
public class Main {
long temp = System.currentTimeMillis() + frameRate;
public static void main(String[] args) {
myGUI = new GUI(width, height);
while(true) {
if (System.currentTimeMillis() >= temp) {
temp += frameRate;
rotateObject();
myGUI.myCanvas.myLabel.repaint();
}
}
}
}
public class GUI extends JFrame {
public Canvas myCanvas;
public GUI(int w, int h) {
this.setSize(w, h);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
myCanvas = new Canvas(w, h);
this.getContentPane().add(myCanvas);
this.setVisible(true);
this.pack();
}
}
public class Canvas extends JPanel {
public BufferedImage myImg;
public Graphics2D g2d;
public JLabel myLabel;
public Canvas(int w, int h) {
myImg = new BufferedImage(w, h, BufferedImage.TYPE_4BYTE_ABGR);
myLabel = new JLabel(new ImageIcon(myImg));
this.add(myLabel);
g2d = myImg.createGraphics();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g2d.clearRect(0, 0, myImg.getWidth(), myImg.getHeight());
g2d.setColor(Color.RED));
g2d.fillPolygon(pointsX, pointsY, 3);
g.drawImage(myImg, 0, 0, null);
}
}
This is how my object is flickering
You really need to take the time to read through:
Performing Custom Painting
Painting in AWT and Swing
2D Graphics
Concurrency in Swing
How to Use Swing Timers
These aren't "beginner" topics and a reasonable understanding of Swing in general and the language in particular would be very advantageous.
Don't, ever, use getGraphics on a component. This is simply a bad idea (and I have no idea why this method is public).
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage myImg;
private double rotation;
public TestPane() throws IOException {
myImg = ImageIO.read(getClass().getResource("/images/happy.png"));
Timer timer = new Timer(33, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
rotation += Math.toRadians(5);
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
AffineTransform at = AffineTransform.getTranslateInstance((getWidth() - myImg.getWidth()) / 2, (getHeight() - myImg.getHeight()) / 2);
at.rotate(rotation, myImg.getWidth() / 2, myImg.getHeight() / 2);
g2d.transform(at);
g2d.drawImage(myImg, 0, 0, this);
g2d.dispose();
}
}
}
I created a chart extending JPanel and overriding paint() method. When I click on this panel I'd like to create a "snapshot" of the chart and open it in a new JFrame.
I tried by creating a buffered image as shown below, but the problem is that when I resize the window I loose lot of quality.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class SnapshotDemo extends JPanel{
private JComponent src;
private BufferedImage img;
private final JFrame f = new JFrame();
public SnapshotDemo(JComponent src) {
super(new BorderLayout());
this.src = src;
this.img = takeSnapshot(src);
}
#Override
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.drawImage(img, 0, 0, getWidth(), getHeight(), null);
}
public void display(){
f.setContentPane(this);
f.setSize(1000,600);
f.setVisible(true);
}
public static BufferedImage takeSnapshot(JComponent src){
BufferedImage i = new BufferedImage(src.getWidth(), src.getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = i.createGraphics();
src.printAll(g2);
return i;
}
public static void main(String[] args) {
JPanel p = new JPanel(){
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D)g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
int x = 0;
int y = 0;
int stepx = getWidth()/5;
int stepy = getHeight()/5;
for(int i=0; i<10; i++){
g2.drawLine(x, y, x+stepx, y+stepy);
x+=stepx;
y+=stepy;
}
g2.setPaint(Color.red);
g2.drawOval(getWidth()/2-30, getHeight()/2-30, 60, 60);
}
};
p.addMouseListener(new MouseListener() {
#Override
public void mouseReleased(MouseEvent e) {}
#Override
public void mousePressed(MouseEvent e) {}
#Override
public void mouseExited(MouseEvent e) {}
#Override
public void mouseEntered(MouseEvent e) {}
#Override
public void mouseClicked(MouseEvent e) {
SnapshotDemo d = new SnapshotDemo(p);
d.display();
}
});
JFrame f = new JFrame();
f.setContentPane(p);
f.setSize(1000, 600);
f.setVisible(true);
}
}
Any idea about how to handle this issue better? Is there a way to avoid the use of images (BufferedImage etc...)?
Thanks in advance.
but the problem is that when I resize the window I loose lot of quality.
When you resize any image you lose quality. This is called pixilation. If you don't want to loose quality the don't resize the image larger.
I created a chart extending JPanel and overriding paint() method.
Don't override paint(). Custom painting is done by overriding paintComponent(...) and don't forget the super.paintComponent(...) at the start of the method.
But why are you even creating a custom panel. Just use the BufferedImage to create an ImageIcon and then add the ImageIcon to a JLabel. This way the image will always be displayed at its actual size so resizing the window will not affect the quality.
I've made a JFrame, and added a JPanel to it. I am trying to paint the window completely black, and it isn't working! Thank you in advance.
This is my Main class!
package com.lootdatdungeon.net;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
public class Main {
public static void main(String[] args){
initWindow();
}
public static void initWindow(){
Window window = new Window();
Thread windowThread = new Thread(window);
windowThread.start();
}
}
Okay, and here is my Window class!
package com.lootdatdungeon.net;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Window extends JFrame implements Runnable{
private static final long serialVersionUID = 1L;
private static final int HEIGHT = 240;
private static final int WIDTH = 320;
private static final int SCALE = 2;
private BufferedImage image;
private Graphics2D g;
private boolean running = true;
public Window(){
System.out.println("Window object made");
JFrame frame = new JFrame("Loot dat dungeon");
JPanel panel = new JPanel();
frame.setSize(WIDTH*SCALE,HEIGHT*SCALE);
frame.setVisible(true);
frame.setResizable(false);
frame.add(panel);
image = new BufferedImage(WIDTH,HEIGHT,BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) image.getGraphics();
}
public void Draw(Graphics g){
System.out.println("Draw method ran");
g.setColor(Color.BLACK);
g.drawRect(0, 0, WIDTH, HEIGHT);
}
#Override
public void run() {
while(running){
Draw(g);
}
}
}
Simply do in this way using overriiden paintComponent() method of JPanel
JPanel panel = new JPanel() {
#Override
public void paintComponent(Graphics g) {
// don't forget to call it
super.paintComponent(g);
// set color
g.setColor(Color.BLACK);
// fill a rect that cover whole panel with specified color
g.fillRect(0, 0, getWidth(), getHeight());
}
};
Note: There is no need of Thread and BufferedImage in this case.
You should give this a read http://www.gametutorial.net/article/Java-game-framework
That site has a lot of good info if you want to create games. That's there I started.
So Im drawing a BufferedImage 'bird' but I want to rotate it according to the angle that it is falling. I have a bird object which contains the BufferedImage and a render() method which draw it rotated.
public void render(Graphics2D g, ImageObserver io) {
double theta = Math.tan((height - pastHeight) / .875);
System.out.println(theta);
Graphics2D g2 = (Graphics2D) bird.getGraphics();
g2.drawImage(bird, 100, (int) height, null);
g2.rotate(theta);
g2.drawImage(bird, 100, (int) height, io);
}
I call this as such
bird.render(g2, ???);
in my paintcomponent method in my jcomponent.
only problem is I dont know what to use as my ImageObserver... I've tried passing in my JFrame and my JComponent but the image no longer appears when I do that... what would I pass in for the image to appear in my window and/or how else would I achieve this rotation?
Assuming that you are doing this in something that extends JComponent, you should use
bird.render(g2, this);
As JComponent implements ImageObserver
The problem with the image disappearing isn't an issue with the ImageObserver but the point around which the rotation is occurring, which I believe is the top/left corner of the Graphics context.
Try using Graphics2D#rotate(double, int, int) which will allow you to specify the origin points of the rotation (pivot point).
Don't forget to reset your translations, as they will effect everything that is painted after your supply them and may be re-used in subsequent paint cycles.
Updated with simple example
This is a basic example that demonstrates the different uses of rotate.
First, I simply used Graphics#rotate(double)
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
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.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class RotateImage {
public static void main(String[] args) {
new RotateImage();
}
public RotateImage() {
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.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private BufferedImage img;
private double angel = 0d;
public TestPane() {
try {
img = ImageIO.read(...);
} catch (IOException ex) {
ex.printStackTrace();
}
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
angel += 5;
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.rotate(Math.toRadians(angel));
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
g2d.dispose();
}
}
}
}
Then I replaced g2d.rotate(Math.toRadians(angel)); with g2d.rotate(Math.toRadians(angel), getWidth() / 2, getHeight() / 2);, which used the center position of the component (or the Graphics context) as the anchor point around which the rotation would occur...
Now, because you only want to rotate your image, you're going to need to calculate the anchor point around the current position of the image's center position (assuming you want it to rotate around the middle)
I am using JFrame and I have kept a background image on my frame. Now the problem is that the size of image is smaller then the size of the frame so i have to keep the same image once again on the empty part of the window. If user clicks maximize button than I may have to put the image on empty region of the frame at run time. Can anyone tell me how to accomplish this?
It sounds as though you are talking about tiling vs. stretching, though it's not clear which behaviour you want.
This program has examples of both:
import java.awt.BorderLayout;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.JCheckBox;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main(String[] args) throws IOException {
final Image image = ImageIO.read(new URL("http://sstatic.net/so/img/logo.png"));
final JFrame frame = new JFrame();
frame.add(new ImagePanel(image));
frame.setSize(800, 600);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
#SuppressWarnings("serial")
class ImagePanel extends JPanel {
private Image image;
private boolean tile;
ImagePanel(Image image) {
this.image = image;
this.tile = false;
final JCheckBox checkBox = new JCheckBox();
checkBox.setAction(new AbstractAction("Tile") {
public void actionPerformed(ActionEvent e) {
tile = checkBox.isSelected();
repaint();
}
});
add(checkBox, BorderLayout.SOUTH);
};
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (tile) {
int iw = image.getWidth(this);
int ih = image.getHeight(this);
if (iw > 0 && ih > 0) {
for (int x = 0; x < getWidth(); x += iw) {
for (int y = 0; y < getHeight(); y += ih) {
g.drawImage(image, x, y, iw, ih, this);
}
}
}
} else {
g.drawImage(image, 0, 0, getWidth(), getHeight(), this);
}
}
}
You want something like the background image of the windows desktop, when using the background image multiple times instead of resizing it or just display it centered?
You only have to keep the image once and just paint it multiple times in the paintComponent method.
Another way to tile an image is with TexturePaint:
public class TexturePanel extends JPanel {
private TexturePaint paint;
public TexturePanel(BufferedImage bi) {
super();
this.paint = new TexturePaint(bi, new Rectangle(0, 0, bi.getWidth(), bi.getHeight()));
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(paint);
g2.fill(new Rectangle(0, 0, getWidth(), getHeight()));
}
}