Rounded Swing JButton using Java - java

Well, I have an image that I would like to put as a background to a button (or something clicable). The problem is that this image is round, so I need to show this image, without any borders, etc.
The JComponent that holds this button has a custom background, so the button really needs to only show the image.
After searching Google, I couldn't manage to do so. I have tried all the following, but with no luck:
button.setBorderPainted(false);
button.setContentAreaFilled(false);
button.setOpaque(true);
And after I paint the icon at the background, the button paints it, but holds an ugly gray background with borders, etc. I have also tried to use a JLabel and a JButton. And to paint an ImageIcon at it, but if the user resizes or minimizes the window, the icons disappear!
How can I fix this?
I just need to paint and round an image to a JComponent and listen for clicks at it...

Create a new Jbutton:
JButton addBtn = new JButton("+");
addBtn.setBounds(x_pos, y_pos, 30, 25);
addBtn.setBorder(new RoundedBorder(10)); //10 is the radius
addBtn.setForeground(Color.BLUE);
while setting the border for a JButton, call the overridden javax.swing.border.Border class.
addBtn.setBorder(new RoundedBorder(10));
Here is the class
private static class RoundedBorder implements Border {
private int radius;
RoundedBorder(int radius) {
this.radius = radius;
}
public Insets getBorderInsets(Component c) {
return new Insets(this.radius+1, this.radius+1, this.radius+2, this.radius);
}
public boolean isBorderOpaque() {
return true;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
g.drawRoundRect(x, y, width-1, height-1, radius, radius);
}
}

Did you try the following?
button.setOpaque(false);
button.setFocusPainted(false);
button.setBorderPainted(false);
button.setContentAreaFilled(false);
setBorder(BorderFactory.createEmptyBorder(0,0,0,0)); // Especially important
setBorder(null) might work, but there is a bug described at Sun explaining it is by design that the UI sets a border on a component unless the client sets a non-null border which does not implement the UIResource interface.
Rather than the JDK itself setting the border to an EmptyBorder when null is passed in, the clients should set an EmptyBorder themselves (a very easy workaround). That way there is no confusion about who's doing what in the code.

I wrote an OvalButton class that can handle oval, circular and capsule-like shaped JButtons.
In your case, extend the OvalButton class and override getBackgroundImage() method to return the image you want to set as the background.
Then add listeners and text as usually. Only a click on the oval/circular area triggers the action.
Example of your button class:
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageButton extends OvalButton {
private BufferedImage image;
public ImageButton() {
super(); // Default is oval/circle shape.
setBorderThickness(0); // Oval buttons have some border by default.
try {
image = ImageIO.read(new File("your_image.jpg")); // Replace with the path to your image.
}
catch (IOException e) {
e.printStackTrace();
image = null;
}
}
#Override
protected BufferedImage getBackgroundImage() {
return image;
}
}

