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).
Related
I have a JLayeredPane with 2 JPanels inside of it.
The one on top has a semi transparant background color:
this.setBackground(new Color(0, 0, 0, 150));
Now when i set the size of this JPanel to be equal to the size of the frame
`
this.ghostPanel.setSize(width, height);
My whole JPanel turns gray, but when i give it height - 1, It displays the correct transparant view.
this.ghostPanel.setSize(width, height - 1);
I don't really understand why this happens. I would love to find a valid explanation!
Thanks in advance!
Swing components are either opaque or transparent, Swing doesn't know how to paint components which have a alpha based color.
By using a alpha based color, Swing doesn't know that it should paint the components below it when the component is updated, so you be up with some weird paint artefacts
Instead, make the component transparent (setOpaque(false)), then override it's paintComponent method and use a AlphaComposite to fill the background
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
I'm experiencing a problem with AWT in rotating the graphics in a panel by 90 degrees.
I can rotate the graphics in a panel by casting to Graphics2D and applying a transform.
The problem with this is that if the panel area is rectangular then part of the graphics becomes hidden. I can't seem to set clip bounds to the whole area.
If, for example, the window is short and wide then the clip region becomes narrow and tall. If the window is narrow and tall the clip region becomes short and wide. I don't know how to override this behavior.
Is there a better way of doing this or a way to work around the problem?
EDIT SOLVED:- It turns out that overriding behavior of getWidth() and getHeight() is a bad idea lol
As shown here, override getPreferredSize() on the enclosing panel to return a Dimension that can accommodate your desired view, e.g. Math.max(width, length). As shown here,
Translate the image to the origin.
Rotate the image.
Translate the image back to the center of the panel.
I have an algorithm that displays pretty fractals on a JPanel. I want to be able to show gridline 'crosshairs' on the fractal panel whenever a user hovers his mouse on the panel.
I have achieved this with a quick, simple method for drawing on stuff which goes like this (fractal is the name of the fractal JPanel):
#Override
public void mouseMoved(MouseEvent e) {
Graphics g = fractal.getGraphics();
// <problematic part>
// This is computationally expensive to do. It takes a good 0.2 seconds.
// This must be done to clear 'old' crosshairs
fractal.repaint();
// </problematic part>
Color c = fractal.getColourScheme().getGridlineColour();
g.setColor(new Color(c.getRed(), c.getGreen(), c.getBlue(), 50));
// Draw crosshairs around the mouse's current point
g.drawLine(e.getX(), 0, e.getX(), fractal.getHeight());
g.drawLine(0, e.getY(), fractal.getWidth(), e.getY());
}
As illustrated, I have to repaint() in order to clear any old crosshairs.
I have a feeling I need to paint my crosshairs on something like a Glass Pane to go on top of my JPanel - however, JPanel doesn't provide a Glass Pane. JRootPane does though - can I use this instead?
P.S.
Currently I'm painting the fractal pixel by pixel with lots of little Line2Ds. I will eventually change this to producing a BufferedImage - in which case, would this problem even matter?
I think your idea of using a glass pane would solve your problem permanently. It would no longer matter how you choose to draw the fractal itself, or whether it is displayed in a JPanel or any other type of component. Each object should really be responsible for it's own data and actions, so a different object really would probably be a good design choice.
That being said, you will still need to repaint the glass pane.
You should be able to use the public void repaint(long tm, int x,
int y,
int width,
int height) method to only repaint certain parts of the panel, instead of repainting the whole thing.
The JavaDoc states:
Adds the specified region to the dirty region list if the component is
showing. The component will be repainted after all of the currently
pending events have been dispatched.
I have subclassed java.awt.Frame and have overridden the paint() method as I wish to draw the entire contents of the window manually.
However, on the graphics object, (0,0) corresponds to the upper left hand corner of the window inside the title bar decoration, not the first drawable pixel.
Can I determine the co-ordinate of the first drawable pixel (ie, the height of the decoration) in a cross-platform manner, avoiding using a Mac OS X-specific fudge factor? Will I be forced to nest a Panel component in order to find the actual drawable area of the window?
Here, my code fails to centre the blue square inside the paintable area of the window:
#Override
public void paint (Graphics g) {
g.setColor(Color.BLUE);
g.setPaintMode();
g.fillRect(30, 30, getWidth()-60, getHeight()-60);
}
You can find the frame insets by calling the getInsets method (defined in Container). Frame insets are discussed at the top of the Frame API docs.
So you want to paint the whole area and don't want a title bar at all?
Assuming that you use JDk 1.4 (at least) then you can declare the frame to be "undecorated" (java.awt.Frame#setUndecorated(boolean)). This way no title bar is created and therefore the frames-paintable area is the same as the frames-consumed area.