Java Graphics: setClip vs clipRect vs repaint(int,int,int,int) - java

Similar to my last post apologies, but a lot less long winded. Basically I am wondering what is the best option for optimising redrawing to a JFrame/JPanel when each repaint call only a small part of the screen would be redrawn.
Also apart from overloading repaint I am not 100% on how to implement setClip or clipRect. My understanding they are used when overriding paint or update?
See below for some code:
public class AquaSim extends JPanel {
//variables
//other methods...
public void paint (Graphics g) {
super.paintComponent(g); //needed?
Graphics2D g2d = (Graphics2D) g;
//Draws the land area for penguins.
g2d.setColor(new Color(121,133,60));
g2d.fill(land);
g2d.setColor(Color.BLUE);
g2d.fill(sea);
drawCreatures(g2d);
}
public void drawCreatures(Graphics2D g2d) {
for (Creature c : crlist) //a list of all alive creatures {
//each creature object stores its image and coords.
g2d.drawImage(c.createImage(txs,tys), c.getPos(1).width, c.getPos(1).height, this);
}
}
}
Ideally I would prefer not to have to loop though each creature object each repaint request, which is part of the reason for this post. I dont know if there would be a way of sending the current creature being drawn to paint or overriding paint in the Creature class to get it draw itself on to the graphics object in the main class.
A bit more code...
private class Creature implements ActionListener {
//variables & other methods
#Override
public void actionPerformed(ActionEvent e) {
if (getState()!=State.DEAD) {
move();
repaint(); //<---Would rather set clipping area in paint/update. x,y,w,h needs to include ICON & INFO BOX.
//repaint(gx,gy,getPos(1).width,getPos(1).height);
}
else anim.stop();
}
//...
public void move() {
//Determines how it will move and sets where to here by updating its position that is used in drawCreatures.
}
}
So any suggestions as which would be the most efficient method to use? Baring in mind repaint will be called a lot by many objects/creatures i.e. many times a second, hence why I dont want it redrawing everythin on the screen each repaint request.

only a small part of the screen would be redrawn.
Use repaint(....).
The RepaintManager will worry about what needs to be painted and will set the clip of the Graphics object for you.

Just out of interest is it possible to make the Graphics object attached to the JPanel in AquaSim global and then from within each Creature object use this graphics object to draw to the JPanel?
Although I guess I would need to figure out how to implement/override the paint method within the Creature class a bit like Gilbert is trying to tell me.

Related

How to deal with SwingWorker thread that goes slow?

