How to put transparent JPanel over an opaque JPanel? - java

I have added webcam to my software using com.github.sarxos.webcam. It has a JPanel named WebcamPanel and has predefined webcam sizes while I need my custom size of pictures. I managed to crop the images taken from webcam at 640 x 480. I want to put a red rectangle over the WebcamPanel to show that this part of the image will be saved.
public class CardPanel {
Dimension panelDim = new Dimension(640, 480);
public Cardpanel(){
//....Button Defined earlier
btnTakePhoto.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
webcameFrame();
}
});
}
private void webcamFrame(){
imageFrame = new JFrame("Photo Capture");
// Did some calculations to put window at center
imageFrame.setBounds(screenSize.width / 2 - frameWidth / 2, screenSize.height / 2 - frameHeight / 2, frameWidth,
frameHeight);
imageFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
imageFrame.setContentPane(contentPane);
JPanel webcamWindow = new JPanel();
RedHighlighter redHighlighter = new RedHighlighter();
Webcam webcam = Webcam.getDefault();
webcam.setViewSize(WebcamResolution.VGA.getSize());
webcamPanel = new WebcamPanel(webcam);
webcamPanel.setFillArea(true);
webcamPanel.setMirrored(false);
webcamPanel.setPreferredSize(panelDim);
webcamWindow.add(webcamPanel);
webcamWindow.add(redHighlighter);
hBox.add(webcamWindow);
}
// Sub Class just for drawing the rectangle
public class RedHighlighter extends JPanel{
public RedHighlighter() {
// If you delete the following line, nothing will appear
setPreferredSize(new Dimension(400, 400));
}
#Override
public void paint(Graphics g) {
g.setColor(Color.RED);
g.drawRect(100, 100, 200, 200);
}
}
}
I used JLayeredPanes but no matter what you do it will cover whole size and will show only one item at a time.
Overriding paint method helped me draw the rectangle but it's on side and not on top.
As you can see the rectangle has pushed WebcamPanel towards left. I want webcamPanel to remain in it's position while the rectangle on top of it at center. Please suggest an efficient approach to this problem. Thanks!

