Custom painting of a Swing component with multiple calls to repaint - java

private void moveSquare(int x, int y) {
int OFFSET = 1;
if ((squareX!=x) || (squareY!=y)) {
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
}
}
In the above code (complete code can be found in the Demo App of the "Performing Custom Painting" Java Tutorial), the first repaint method should paint a square at the position of the previous square, and the second repaint should paint another square at position of the new square. But this is actually not happening. Instead, the previous square disappears and the new one is painted.
How does the new square get painted while the previous one disappears?

The docs you linked to answer your question, at least in general:
although we have invoked repaint twice in a row in the same event handler, Swing is smart enough to take that information and repaint those sections of the screen all in one single paint operation.
When you call repaint, you are not actually painting anything yet, but requesting a repaint at some time in the future.
While the repaint JavaDoc doesn't go into any detail, it includes a link to "Painting in AWT and Swing", which includes in the "Paint Processing" section two cases, the second of which applies here:
(B) [When the] paint request originates from a call to repaint() on an extension of javax.swing.JComponent:
JComponent.repaint() registers an asynchronous repaint request to the component's RepaintManager, which uses invokeLater() to queue a Runnable to later process the request on the event dispatching thread.
And later in that section:
NOTE: if multiple calls to repaint() occur on a component or any of its Swing ancestors before the repaint request is processed, those multiple requests may be collapsed into a single call back to paintImmediately() [...]
By the time your event handler returns, some portion(s) of the JPanel will have been marked for repainting, possibly all of it. These are called "dirty regions". Swing (eventually) repaints all the dirty regions at once, and only once. This painting happens after your event handler has returned --- meaning after the JPanel's appearance has been changed --- so the colored square appears in its new location, without any "leftovers" in its old location.
In short, don't think of repaint as "repaint this area right now", but "add this area to your list of stuff-to-paint-sometime-later".

After you call repaint() it do not repaint the component instantly. But it add the request to paint the component again in the event queue in EDT.
What happens in each code line is expained below..
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
Mark the area bounded by the square (squareX,squareY,squareW+OFFSET,squareH+OFFSET) is going to be repaint. But it do not get repainted until RepaintManager do so..
squareX=x;
squareY=y;
Change the value of squareX and squareY. But it do not change the earlier marked region to be repainted. Now also, the region to be repainted is previous values.
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
Mark the area bounded by the square (squareX,squareY,squareW+OFFSET,squareH+OFFSET) is going to be repaint. Now there are two parts that RepaintManager has to repaint. Previous square and new square. But it do not get repainted until RepaintManager do so..
Finally when the time comes, RepaintManager paints the component.
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawString("This is my custom Panel!",10,20);
g.setColor(Color.RED);
g.fillRect(squareX,squareY,squareW,squareH);
g.setColor(Color.BLACK);
g.drawRect(squareX,squareY,squareW,squareH);
}
Now the component is painting only 2 areas. (Previous square and the new square) But the red square will be drawn only within the new square. In the old square there is nothing to draw. So previously drawn things will be erased..
Actually though there are 2 method calls for repaint(), paintComponents() will be called only once. The total area to be repainted is handled by RepaintManager and paintComponents() is called only once..

Oracle docs gives good explanation:
moveSquare method invokes the repaint method not once, but twice. The first invocation tells Swing to repaint the area of the component where the square previously was (the inherited behavior uses the UI Delegate to fill that area with the current background color.) The second invocation paints the area of the component where the square currently is.

Related

How to make graphics move without showing tracing but keep it when I draw another one?

I was trying to draw a circle in JPanel with paintComponent, when I call repaint() every time it remove all original paint. But when I remove super, it will show the trace of circle moving. So how should I do for keeping the circle after moving without showing the trace of it?
Painting in Swing is destructive, that's how it works.
When a paint cycle occurs, you are expected to repaint the component state from scratch.
This would suggest that you need to maintain some kind of model which the paint methods can use to repaint the state in it's entirety
I would recommend having a look at Painting in AWT and Swing for more details about how painting works
Start by reading the section from the Swing tutorial on Custom Painting. It shows how to draw a square at the location where the mouse is clicked.
So your logic would be similar except that instead of using the mouse to determine the new location of the circle you will use your programmed logic to change the x/y location of the circle.
You can use a Swing Timer to schedule this animation. The tutorial also has a section on How to Use Swing Timers which contains a working example to get you started.

What does the OFFSET do in repaint()-method with varargs?