Hardly, do I understand multithreading stuff. And beginning look into it , I faced one issue, that came in my mind. I have recently wrote one simple application, and as soon as I get some new knowledge about Java I wish to improve my app with that I’ve learned.
It looks like simple swing GUI that updates images every period of time. I implemented ActionListener and overrode actionPerformed method. Timer with 15ms delay, repainted JPanelclass and everything worked fine. But I thought that updating my GUI using timer directly in actionPerformed (I presume that it’s another thread, but I’m barely sure) is bad idea. So I decided to change code and use SwingWorker. I called all my methods for my animation inside process() .. and again app working fine but it became extremely slow.
Now I’m thinking what’s wrong? Why it acts slower then before ? My timer delay is actually not waiting 15ms , it’s much slower even though delay the same. Am I made mistake with multithreading ?
Help me understand this stuff. Thanks in advance
public class GameEngine() extends SwingWorker<Void, Drawable>
GamePanel gp; // ref to JPanel class
{
public GameEngine(GamePanel gp)
{
this.gp = gp;
}
}
protected void doInBackground()
{
publish();
}
protected void process(List<Drawable> chunks)
{
Timer timer = new Timer(15, e ->
{
//methods for animation
fall();
animate();
checkTouch();
});
}
Some code I left beyond. If you need it I can write...
EDITION
Just for clarity of my issue I provide some more examples and addition explanation.
**Used to be: **
public class GamePanel extends JPanel
{
public void GamePanel()
{
GameEngine engine = new GameEngine(this);
}
//some images , variables etc...
protected void paintComponent(Graphics g)
super.paintComponent(g)
g.drawImage(image1, x, y, null);
g.drawImage(image2, w, z,null);
...
}
public class GameEngine () implements ActionListener
{
GamePanel gp;
Timer timer;
public void GameEngine(GamePanel gp)
{
this.gp = gp;
timer = new Timer( 15 , this );
}
public void actionPerformed()
{
//these methods repaint my GamePanel every 15ms.
fall(); // make object (image) increment on Y Axis
animate(); // make another object (image) decrement on X Axis
checkTouch(); // check if objects collided
}
}
**Became: **
public class GamePanel extends JPanel
{
public void GamePanel()
{
GameEngine engine = new GameEngine(this);
}
//some images , variables etc...
protected void paintComponent(Graphics g)
super.paintComponent(g)
g.drawImage(image1, x, y, null);
g.drawImage(image2, w, z,null);
...
}
public class GameEngine () extends SwingWorker<Void, Drawable>
{
GamePanel gp;
Timer timer;
public void GameEngine(GamePanel gp)
{
this.gp = gp;
}
protected void doInBackground()
{
process();
}
protected void progress()
{
timer = new Timer (15, e->
{
new ActionListener(new actionPerformed)
{
//these methods repaint my GamePanel every 15ms.
fall(); // make object (image) increment on Y Axis
animate(); // make another object (image) decrement on X Axis
checkTouch(); // check if objects collided
}
});
}
protected void done()
{
};
}
When I created it first I implemented ActionListener and updated my panel through timer declared in constructor.. I presumed that it’s thread-unsafe.
That’s why I transfer everything in progress method where I declared timer which ActionListener as lambda argument.
In other words I call all methods for animation in another thread.
Finally it became slower, comparing with first example..
I don’t understand
Is Timer from first example EDT or it’s another thread ?
My first example is thread safe ?
Why my second example goes much slower then first one ?
I heard about NOT update your GUI outside EDT, is it that case?
There is not enough information in your question to answer it, but I will take your queries about multi-threading and the implied question about Swing and rendering and see if I can help you out.
I think the most likely reason for your slowdown is unnecessary screen updating. Once a pixel has been drawn onto the canvas or whatever in your application, usually your application does not need to redraw it unless it is supposed to change; either a sprite moves or some other image in your app obscures a part of the drawing temporarily and then needs to be restored.
It is common for a novice repainting to ignore this, and just repaintng the entire painted surface. Although this will work, it is slow; if you're doing it a number of times in a loop, then the loop will seem slow.
The better way to do this is to use the rectangle passed into the redrawing routine and only repaint its intersection with the entire surface redrawn by your routine -- this cuts way down on the part that needs to be redrawn, and therefore on the time it takes to redraw it.
As for multithreading, I think it's helpful to think of it the way we used to think of things in a single-processor world -- the computer does something for a while, then stops in a place you cannot predict and does something in the other thread for a while, etc. You cannot assume the order in which things are going to get done, or how long it will spend on each thing, etc.
With modern multi-core computers, it is possible that these things in fact get done at the same time, but I don't know that trying to envision that helps you any.

Why does a MouseListener not function whilst a loop is running?

