Java Graphics(2D) for offscreen rendering - java

I'm getting into Java graphics at the moment and I'm reading the book Killer game programming in Java by Andrew Davison. He programs a simple window application that uses the Image class for off-screen rendering and then copying the image to the component. What I find confusing is that one can just create the image and then get the graphics context with:
dbg = dbImage.getGraphics();
I find it confusing, because then we only use the dbg Graphics object to draw stuff but later on we use the dbImage in the paintComponent method to display all the stuff we have drawn to the dbg Object:
g.drawImage(dbImage, 0, 0, null);
So how does the dbImage "know", that it contains all the graphics stuff we have drawn onto the dbg Graphics object?
Here is the whole code:
public class GamePanel extends JPanel implements Runnable{
private static final int PWIDTH = 500;
private static final int PHEIGHT = 400;
private Thread animator;
private volatile boolean running = false;
private volatile boolean gameOver = false;
private Graphics dbg;
private Image dbImage = null;
private Counter counter;
public GamePanel(){
setBackground(Color.white);
setPreferredSize(new Dimension(PWIDTH, PHEIGHT));
// create game components eg. counter.
counter = new Counter(10);
counter.start();
}
private void stopGame(){
running = false;
}
#Override public void addNotify(){
super.addNotify();
startGame();
}
public void startGame(){
if(animator == null || !running){
animator = new Thread(this);
animator.start();
}
}
#Override
public void run() {
running = true;
while(running){
gameUpdate();
gameRender();
repaint();
try{
Thread.sleep(20);
}
catch(InterruptedException ex){
// do something with the exception...
}
}
}
private void gameUpdate(){
if(!gameOver){
// update game state...
if(counter.getCounter() == 0)
gameOver = true;
}
}
private void gameRender(){
if(dbImage == null){
dbImage = createImage(PWIDTH, PHEIGHT);
if(dbImage == null){
System.out.println("dbImage is null");
return;
}
else
dbg = dbImage.getGraphics(); // get graphics context for drawing to off-screen images.
}
// clear the background
dbg.setColor(Color.white);
dbg.fillRect(0, 0, PWIDTH, PHEIGHT);
// draw game elements here...
dbg.setColor(Color.black);
dbg.drawString(Integer.toString(counter.getCounter()), 10, 10);
if(gameOver)
dbg.drawString("Game over...", 10, 20);
}
#Override public void paintComponent(Graphics g){
super.paintComponent(g);
if(dbImage != null)
g.drawImage(dbImage, 0, 0, null);
}
}
the important parts are only the paintComponent method and the gameRender() method though.

Basically, a Graphics object is not something you draw on. It's something you draw with.
When you call dbImage.getGraphics(), you are saying "Give me a toolbox that allows me to draw on this image". All of the draw methods in a Graphics object draw on the component for which it was created. They are not drawn on the Graphics object, they are drawn on the Image or the Component that belong to it.
Think of the Graphics object as the palette and brushes, and on the Image or Component as the canvas.
So when you are done running the operations with your dbg, it means you have an image full of colorful pixels that this Graphics object drew on it. Now you can take this image and copy it to another component - by using the drawImage "tool" in that component's Graphics - its "toolbox".

Related

JPanel drawing is slowed down after several instances are painted