private void moveSquare(int x, int y) {
int OFFSET = 1;
if ((squareX!=x) || (squareY!=y)) {
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
squareX=x;
squareY=y;
repaint(squareX,squareY,squareW+OFFSET,squareH+OFFSET);
}
}
This is a snippet from the Java-Tutorials. It's a tutorial about painting in Java.
https://docs.oracle.com/javase/tutorial/uiswing/painting/step3.html
I don't understand what the OFFSET is exactly doing.
Or what happens inside the repaint()-method.
I know, that it is necessary to paint the square correctly, because I noticed, that without the OFFSET, the Square is sometimes missing a side, which is not painted, or not deleted by the first repaint()-method.
I let the program write the variables (used for painting the rectangle (squareX...and so on)) in the console. But the width and heigth were always 20 and not 21 (width + OFFSET). Even when I read the variables inside the paintComponent()-method.
That is why I don't understand, why the square is drawn correctly the very first time, but every other time it gets repainted without the OFFSET, then it is drawn incorrectly.
And I can't look inside the repaint()-method (at least I don't know how to do it)
Another little question: Does the repaint method always "delete / overwrite" the object (in this case the square) it wants to draw, if the variables like color, position haven't changed?
This is what the paintComponent()-method is doint.
g.setColor(Color.RED);
g.fillRect(squareX,squareY,squareW,squareH);
g.setColor(Color.BLACK);
g.drawRect(squareX,squareY,squareW,squareH);
What I don't understand is, why the first repaint()-method deletes the old square. squareX/Y/W/H are the same as before. Afterwards they get the new coordinates from the mouse click, and then the square gets painted at the new location.
Why does the same code delete in the first repaint() and in the second one it creates a new square?
Sorry, if my english is bad. I'm from Germany.
Thanks in advance for all your answers!!!
I don't understand, why the square is drawn correctly the very first time,
The paintComponent(...) method is invoked WITHOUT clipping, so the entire (250 x 200) area of the panel is repainted.
but every other time it gets repainted without the OFFSET, then it is drawn incorrectly.
When you click on the panel the paintComponent(...) method is invoked WITH clipping as the two repaint(...) requests are consolidated into a single clipped painting request to make the painting more efficient.
For example, initially the square is painted at (50, 50). If you now click at (80, 80) the area repainted will be: (50, 50, 101, 101), which is the minimum area needed to clear the old square and paint the new square.
You can see the size of the clipped area change by adding the following to the paintComponent() method:
System.out.println(g.getClipBounds());
Note: for a simple painting like this you don't really need to be fancy. You could just invoke a single repaint() statement after resetting the x/y values and the entire panel will be repainted.
I don't understand what the OFFSET is exactly doing.
I think this comes down to how what a width and height value actually is.
Generally speaking, most people see a width and height as been 1 indexed (1 to width/height), where as the computer sees it as 0 indexed (0 to width/height).
So, when you define a area as been 20 pixels wide/high, is it 0-20, 0-19 or 1-20?
You can see this if you try and draw a rectangle surrounding the entire component. If you were to do something like....
g.drawRect(0, 0, getWidth(), getHeight());
the right/bottom edges would appear of the screen, instead, you need to use something more like
g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
What is happening here is, the API is allowing for both circumstances. The size of the component (defined by the width and height properties) is represented as 1 indexed, but we need to adjust for this to make it 0 indexed.
Why EXACTLY this is happening is beyond my knowledge (and generally my caring), all I care about is knowing that it happens ;)
Or what happens inside the repaint()-method.
Generally, you shouldn't. What you should care about is the fact that the API provides a consistent result, as the underlying workings of the API are delegating to native functionality (DirectX or OpenGL in most cases)
I let the program write the variables (used for painting the rectangle (squareX...and so on)) in the console. But the width and heigth were always 20 and not 21 (width + OFFSET). Even when I read the variables inside the paintComponent()-method.
That is why I don't understand, why the square is drawn correctly the very first time, but every other time it gets repainted without the OFFSET, then it is drawn incorrectly.
You wouldn't. The repaint isn't changing the physical state of the variables, but instead is asking that a small area of the component be updated, without effecting the rest of the component. What the API does is generate a clipping rectangle (see, it's another rectangle), which only allows updates to appear within that area (painting beyond it has no effect).
This is why it's called twice, once for the old position and once for the new
Another little question: Does the repaint method always "delete / overwrite" the object (in this case the square) it wants to draw, if the variables like color, position haven't changed?
It depends. Painting is considered destructive, that is, each time paintComponent is called, you are expected to repaint the entire state of the component from scratch. So, even if the state hasn't changed, the entire area must be repainted, because the paintComponent has no idea what the previous state was.
In Swing, the Graphics context is a shared resource, it is used to paint all the components for a common native peer.
You could have a look at Painting in AWT and Swing and Performing Custom Painting which might provide you with some more information about the painting process

Multiple 2D Graphics Drawing Functions in Java

The program I am working on includes a class named GameForm that extends JFrame. This form is going to contain a map (just a series of rectangles), as well as certain objects on the map.
However, I would not be able to draw all of these objects with a single paintComponent(Graphics g) function, since not all objects in the game always have to be drawn at the same time. For example, the drawMap() function would only be called when the form first loads, whereas all other drawing functions would be called after each turn.
However, from what I have read (and please correct me if I am wrong), only one paintComponent function is allowed in the class, and other functions cannot make use of its Graphics2D object.
Are there any ideas as to how this can be implemented?
People who are new to Swing / GUI programming often imagine JFrames to be like a draw surface or paper. However, you will have to get used to the fact that this is not the case.
First of all, a GUI program has some kind of EDT (Event Dispatch Thread). This thread is where all the GUI action happens. Updating the GUI and responding to user input happens here. This is necessary because user interaction and programmatic changes to the GUI need to be synchronized well.
Back to the topic, a JFrame is basically just a rectangle that is registered to the System to be your "draw surface". Rather than just painting on top of it, you are asked to paint it.
That's what paintComponent(Graphics) is good for. Obviously, you don't want to paint all the time. It just works like:
user opens your window
system tells your app: "hey, you wanted this surface, please paint it"
the Graphics from paintComponent() is used to repaint your frame (quickly)
your application remains inactive until the user makes the next input
If you want to animate your frame, you have to work like this:
tell the system: "hey, I'd like to repaint my surface" (calling repaint())
system calls paintComponent() and you repaint your stuff
the next call must be delayed
start over, paint the next image
Note that the delay is important because all of this happens on the holy EDT. The EDT handles everything and needs to "breathe" so the user can do stuff while you're doing your animation.
All in all, what you've learned is:
Save all the state you need for painting in variables.
When paintComponent() is called, draw onto the surface
If you want to animate, call repaint() -> paintComponent() will be called
never block the EDT
Last thing to consider: don't use JFrame to paint directly to it.
Rather than that, add a JPanel to the frame and override its paintComponent() method.
Generally speaking, what you wish to achieve can be done in a couple of ways. It's strictly related to so-called sprites (http://en.wikipedia.org/wiki/Sprite_%28computer_graphics%29) and image buffering (http://en.wikipedia.org/wiki/Multiple_buffering). Simplest approaches would be:
a) in paintComponent() of a JPanel added to your JFrame generate the resulting image by processing all the input data/user events/machine state,
b) you can prepare & store the overlay as e.g. BufferedImage, updating it as needed, and then paint it over your JFrame during a single call - the state of JFrame will be updated only on paint events (paint(), paintComponents() etc, so you must force invalidation by hand if the map changes without direct JFrame interaction (resizing the window, covering it with other frames etc), e.g. by calling repaint() etc.
c) you can get the drawing context by calling getGraphics() (http://docs.oracle.com/javase/7/docs/api/javax/swing/JFrame.html#getGraphics%28%29), and then using the returned object (probably casted to Graphics2D) as your canvas whenever the need arises. Note that this is actually the worst solution in terms of efficiency.
They ain't the only ones possible - I, for once, use OpenGL/JOGL for most of my 2D rendering needs, since it allows insane rendering speed with all the profits of 3D graphics [interpolation, scaling, rotations, alpha-blending, perspective, geometry morphing, shading etc] with only minimal functional overhead.
Also, note it is usually advisable to draw on a dedicated canvas component (e.g. JPanel) instead of global JFrame - it's connected to so-called lightweight vs heavyweight component difference and other OOP/Swing/AWT/EDT concerns; it also allows to hide the map and reuse the JFrame for something else with one simple JPanel#setVisible(false) call.
See java what is heavier: Canvas or paintComponent()? for more information.

java+Swing: efficient overlay of rectangle or other "sprite"

I'm not quite sure how to phrase this, so bear with me.
I have two JPanels in a container JPanel with an OverlayLayout. Both JPanels in the container override paint(Graphics).
The bottom JPanel is opaque and draws some fairly complicated graphics, so it takes a "long" time (10s or 100s of milliseconds) to render.
The top JPanel is transparent and just draws a rectangle or line or simple shape based on mouse input, so it's really quick.
Is there a way to set things up so when I change the simple shape in the upper panel, it doesn't redraw the bottom panel? (e.g. it somehow caches the bottom panel)
I'm vaguely familiar w/ concepts like bitblt, double-buffering, and XOR-drawing but not really sure what to apply here.
You'd be best off using a single JComponent and creating a BufferedImage to store the bottom image. When the paintComponent operation happens on the JComponent, you just blit the bottom image and use the Graphics object to do any further drawing on top of that (from a stored state). Should be fairly efficient.
You'll want to do the complex drawing operations for the bottom BufferedImage in another thread, as the other poster mentions (omitted this by accident, sorry :)). However, you don't want to cause contention on this image, so you must store an additional BufferedImage for this, and blit it synchronously to the other image at the very moment the drawing operations are complete on it.
Focusing on the complicated panel, the key is factoring everything out of paintComponent() except drawImage(). Put everything else in another thread that continually updates an offscreen buffer. Periodically update the screen at some rate that keeps the simple panel responsive. The only hard part is synchronizing, but SwingWorker is a good choice. There's more here.
What's sure is that if the upper panel is target for a full repaint(), then the lower one will be also.
Maybe you can try to optimize the region to repaint on the upper panel in order to avoid repainting all the lower one. But if the painted rectangle in the upper panel covers the whole area, then you end up with full repaint() once again.
Normally, Swing tries to optimize the regions that need a repaint, but it also aggregates these regions when several repaint are performed in a short time, and if I remember well, the aggregated region is just a rectangle that is the union of all repaint rectangles, which is not always optimized but allows for fast computation of repaint events creation.
Now, I think you should follow the advices given in previous replies; indeed, you should really avoid having a paint() method that can perform computations that can be that long (a few 10s of ms should be the real maximum). Painting should be as fast as possible if you don't want to have a GUI that looks unresponsive to the end user. Hence, favour performing the computation only once (and outside the EDT if possible) store the result in a BufferedImage that you just simply draw later on in the paint() method.
EDIT: added other sources of reflection
If you want to optimize the update of the list of points but still keep it in the paint() method, then you can use the clipping region of the passed Graphics to limit the calls to drawing methods, something like:
Rectangle clip = g.getClipBounds();
for (Point p: allPoints) {
if (clip.contains(p)) {
// draw p with g graphics
}
}
You can even try to optimize the list of points to draw by using a QuadTree instead of a simple List, but you'll have to code it yourself (or find some free implementations, there are probably a few of them out there). With a quadtree, you can optimize the time to find the list of all points that have to be redrawn (based on the Graphics clipping rectangle) and only redraw those points.
Addenda for answer by trashgod and jfpoilpret
1/ OverlayLayout is strange way how to layout JPanels, are you same output with once JPanel (without OverlayLayout and Translucentcy)
2/ (10s or 100s of milliseconds) is maybe small value because there is Native OS Latency (45-75ms for today OS and PC)
3/ synchronizations would be managed by using SwingWorker on BackGround Task and with order, directions and synchronizations for painting processes to the JPanel, maybe your paints are too fast/quickly
4/ you didn't describe more about how, where and which about paint()/paintComponent()
if (SwingUtilities.isEventDispatchThread()) {
paintImmediately(int x, int y, int w, int h) // or Rectangle r
} else {
Runnable doRun = new Runnable() {
#Override
public void run() {
repaint(long tm, int x, int y, int width, int height) // or Rectangle r
}
};
SwingUtilities.invokeLater(doRun);
}

Drawing over a BufferedImage. repaint()

I have a component on which I'm drawing a BufferedImage on all the surface.
I would like to draw something more over it, following the mouse when it passes over the area.
To do it, I'm adding a MouseMotionListener on the component and implement mouseMove method. Inside mouseMoved method I'm calling repaint() at the end of the drawing of the drawing of the cursor image. I would like to know if there is a better way to do it, cause the image following the cursor is really small, and I'm repainting every thing each time.
Add a JLabel containing an Icon to the panel with the buffered image.
Then when you move the mouse you just change the location of the label. Swing will repaint the last location so the buffered image shows through, then it will repaint the label at the new location. So let Swing manage the repaint.
Since you know the coordinate of your mouse and the small image you gonna paint over your background, you can optimize like this [pseudo-code]:
void mouseMoved(event) {
lastCoordinates = currentCoordinates;
currentCoordinates = event.coordinates;
image.repaint(lastCoordinates.x, lastCoordinates.y, smallImage.width, smallImage.height);
image.repaint(currentCoordinates.x, currentCoordinates.y, smallImage.width, smallImage.height);
}
that way you only repaint the two regions you actually care about instead of the whole background.
Also, reading the javadoc it seems the code above my actually trigger 2 separate calls to painting stuff, which would be inefficient. You may want to try to pass in a 10 milliseconds value or so to make sure the 2 paints execute together.
Check out javadoc for repaint() that takes 4 and 5 arguments:
4-argument version
5-argument version

Categories