I am creating a java program and want to pause the program until a mouse click using a MouseListener is identified. How would I 'pause' the program, for example using a loop, in such a way that the MouseListener still works and the program can return to the same method?
I have tried putting a loop to stop the program until a variable is true, but the MouseListener cannot work if a loop is running.
I have also attempted to put the rest of my code in the mouseClicked method, or running new methods from within mouseClicked, however another error occurred as I cannot use Graphics g anywhere except for paintComponent, for this reason it seems to me that a loop is necessary to pause the program.
Here is a simplified program which I created to show the core of the problem. (Not full code).
class Surface extends JPanel implements MouseListener{
#Override
public void mouseClicked(MouseEvent arg0) {
//Some way to unpause program
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawLine(30, 30, 200, 30);
//The program needs to 'pause' here until a click is identified.
System.out.println("Program finishes here");
}
}
The MouseListener does work, however it only seems to work if the program is dormant, and has finished all of the code in paintComponent.
Here is the code that does not work, as a loop is running.
class Surface extends JPanel implements MouseListener{
public static boolean repeatLoop = true;
#Override
public void mouseClicked(MouseEvent arg0) {
repeatLoop = false;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawLine(30, 30, 200, 30);
while (repeatLoop) {
}
System.out.println("Program finishes here");
}
}
I expected a loop telling the thread to sleep might work as well, but this has the same outcome and the MouseListener cannot be called.
Therefore I am asking why can a MouseListener not function when a loop is running, and is there an easy way to avert this problem and pause the program until the mouseClicked code is run.
Your loop in the paintComponent blocks the main thread, this is why this won't work. You shouldn't put that kind of logic in the paintComponent. The best thing you could do is create a separate Thread which checks for the repeatLoop. If the reapetLoop variable gets to false you could finish the application.
The paintComponent method is there just to paint on the JPanel.

Java Swing - flickering Canvas graphics

I have to write a simple Java app which can load pictures, show it in a GUI form, allow the user to apply some transformation, and show the transformed picture.
My solution is working fine, but the UI is flickering a bit, because the repaint method called too often (for example when the user scaling the image with a JSlider)
My code looks like this:
public class ImageCanvas extends Canvas
{
private BufferedImage image;
// ...
#Override
public void paint(Graphics g)
{
Graphics2D g2d = (Graphics2D) g;
if(image != null)
{
// I draw out the image...
}
}
public void setImage(BufferedImage image)
{
this.image = image;
this.repaint();
}
public void setRotation(double rotation)
{
this.rotation = rotation;
this.repaint();
}
public void setScale(double scaleX, double scaleY)
{
//set the scaling field, then repaint ....
}
// and so on...
}
And, of course, I have an ImageCanvas control on my main UI, and I simply call the public methods (see for example the "setRotation" method above) which repaint the canvas area. I know it's a simple question, but I don't even find a DoubleBuffered property on the Canvas...
Any help appreciated.
Double buffering is built-in for Swing (i.e. JComponent derived) classes.
If you want built-in double-buffering, you should extend JPanel rather than Canvas, and override paintComponent, not paint.
If you can use JPanel than go for it. Please make sure you are not overriding the JPanel.paint method, override JPanel.paintComponent instead.
See this link for details.
Usually graphic lags in these applications can be caused by setting a empty variable at the top of the script, then changing its value, then waiting for the repaint to update it. You could try changing the:
setRotation(double rotation);
so that it rotates the image in that method.
Just a general thing I happen to see while dealing with graphics.

Using Graphics from anonymous class

This is a part of my code which does some sort of animation; however, there seems to be something wrong: Whenever I try to use the passed 'g' from inside the anonymous class to draw anything, it does nothing, yet when I used it outside the anonymous class (inside the rollBalls method) it does what it's supposed to do. Any idea why? And how do I fix this? Thank you.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
rollBalls(g);
}
private void rollBalls(final Graphics g) { //Roll all 3 balls on the bar
new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
g.setColor(Color.red);
g.fillRect(0, 0, 500, 500);
}
}).start();
}
Your problem is several fold but first and foremost you understand that the Graphics object passed into a paint or paintComponent method usually does not persist and may be disposed of after the method completes. Next you have program logic being called from within a paintComponent method which should never be done. You do not have full control of when or even if the paint or paintComponent methods are called and thus should not have it dictating your app's logic.
For this reason you do not do Swing graphics in this way. Instead, have your timer outside of any paintComponent method, have it update class fields, have it then call repaint() and have your paintComponent use these class fields to do drawing as needed and this time with a stable Graphics object that is passed into it via its parameters.
I understand that your code is "just a sample", but your doing things wrong by trying to paint directly from within actionPerformed. You simply shouldn't do this.
I agree with Hovercraft Full Of Eels's comment. you should do something like this
class MyClass extends JComponent{
MyClass(){
new Timer(1, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
MyClass.this.repaint();
}
}).start();
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.red);
g.fillRect(0, 0, 500, 500);
}
Maybe living on the wrong thread for GUI update? Try putting the drawing commands in the anonymous class into a Runnable and pass that to SwingUtilities.invokeLater.
I don't think it has anything to do with inner-class-ness.

