Following a guide that was released in Nov, 2015. I have copied his code verbatim at this point and it still won't work for me. Has something been deprecated?
I have 3 buffers (call them 1,2, and 3). When 2 and 3 are drawn to the screen they have black lines on the top and left sides of the screen. This same code works fine with two buffers.
Bug footage: https://gfycat.com/gifs/detail/GraveCompetentArmyworm
package field;
import javax.swing.JFrame;
import java.awt.*;
import java.awt.image.BufferStrategy;
public class Main extends JFrame{
private Canvas canvas=new Canvas();
public Main() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
setBounds(0,0,1000,1000);
setLocationRelativeTo(null);
add(canvas);
setVisible(true);
canvas.createBufferStrategy(3);
BufferStrategy buffert = canvas.getBufferStrategy();
int p=0;
int ap=0;
while(p<1000) {
if (ap==100){
p++;
ap=0;
}
ap++;
buffert=canvas.getBufferStrategy();
Graphics g = buffert.getDrawGraphics();
super.paint(g);
g.setColor(Color.GREEN);
g.fillOval(p+100, 200, 50, 50);
buffert.show();
}
}
// public void paint(Graphics graphics) {
// super.paint(graphics);
// graphics.setColor(Color.RED);
// graphics.fillOval(100, 100, 100, 100);
//
// }
public static void main(String[] args){
new Main();
}
}
You need to go read the JavaDocs for BufferStrategy and Full-Screen Exclusive Mode API, which a number of important tutorials and examples on BufferStrategy
A BufferStrategy is a means to perform "page flipping", which is independent of the regular painting system. This provides you with "active" control over the painting process. Each buffer is updated off screen and the pushed onto the screen when it's ready.
This generally does not involve the component's own painting system and the intention is to avoid it.
This means you should NOT be calling super.paint(g) on the JFrame or canvas.paint. In fact, as a general rule, you should NEVER call paint manually.
Each time you want to update a buffer, you will be required to "prepare" it. This typically means filling it with some base color
So, based on the example from the JavaDocs, you could do something like...
// Check the capabilities of the GraphicsConfiguration
...
// Create our component
Window w = new Window(gc);
// Show our window
w.setVisible(true);
// Create a general double-buffering strategy
w.createBufferStrategy(2);
BufferStrategy strategy = w.getBufferStrategy();
// Main loop
while (!done) {
// Prepare for rendering the next frame
// ...
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// Determine the current width and height of the
// output
int width = ...;
int height = ...l
// to make sure the strategy is validated
Graphics graphics = strategy.getDrawGraphics();
graphics.setColor(Color.WHITE);
graphics.fillRect(0, 0, width, height);
// Render to graphics
// ...
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
// Dispose the window
w.setVisible(false);
w.dispose();
Now, personally, I'd prefer to use Canvas as the base, as it gives a more re-usable solution and it's easier to determine dimensions from
Related
I'm pretty new to Java and the GUI world. Right now I'm trying to create a really basic space shooter. To create it I started creating a JFrame, in which I've later on put a personal extension of a JPanel called GamePanel, on which I'm now trying to display all my components. Until here it's all pretty clear, the problem comes now: I have my GamePanel in which I display my player, and on the KeyEvent of pressing S the player should shoot the Bullets. I've managed the bullets as an Array, called Shooter[], of Bullet Objects, created by myself this way:
public class Bullet implements ActionListener{
Timer Time = new Timer(20, this);
private int BulletY = 430;
public int PlayerX;
public Rectangle Bound = new Rectangle();
public Bullet(int playerx) {
this.PlayerX = playerx;
Time.start();
}
public void draw(Graphics g){
g.setColor(Color.RED);
g.fillRect(PlayerX + 2, BulletY, 3, 10);
g.dispose();
}
#Override
public void actionPerformed(ActionEvent e) {
if (Time.isRunning()) {
BulletY = BulletY - 5;
Bound = new Rectangle (PlayerX + 2, BulletY, 3, 10);
}
}
}
I thought that calling the draw method in the GamePanel's paint() method would have allowed me to display both all the bullets shot and the player. What actually happens is that at the start it seems allright, but when I press S the player disappears and just one bullet is shot. Can you explain me why? This is how my paint() method looks like:
public void paint(Graphics g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, 500, 500);
for(int i = 0; i < BulletsCounter; i++) {
Shooter[i].draw(g);
}
g.setColor(Color.RED);
g.fillRect(PlayerX, PlayerY, 20, 20);
//System.out.println("Here I should have painted the player...");
g.dispose();
}
BulletsCounter is a counter I've created to avoid any NullPointerExceptions in painting the whole array, it increases when S is pressed and so another bullet of the array is initialized and shot.
Thank you for your patience, I'm new to the site, so warn me for any mistake.
You've several significant problems, the biggest given first:
You're disposing a Graphics object given to you by the JVM. Never do this as this will break the painting chain. Instead, only dispose of a Graphics object that you yourself have created.
You're drawing within paint which is not good for several reasons, but especially bad for animation since you don't have automatic double buffering of the image
You don't call the super painting method within your override and thus don't allow the JPanel to do house-keeping painting.
Recommendations:
Don't dispose of the Graphics object, not unless you, yourself, create it, for example if you extract one from a BufferedImage.
Override the JPanel's paintComponent method, not its paint method to give you double buffering and smoother animation.
And call super.paintComponent(g) first thing in your override to allow for housekeeping painting
I have a problem with the method fillArc of the class Graphics.
When I give the int values of the size of the arc,it paint an arc with wrong dimensions.
Dimension dimensione; //dimension of the window.
public void paint() {
BufferStrategy bS = this.getBufferStrategy();
Graphics g=bS.getDrawGraphics();
g.clearRect(0, 0,(int)dimensione.getWidth(), (int)dimensione.getHeight());
// g.fillArc((int)dimensione.getWidth()/2-150,(int)dimensione.getHeight()/2-150, 300,300, 0, 360);
g.fillArc(0,0,(int)dimensione.getWidth(), (int)dimensione.getHeight(),0, 360);
bS.show();
try {
Thread.sleep(100);
} catch (InterruptedException ex) {
Logger.getLogger(Disegno.class.getName()).log(Level.SEVERE, null, ex);
}
}
It should create an arc as big as the window... but this is the result:
What am I doing wrong?
Understand that the “viewable area” is the window size MINUS the frame decorations.
You shouldn’t be using the window size as a baseline, instead, override getPreferredSize (in your case of java.awt.Canvas) and return the preferred size you would like to use, you can then use Window#pack to pack the window around the content and it will become platform independent.
You can then use the getWidth and getHeight methods of Canvas to determine the actual size available
Hey :) So I'm making buttons for a game I'm making. The graphics work, at least to the extent that I don't have to fix them yet. However, click detection is a bit iffy. For example pressing where the black line is in the first picture below, triggers a response. Now obviously that point is not on the button. I have tested the buttons bounding box by drawing a rectangle around it, using it's getBounds() method (which is also used for click detection) and it draws a perfect rectangle around it. So then I tested the mouse click points and it turns out that even though the button is placed at y = 100, at the black line, the mouse point is also equal to 100... Now I have no idea why that is happening, especially because, if I place the button in the top left corner, the mouse detection correctly detects the top pixels and there is no offset...
This is rather interesting, and during my times in have had similar problems. This all really depends on why the Mouse Listener is attached to. Many people attach the listener to the frame, but draw on a panel. This can have the effects you are describing so it is usually better to either draw directly onto the frame, or attach the listener to the panel. In 99.99% of cases, I would always choose the latter. Really, no one should ever choose the latter UNLESS it's something very small.
Panels are exactly that; they're boxes which hold things, hence 'panel'. In my experiences it has always been more effective to use a panel. Frames are just the container to hold multiple panels.
Hope I could help, report your findings in a comment and/or post update.
Jarod.
Got bored so I whipped up an example of what I think is going on.
In essence, I do full rendering to a buffer (BufferedImage here). And then draw the render to the canvas. This may or may not be what you do, but I did it merely for example.
Seeing as you did say that it works fine in the top-left corner, I came to the hypothesis that scaling is the issue, since the x,y-values near the top left approach 0, and 0 * scale = 0, even a scaling of 1000 won't have any offset. The issue is when those components are not at the top-left corner, which you demonstrated for us.
Hopefully this answers your question. As for solving it, you can either accommodate for scaling, or use a letterboxing technique. Beyond those two, there are certainly many other ways to deal with this (such as fixing the screen size).
import javax.swing.*;
import java.awt.*;
import java.awt.image.BufferedImage;
/**
* #author Obicere
*/
public class GraphicScale {
public GraphicScale(){
final JFrame frame = new JFrame("Graphic Scale Example");
final MyPanel panel = new MyPanel();
final Timer repaintTimer = new Timer(50, e -> frame.repaint());
frame.add(panel);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
repaintTimer.start();
}
public static void main(final String[] args){
SwingUtilities.invokeLater(GraphicScale::new);
}
public class MyPanel extends JPanel {
private final Rectangle box = new Rectangle(100, 100, 100, 50);
private final Dimension size = new Dimension(500, 500);
private final BufferedImage render = new BufferedImage(500, 500, BufferedImage.TYPE_INT_RGB);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
render.flush();
render();
g.drawImage(render, 0, 0, getWidth(), getHeight(), this); // Trick is that this gets rescaled!
}
public void render(){
final Graphics2D g = (Graphics2D) render.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, render.getWidth(), render.getHeight());
final Point mouse = getMousePosition();
if(mouse != null){
if(box.contains(mouse)) {
g.setColor(Color.GREEN);
g.fill(box);
}
g.setColor(Color.DARK_GRAY);
g.drawOval(mouse.x - 3, mouse.y - 3, 6, 6);
}
g.setColor(Color.BLACK);
g.draw(box);
}
#Override
public Dimension getPreferredSize(){
return size;
}
}
}
Ok, so it turns out that there was some scaling going on with the frame, however I have no idea where it came from. I prepped the game to be scalable so I did all the painting to the optimal size BufferedImage and then I scale that image to the frame. However, even when I removed that the mouse location was still offset. In the end I overcame it by finishing the scaling optimization which required finding the scale of the frame by dividing the current width and height by the optimal width and height. And then dividing the mouse location by that value.I just figured this out. Setting the size of a component and packing the frame after adding the component, results in the actual frame being that size (counting the border), yet when you retrieve the size of the frame, it disregards the border... Why does this happen?
Solved
When I did the game screen scaling, I used the actual frame's height and width to scale the screen, instead of the canvas's height and width. I changed that and now it works perfectly!
import java.awt.*;
import javax.swing.JFrame;
public class GraphicsDemo1 extends Canvas
{
public void paint( Graphics g )
{
g.setColor(Color.green);
g.drawRect(50,20,100,200); // draw a rectangle
g.fillOval(160,20,100,200); // draw a filled-in oval
g.setColor(Color.blue);
g.fillRect(200,400,200,20); // a filled-in rectangle
g.drawOval(200,430,200,100);
g.setColor(Color.black);
g.drawString("Graphics are pretty neat.", 500, 100);
int x = getWidth() / 2;
int y = getHeight() / 2;
g.drawString("The first letter of this string is at (" + x + "," + y + ")", x, y);
}
public static void main( String[] args )
{
// You can change the title or size here if you want.
JFrame win = new JFrame("GraphicsDemo1");
win.setSize(800,600);
win.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
GraphicsDemo1 canvas = new GraphicsDemo1();
win.add( canvas );
win.setVisible(true);
}
}
Thanks. awt and swing are very confusing to me.
why does it extend canvas?
Because who ever wrote it chose to do so. Only those classes that extend from Component can actually be painted to the screen and only when they are attached to a valid, visible window
When is paint called in this program?
Painting is the responsibility of the RepaintManager. It will decide when components need to be repainted and schedule a repaint event on the Event Dispatching Thread. This in turn calls (in your case update which calls) paint on your behalf.
You might like to have a read through Painting in AWT and Swing for more details on the subject
paint() is called whenever the control is invalidated and needs to repaint itself. Think of moving the app partially off screen and then back. Paint would get called to redraw...
I suppose a number of different controls could be extended to achieve the same goal which is basically to create a custom-drawn control. An existing control is extended to get the ability to draw on its surface, be placed in a JFrame, get repainted automatically etc.
I have a JPanel subclass with custom paintComponent() implementation. It is being refreshed at 50fps. It is typically in the range of 500x300 pixels in size. I'm seeing some flickering (not too bad but noticeable) and I've inserted some debug code that indicates that Swing/EDT is skipping (presumably) redundant painting. I am guessing that's because the EDT is not giving enough time for paintComponent() to always finish or it is taking too much time on the EDT.
My thinking is that I need to take the code currently implementing paintComponent() (which is not very complex but not completely trivial either) and refactor it so it is executed on its own Thread (or at least not the EDT) and draws to an ImageBuffer. I then implement paintComponent on my custom JPanel and draw (render) from the ImageBuffer to the screen (actually to the buffer behind Swing components as my research into the solution led me some information about Swing being (by default) double-buffered, though I'm not completely clear on that). If it is true that the rendering from the ImageBuffer to the JPanel is faster than my implementation that constructs the ImageBuffer then I will be going in the right direction.
Is this the proper design direction for me to take?
UPDATE
I modified my implementation as discussed in reponses below:
1) Create a BufferedImage
BufferedImage myBufferedImage = new BufferedImage(mySize.width,mySize.height,BufferedImage.TYPE_INT_ARGB)
2) Create a Thread dedicated to peforming the processing to determine what is to be drawn.
3) Move the code previously in paintComponent() to another method that is executed by the dedicated Thread. At the end of this method, call repaint();
4) Create a new paintComponent() that simply calls g.drawImage(myBufferedImage,0,0,null);
5) Where I previously would call repaint(), trigger myThread to perform the drawing to myBufferedImage.
This was a disaster, as predicted. Much worse flickering and sluggishness, partial paints, etc. I believe this was due to contention reading/writing myBufferedImage (as mentioned below). So I then created a lock and lock myBufferedImage when I am writing to it (in the dedicated drawing Thread) and wait to get that lock in paintComponent() before calling Graphics2D.drawImage(); The flicker and partial paints go away - but performance is no better (maybe even worse) than when I was doing all the calculations for the drawing in paintComponent (and therefore in the EDT).
This has me stumped at this point.
If you're not updating the entire component (ie only small areas are changing), you could use JComponent#repaint(Rectangle r) indicating the areas that have changed. This will result in a repaint cycle that updates (potentially) a much smaller area.
I generated a "animated sequence" library some time ago to take a series of images and layer them ontop of each, given a "speed" of each layer, it would transpose them from right to left.
The whole sequence would cycle for 10 seconds, where a speed of 1 would take take 10 seconds to complete. Each layer is moving at difference speeds.
The original images where 1024x256, and the sequence was devised of 5 animated layers and 2 static layers...
I only wish I could show you how smooth this plays on my PC and Mac.
The only signification issue I had to over come was making sure that the images where compatible with the screen devices color model.
UPDATED
These are some utility classes I use when loading or creating BufferedImages, especially for animation. The make sure that the colour models are the same as those used by the screens, which will make them faster to update/repaint
public static BufferedImage loadCompatibleImage(URL resource) {
BufferedImage image = null;
try {
image = ImageIO.read(resource);
} catch (IOException ex) {
}
return image == null ? null : toCompatibleImage(image);
}
public static BufferedImage toCompatibleImage(BufferedImage image) {
if (image.getColorModel().equals(getGraphicsConfiguration().getColorModel())) {
return image;
}
BufferedImage compatibleImage =
getGraphicsConfiguration().createCompatibleImage(
image.getWidth(), image.getHeight(),
image.getTransparency());
Graphics g = compatibleImage.getGraphics();
g.drawImage(image, 0, 0, null);
g.dispose();
return compatibleImage;
}
public static GraphicsConfiguration getGraphicsConfiguration() {
return GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
}
// Check out java.awt.Transparency for valid values
public static BufferedImage createCompatibleImage(int width, int height, int transparency) {
BufferedImage image = getGraphicsConfiguration().createCompatibleImage(width, height, transparency);
image.coerceData(true);
return image;
}
I think this is what you're looking for on information about double buffering:
http://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
You could turn off double buffering with setDoubleBuffered(false) if you can't get access to the underlying buffer which I'm not entirely sure you can.
I don't think you can safely draw on an image from another thread because you'll get into the thread writing to the image while the EDT is reading that same image as it redraws. If you share an image between them you're going to have multi-threading issues that you'll have to synchronize. If you synchronize then you're performance isn't going to be very good. If you instantiate a new image every frame you're memory is going to skyrocket and GC will get you. You may be able to instantiate 10 frames and keep the writing away from the reading or something like that, but either way this is going to very tricky to make it performant and correct.
My suggestion is to do all drawing from EDT, and figure out a way to do the calculations (rendering) on another thread that doesn't involve ImageBuffer sharing.
Update While it is used for fullscreen. The suggestions in there apply to windowed mode as well: "Separate your drawing code from your rendering loop, so that you can operate fully under both full-screen exclusive and windowed modes." See this http://docs.oracle.com/javase/tutorial/extra/fullscreen/rendering.html
i've has similar problems trying to paint smoothly.
try running this and see how smooth it is (its smooth for me).
profiler says most of the time is in paint component. interestingly draw image is not mentioned.
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import javax.swing.*;
class P extends JPanel {
void init(Dimension d) {
GraphicsConfiguration gc=getGraphicsConfiguration();
bi=gc.createCompatibleImage(d.width,d.height);
}
#Override public void paintComponent(Graphics g) {
//super.paintComponent(g);
if(bi!=null)
g.drawImage(bi,0,0,null);
}
BufferedImage bi;
}
public class So13424311 {
So13424311() {
p=new P();
}
void createAndShowGUI() {
Frame f=new JFrame("so13424311");
// f.setUndecorated(true);
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
f.add(p);
p.init(d);
p.setSize(d);
p.setPreferredSize(d);
f.pack();
// if(moveToSecondaryDisplay)
// moveToSecondaryDisplay(f);
f.setVisible(true);
}
void run() {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
Timer t=new Timer(20,new ActionListener() {
#Override public void actionPerformed(ActionEvent e) {
Graphics g=p.bi.getGraphics();
Color old=g.getColor();
g.fillRect(0,0,d.width,d.height);
g.setColor(Color.red);
g.fillRect(n%(d.width/2),n%(d.height/2),20,20);
g.setColor(Color.green);
g.fillRect(n%(d.width/2)+20,n%(d.height/2),20,20);
g.setColor(Color.blue);
g.fillRect(n%(d.width/2),n%(d.height/2)+20,20,20);
g.setColor(Color.yellow);
g.fillRect(n%(d.width/2)+20,n%(d.height/2)+20,20,20);
g.setColor(old);
g.dispose();
p.repaint();
n++;
}
int n;
});
t.start();
}
public static void main(String[] args) {
new So13424311().run();
}
final P p;
Dimension d=new Dimension(500,300);
}