Drawing in java with JPanel - java

I'm making a program where you have a server and a client, and the idea is that you draw on the client jpanel, and the coordinates will then be sent to the server, which will sort of mimic the drawing. I've done that, but the problem is now, that my drawing mechanism is pretty bad. Right now I'm just using an oval that gets drawn over and over again on the coordinate of the mouse, which sometimes leaves spaces between the ovals if you move the mouse too fast.
To better illustrate, here's an SS: http://gyazo.com/6ed1017e9efd6beaa4b5d56052fda260
As you can see, it's only consistent when you move the mouse relatively slow, but as soon as you move it a bit fast, it leaves spaces.
How do I prevent this from happening?
Right now the client just sends x and y coordinates, so here's the server side code:
package com.company;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Server extends JPanel{
static MouseData mouseReceive;
static Draw draw;
static int x;
static int y;
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
Server server = new Server();
JFrame frame = new JFrame("Server");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(server);
frame.setSize(1024, 600);
frame.setVisible(true);
draw = new Draw(x,y);
ServerSocket serverSock = new ServerSocket(1234);
Socket s = serverSock.accept();
ObjectInputStream in = new ObjectInputStream(s.getInputStream());
while(true) {
mouseReceive = (MouseData) in.readObject();
draw = new Draw(mouseReceive.mouseX,mouseReceive.mouseY);
}
}
public void paint(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
draw.display(g);
repaint();
}
}
And here's my draw class:
package com.company;
import java.awt.*;
/**
* Created by John on 21/04/2015.
*/
public class Draw {
int xLoc;
int yLoc;
Draw(int x, int y){
xLoc = x;
yLoc = y;
}
public void display(Graphics g){
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.fillOval(xLoc,yLoc,7,7);
}
}
I tried finding someone else having the same problem on this site through the search function, but I had no luck in doing so :( If I missed it however, please direct me to that topic!
If anyone could help me out, I'd appreciate it a whole lot! Have a nice day :)

Presuming the Client is using a MouseListener (or MouseMotionListener): the MouseListener can only fire as fast as a certain interval. For example when the mouse is constantly moved your listener will receive a MouseEvent for every interval rather than every pixel. As a result, moving the mouse fast may result in drawing items that are not adjacent to each other. AFAIK, you cannot increase the speed, but you can draw lines between two sequential points making them look continuous (eg by using a List of each event location and using g.drawLine on each two adjacent points in the List).
Other notes:
You should override paintComponent rather than the paint method.
I would recommend calling super.paintComponent in this method. This will clear the component (hence your code will then only draw the last point - see (3))
I would recommend keeping a List of locations to use for drawing, which you can iterate over and draw each circle (or draw a line between adjacent points)
Do NOT call repaint within your painting methods. The idea here is that when a new item is received from the Client, add it to the List in (3) and then call repaint.

Related

Issues with drawing multiple circles (drawOval / Java)

My issues is the following: My actual project (of which the code below is a simplified version of) involves many concentric circles (each with a different colour) and animation utilising a Timer. The circles are drawn using the drawOval method.
My problem is that when these concentric circles are drawn, there appears to be loads of gaps in the outline of these circles, which I'm guessing is something to do with the fact that a circle is composed of pixels and lines as is any shape so the appearance of roundness is an illusion. I say this because when I swap the drawOval method for drawRect the painting looks as you would expect.
When messing around with other people's codes I saw that using RenderingHints somehow solved this problem however slowed down the animation beyond a point that I felt was acceptable.
Below is a screenshot of what is painted. Rather than seeing a solid opaque circle (as all of the circles drawn have the same colour in this example) we see this:
Here is my simplified code:
Test10
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Test10 extends JPanel {
Circle[] circles;
public static void main(String[] args) {
new Test10().go();
}
void go() {
JFrame frame = new JFrame("Circle Test");
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
circles = new Circle[200];
for (int i = 0; i < 200; i++) {
circles[i] = new Circle(i, ((2 * ( 200 - i) + 1)));
}
repaint();
frame.setPreferredSize(new Dimension(500,500));
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
for (Circle circle : circles ) {
circle.draw(g);
}
}
}
Circle
import java.awt.Graphics;
public class Circle {
int topLeft;
int diameter;
public Circle(int topLeft, int diameter) {
this.topLeft = topLeft;
this.diameter = diameter;
}
void draw(Graphics g) {
g.drawOval(topLeft, topLeft, diameter, diameter);
}
}
Could anyone explain to me a) Why this is happening and b) How to overcome this problem.
UPDATE
Having tried various methods including starting with the outermost circle and using fillOval instead of drawOval, and using a higher stroke value, I still find I have a problem with certain artefacts appearing similar to the screenshot Pavel posted. Here is a screenshot from my full application running the animation, if you look carefully you can see inconsistencies in the colour of mostly any given circle, resulting in these strange results. Their distribution actually follows the same pattern as the screenshot posted above so clearly something fundamental isn't being addressed by these options. Here is my screen shot:
It is impossible to draw perfect circle.
Try using the following method
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
int i = 0;
for (Circle circle : circles ) {
Shape circle2 = new Ellipse2D.Double(i++, i, circle.diameter, circle.diameter);
g2d.draw(circle2);
}
}
You said you tried with RenderingHints, and it slowed your animation, but you haven't give us any code with animation, so maybe try my code (it would be good to see animation implementation). It looked better, but still not what you wanted. Setting stroke to another value will solve this (set to at least 2). Another one is to use .fill() instead of .draw(). I know that it is not perfect, but you may try it.
ANOTHER IDEA
I thought, that maybe you could add some blur to your image, so those artifacts are not visible?
I haven't done it before, but I found this (found HERE):
private class BlurGlass extends JComponent {
private JFrame f;
public BlurGlass(JFrame f) {
this.f = f;
setOpaque(false);
setFocusable(false);
}
public void paintComponent(Graphics g) {
int w = f.getWidth();
int h = f.getHeight();
setLocation(0, 0);
setSize(w, h);
g.setColor(new Color(0, 0, 0, 0.3f));
g.fillRect(0, 0, w, h);
}
}
now somwhere in go() method:
frame.setGlassPane(new BlurGlass(frame));
frame.getGlassPane().setVisible(true);
It looks a lot better for me. Play a bit with this GlassPane color (try changing .3f to some other value).
You might want to make the Stroke bigger. I've had luck with this in situations similar to yours
You can try by adding this line in your Circle class inside draw function:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//and draw the Oval on g2
Also another solution might be to fill the circles:
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.fill(circle);
That happens because a computer cannot draw a perfect circle.
A computer uses square pixels to approximate a real circle but its just not possible to achieve perfection and that results in some pixels not being shown
Drawing a filled circle will help you
a detailed explanation
Can you please try fillOval method instead of drawOval.
g.fillOval(topLeft, topLeft, diameter, diameter);
Reverse your idea. Start with the outermost circle, then draw the inner circle and so on, finishing with the smallest circle. You should use fillOval in the process.
The other rendering hint that is often useful for circles/ovals is
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
See my other answer with more details and example.