The one JPanel is being pushed over due to the layout managers that you are using. If you want one JPanel to overly another, you'll want to consider using a JLayeredPane, with the video images in the lower level, perhaps the JLayeredPane.DEFAULT layer, and the drawing JPanel above it.
Other options and issues:
You could potentially draw in the same JPanel that the image is being displayed in by displaying the image in a paintComponent method as well as the drawing (in lines of code after the image is displayed.
Look into use of a JLayer as a way of adding a drawing "decoration" over your image.
Always override paintComponent, not paint
Always call the super's painting method within your override.

It worked!
public class MyWebcamPanel extends WebcamPanel {
/**
*
*/
private static final long serialVersionUID = 2808353446021354508L;
public MyWebcamPanel(Webcam webcam) {
super(webcam);
}
#Override
protected void paintComponent(Graphics g) {
int x = 180;
int y = 87;
super.paintComponent(g);
g.setColor(Color.RED);
g.drawRect(x, y, 640-2*x, 480-2*y);
}
}

Related

Overlay game pieces on game tile Java

I have a board game (think Monopoly) where multiple game pieces can be located on a single tile. I want to be able to arbitrarily place game pieces on the any given tile. I want the tile to have a background (image or just flat color) and be able to place up to 4 game pieces on the tile in a grid. I am currently using this code but the circles do not display.
tank.png is a 135 x 135 pixel background.
GraphicsTile:
public class GraphicsTile extends JPanel {
public static final Dimension SIZE = new Dimension(135, 135);
public static final GridLayout MGR = new GridLayout(4, 4);
public GraphicsTile() {
super();
setLayout(MGR);
initGraphics();
setSize(SIZE);
add(new CirclePanel());
}
private void initGraphics() {
JLabel panel = null;
try {
Image image = ImageIO.read(new File("tank.png"));
panel = new JLabel(new ImageIcon(image));
panel.setSize(SIZE);
} catch (IOException e) {
e.printStackTrace();
}
add(panel);
}
}
CirclePanel:
public class CirclePanel extends JPanel {
public CirclePanel() {
setSize(33, 33);
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.RED);
Ellipse2D.Float circle = new Ellipse2D.Float(50, 50, 0, 0);
g2d.draw(circle);
g2d.fill(circle);
}
}
public class GraphicsTile {
I don't know how your code compiles since your GraphicsTile doesn't extend any Swing component yet you use methods like setLayout(...) and setSize(...) which implies you are trying to use it like a JPanel.
You should not be using setSize(...). A Swing component should have a preferred size. Then the layout manager will set the size and location of the component based on the rules of the layout manager. I'm guessing you have a problem because the preferred size is (0, 0).
I also have no idea how you add the GraphicsTile to the parent component. Again it looks like you are using setSize() when you should let the layout manager position the tiles on the game board.
Also, if you want to have a background image with circles on top then you need a hierarchical structure. That is you need something like:
panel
background image
circle component.
So my suggestions are:
CirclePanel needs to implement the getPreferredSize(...) method to return the size of your custom painting.
Your GraphicsTile class needs to extend JPanel. You would then override the paintComponent(...) method to draw your background image. Now you can add the CirclePanel instances to the this panel which will use the GridLayout.

How can I display two JPanels with images on a JFrame, and both of the img to be visible?

I am trying to set as background an aquarium (which is a class that extends JPanel and contain the aquarium img), and on top a fish (which is also a class that extends a JPanel and contain the fish img).
The problem is that it shows only one image instead of fish in top of the aquarium (either aquarium, or fish depending on which one is added first to the JFrame).
Main
public class Core {
JFrame window;
JLabel label;
ImageIcon img;
Aquarium aquarium = new Aquarium();
JavaFish javaFish = new JavaFish();
public void start() {
window = new JFrame();
window.setPreferredSize(new Dimension(600, 400));
window.setVisible(true);
window.setTitle("Java Game");
aquarium.add(javaFish);
window.add(aquarium);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
Core c = new Core();
c.start();
}
}
Aquarium
public class Aquarium extends JPanel {
private BufferedImage img;
//Initiate aquarium width
public int width;
//Initiate aquarium height
public int height;
#Override
protected void paintComponent(Graphics g) {
width = getSize().width;
height = getSize().height;
try {
img = ImageIO.read(new File("img/AquariumBackground.png"));
} catch (IOException e) {
e.printStackTrace();
System.out.println("Image not fount!");
}
g.drawImage(img, 0, 0, width, height, this);
}
}
Fish
public class JavaFish extends JPanel {
BufferedImage img;
int xPos = 50;
int yPos = 50;
public JavaFish() {
this.setOpaque(false);
}
#Override
protected void paintComponent(Graphics g) {
BufferedImage JavaFish = LoadImage("img/JavaFish.png");
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(JavaFish, xPos, yPos, 100, 100, null);
repaint();
}
BufferedImage LoadImage(String FileName) {
img = null;
try {
img = ImageIO.read(new File (FileName));
} catch (IOException e) {
e.printStackTrace();
}
return img;
}
}
The problem is that it shows only one image instead of fish in top of the aquarium (either aquarium, or fish depending on which one is added first to the JFrame).
By default a JPanel uses a FlowLayout which respects the preferred size of any component added to it.
By default a JFrame uses a BorderLayout and if you don't specify a constraint the component gets added to the CENTER of the BorderLayout, which means the component is automatically resized to fill the space of the frame.
So the component you add to the frame will be sized to fill the frame. The component you add to the panel will have a size of (0, 0) so there is nothing to paint.
So some custom painting tips:
Override the getPreferredSize() method of the panel to return the size of the image so the layout manager can do its job
Invoke super.paintComponent(..) as the first statement to make sure the background gets cleared.
Don't read the image in the paintComponent() method. This method can be called whenever Swing determines the component needs to be repainted, so it not efficient to keep reading the image. Instead the image should be read in the constructor of the class.
Don't invoke repaint() in a painting method. This will cause an infinite painting loop.
Also, components should be added to the frame BEFORE you make the frame visible.
Having said all of the above, Alerra's suggestion in the comment to paint both images in the same panel is a good idea. It simplifies the painting and you can even paint multiple fish easily by keeping an ArrayList of imgages that you want to paint. Then you would just paint the background and then iterates through the ArrayList to paint the individual fish.
Check out Custom Painting Approaches for a working example. The example only draws Rectangle, but the concept is the same.

Java - Top inset not returning the proper value when painting

having a problem with top insets not giving the proper value when painting ... i am looking for y to be just under the title bar but it is not working correctly and putting it 25 pixels from the top where as the title bar only accounts for about 14 pixels .
Game Frame :
import java.awt.Dimension;
import javax.swing.JFrame;
public class gameFrame extends JFrame {
static int width = 400;
static int height = 400;
static int topinset;
static int FPS = 30;
public gameFrame(){
gamePanel gamePanel = new gamePanel();
gamePanel.setPreferredSize(new Dimension(width,height));
Thread thread = new Thread(gamePanel);
this.add(gamePanel);
this.setResizable(false);
this.setSize(width, height);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
this.setVisible(true);
topinset = this.getInsets().top;
thread.start();
}
public static void main(String[] args){
gameFrame gameFrame = new gameFrame();
}
}
Game Panel :
import java.awt.Graphics;
import javax.swing.JPanel;
public class gamePanel extends JPanel implements Runnable {
public gamePanel(){
this.setBounds(0,0,gameFrame.width,gameFrame.height);
this.setVisible(true);
}
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawString("Width: "+ gameFrame.width + " Height: " + gameFrame.height, 0, gameFrame.topinset);
}
#Override
public void run() {
while(true){
repaint();
try {Thread.sleep(1000*gameFrame.FPS);}
catch (InterruptedException e) {}
}
}
}
You are doing your custom painting on the panel so there is no need to worry about the offsets of the frame. Just use the getWidth() and getHeight() methods to get the current size of the panel.
If you want your panel to be (400, 400), then override the getPreferredSize() method of your panel class to return that dimension. Then use frame.pack() and the frame will size itself properly to include the panel size and the frame decorations.
There is no need for static variables. Those variables should just be instance variables or constants.
There is no need for the setBounds() since the layout manager will determine the size/location of the panel in the frame.
There is no need to make the panel visible since is it visible by default.
Follow Java naming conventions. Class names should start with an upper case character.
Why are you doing custom painting? Why not just add a JLabel to the NORTH of the frame and display your text in the label?
Start by taking a look through Working with Text APIs
Text rendering is typically done from the base line, meaning text will painted above the y position you specify
As has already being stated, a components Graphics context is translated before it's painted so that the 0x0 position will be the top/left corner of the component
In the following example, the first String will not appear on the screen, as it's base line is along the top edge of the component, whereas the second String has being adjusted so that it's ascent line is along the top top
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics g2d = (Graphics2D) g.create();
FontMetrics fm = g2d.getFontMetrics();
g2d.drawString("Hello", 0, 0);
g2d.drawString("Hello", fm.stringWidth("Hello"), fm.getAscent());
g2d.dispose();
}

