I'm working on a project and I've read up as much as I can on double buffering in java. What I want to do is add a component or panel or something to my JFrame that contains the double buffered surface to draw to. I want to use hardware acceleration if possible, otherwise use regular software renderer. My code looks like this so far:
public class JFrameGame extends Game {
protected final JFrame frame;
protected final GamePanel panel;
protected Graphics2D g2;
public class GamePanel extends JPanel {
public GamePanel() {
super(true);
}
#Override
public void paintComponent(Graphics g) {
g2 = (Graphics2D)g;
g2.clearRect(0, 0, getWidth(), getHeight());
}
}
public JFrameGame() {
super();
gameLoop = new FixedGameLoop();
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new GamePanel();
panel.setIgnoreRepaint(true);
frame.add(panel);
panel.setVisible(true);
frame.setVisible(true);
}
#Override
protected void Draw() {
panel.repaint(); // aquire the graphics - can I acquire the graphics another way?
super.Draw(); // draw components
// draw stuff here
// is the buffer automatically swapped?
}
#Override
public void run() {
super.run();
}
}
I created an abstract game class and a game loop that calls Update and Draw. Now, if you see my comments, that's my main area of concern. Is there a way to get the graphics once instead of going through repaint and paintComponent and then assigning a variable every redraw? Also, is this hardware accelerated by default? If not what should I do to make it hardware accelerated?
If you want more control over when the window is updated and to take advantage of hardware page flipping (if available), you can use the BufferStrategy class.
Your Draw method would then look something like this:
#Override
protected void Draw() {
BufferStrategy bs = getBufferStrategy();
Graphics g = bs.getDrawGraphics(); // acquire the graphics
// draw stuff here
bs.show(); // swap buffers
}
The downside is that this approach does not mix well with event-driven rendering. You generally have to choose one or the other. Also getBufferStrategy is implemented only in Canvas and Window making it incompatible with Swing components.
Tutorials can be found here, here and here.
Don't extend JPanel. Extend JComponent. It's virtually the same and has less interfering code. Also, you'd do the drawing code in paintComponent only. If you need to manually refresh the component, you'd use component.redraw().
Related
I am trying to create a 3D game engine. I am using the triangle rasterizing technique so I have a list of lines (that make up triangles) in my CanvasPanel class and each time that paintComponent() is called it goes through all the lines and displays them on the screen. The problem is in my main loop, since I want to remove all lines in order to update the scene but when I call it it's being called while the paintComponent() method is still running so I get a ConcurrentModificationException. I tried having a loop going that checks if it has finished drawing and only then clear the lines list but for some reason it works only when adding a print statement, otherwise it stops working after a few seconds. What should I do to make sure the timing is right?
Relevant code in Main:
import javax.swing.*;
import java.awt.*;
public class Main
{
public static int width = 1280, height = 720;
public static void main(String[] args)
{
JFrame window = new JFrame("3D Game");
CanvasPanel canvas = new CanvasPanel(width, height);
window.setLayout(new FlowLayout());
window.getContentPane().add(canvas);
window.pack();
window.setVisible(true);
window.setResizable(false);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Some irrelevant calculations
while (true) // Main loop
{
// Some irrelevant calculations
for (float[][] triangle: cubeMesh)
{
// Here I'm adding lines to the lines list.
}
canvas.repaint(); // update canvas
// My attempt at making sure that clearing the lines list happens only after drawing
while (true)
{
if (!canvas.isDrawing()}
{
// Only works if I print something
break;
}
}
canvas.clearLines(); // here is the problematic line
}
}
}
Relevant code in CanvasPanel:
public class CanvasPanel extends JPanel
{
BufferedImage canvas;
JLabel label;
private ArrayList<Line> lines;
private boolean drawing;
public CanvasPanel(int width, int height)
{
canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
label = new JLabel(new ImageIcon(canvas));
lines = new ArrayList<>();
add(label);
drawing = false;
}
public boolean isDrawing()
{
return drawing;
}
public void clearLines()
{
lines.clear();
}
#Override
protected void paintComponent(Graphics g)
{
drawing = true;
super.paintComponent(g);
g.setColor(Color.black);
g.fillRect(0, 0, getWidth(), getHeight());
for (Line line: lines)
{
g.setColor(line.color);
g.drawLine(line.x1, line.y1, line.x2, line.y2);
}
drawing = false;
}
public void addLine(int x1, int y1, int x2, int y2, Color color)
{
lines.add(new Line(x1, y1, x2, y2, color));
}
First, go read Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how painting works in Swing.
Swing is lazy and makes use of a passive rendering workflow. This means that painting is done when the system decides it needs to be done. You can "request" a paint pass via the RepaintManager (normal via calling repaint on the component)
What this does is, if the RepaintManager agrees, places a "paint" event on the Event Dispatching Queue, which is, at some point in the future picked up by the Event Dispatching Thread and a paint pass is executed (this may be consolidated so that multiple components are painted in one pass).
There are two problems you're facing:
Painting can occur for any number of reasons, many of which you don't control and you're notified by
There's no reliable way to determine if or when a paint pass will/has occurred.
Now, add in the issue that Swing is not thread safe and you run into a mountain of other issues.
See Concurrency in Swing for more details.
So, what's the answer?
Well, you could use Worker Threads and SwingWorker or a Swing Timer to synchronise the updates depending on your needs, but honestly, I don't think making use of Swing's paint process is what you really need.
Another consideration would be use a backing buffer of your own. That is, use two or more BufferedImages onto which you draw the "state" and then pass these to the canvas to be drawn at some point in the future.
However, I would suggest taking control of the painting process directly yourself. Instead, have a look at BufferStrategy and BufferCapabilities (and the JavaDocs because it has a nice example).
This will provide you with direct control over the painting process so you can decide when and how it should be painted.
So basically it won't leave a trail. I tried to remove super.paint and i've tried to make multiple but it either creates an error or it doesn't. I've gone throught it atleast 10 times which is why i went here. Thanks in advance!
import javax.swing.*;
import java.awt.*;
public class Grafik extends JPanel {
private int x = 0;
private void moveBall()
{
x += 1;
}
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.fillRect(x, 50, 20, 80);
}
public static void main(String[] args)
{
JFrame frame = new JFrame();
Grafik grafik = new Grafik();
frame.setSize(700, 800);
frame.setLocation(300, 200);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(grafik);
frame.setTitle("Mitt spel");
frame.setResizable(false);
frame.setVisible(true);
while(true)
{
grafik.repaint();
grafik.moveBall();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Problems/Solutions:
Swing painting is done in the paintComponent method override, not the paint method.
Be sure to call the `super's paintComponent method first thing in your own override method, so the JPanel can do house-keeping painting
Your animation loop should be a Swing Timer, not a while (true) loop as the latter risks running afoul of Swing threading rules
If you want to create an animation but leave persisting images within the drawing then
Either create an ArrayList of objects, perhaps Points, that represent the trail, and in your paintComponent method draw the trail using a for loop that iterates through the ArrayList, or
use a BufferedImage as a background image, one that is drawn within the paintComponent method immediately after calling super.paintComponent(g) but before drawing the moving sprint. Draw your trail into this BufferedImage by getting a Graphics object out of it, by calling getGraphics() on the BufferedImage.
I'm trying to make a Paint program using java , I have three events in the jPanel to draw my line.
my problem is that when I am drawing the new line , the first one removed (I think the problem in the dragged event!) .. and so on.
Note that while the mouse is dragged the line will be stucked to the mouse
here is my events code:
private void jPanel1MousePressed(java.awt.event.MouseEvent evt) {
g1=(Graphics2D) jPanel1.getGraphics();
p1=jPanel1.getMousePosition();
}
JLayer lpane;
private void jPanel1MouseDragged(java.awt.event.MouseEvent evt) {
if(p1!=null){
lpane = new JLayer();
jPanel1.add(lpane, BorderLayout.CENTER);
lpane.setBounds(0, 0, 328, 257);
g2=(Graphics2D) lpane.getGraphics();
l=new Line(p1.x,p1.y,jPanel1.getMousePosition().x,jPanel1.getMousePosition().y);
l.draw(g2);
//lpane.repaint();
lpane.setVisible(false);
lpane.removeAll();
lpane.disable(); jPanel1.remove(lpane);
}
}
private void jPanel1MouseReleased(java.awt.event.MouseEvent evt) {
if(p1!=null)
{
g1=(Graphics2D) jPanel1.getGraphics();
p2=jPanel1.getMousePosition();
l=new Line(p1.x,p1.y,p2.x,p2.y);
g1.setColor(Color.red);
l.draw(g1);
p1=null;
}
}
Graphics2D g1,g2; Point p1=null,p2=null; Line l;
getGraphics is not how painting should be done in Swing, instead override the panels paintComponent and paint your components state there.
The paintComponent method needs to know what to paint whenever it is called, as it may be called any number of times, many times without your interaction or knowledge.
One approach is to build a List of shapes or Points, which can then be looped through and painted each time paintComponent is called. The benefit of this is you can remove these shapes/points should you wish.
See Pinting in AWT and Swing and Performing Custom Painting for more detals
Also take a look at this example for an idea
The usual way of doing this is to create a (Buffered)Image the size of your Component, fill the background color, and then draw each new line on the Image as well. In your paintComponent method, all you call is g.drawImage(...);
In your panel:
public void paintComponent(Graphics g) {
if (mSizeChanged) {
handleResize();
}
g.drawImage(mImg, 0, 0, null);
}
In your MouseMotionListener:
public void mouseDragged(MouseEvent me) {
Graphics g = mImg.getGraphics();
Point p = me.getPoint();
g.drawLine(mLastPoint.x, mLastPoint.y, p.x, p.y); }
I am trying to call the paint() method from another class, but it just doesn't work.
Here is the code:
Main.java:
public class Main extends JFrame {
private static final long serialVersionUID = 1L;
private int WIDTH = 600;
private int HEIGHT = 400;
private String NAME = "Dark Ages";
private String VERSION = "0.0.1 Pre-Alpha";
static boolean running = false;
private Image dbImage;
private Graphics dbg;
public Main() {
//Initialize window
JFrame frame = new JFrame();
frame.setTitle(NAME + " - " + VERSION);
frame.setSize(WIDTH, HEIGHT);
frame.setVisible(true);
frame.setResizable(false);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
//Running
running = true;
}
public void paint(Graphics g) {
dbImage = createImage(getWidth(), getHeight());
dbg = dbImage.getGraphics();
paintComponent(dbg);
g.drawImage(dbImage, 0, 0, this);
}
public void paintComponent(Graphics g) {
// Draw Images
repaint();
}
public static void main(String args[]) {
new Main();
Player player = new Player();
}
}
Player.java:
public class Player {
public void paint(Graphics g) {
g.drawRect(100, 100, 100, 100);
}
}
How do I call the paint() method from another class in java?
In brief, you don't. And in fact, you shouldn't call it directly from the same class either. Instead you should change the state of the class (change its fields), and then call repaint() on it, which will suggest to the JVM that the component should be painted.
Other issues and suggestions:
An obvious issue I see immediately in your code is that your class has a Graphics field, something that is dangerous to do. I suggest that you get rid of that field so that you're not tempted to use it.
Do not draw directly within a JFrame, JDialog or other top-level window. These windows have too many roles to play behind the scenes that you don't really want to mess with how they render themselves or their children (unless you really know what you're doing and have definite need).
Better to draw in the paintComponent(...) method override of a JPanel or other JComponents.
In your paintComponent(...) override, don't forget to call the super's method: super.paintComponent(g);. This will allow Swing to do housekeeping drawing, such as erasing old out of date or deleted images, before you do your drawing.
Run, don't walk to your nearest Swing painting tutorial.
Your current Player class extends no Swing component and is added to no top level window, so its code will do nothing useful. Again, read the tutorials.
Never call repaint() from within paint(...) or paintComponent(...).
Please post modified code if possible.: please don't ask us to create code for you as that's your job. Please understand that the more code you create, the better a coder you'll be, and because of this, most of us don't feel that we should cheat you out of the opportunity to create your own code.
Useful Java Tutorials:
The Really Big Index: The main tutorial where you should start.
Using Swing Components: How to create Swing GUI's
Lesson: Performing Custom Painting: Introductory tutorial to Swing graphics
Painting in AWT and Swing: Advanced tutorial on Swing graphics
I have an assignment to create a paint program in Java. I have managed to create something but not exactly what I wanted.
My problem is that I cannot create a JFrame in my IDE(NetBeans 7.0.1) from the options that the IDE gives me, and call the paint classes correctly.
To be more specific I want to press a button from one panel(ex. Panel1) and paint in Panel2,in the same frame.
That's the calling of the class:
private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
PaintFlower102 f = new PaintFlower102();
}
Part of Class:
super("Drag to Paint");
getContentPane().add(new Label ("Click and Drag"),BorderLayout.SOUTH);
// add(new JButton("Brush 20"),BorderLayout.NORTH);
addMouseMotionListener( new MouseMotionAdapter() {
#Override
public void mouseDragged(MouseEvent event) {
xval=event.getX();
yval=event.getY();
repaint();
}
});
setSize(500, 500);
setVisible(true);
setDefaultCloseOperation(PaintFlower102.DISPOSE_ON_CLOSE);
}
public void paint(Graphics g) {
g.fillOval(xval, yval, 10, 10);
}
The problem is that if I do not put the extend JFrame in the class this doesn't work. And if I do, it creates a new frame in which I can draw.
Suggestions:
Don't ever paint directly in a JFrame except under rare circumstances of absolute need (this isn't one of them).
Instead paint in a JPanel or JComponent or other derivative of JComponent.
Paint in the class's paintComponent(Graphics g) method, not in paint(Graphics g).
Read the Java tutorials on this as it's all explained well there. Check out Trail: 2D Graphics and Performing Custom Painting.
I might be wrong, but I think that you need to include super.paintComponent(g), and override the paintComponent method like Hovercraft Full Of Eels said.
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw Oval
g.fillOval(xval, yval, 10, 10);
}