Moving Java Swing rectangle leaves rectangles behind

When I finally figured out the repaint method, I came to a problem. I want to move a rectangle across the screen, rather than re-drawing it again. Redrawing is fine, but it leaves the older rectangle behind it! This is my code:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Tutorial3{
public static void main(String[] agrs){
createAndStartGui();
}
static void createAndStartGui(){
JFrame f = new JFrame("tutorial 3");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setPreferredSize(new Dimension(500, 300));
MyPanel panel = new MyPanel();
f.add(panel);
f.pack();
f.setVisible(true);
for (int i = 0; i < 10; i++){
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Tutorial3.class.getName()).log(Level.SEVERE, null, ex);
}
panel.user.move("right");
panel.repaint();
}
}
}
class MyRectangle{
int x;
int y;
public MyRectangle(int x, int y){
this.x = x;
this.y = y;
}
void move(String direction){
switch (direction){
case "up":
this.y -= 10;
break;
case "down":
this.y += 10;
break;
case "left":
this.x -= 10;
break;
case "right":
this.x += 10;
break;
default:
break;
}
}
}
class MyPanel extends JPanel{
MyRectangle user = new MyRectangle(10, 10);
public MyPanel(){
}
public void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
}
How do I get the rectangle that is left behind disappear (I DO NOT WANT TO CLEAR THE FULL WINDOW)? Or even better yet, how do I get the rectangle to 'move' (if it is possible)?
My end result:
What I want in the end:
Note: simply drawing the rectangle in that point isn't what I want. I want to see it getting dragged across.
Your problem is that you are only painting the rectangle, rather than the whole panel, so the panel ends up full of rectangles as you call the method. You need to draw the background of the panel too. This will "erase" the previous rectangles so the panel only has whatever you paint in that particular call and not what you did previously. To accomplish this you need to call:
super.paintComponent(g);
at the beginning of your paintComponent method (before drawing anything else). This works because the only thing that paintComponent needs to do in an empty JPanel is painting the background.
So:
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g.drawRect(user.x, user.y, 10, 10);
}
EDIT:
To answer some of your comments:
I want to move a rectangle across the screen, rather than re-drawing it again.
There's no such thing as "moving" a rectangle. You can have things painted on the screen. If you want to see other things you have to paint those other things. There's no inherent "move the color of the pixels to the pixels...", that's not how it works. Do you want things? Draw them. Do you want them to move? Draw them repeatedly in different positions.
(I DO NOT WANT TO CLEAR THE FULL WINDOW)
But you do. You want to repaint the whole panel each time something has to change. If there are other things in the panel that you don't want "erased" then repaint them.
To be clear, it would be possible to only clear the "old" rectangle position and paint a new one, without affecting the rest of the panel. But that is unnecesarily tricky. When you override paintComponent calling super.paintComponent(g) in the first line is the standard procedure. Not putting it has to be a very conscious decision and you better are sure of what are you doing.
If your program is done in a way that part of your code misbehaves when you repaint the background of your panel, I can tell you with confidence that is those parts that aren't well designed and not that calling super.paintComponent(g) is a bad idea.
paintComponent has the responsibility of painting the whole component. The background is part of the component. It's natural, and good design within Swing, to do it when you override it.

