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
Related
first of all I have scoured Google and SO for this answer, finding only how to change the actual pixels to be of a certain alpha value, which would be incredibly slow or actually making a part of the BufferedImage completely transparent via the use of lwg2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)). This is the exact functionality I need, however, I need to have the value to be less than 1f, which you cannot do with this specific instance of AlphaComposite.CLEAR.
What I want this implementation for is to make a wall inside my 2.5d game become transparent when the player goes behind it, like so:
The logic behind my game is that the terrain is one BufferedImage which is only updated when called, and then having the rest of the walls, etc, being drawn onto another BufferedImage where entities are also drawn, so the opacity transformation would only affect the trees (or walls).
This is the code I am using atm, but as I said I don't want the circle that I am drawing to make a part of the image completely transparent, but only slightly (about 50%):
g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR, 0.5f));
g2.fillOval(x - (int) (TILE_WIDTH * 1), y - (int) (TILE_HEIGHT * 1.5), TILE_WIDTH * 2, TILE_HEIGHT * 3);
(The 0.5f in the AlphaComposite constructor does nothing).
The reason I need this to be efficient is because I am updating this image 30 times a second, so efficiency > quality.
So, I ended up solving the issue by not manipulating the image directly via making a part of the image translucent, but manipulating the opacity of the images I am drawing with. As #user343760 and #NESPowerGlove mentioned, I could just make the assets I am using translucent when the player is behind it. Since I am using a underlying grid array to back my game, I could do this by working out if the tile.x - 1 == (int) player.x and tile.y - 1== (int) player.y. In isometry, this meant that the player was on the tile directly above it in our perspective. Then I had to solve the issue if the wall.z is bigger than 0 or 1, hence having the problem where a tile 5 blocks "bellow" the player could obstruct him if the walls extended z = 5 above the tile. For this problem, I implemented the following solution:
for(int i = 0; i < wall.getAsset(1f).getHeight()/TILE_HEIGHT; i++) {
if((tile.x - i - wall.z == (int) world.player.getX() && tile.y - i -wall.z == (int) world.player.getY())) {
lwg2.drawImage(wall.getAsset(0.5f), x, y, this);
}
}
This also ensures that the image is transparent even if the player is "above" the tile "above" the tile where the wall is situated, in terms of the image extending above that limit. I have fixed this via using the for loop which looks above for i number of times, depending on the image.height/tile_height, which is an universal constant.
If you require to make a part of the image transparent, I have not found solutions which would work fault free, except for manipulating the pixels in the low-levels of BufferedImage. If you also want to erase a part of an image directly, use the code g2.setComposite(AlphaComposite.getInstance(AlphaComposite.CLEAR)); and draw as you would normally. Remember to switch back to a normal composite via g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));.
You could also draw with a certain opacity in the first place using the Composite g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity));, where opacity is a float with values from 0f to 1f, 0f being completely transparent and 1f being completely opaque.
I hope this helped anyone out there. If you find a better way of doing this, please leave a comment for future readers.
This is what my solution looks like :):
I am making a 2d path-geometry based game in java. If I have a bunch of large shapes (Path2D's) that I am rendering every frame, is java taking the time to process the whole thing, or is it only processing the parts actually in the window?
For example say I have an rectangle that is 1000 by 1000 with the top left corner at 10, 10. My window is only 100 by 100. Is java processing the whole thing, or only the part smaller than 100, 100?
Thanks in advance!
Rendering is clipped to the visible portion only.
Obviously.
The clip area of the Graphics2D (see getClip()) is set automatically to the visible portion, but at some moment "Java" will still "take time" to determine whether the generic shapes are inside this clip area.
So it might be a valid optimization technique if you don't draw the shapes that are outside the visible area. Or you could draw the static shapes to an image, and then render this image.
After receiving some information, I decided to test it myself.
I made a complex shape using Path2D and rendered with the whole shape inside the window. Then, I rendered the same thing 90% outside the window, with only a small part of the shape showing.
The one that was outside the window showed much higher performance (260 FPS) than the one entirely in the window (50 FPS).
This suggests that java only processes the part of the shape actually within the window boundaries.
The scenario here is as so: I am using a Frame object to do some lower-ish level rendering with AWT (no Swing). The only issue is, Frames, when rendering directly to them, do not account for their borders. So, as we all likely know, rendering a Rectangle at (0,0) does not look like it is doing the right thing. This is because (0,0) is the literal top-left of the Frame.
So the problem is, instead of adding in the Frame insets for everything to be rendered on-screen like so:
//This is within the rendering method in the Frame subclass. A buffer strategy is already created
Graphics2D g = (Graphics2D)bufferStrategy.getDrawGraphics();
g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(Color.WHITE);
Insets insets = this.getInsets();
g.drawString("FPS: " + getFPS(), 100 + insets.left, 100 + insets.top); //<-- Ugh.
g.dispose();
I would like to be able to simply add an offset of sorts to the underlying graphics of the Frame. Is this possible? To be clear, it would be nice to have some functionality like this:
g.setDrawingOrigin(x, y);
With this sort of method, I could get away with murder. I don't know why there wouldn't be one buried somewhere...
NOTE: It is a Frame and not a JFrame, so we lack a content pane to reference. Another thing, it would be nice to avoid adding any other component to the Frame. I am trying to keep this as lightweight as possible (hence, the Frame instead of JFrame. Okay, there isn't much of a difference, but let me have my fun :D).
The method you are looking for is Graphic's translate(int, int).
So you would call,
g.translate(insets.left, insets.top);
One other approach is that instead of drawing to the Frame directly, you can add another component which fits and uses all of the Frame's space, and then do all the drawing in that subcomponent's paint method where x and y are where you expect.
Directly from the JavaDocs...
The size of the frame includes any area designated for the border. The dimensions of the border area may be obtained using the getInsets method, however, since these dimensions are platform-dependent, a valid insets value cannot be obtained until the frame is made displayable by either calling pack or show. Since the border area is included in the overall size of the frame, the border effectively obscures a portion of the frame, constraining the area available for rendering and/or displaying subcomponents to the rectangle which has an upper-left corner location of (insets.left, insets.top), and has a size of width - (insets.left + insets.right) by height - (insets.top + insets.bottom).
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);
}
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