I would recommend overriding paint(Graphics g) method as so:
class JImageButton extends JComponent implements MouseListener {
private BufferedImage img = null;
public JImageButton(BufferedImage img) {
this.img = img;
setMinimumSize(new Dimension(img.getWidth(), img.getHeight()));
setOpaque(false);
addMouseListener(this);
}
public void paintComponent(Graphics g) {
g.drawImage(img, 0, 0, img.getWidth(), img.getHeight(), null);
}
#Override
public void mouseClicked(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
}

You can try the following. It works fine for me, and I also faced the same issue with the button.
// Jbutton
JButton imageButton = new JButton();
// Buffered Icon
BufferedImage buttonIcon = null;
try {
// Get the image and set it to the imageicon
buttonIcon = ImageIO.read(getClass().getClassLoader().getResource("images/login.png"));
}
catch(Exception ex) {
}
// Set the image icon here
imageButton = new JButton(new ImageIcon(buttonIcon));
imageButton.setBorderPainted(false);
imageButton.setContentAreaFilled(false);
imageButton.setFocusPainted(false);
imageButton.setOpaque(false);

Drag a normal button to your panel
Right click your button and go to properties:
border = no border
border painted = false
contentAreaFilled = false
focusPainted = false
opaque = false
Set an (icon) and a (rolloverIcon) by importing to project.

Opacity should be set to false, so
button.setOpaque(false);
could already be what you want.

I just had the same problem and answer of #Lalchand inspired me. I created custom class for rounded borders, that is actually a modified version of LineBorder class. It draws two rectangles: inner and outer. For my case it is ok if border is colored like a background, but if you need something else, you should tinker with detentions of inner and outer. And as creative liberty I used subpixel rendering for smoother borders.
import javax.swing.border.LineBorder;
import java.awt.*;
import java.awt.geom.Path2D;
import java.awt.geom.RoundRectangle2D;
class RoundedBorder extends LineBorder {
private int radius;
RoundedBorder(Color c, int thickness, int radius) {
super(c, thickness, true);
this.radius = radius;
}
public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
// adapted code of LineBorder class
if ((this.thickness > 0) && (g instanceof Graphics2D)) {
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
Color oldColor = g2d.getColor();
g2d.setColor(this.lineColor);
Shape outer;
Shape inner;
int offs = this.thickness;
int size = offs + offs;
outer = new RoundRectangle2D.Float(x, y, width, height, 0, 0);
inner = new RoundRectangle2D.Float(x + offs, y + offs, width - size, height - size, radius, radius);
Path2D path = new Path2D.Float(Path2D.WIND_EVEN_ODD);
path.append(outer, false);
path.append(inner, false);
g2d.fill(path);
g2d.setColor(oldColor);
}
}
}

You can create an empty border to the button like this:
button.setBorder(BorderFactory.createEmptyBorder(0,0,0,0));

Related

Java (Graphics2D): triangle drawn by created Graphics2D not visible until second repaint