Applying gravity to a bouncing ball

I'm working on a bouncing ball program. I have successfully made a ball that goes up and down. I have set so that the ball are unable to go out of bounds so when it hits the edge of the screen, it simply bounces back up and so on.
Now, the thing is, I want the ball to eventually stop moving. For example, I start the program, the ball drops and bounces back up to maybe 80% of it's starting height. And when it comes down again it will accelerate due to gravity and then go back up but maybe only reach about 60% of it's original height, and eventually it will stop moving.
How do I create such a thing? I've googled for hours but found nothing of help. So now I implore you to give me a hand. Also, if you do decide to give me a handy tip, please try to be specific and very clear. I have not been programming for that long. Thanks in advance.
Here is my code:
EDIT: OBSERVE that I don't have a main method for this class since I don't need it. I'm running it through another class via an object.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Game extends JPanel implements ActionListener{
int DIAMETER = 40;
int yPos;
int yVel = 3;
int GRAVITY =1;
Timer tm = new Timer(5,this);
public void paintComponent(Graphics g){
super.paintComponent(g);
//Setting the characteristics for the ball
g.setColor(Color.red);
g.fillOval(0, yPos, DIAMETER, DIAMETER);
tm.start();
repaint();
}
public void actionPerformed(ActionEvent e) {
//If it decides to go out of the screen, change direction.
if(yPos<0 || yPos>430)yVel=-yVel;
//This basically is the "engine". It moves the ball.
yPos = yPos + yVel;
}
}
Take some energy out of the system.
Just as it hits the ground, the kinetic energy is E = 0.5 * m * v * v where m is the mass and v the speed.
Reduce E by a certain amount, say new_E = 0.8 * E. Then compute the new initial upward speed using the kinetic energy formula rearranged.
That is surprisingly realistic. Of course, you don't really need the 0.5 coefficient but I've retained it there to keep the physicists happy. You also don't need m either.

I am trying to make a stick figure in java