I'm building a game and I'm painting the road sprites with the JPanel's draw function. The roads (Building class) can be built by dragging the mouse and on each field a new road sprite appeares. But after I've drawn like 20 road sprites, the drawing gets really slow.
I have a frame and there is this JPanel on it.
Here is the code of the JPanel on which my game drawing is:
private class GamePanel extends JPanel implements ActionListener{
Field[][] map = gameEngine.getMap().getFields();
ArrayList<Building> buildings = gameEngine.getBuildings();
Timer timer;
ArrayList<Field> fields = new ArrayList<>();
GameFrame frame; //REFERENCE FOR THE CONTAINER OF THIS PANEL
private int mousePosX;
private int mousePosY;
GamePanel(GameFrame frame){
/*...*/
Mouse mouseListener = new Mouse();
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
timer = new Timer(1000/30,this);
timer.start();
/*...*/
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
for(Building b : buildings){
int drawPosX = b.getLocation().getPos().x*40;
int drawPosY = b.getLocation().getPos().y*40;
try {
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
} catch (IOException e) {
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// I thought this is not needed because I call "repaint()" only at mouse events (like
// building road by dragging)
}
public class Mouse extends MouseAdapter{
#Override
public void mouseDragged(MouseEvent evt){
repaint(); // THIS IS AN EXAMPLE TO WHERE I CALL THE REPAINT()
fieldPosX = evt.getX() - (evt.getX() % 40);
fieldPosY = evt.getY() - (evt.getY() % 40);
gameEngine.placeRoad(new SimpleRoad(new Field(fieldPosX/40,fieldPosY/40)));
}
/*... OTHER MOUSE EVENTS ...*/
}
I thought that calling repaint() only at mouse events will optimise the speed but it really isn't. I attached a GIF on which it can be seen that after 2 line of roads, it gets really slow.
I heard about invokeLater and people advised me to use it but I don't know how to implement that in this project. Why is my game getting slower after several buildings, where am I making a mistake? Would invokeLater solve the problem? How do I place it in my project?
Thanks for helping!
Why is my game getting slower after several buildings,
try
{
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
}
Don't do I/O in a painting method. As you add more building you are doing more I/O.
The images should be read in the constructor of your class.
You can save them in a HashMap for easy access in the painting method.
Or, the image can be saved as part of the Building class itself.

image is drawn half second later then other paint compontents

After I start my applet every component is drawn alright, besides my background image that is drawn with about a half second delay. I deleted my thread thinking it's maybe the cause of my problem, but it's not, so i didn't include it here.... I use Double Buffering, because I would have flickering of my components that are repainted by thread. I tried to provide as little code as possible....
public class balg extends Applet implements Runnable {
private Image i;
private Graphics doubleG;
URL url;
Image city; //background image
public void init(){
setSize(800, 600);
try{
url = getDocumentBase();
}catch(Exception e){
}
city = getImage(url , "multiplen/images/SPACE.png");
}
public void start(){
Thread thread = new Thread(this);
thread.start();
}
public void run(){
// here goes the repiant();
}
public void stop(){
}
public void destroy(){
}
#Override
public void update(Graphics g) {
if(i == null){
i = createImage(this.getSize().width, this.getSize().height);
doubleG = i.getGraphics();
}
doubleG.setColor(getBackground());
doubleG.fillRect(0, 0, this.getSize().width, this.getSize().height);
doubleG.setColor(getForeground());
paint(doubleG);
g.drawImage(i, 0,0, this);
}
public void paint(Graphics g){
g.drawImage(city,(int) 800 , 0 , this); // it's drawn here
String s = "15";
g.setColor(Color.BLACK);
g.drawString(s, getWidth() - 150, 50);
}
}
It takes that much time to read the image, about 100-200 ms.

How to create a BufferedImage (for a screenshot) from a Java Applet?

I am creating a lightweight client for the game Runescape that has various user features such as custom keybinds/hotkeys. I'm pretty good with java and swing, but AWT and applets I am mediocre at best. I was able to get the applet downloaded and displayed on a JPanel and then in a JFrame and the game runs perfectly so far. However, when adding features I ran into a problem when I tried to implement a screenshot function to the client.
It's hard to post a concise working example so I'll post the methods I was trying based off of some other SO answers I was reading. I tried creating BufferedImage from the Applet, applet Canvas, the JPanel it is handled by, and the JFrame.
Here are some things I tried to create a screenshot from the Applet canvas:
public BufferedImage getScreenShot() {
//getting the applet canvas
Canvas canvas = (Canvas) this.getApplet().getComponent(0);
//bufferedImage to draw to
BufferedImage image = new BufferedImage(canvas.getWidth(), canvas.getHeight(), BufferedImage.TYPE_INT_ARGB);
//Graphics2D g2=(Graphics2D)image.getGraphics();
//test.print(g2);
//g2.dispose();
Graphics g = image.getGraphics();
Image image2 = canvas.createImage(canvas.getWidth(), canvas.getHeight());
canvas.print(g);
System.out.println("Canvas Size: " + canvas.getSize().width + " x " + canvas.getSize().height);
return image;
}
This one I tried in the JPanel class which holds only the Applet:
public BufferedImage getScreenShotFINAL(){
BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
applet.paint(bi.createGraphics());
return bi;
}
I understand that the idea here is to create an off-screen BufferedImage, then to create a Graphics2d object and to then call the applet's paint() method to paint to the off-screen image. I tried this solution from here:
public BufferedImage getScreenShotFINAL2() {
Dimension size = applet.getSize();
BufferedImage offScreenImage = (BufferedImage) applet.createImage(size.width, size.height);
Graphics2D g2 = offScreenImage.createGraphics();
g2.setBackground(applet.getBackground());
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.clearRect(0, 0, size.width, size.height);
applet.paint(g2);
return offScreenImage;
}
When saving the BufferedImages these and other methods I tried to a file the images just show up as black, just the default background color. Is there a good way to just get a BufferedImage from the JPanel which would include the Applet so I never have to deal with the Applet directly? Another thing, I had to override the java Canvas class in order to get the game to display (because it is double buffered I believe). I tried following a bunch of solutions from here but had no success. Any suggestions would be greatly appreciated. I also can provide a link to the full project or the custom Canvas project if you would like. Thanks!
EDIT
Here is the Canvas class which overrides the java AWT one. Didn't write it, followed a tutorial for this one and I think this is where the problem is, because the paint() method only calls clearRect():
package java.awt;
import Client.Client;
import java.awt.image.BufferStrategy;
import java.awt.peer.CanvasPeer;
import javax.accessibility.*;
import loaders.ClientPool;
public class Canvas extends Component implements Accessible {
private Client client = null;
private static final String base = "canvas";
private static int nameCounter = 0;
private static final long serialVersionUID = -2284879212465893870L;
public Canvas() {
super();
}
public Canvas(GraphicsConfiguration config) {
this();
setGraphicsConfiguration(config);
}
#Override
void setGraphicsConfiguration(GraphicsConfiguration gc) {
synchronized(getTreeLock()) {
CanvasPeer peer = (CanvasPeer)getPeer();
if (peer != null) {
gc = peer.getAppropriateGraphicsConfiguration(gc);
}
super.setGraphicsConfiguration(gc);
}
}
#Override
public Graphics getGraphics(){
if (this.client == null) {
this.client = ClientPool.getClient(this);
}
if (client != null) {
//call custom draw functions for specific client. for drawing/double buffering
return client.drawGraphics((Graphics2D) super.getGraphics());
}
return super.getGraphics();
}
#Override
String constructComponentName() {
synchronized (Canvas.class) {
return base + nameCounter++;
}
}
#Override
public void addNotify() {
synchronized (getTreeLock()) {
if (peer == null)
peer = getToolkit().createCanvas(this);
super.addNotify();
}
}
#Override
public void paint(Graphics g) {
g.clearRect(0, 0, width, height);
}
#Override
public void update(Graphics g) {
g.clearRect(0, 0, width, height);
super.paint(g);
}
#Override
boolean postsOldMouseEvents() {
return true;
}
#Override
public void createBufferStrategy(int numBuffers) {
super.createBufferStrategy(numBuffers);
}
#Override
public void createBufferStrategy(int numBuffers,
BufferCapabilities caps) throws AWTException {
super.createBufferStrategy(numBuffers, caps);
}
#Override
public BufferStrategy getBufferStrategy() {
return super.getBufferStrategy();
}
#Override
public AccessibleContext getAccessibleContext() {
if (accessibleContext == null) {
accessibleContext = new AccessibleAWTCanvas();
}
return accessibleContext;
}
protected class AccessibleAWTCanvas extends AccessibleAWTComponent
{
private static final long serialVersionUID = -6325592262103146699L;
public AccessibleRole getAccessibleRole() {
return AccessibleRole.CANVAS;
}
}
}

Java Game Image Buffering Leaves Tail

I have the following class (Snippet), and in my render() method, I am doing buffering using BufferStrategy. The issue I am having is when I move an image, it leaves a tail.
What Do I need to do with my code to make it so the tail doesn't show? Here is the code:
public class Main extends JFrame implements Runnable{
private BufferStrategy bufferStrategy;
public synchronized void start(){
Thread thread = new Thread(this);
thread.start();
}
public void run(){
// Main Game Loop
this.render();
// End Main Game Loop
}
protected void render(){
if(bufferStrategy == null){
this.createBufferStrategy(3);
bufferStrategy = this.getBufferStrategy();
}
Graphics g = bufferStrategy.getDrawGraphics();
// Loop through a list of items to draw
for(GameObject go : gameObjects){
Image sprite = go.getComponent(SpriteRenderer.class).getSprite();
Vector2 pos = go.getComponent(Transform.class).getPosition();
g.drawImage(sprite, (int)pos.x, (int)pos.y, this);
}
g.dispose();
bufferStrategy.show();
Toolkit.getDefaultToolkit().sync();
}
}
Edit
I figured it out:
Graphics g = bufferStrategy.getDrawGraphics();
super.paint(g);
You need to refresh every time the wheel moves and repaint the Canvas to black before painting the wheel's position again.

Drawing a rectangle over an existing Graphics page

I have a Java application which draws a drawing. I want to give the user the possibility to mark an area with the mouse (in order to, for example, zoom into it).
For that I use the MouseMotionListener class, and when the mouse is (clicked and then) moved, I save the location of the currently selected (it isn't final since the user haven't released the mouse) rectangle, and use the repaint() function. I wish to display that rectangle over the original drawing, making it similar to the Selection tool in MSPaint.
The problem is that when I call the repaint() function, the method paintComponent (Graphics page) is invoked, in which I use the method super.paintComponent(page) which erases my drawing. However, if I don't use that method when I know the user is selecting a rectangle, I get that all the selected rectangles are "packed" one above the other, and this is an undesirable result - I wish to display the currently selected rectangle only.
I thought I should be able to save a copy of the Graphics page of the drawing and somehow restore it every time the user moves the mouse, but I could not find any documentation for helpful methods.
Thank you very much,
Ron.
Edit: Here are the relevant pieces of my code:
public class DrawingPanel extends JPanel
{
public FractalPanel()
{
addMouseListener (new MyListener());
addMouseMotionListener (new MyListener());
setBackground (Color.black);
setPreferredSize (new Dimension(200,200));
setFocusable(true);
}
public void paintComponent (Graphics page)
{
super.paintComponent(page);
//that's where the drawing takes place: page.setColor(Color.red), page.drawOval(..) etc
}
private class MyListener implements MouseListener, MouseMotionListener
{
...
public void mouseDragged (MouseEvent event)
{
//saving the location of the rectangle
isHoldingRectangle = true;
repaint();
}
}
}
I'm betting that you are getting your Graphics object via a getGraphics() call on a component, and are disatisfied since this obtains a Graphics object which does not persist. It is for this reason that you shouldn't do this but instead just do your drawing inside of the JPanel's paintComponent. If you do this all will be happy.
As an aside -- we'll be able to help you better if you tell us more of the pertinent details of your problem such as how you're getting your Graphics object and how you're trying to draw with it, key issues here. Otherwise we're limited to taking wild guesses about what you're trying to do.
e.g.,
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
public class MandelDraw extends JPanel {
private static final String IMAGE_ADDR = "http://upload.wikimedia.org/" +
"wikipedia/commons/thumb/b/b3/Mandel_zoom_07_satellite.jpg/" +
"800px-Mandel_zoom_07_satellite.jpg";
private static final Color DRAWING_RECT_COLOR = new Color(200, 200, 255);
private static final Color DRAWN_RECT_COLOR = Color.blue;
private BufferedImage image;
private Rectangle rect = null;
private boolean drawing = false;
public MandelDraw() {
try {
image = ImageIO.read(new URL(IMAGE_ADDR));
MyMouseAdapter mouseAdapter = new MyMouseAdapter();
addMouseListener(mouseAdapter);
addMouseMotionListener(mouseAdapter);
} catch (MalformedURLException e) {
e.printStackTrace();
System.exit(-1);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
#Override
public Dimension getPreferredSize() {
if (image != null) {
return new Dimension(image.getWidth(), image.getHeight());
}
return super.getPreferredSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
if (image != null) {
g.drawImage(image, 0, 0, null);
}
if (rect == null) {
return;
} else if (drawing) {
g2.setColor(DRAWING_RECT_COLOR);
g2.draw(rect);
} else {
g2.setColor(DRAWN_RECT_COLOR);
g2.draw(rect);
}
}
private class MyMouseAdapter extends MouseAdapter {
private Point mousePress = null;
#Override
public void mousePressed(MouseEvent e) {
mousePress = e.getPoint();
}
#Override
public void mouseDragged(MouseEvent e) {
drawing = true;
int x = Math.min(mousePress.x, e.getPoint().x);
int y = Math.min(mousePress.y, e.getPoint().y);
int width = Math.abs(mousePress.x - e.getPoint().x);
int height = Math.abs(mousePress.y - e.getPoint().y);
rect = new Rectangle(x, y, width, height);
repaint();
}
#Override
public void mouseReleased(MouseEvent e) {
drawing = false;
repaint();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("MandelDraw");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new MandelDraw());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
You need to repaint on every mouse movement:
public void mouseDragged(MouseEvent e){
int x = e.getX();
int y = e.getY();
//Update the rectangle holder object with that point coordinates
repaint();
}
You'll probably have a holder rectangle object to hold the initial and final rectangle points. The initials are set on mouse click, the final are modified on mouse dragged and on mouse released.
In paint method, clear the graphics and draw a rectangle with the coordinates in the holder. This is the basic idea.
UPDATE: How to draw a new shape on top of the existing image:
I'm thinking of two options:
If you are only drawing shapes (such as lines, rectangles and other Java2D stuff) you could have a Collection holding these shapes coordinates, and draw all of them on each paint. Pros: good when there are few shapes, allows undoing. Cons: When the number of shapes increase, the paint method will take more and more time in each pass.
Have a "background image". On each paint call, draw first the image and then the currently active shape on top. when an active shape is made persistent (onMouseReleased), it is saved to the background image. Pros: efficient, constant time. Cons: drawing a big background image on every mouse movement could be "expensive".

Categories

Resources