I have the following minimal code to draw a line with an arrow head:
package gui;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import javax.swing.JPanel;
public class StateBtn extends JPanel {
private static final long serialVersionUID = -431114028667352251L;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// enable antialiasing
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// draw the arrow
Line2D.Double line = new Line2D.Double(0, getHeight()/2, 20, getHeight()/2);
drawArrowHead(g2, line);
g2.draw(line);
// If I call repaint() here (like in my answer below), it works
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2-line.y1, line.x2-line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle-Math.PI/2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0,5);
arrowHead.addPoint(-5,-5);
arrowHead.addPoint(5,-5);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose();
}
}
It is created like this:
package gui;
import java.awt.EventQueue;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
public class Main extends JFrame {
private static final long serialVersionUID = 4085389089535850911L;
private JPanel contentPane;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
Main frame = new Main();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public Main() {
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(500, 500);
setLocation(0, 0);
contentPane = new JPanel();
contentPane.setBorder(new EmptyBorder(5, 5, 5, 5));
setContentPane(contentPane);
contentPane.setLayout(null);
StateBtn stateBtn = new StateBtn();
stateBtn.setBounds(200,200,35,35);
contentPane.add(stateBtn);
}
}
The line is drawn properly but the arrow head is invisible until I call repaint(). The problem is that the element is a draggable one, so I would have to call repaint() twice every time the position is changed. This would make the code more complex and the GUI would be laggy.
Why can't the arrow head just be drawn together with the line? Is there really no one who can help me?
You've not posted a true MCVE, so it's impossible to know what you could be doing wrong, but there's no need for the kludge you've used in your answer, where you re-call repaint() within paintComponent. If you still need help with your own code, then please post a valid MCVE, code we can compile and run without modification. For an example of what I mean by MCVE, please read the MCVE link and look at the example MCVE that I've posted in my answer below.
Having said this, understand that generally Swing graphics are passive, meaning that you would have your program change its state based on an event, then call repaint() and this suggests to the Swing repaint manager to call paint. There is no guarantee that painting will occur, since repaint requests that have "stacked", that are backing up due to many being called in a short time, may be ignored.
So in your case, we can use your code and modify it to see how this works. Say I give my JPanel a MouseAdapter -- a class that is both a MouseListener and MouseMotionListener, and in this adapter I simply set two Point instance fields, p0 -- for where the mouse was initially pressed, and p1 -- for where the mouse drags or releases. I can set these fields and then call repaint, and let my painting methods use p0 and p1 to draw my arrow. So the mouse adapater could look like so:
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint();
p1 = null;
settingMouse = true; // drawing a new arrow
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint();
repaint();
}
}
}
And then within my painting code, I'd use your code, modified to make it use my p0 and p1 points:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
The whole shebang would look like so:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class StateBtn extends JPanel {
// constants to size the JPanel
private static final int PREF_W = 800;
private static final int PREF_H = 650;
private static final int AH_SIZE = 5; // size of arrow head -- avoid "magic"
// numbers!
// our start and end Points for the arrow
private Point p0 = null;
private Point p1 = null;
public StateBtn() {
// create and add a label to tell the user what to do
JLabel label = new JLabel("Click Mouse and Drag");
label.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 42));
label.setForeground(new Color(0, 0, 0, 50));
setLayout(new GridBagLayout());
add(label); // add it to the center
// create our MouseAdapater and use it as both MouseListener and
// MouseMotionListener
MyMouse myMouse = new MyMouse();
addMouseListener(myMouse);
addMouseMotionListener(myMouse);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
// only do this if there are points to draw!
if (p0 != null && p1 != null) {
Line2D.Double line = new Line2D.Double(p0.x, p0.y, p1.x, p1.y);
drawArrowHead(g2, line);
g2.draw(line);
}
}
private void drawArrowHead(Graphics2D g2d, Line2D.Double line) {
AffineTransform tx = new AffineTransform();
tx.setToIdentity();
double angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
tx.translate(line.x2, line.y2);
tx.rotate((angle - Math.PI / 2d));
Polygon arrowHead = new Polygon();
arrowHead.addPoint(0, AH_SIZE); // again avoid "magic" numbers
arrowHead.addPoint(-AH_SIZE, -AH_SIZE);
arrowHead.addPoint(AH_SIZE, -AH_SIZE);
Graphics2D g = (Graphics2D) g2d.create();
g.setTransform(tx);
g.fill(arrowHead);
g.dispose(); // we created this, so we can dispose of it
// we should **NOT** dispose of g2d since the JVM gave us that
}
#Override
public Dimension getPreferredSize() {
// size our JPanel
return new Dimension(PREF_W, PREF_H);
}
private class MyMouse extends MouseAdapter {
private boolean settingMouse = false;
#Override
public void mousePressed(MouseEvent e) {
// if we press the wrong mouse button, exit
if (e.getButton() != MouseEvent.BUTTON1) {
return;
}
p0 = e.getPoint(); // set the start point
p1 = null; // clear the end point
settingMouse = true; // tell mouse listener we're creating a new arrow
repaint(); // suggest a repaint
}
#Override
public void mouseReleased(MouseEvent e) {
setP1(e);
settingMouse = false; // no longer drawing the new arrow
}
#Override
public void mouseDragged(MouseEvent e) {
setP1(e);
}
private void setP1(MouseEvent e) {
if (settingMouse) {
p1 = e.getPoint(); // set the end point
repaint(); // and paint!
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
private static void createAndShowGui() {
StateBtn mainPanel = new StateBtn();
JFrame frame = new JFrame("StateBtn");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
This code is what I meant by my example of a MCVE. Actually it's a little large for a decent MCVE, but it will do. Please compile and run the code to see that it works. If this doesn't help you, if you still must use a kludge with your repaint calls, then I urge you to create your own MCVE and post it with your question, and then comment to me so I can see it.
An aside, someone questioned if it was OK to create a new Graphics object as you're doing within drawArrowHead(...) method, and yes this not only OK, it's the preferred thing to do when dealing with AffineTransforms, since this way you don't have to worry about down-stream effects that the transform might have on border and child components that might share the original Graphics object. Again this is OK, as long as you follow the rule of disposing Graphics objects that you yourself create, and not disposing Graphics objects given to you by the JVM.
OK, it seems, there is no other way than to call repaint again. I did like this at the end of the paintComponent-method:
if (repaint == false) {
repaint = true;
} else {
repaint = false;
repaint();
}
Hence, it repaints exactly one time. But is there no cleaner solution?

Hide/Show an image drawn with Graphics

I have multiple images drawn with Graphics. How can I make them appear and disappear using a JCheckBox ?
private void drawImages(int index) {
Graphics g = mNew.getGraphics();
int x = index % this.width;
int y = index / this.width;
g.drawImage(imageLabelPixel.get(idImage-1), x, y, 100, 100, null);
}
You wouldn't use graphics to draw something on the screen you want to remove. Graphics just renders it on the screen along with all the other graphics you have drawn, It doesn't keep track of components
Your options are to add an action event to your checkbox and repaint the screen from scratch not drawing the image, or to just use a Label to draw the image and set it to invisible when the box is checked
I would do it like that:
JCheckBox cb = new JCheckBox();
ImgPanel p = new ImgPanel();
cb.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent evt){
if(cb.isSelected){
p.set(0);
} else {
p.set(-1);
}
}
});
.
public class ImgPanel extends JPanel {
private int i = 0;
private List<BufferedImage> imgs;
public ImgPanel(){
//init imgs
}
public void set(){
i = 0;
repaint();
}
#Override
public void paintComponent (Graphics g){
super.paintComponent(g);
if(i >= 0){
Image img = imgs.get(i-1);
Image img1 = img.getScaledInstance(100, 100, null);
}
g.drawImage(img1, 0, 0, null);
}
}
You can't simply draw on a graphic and then hand it to a compoennt or so (I don't really understand what your given code should have done). Instead you have to overwrite the paintComponent method of a Component and put your custom drawing code in there.