I got the head, one arm and the body. I am trying to make another arm using the same first two coordinates, which starts at the bottom of the head, but a negative last (but same number) last two coordinates. I assumed that if I made a negative version, it would just make an opposite version of the line. Instead, its just sticking straight up! I am confused on why this is happening.
import javax.swing.JComponent;
import java.awt.*;
import java.awt.geom.*;
public class StickFigure extends JComponent
{
public void paintComponent(Graphics g)
{
Graphics2D g2 = (Graphics2D) g;
Ellipse2D.Double head = new Ellipse2D.Double(5, 10, 50, 50);
g2.draw(head);
Line2D.Double body=new Line2D.Double(30,60, 30,150);
g2.draw(body);
Line2D.Double arm1=new Line2D.Double(30,60,75,75);
g2.draw(arm1);
Line2D.Double arm2=new Line2D.Double(30,60,-75,-75);
g2.draw(arm2);
}
}
That is the code that is giving me trouble. I am using a viewer which is the following:
import javax.swing.JFrame;
public class Viewer
{
public static void main(String[] arg)
{
JFrame frame = new JFrame();
frame.setSize(1000,500);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
StickFigure fig1=new StickFigure();
frame.add(fig1);
frame.setVisible(true);
}
}
Please let me know what I am doing wrong, I would greatly appreciate it.
Line2D.Double arm2=new Line2D.Double(30,60,-75,-75);
You need to think about what you're saying with -75 and -75. Remember those make a coordinate, and (0, 0) represents the top left corner in Swing (unless you're explicitly telling it not to). Those coordinates are offscreen to the northwest.
Try something like:
Line2D.Double arm2=new Line2D.Double(30,60, 45,75);
Try using a positive y last coordinate for both:
Line2D.Double arm1=new Line2D.Double(30,60,75,75);
g2.draw(arm1);
Line2D.Double arm2=new Line2D.Double(30,60,-75,75);
g2.draw(arm2);
You are right that -75 -75 "would just make an opposite version of the line", but when you alter both coordinates you get radial simmetry, that is simmetry around a point (the neck) hence one of your arm is low and the other is up. You want axial symmetry in this case, and for that you only need to flip one coordinate; since people's axis of symmetry is the spine, and it is vertical (y-direction) you need to flip coordinate x only.

How Do I Draw Two Overlapping JPanels such that the Graphics Drawn On Both Of Them Display On The Screen?