GUI programming, FlowLayout blocking other things on JFrame(?)

The idea of the program is that I have some buttons and an icon SOMEWHERE on the frame. I want the buttons to change the color. I'm only worried about making all the elements show up right now. If I comment out lines 11-13, I see "hello," printed out, with a red circle on top of it. Otherwise, I just have the button "red" without "hello" or my red circle. So here's my code:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.*;
public class ButtonTester
{
public static void main (String[] args)
{
JFrame frame = new ButtonFrame();
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
JButton redButton = new JButton("Red");
frame.add(redButton);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
class ButtonFrame extends JFrame
{
public static final int DEFAULT_WIDTH = 300;
public static final int DEFAULT_HEIGHT = 200;
public ButtonFrame()
{
setTitle("Hello");
setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
ButtonPanel panel = new ButtonPanel();
add(panel);
}
}
class ButtonPanel extends JPanel
{
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
Icon ico = new ColorIcon(32);
ico.paintIcon(null, g, 75, 75);
}
}
I'm 90% sure the problem is lines 11-13, but I'm not sure what to change to make everything visible.
Your problem is that your ButtonPanel's size is 0. Have it override getPreferredSize() and you will see what I mean:
class ButtonPanel extends JPanel {
private static final int PREF_W = 150;
private static final int PREF_H = PREF_W;
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawString("Hello !", 100, 100);
// !! Icon ico = new ColorIcon(32);
// Icon ico = new ImageIcon();
// ico.paintIcon(null, g, 75, 75);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
}
Also as an unrelated aside, why are you creating an Icon inside of the paintComponent method? This doesn't make sense to me and would only serve to needlessly slow your graphics down.
Edit
You state:
Ok, I see the difference after overriding getPreferredSize() But what would be the "better" or "correct" way to create the icon? I'm just trying to follow the directions for an exercise out of a Java textbook: Exercise 4.14. Write a program that shows a frame with three buttons labeled "Red", "Green", and "Blue", and a label containing an icon showing a circle that is initially red. As the user clicks the buttons, the fill color of the circle should change. When you change the color, you need to invoke the repaint method on the label. The call to repaint ensures that the paintIcon method is called so that the icon can be repainted with the new color.
You need to think on this a different way. Myself I'd create three ImageIcons one for a blue circle, one for red, and one for green. I'd then display the ImageIcon in a JLabel on my JFrame. I'd change the color by simply swapping the label's icons via its setIcon(...) method. I wouldn't worry about futzing with paintComponent(...) but rather would try to solve this in as simple a fashion as possible.

getWidth() and getHeight() are 0 after calling setPreferredSize()

I've been working on my project for a few hours now, only to be constantly frustrated at this.
I have a parent JFrame that adds a JPanel to it, and it is going to be used for the rendering and display of my simulation I'm developing. There are no swing objects that are going to be added to the JPanel, as I will only be using it for rendering shapes using a graphics object.
My code is as such below:
public class SimulationPanel extends JPanel {
private BufferedImage junction;
private Graphics2D graphics;
public SimulationPanel() {
super();
initPanel();
}
private void initPanel() {
this.setPreferredSize(new Dimension(600, 600)); //TODO: bug with not sizing the junction correctly.
junction = new BufferedImage(this.getWidth(), this.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
setBackground(Color.white);
System.out.println(getWidth());
}
The code specifically breaks on the second line of the initPanel() method where I try to create a new BufferedImage.
The output from the exception states "Exception in thread "AWT-EventQueue-0" java.lang.IllegalArgumentException: Width (0) and height (0) must be > 0"
I'm really unsure on why this is. I've tried to use past answers from Stack Overflow but they were unsuccessful in helping.
This is my first post, so I hope it isn't too bad.
Thanks.
When you set the preferred size, you tell the various Java layout managers how you would like your panel to be laid out once it's added to a container. But until it actually is added to the container, it won't have a width or height, and even after it is, it might not have the width and height that you asked for.
One option is to just use 600 directly for the width and height of your new buffered image, and when you add the panel to the JFrame make sure you call pack() on the JFrame to allow the window to size to the preferred size of your panel.
Create your a BufferedImage cache inside your component's paintComponent method. There, you will know the actual size of the component and take that into account for the rendering. The image acts as a cache of your component's content, but you fail to take into account that its size is part of the cached information.
#Override protected void paintComponent(Graphics g) {
// create cache image if necessary
if (null == image ||
image.getWidth() != getWidth() ||
image.getHeight() != getHeight() ||) {
image = new BufferedImage(getWidth(), getHeight());
imageIsInvalid = true;
}
// render to cache if needed
if (imageIsInvalid()) {
renderToImage();
}
// redraw component from cache
// TODO take the clip into account
g.drawImage(image, 0, 0, null);
}
The reason this won't work is that pack() is not called (to set all the width and height values) until after the panel has been initiated which is why height and width are not set yet. And BufferedImage will throw an exception if the width or height are non-positive integers.
So why don't you just set the values yourself? Here is how to do it in your example:
private void initPanel() {
final int width = 600;
final int height = 600;
this.setPreferredSize(new Dimension(width, height));
junction = new BufferedImage(width, height, BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
setBackground(Color.white);
}
Alternatively: If you have a requirement where the image has to be resized with the component then you need to. I'm pretty sure when pack() is called that it will fire the ComponentListener.componentResized() event so this should work when you initiate the component even if you don't resize the component. So instead do this in your code:
private void initPanel() {
this.setPreferredSize(new Dimension(600, 600));
this.addComponentListener(new ComponentListener() {
public void componentResized(ComponentEvent e) {
Component c = (Component) e.getSource();
Dimension d = c.getSize();
resizeImage(d);
}
});
this.setBackground(Color.white);
}
public void resizeImage(Dimension d) {
junction = new BufferedImage(d.getWidth(), d.getHeight(), BufferedImage.TYPE_3BYTE_BGR);
graphics = junction.createGraphics();
}

Categories

Resources