Java robot class - getting Pixel color after they're painted

I need to create a colorPicker tool for my little ms paint app.
I originally asked how to switch from my Graphics 2D implementation to a Graphics2D-->BufferedImage one, (and then it would be easy to get the pixels) but I have instead been suggested to get the pixel colors thought the robot class.
First of all, here's my MCEVE: NB. It cannot be a single class, it won't save.
import java.awt.*;
import javax.swing.*;
public class Runner {
public static void main(String[] args){
JFrame Maiframe = new JFrame("Paint");
Canvas DrawingBoard = new Canvas();
Maiframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Maiframe.setSize(700, 500);
Maiframe.add(DrawingBoard);
Maiframe.setVisible(true);
}
}
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
public class Canvas extends JPanel {
public int xp; //present x
public int yp; //present y
public int xo; //old x
public int yo; //old y (Drawing from starting to new point as mouse drags)
public Canvas(){
super();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println("RGB : Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
addMouseMotionListener(new MouseMotionAdapter() { //get coords as mouse drags
#Override
public void mouseDragged(MouseEvent e) {
xp = e.getX();
yp = e.getY();
if(SwingUtilities.isLeftMouseButton(e))
repaint(); //call paintcomponent
}
public void mouseMoved(MouseEvent e1){ //keep trak of coords when mouse is not dragging
xo = e1.getX();
yo = e1.getY();
}
});
}
public void draw(int x, int y, Graphics g){ //draw the line
if(xo != 0)
g.drawLine(xo, yo, x, y);
xo = x; //old x is now present x and so on
yo = y;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(xp, yp, g);
}
}
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
And it works just fine.
I needed to implement something to get the color from any pixel, when the mouse clicks.
I did this: (adding this method to Canvas, and calling it from the mouse clicker listener)
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
Example of call:
//replace old mouseListener with this
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println(" da tela Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
Unfortunately, from this implementation I get undefined behaviour. The color read is always 255, 255, 255. Exept if I color the hole panel, then it gets it right 9 times out of 10, but in some areas it's still missing it.
I have also tried wrapping the whole thing into a bufferedImage with robot#screenCap but that doesn't even remotely work.
What am I doing wrong here?
Thank you very much.
EDIT1:
There are doubts as to how a line can remain on screen after a second one has been drawn. I'll provide a screenshot:
NBB. This works because an instance of Canvas is create inside of a JFrame into Runnable, so the changes are saved, avoiding the need for shapes and arrayLists.
I'll also add a full version of the code which prints wrong RGB results, remember that this does not save the lines as it stands. Please refer to the two separate classes above for testing.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionAdapter;
public class Canvas extends JPanel{
public int xp; //present x
public int yp; //present y
public int xo; //old x
public int yo; //old y (Drawing from starting to new point as mouse drags)
public Robot robot;
public static void main(String[] args){
JFrame Maiframe = new JFrame("Paint");
Canvas DrawingBoard = new Canvas();
Maiframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Maiframe.setSize(700, 500);
Maiframe.add(DrawingBoard);
Maiframe.setVisible(true);
}
public Canvas(){
super();
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
xo = e.getX();
yo = e.getY();
Color col = RGBFinder();
System.out.println("RGB --> Red =" + col.getRed() + "Green" + col.getGreen() + "Blue" + col.getRed());
}
});
addMouseMotionListener(new MouseMotionAdapter() { //get coords as mouse drags
#Override
public void mouseDragged(MouseEvent e) {
xp = e.getX();
yp = e.getY();
if(SwingUtilities.isLeftMouseButton(e))
repaint(); //call paintcomponent
}
public void mouseMoved(MouseEvent e1){ //keep trak of coords when mouse is not dragging
xo = e1.getX();
yo = e1.getY();
}
});
}
public void draw(int x, int y, Graphics g){ //draw the line
if(xo != 0)
g.drawLine(xo, yo, x, y);
xo = x; //old x is now present x and so on
yo = y;
}
public void paintComponent(Graphics g){
super.paintComponent(g);
draw(xp, yp, g);
}
public Color RGBFinder(){
try{
robot = new Robot();
}
catch(AWTException e){
System.out.println("Could not create color picker robot");
}
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
//also tried robot.getPixelColor(p.getX(), p.getY());
//also tried to pass coordinates from listener to RGBFinder, and use those, no luck. (event.getX() ...)
return pixelColor;
}
}
my little ms paint app
Well its not much of a paint app. All it does is draw a single line. Whenever you attempt to draw the second line the first will be removed.
So the first step you need to do is decide how you want the painting to work. There are two common approaches:
Store Objects you want to paint in an ArrayList and then the paintComponent(...) method will paint each Object in the List.
Paint directly to a BufferedImage and then the paintComponent(...) method can just paint the BufferedImage.
Check out Custom Painting Approaches for working examples of both of these approaches and give the pros/cons of using each approach.
I have instead been suggested to get the pixel colors thought the robot class
It depends which painting approach you want to use.
If you use the Draw on Component approach then you would use the MouseInfo and Robot to get the pixel color:
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
System.out.println( robot.getPixelColor(p.x, p.y) );
If you use the Draw on Image approach then you would get the pixel color from the BufferedImage:
int rgb = bufferedImage.getRGB(mouseEvent.getX(), mouseEvent.getY());
Color color = new Color( rgb );
System.out.println( color );
Final Update
You still have not posted a SSCCE. The code you posted does NOT draw a line. Even if it did draw a line, how do you expect us to click (with accuracy) a single pixel line.
The point of a SSCCE is to demonstrate the concept you are asking about. You are asking how to get the Color of a pixel on your panel. How the drawing gets on the panel is irrelevant to the question so the painting code should be as simple as possible.
Following is a proper SSCCE. Note:
The createAndShowGUI() and main() methods will be the same for all SSCCE. The difference is the `DrawingPanel code/class will change to demonstrate your problem.
The custom painting is hardcoded. There is no need for mouseMoved/mouseDragged logic (unless that is the problem) you are trying to solve. All you care about is having different colors to click on.
Just use a simple System.out.println(...) to display the value of the Color object.
All I did was copy the relevant pieces of code from you class and remove the irrelevant code to keep the code simple and straight forward. Anybody can do that.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DrawingCanvas extends JPanel
{
private Robot robot;
public DrawingCanvas()
{
addMouseListener(new MouseAdapter()
{
#Override
public void mouseClicked(MouseEvent e)
{
try
{
robot = new Robot();
}
catch(Exception re) { System.out.println(re); }
PointerInfo pi = MouseInfo.getPointerInfo();
Point p = pi.getLocation();
Color pixelColor = robot.getPixelColor(p.x, p.y);
System.out.println(pixelColor);
}
});
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor( Color.RED );
g.fillRect(0, 0, 40, 40);
g.setColor( Color.GREEN );
g.fillRect(40, 40, 40, 40);
g.setColor( Color.BLUE );
g.fillRect(80, 80, 40, 40);
}
private static void createAndShowGUI()
{
JPanel panel = new DrawingCanvas();
JFrame frame = new JFrame("DrawingCanvas");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setSize(200, 200);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args)
{
EventQueue.invokeLater( () -> createAndShowGUI() ); // Java 8 only
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}
Just copy/paste/compile/execute to see that the code works.
I'll let you figure out why your code doesn't appear to work.
NBB. This works because an instance of Canvas is create inside of a JFrame into Runnable, so the changes are saved, avoiding the need for shapes and arrayLists.
That statement is completely wrong. Painting done with the Graphics Object in the paintComponent() method is only temporary until the next time the paintComponent() method is invoked.
For example add the following code after the frame.setVisible() statement:
SwingUtilities.invokeLater(new Runnable()
{
public void run()
{
Graphics g = panel.getGraphics();
g.setColor( Color.YELLOW );
g.fillRect(120, 120, 40, 40);
}
});
The yellow square will disappear when the frame is resized. For permanent painting you need to use one of the two approaches I suggested in my original answer.
Edit 2:
This is the output I get when I click on the red, green, blue, background respectively:
C:\Java>java DrawingCanvas
java.awt.Color[r=255,g=0,b=0]
java.awt.Color[r=0,g=255,b=0]
java.awt.Color[r=0,g=0,b=255]
java.awt.Color[r=238,g=238,b=238]

Java JPanel getGraphics()

Since Java only supports single inheritance, I desire to paint directly on an instance of a JPanel that is a member of the class Panel. I grab an instance of Graphics from the member and then paint whatever I desire onto it.
How can I not inherit from JComponent or JPanel and still utilize getGraphics() for painting on this without overriding public void paintComponent(Graphics g)?
private class Panel {
private JPanel panel;
private Graphics g;
public Panel() {
panel = new JPanel();
}
public void draw() {
g = panel.getGraphics();
g.setColor(Color.CYAN);
g.draw(Some Component);
panel.repaint();
}
}
The panel is added to a JFrame that is made visible prior to calling panel.draw(). This approach is not working for me and, although I already know how to paint custom components by inheriting from JPanel and overriding public void paintComponent(Graphics g), I did not want to inherit from JPanel.
Here are some very simple examples which show how to paint outside paintComponent.
The drawing actually happens on a java.awt.image.BufferedImage, and we can do that anywhere, as long as we're on the Event Dispatch Thread. (For discussion of multithreading with Swing, see here and here.)
Then, I'm overriding paintComponent, but only to paint the image on to the panel. (I also paint a little swatch in the corner.)
This way the drawing is actually permanent, and Swing is able to repaint the panel if it needs to without causing a problem for us. We could also do something like save the image to a file easily, if we wanted to.
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JPanel panel = new JPanel() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Creating a copy of the Graphics
// so any reconfiguration we do on
// it doesn't interfere with what
// Swing is doing.
Graphics2D g2 = (Graphics2D) g.create();
// Drawing the image.
int w = img.getWidth();
int h = img.getHeight();
g2.drawImage(img, 0, 0, w, h, null);
// Drawing a swatch.
Color color = colors[currentColor];
g2.setColor(color);
g2.fillRect(0, 0, 16, 16);
g2.setColor(Color.black);
g2.drawRect(-1, -1, 17, 17);
// At the end, we dispose the
// Graphics copy we've created
g2.dispose();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(img.getWidth(), img.getHeight());
}
};
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
panel.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// panel to make sure the
// changes are visible
// immediately.
panel.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
panel.setBackground(Color.white);
panel.addMouseListener(drawer);
panel.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
panel.setCursor(cursor);
frame.setContentPane(panel);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
It's also possible to set up a JLabel with an ImageIcon, although personally I don't like this method. I don't think JLabel and ImageIcon are required by their specifications to see changes we make to the image after we've passed it to the constructors.
This way also doesn't let us do stuff like painting the swatch. (For a slightly more complicated paint program, on the level of e.g. MSPaint, we'd want to have a way to select an area and draw a bounding box around it. That's another place we'd want to be able to paint directly on the panel, in addition to drawing to the image.)
import javax.swing.*;
import java.awt.*;
import java.awt.image.*;
import java.awt.event.*;
/**
* Holding left-click draws, and
* right-clicking cycles the color.
*/
class PaintAnyTime {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new PaintAnyTime();
}
});
}
Color[] colors = {Color.red, Color.blue, Color.black};
int currentColor = 0;
BufferedImage img = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB);
Graphics2D imgG2 = img.createGraphics();
JFrame frame = new JFrame("Paint Any Time");
JLabel label = new JLabel(new ImageIcon(img));
MouseAdapter drawer = new MouseAdapter() {
boolean rButtonDown;
Point prev;
#Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = e.getPoint();
}
if (SwingUtilities.isRightMouseButton(e) && !rButtonDown) {
// (This just behaves a little better
// than using the mouseClicked event.)
rButtonDown = true;
currentColor = (currentColor + 1) % colors.length;
label.repaint();
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (prev != null) {
Point next = e.getPoint();
Color color = colors[currentColor];
// We can safely paint to the
// image any time we want to.
imgG2.setColor(color);
imgG2.drawLine(prev.x, prev.y, next.x, next.y);
// We just need to repaint the
// label to make sure the
// changes are visible
// immediately.
label.repaint();
prev = next;
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
prev = null;
}
if (SwingUtilities.isRightMouseButton(e)) {
rButtonDown = false;
}
}
};
PaintAnyTime() {
// RenderingHints let you specify
// options such as antialiasing.
imgG2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
imgG2.setStroke(new BasicStroke(3));
//
label.setPreferredSize(new Dimension(img.getWidth(), img.getHeight()));
label.setBackground(Color.white);
label.setOpaque(true);
label.addMouseListener(drawer);
label.addMouseMotionListener(drawer);
Cursor cursor =
Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR);
label.setCursor(cursor);
frame.add(label, BorderLayout.CENTER);
frame.pack();
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class SomeComponent extends JComponent {
private Graphics2D g2d;
public void paintComponent(Graphics g) {
g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLACK);
g2d.scale(scale, scale);
g2d.drawOval(0, 0, importance, importance);
}
public Graphics2D getG2d() {
return g2d;
}
public void setG2d(Graphics2D g2d) {
this.g2d = g2d;
}
}
then you can do the following
get the SomeComponent instance in the panel and modify it
Graphics2D x= v.getPanel().get(i).getG2d;
x.setColor(Color.BLUE);
v.getPanel().get(i).setG2d(x);
v.getPanel().repaint();
v.getPanel().revalidate();
V is a class that extends JFrame and contains the panel in it AND
i is instance of SomeComponent

java 2d puzzle application, cannot draw more then one piece

i'm trying to write a jigsaw puzzle application where an image is cut in pieces, scrambled, and the user have to rearrange them with drag&drop to reassemble the original image. (something like this: http://www.jigzone.com/puzzles/74055D549FF0?z=5).
i have to write this in java with Graphics2d.
so, at first i'm trying to make some kind of component which can show a part of the image (a rectangle for now), and can be dragged with mouse.
the code below works well when there is only one one such component. the problem is, when i add the second component, the first one is no longer visible.
i'm really stuck here. i have a feeling i'm missing something really basic. or maybe i'm on a wrong way. any help will be greatly appreciated.
edit: i changed a bit the code according to suggestions, however, still not working as expected.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
public class GraphicDragAndDrop extends JPanel {
Rectangle rect;
Image img;
public GraphicDragAndDrop(String imgFile, int x0, int y0){
rect = new Rectangle(x0, y0, 150, 75);
img = new ImageIcon(imgFile).getImage();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setClip(rect);
int x = rect.x;
int y = rect.y;
g2d.drawImage(img, x, y, this);
}
public void setRect(int x, int y) {
rect.setLocation(x, y);
repaint();
}
public static void main(String[] args) {
// first piece
GraphicDragAndDrop piece1 = new GraphicDragAndDrop("a.png", 0, 0);
piece1.setRect(0, 0);
new GraphicDragController(piece1);
// second piece --> only this will be visible
GraphicDragAndDrop piece2 = new GraphicDragAndDrop("a.png", 200, 200);
//GraphicDragAndDrop piece2 = new GraphicDragAndDrop("b.png", 200, 200); // does'n work either
piece2.setRect(150, 150);
new GraphicDragController(piece2);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(piece1);
f.add(piece2);
f.setSize(500,500);
f.setLocation(300,100);
f.setVisible(true);
}
}
class GraphicDragController extends MouseInputAdapter {
GraphicDragAndDrop component;
Point offset = new Point();
boolean dragging = false;
public GraphicDragController(GraphicDragAndDrop gdad) {
component = gdad;
component.addMouseListener(this);
component.addMouseMotionListener(this);
}
public void mousePressed(MouseEvent e) {
Point p = e.getPoint();
Rectangle r = component.rect;
if(r.contains(p)) {
offset.x = p.x - r.x;
offset.y = p.y - r.y;
dragging = true;
}
}
public void mouseReleased(MouseEvent e) {
dragging = false;
}
public void mouseDragged(MouseEvent e) {
if(dragging) {
int x = e.getX() - offset.x;
int y = e.getY() - offset.y;
component.setRect(x, y);
}
}
}
Your code above is written to draw only one image:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setClip(rect);
int x = rect.x;
int y = rect.y;
// here
g2d.drawImage(new ImageIcon("a.png").getImage(), x, y, this);
}
If you need to draw more than one image, then consider creating a collection of images and iterating through the collection in paintComponent using a for loop:
also, never read in the image from within paintComponent since this method should be lean, mean and fast, and should concern itself with painting only. Also, there's no need to read the image in each time your program has to draw it as that's very inefficient and will slow the program unnecessarily. Instead read the image in once in the constructor or a similar init method.
For example,
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (Image img: myImageCollection) {
g2d.drawImage(img, 0, 0, this);
}
}
Edit
You state:
also, my plan was to have more objects of GraphicDragAndDrop class, each of them showing a different piece of the original image. is my approach wrong?
You could use components, but I have a feeling that it would be easy to drag images. I think it would be easier to rotate them for instance if you want your program to have this functionality. If not, though then sure use a component, but if you go this route, I would recommend using a JLabel and simply setting its ImageIcon rather than dragging JPanels.

Categories