I have two JPanels. One panel has a 100x100 rectangle drawn at 0,0. And the other has a 100x100 rectangle drawn at 100, 100. My problem is that when both JPanels are drawn on the JFrame's content pane, one JPanel (the last one drawn) covers the other, hiding its graphics. Below is oversimplified code drawing two rectangles and the things I've tried.
package playground;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Playground{
public Playground(){
JFrame frame = new JFrame("My Frame");
frame.setSize(400, 400);
JPanel backPanel = new JPanel(){;
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
Rectangle2D rect = new Rectangle2D.Double(0, 0, 100, 100);
g2.draw(rect);
}
};
JPanel frontPanel = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
Rectangle2D rect = new Rectangle2D.Double(150, 150, 100, 100);
g2.draw(rect);
}
};
frontPanel.setOpaque(true); //Does nothing
frontPanel.setBackground(new Color(0, 0, 0, 0)); //Does nothing
frontPanel.setForeground(new Color(0, 0, 0, 0)); //Erases the rectangle drawn
frame.getContentPane().add(backPanel);
frame.getContentPane().add(frontPanel);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
public static void main(String[] args){
System.out.println("Hello World");
new Playground();
}
}
If anyone cares why I want to do this,
I'm creating the game breakout. I am a novice programmer and I have no knowledge of gaming theory. So I decided the smartest way to avoid a lot of rendering and buffering is to have four JPanels. A static JPanel at the very back with an image drawn on it (fun background image). A JPanel with the paddle drawn on it. A JPanel with bricks drawn on it. And a JPanel with a ball drawn on it. My rationale is that I won't have to redraw the paddle if it is not being moved, the background, and bricks that are not being hit. If a brick lets say is hit, I will update an arrayList of bricks and call repaint on the corresponding JPanel.
I am a novice programmer and I have no knowledge of gaming theory.
Ok, we can work with that.
So I decided the smartest way to avoid a lot of rendering and buffering is to have four JPanels.
You've just unnecessarily complicated your program.
Think of a JPanel as a canvas. You want to draw the entire Breakout game; bricks, paddle, and ball, on one JPanel canvas. Don't worry, you'll be able to redraw the entire canvas fast enough to get 60 frames per second if you want.
The way to do this is to create a Brick class, a Paddle class, and a Ball class. You create a Game Model class that contains one instance of the Paddle class, one instance pf the Ball class, and a List of instances of the Brick class.
The Brick class would have fields to determine its position in the wall, the number of points scored when the ball collides with the brick, the color of the brick, and a draw method that knows how to draw one brick.
The ball class would have fields to determine its x, y position, its direction, its velocity, and a draw method that knows how to draw the ball.
The Paddle class would have fields to determine its x, y position, its direction, its velocity, and a draw method that knows haw to draw the paddle.
The Game Model class would have methods to determine when the ball collides with a brick, determine when the ball collides with a brick, determine when the ball collides with a wall, and a draw method that calls the other model class draw methods to draw a ball, a paddle, and a wall of bricks.
This should be enough for now to get you started going in the right direction.
Edited to answer questions:
How would I implement a draw method in all these classes?
Here's an example Ball class. I haven't tested the moveBall method, so it might need some adjustment
import java.awt.Graphics;
import java.awt.geom.Point2D;
public class Ball {
private Point2D position;
/** velocity in pixels per second */
private double velocity;
/**
* direction in radians
* <ul>
* <li>0 - Heading east (+x)</li>
* <li>PI / 2 - Heading north (-y)</li>
* <li>PI - Heading west (-x)</li>
* <li>PI * 3 / 2 - Heading south (+y)</li>
* </ul>
* */
private double direction;
public Point2D getPosition() {
return position;
}
public void setPosition(Point2D position) {
this.position = position;
}
public double getVelocity() {
return velocity;
}
public void setVelocity(double velocity) {
this.velocity = velocity;
}
public double getDirection() {
return direction;
}
public void setDirection(double direction) {
this.direction = direction;
}
public void moveBall(long milliseconds) {
Point2D oldPosition = position;
// Calculate distance of ball motion
double distance = velocity / (1000.0D * milliseconds);
// Calculate new position
double newX = distance * Math.cos(direction);
double newY = distance * Math.sin(direction);
newX = oldPosition.getX() + newX;
newY = oldPosition.getY() - newY;
// Update position
position.setLocation(newX, newY);
}
public void draw(Graphics g) {
int radius = 3;
int x = (int) Math.round(position.getX());
int y = (int) Math.round(position.getY());
// Draw circle of radius and center point x, y
g.drawOval(x - radius, y - radius, radius + radius, radius + radius);
}
}
The draw method draws the ball wherever it actually is located. That's all the draw method does.
Actually moving the ball is the responsibility of the Game Model class. The method for moving the ball is included in this class because the information necessary to move the ball is stored in the Ball class.
I gave the ball a radius of 3, or a diameter of 6 pixels. You may want to make the ball bigger, and use the fillOval method instead of drawOval.
should I just call repaint() at a 30ms interval
Basically, yes.
In psudeocode, you create a game loop
while (running) {
update game model();
draw game();
wait;
}
First, you update the game model. I gave you a Ball class. You would have similar classes for the paddle and bricks. They all have draw methods.
Your Game model class calls all of these draw methods in the proper order. In Breakout, you would draw the boundaries first, then the bricks, then the paddle, and finally, the ball.
Your JPanel (canvas) calls the one draw method in the Game Model class.
I don't have an example game to show you, but if you read the article Sudoku Solver Swing GUI, you'll see how to put together a Swing GUI and you'll see how model classes implement draw methods.
I suggest that you stop working on Breakout for a while and go through the Oracle Swing Tutorial. Don't skip any sections in your haste to write a program. Go through the entire tutorial so you understand how Swing works before you try and use it.

Categories

Resources