Expanding on Java Swing/AWT Program

I've written a small Swing program that draws a head and when a JCheckBox instance is selected/unselected by the user a hat is drawn or removed from on top of the head. I'm having some trouble taking the next step with this program -- I'd like to add a boolean field to the Head class that causes this component to listen to mouse events with a MouseListener. From there, I'd like to use two methods to set this field to true/false and render the remaining three methods lame ducks. Also, how would I change the paintComponent method so that if the boolean value is true the object is drawn with open eyes, and if it's false, the head is drawn with the eyes closed? Please provide any advice you have. Many thanks!
import javax.swing.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
public class Head extends JPanel {
Rectangle2D.Double chapeau, chapeau2;
Ellipse2D.Double bouche, visage, oeil1, oeil2;
JCheckBox box;
public Head(){
this.setBackground(Color.WHITE);
visage = new Ellipse2D.Double (150,130,100,100);
bouche = new Ellipse2D.Double (170,180,60,27);
chapeau = new Rectangle2D.Double (170,80,60,40);
chapeau2 = new Rectangle2D.Double (125,120,150,10);
oeil1 = new Ellipse2D.Double (170,150,20,20);
oeil2 = new Ellipse2D.Double (210,150,20,20);
box = new JCheckBox("Hat");
this.add(box);
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent ie){
repaint();
}
});
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3.0f));
g2.setPaint(Color.BLUE);
g2.draw(visage);
g2.draw(oeil1);
g2.draw(oeil2);
g2.draw(bouche);
if(box.isSelected()){
g2.draw(chapeau);
g2.draw(chapeau2);
}
}
public static void main(String[] args){
JFrame f = new JFrame("Face Display Window");
f.setSize(425,285);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
f.add(new Head());
}
}
----------------------------------- Second Try
import javax.swing.*;
import java.awt.geom.*;
import java.awt.*;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
public class Head extends JPanel implements MouseListener {
Rectangle2D.Double chapeau, chapeau2;
Ellipse2D.Double bouche, visage, oeil1, oeil2, oeil3, oeil4;
JCheckBox box;
boolean isClosed = false;
public Head(){
this.setBackground(Color.WHITE);
visage = new Ellipse2D.Double (150,130,100,100);
bouche = new Ellipse2D.Double (170,180,60,27);
chapeau = new Rectangle2D.Double (170,80,60,40);
chapeau2 = new Rectangle2D.Double (125,120,150,10);
oeil1 = new Ellipse2D.Double (170,150,20,20);
oeil2 = new Ellipse2D.Double (210,150,20,20);
oeil3 = new Ellipse2D.Double (175,155,25,25);
oeil4 = new Ellipse2D.Double (215,155,25,25);
box = new JCheckBox("Hat");
this.addMouseListener(this);
this.add(box);
box.addItemListener(new ItemListener(){
public void itemStateChanged(ItemEvent ie){
repaint();
}
});
}
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setStroke(new BasicStroke(3.0f));
g2.setPaint(Color.BLUE);
g2.draw(visage);
g2.draw(oeil1);
g2.draw(oeil2);
g2.draw(bouche);
if(box.isSelected()){
g2.draw(chapeau);
g2.draw(chapeau2);
if(isClosed) {
g2.draw(oeil3);
g2.draw(oeil4);
}
else {
g2.draw(oeil1);
g2.draw(oeil2);
}
}
}
#Override
public void mouseClicked(MouseEvent e) {
isClosed = !isClosed;
repaint();
}
#Override
public void mousePressed(MouseEvent e) {
}
#Override
public void mouseReleased(MouseEvent e) {
}
#Override
public void mouseEntered(MouseEvent e) {
}
#Override
public void mouseExited(MouseEvent e) {
}
public static void main(String[] args){
JFrame f = new JFrame("Face Display Window");
f.setSize(425,285);
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setVisible(true);
f.add(new Head());
}
}
I'm intentionally being a little vague here because I'm not sure if this is homework or not since you already have a fair amount of code that does a lot of stuff that is very similar to what you want it to and modifying it shouldn't be very difficult. However, if you're actually stuck, please clarify and I'll add more details if required.
I'd like to add a boolean field to the Head class that causes this component to listen to mouse events with a MouseListener.
This isn't too hard, let's walk through it. It's trivial to add a boolean field to your Head class - you simply declare boolean isClosed = false; - indicating that you begin the execution with the field set to false which your code will later interpret as the instruction to draw eyes open.
Your core requirement is the MouseListener. If you haven't already, check out the Java Trail for events; it explains how to implement a simple MouseListener. At this point, note that MouseListener is an interface and thus, you'd necessarily need to provide an implementation for all it's methods, even if they're empty-bodied methods. You may want to check out the MouseAdapter abstract class. It provides empty implementations of all these methods (and more) so that you only need to override the ones you need - this makes your code cleaner since you don't have a bunch of empty methods just to satisfy the compiler. This would solve the problem I believe you're referring to when you say 'and render the remaining three methods lame ducks' Of course, since you're already extending JPanel, you can't extend MouseAdapter as well so this doesn't apply here. But this (with other Adapters) is a useful class to keep in mind for later.
From there, I'd like to use two methods to set this field to true/false
If I understand you correctly, what you want is to toggle the value of isClosed on mouse clicks. So right now, you have a MouseListener/ MouseAdapter that doesn't really do anything. What you need to do next is provide an implementation for, lets say, the MouseClicked() method where you toggle the value of your boolean field. This is really easy as well - you simply invert the current value using the ! (NOT) operator and assign it back the variable - isClosed = !isClosed;. You may wish to read up on operators in Java in more detail.
Also, how would I change the paintComponent method so that if the boolean value is true the object is drawn with open eyes, and if it's false, the head is drawn with the eyes closed?
One way of doing this is to create two more shapes for the two closed eyes, similar to the ones you have for open eyes. Once you've done that, it's a simple matter of deciding which ones to draw (i.e. the closed eyes or the open ones) on the basis of the value of isClosed. So you'd use an if clause to check the value of isClosed and draw the open eyes when it's false and the closed eyes when true. Note that since your value of isClosed is being modified in your click handler, you need to make sure that you call repaint() when you change the value otherwise Swing may not update the panel immediately to show the change so then you won't see anything happen.
To sum it up, one way to do what you want is to make the following modifications to Head:
public class Head
extends JPanel
implements MouseListener {
//...all your other declarations still go here
boolean isClosed = false;
//also declare new 'eyes' which are closed
public Head() {
//..all your existing code is still here
//add code to instantiate closed eyes
//need to register a new MouseListener
//since head itself is a MouseListener, we can pass this as the argument
this.addMouseListener(this);
}
//...all your existing code still goes here
public void paintComponent(Graphics g) {
//...all your existing code still goes here
//decide which eyes to draw depending on isClosed
if(isClosed) {
//draw closed eyes
}
else {
//draw open eyes
}
//draw everything else as before
}
//implementation for MouseListener
//don't forget the rest of the MouseListener events
//mousePressed, mouseReleased, mouseEntered, mouseExited
public void mouseClicked(MouseEvent e) {
//toggle the value of isClosed
isClosed = !isClosed;
//must repaint
repaint();
}

Categories

Resources