Changing the BasicStroke of a Graphics2D object to anything other than 1 causes it to not draw something on the center of a JPanel on startup.
This is a JPanel which is on a JFrame. This is the basic idea of my project, but it is not the entire thing.
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
if(this.centered){
this.myShape.setCenterX(this.getWidth()/2);
this.myShape.setCenterY(this.getHeight()/2);
}
g2.setStroke(new BasicStroke(3)); //new BasicStroke(1) works fine
g2.draw(this.myShape);
}
When you click and drag the myShape, myShape will immediately jump to the center. But when I initially compile and run it, paintComponent() paints it about a centimeter above the center of the screen if the stroke is not 1.
Is there something wrong with how I'm centering? I defined the MyShape class, so there could be an error there. Maybe the distance between the center and the drawing point is the space between JPanel and the top of the JFrame? How do I fix it?
Edit: added picture
http://s21.postimage.org/dfpmz73et/Untitled_1.png
The first shape is right where I want it. The other two are above where I want it. But it appears the displacement from the center are the same regardless of stroke size.
Yes, I believe this is the normal behaviour for a shape. It assumes an outline of 1 pixel. So you need to change the center calculation when you know you are going to change the basic stroke size. Something like:
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
BasicStroke stroke = new BasicStroke(3);
int adjustment = stroke.getLineWidth() - 1;
if(this.centered){
this.myShape.setCenterX(this.getWidth() + adjustment / 2);
this.myShape.setCenterY(this.getHeight() + adjustment / 2);
}
g2.setStroke(stroke);
g2.draw(this.myShape);
}
Related
I have started using AffineTransform to rotate text I'm drawing with Graphics2D and I noticed it would sometimes work fine and other times it wouldn't when I just realized that it works always as expected on my Windows 7 PC but never on my Windows 10 laptop.
I use Java 15.0.1 on both systems.
Here is a small test case to show you my point:
import javax.swing.*;
import java.awt.*;
import java.awt.geom.AffineTransform;
public class AffineTransformTest extends JPanel {
private static final int SIZE = 40;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
int centerX = getWidth()/2;
int centerY = getHeight()/2;
g2.setColor(Color.BLACK);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
AffineTransform at = g2.getTransform();
at.setToRotation(Math.toRadians(45),centerX, centerY);
g2.setTransform(at);
g2.setColor(Color.RED);
g2.drawRect(centerX - SIZE/2, centerY - SIZE/2, SIZE, SIZE);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
AffineTransformTest test = new AffineTransformTest();
frame.setContentPane(test);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300,300);
frame.setVisible(true);
}
}
The black rectangle is a regular one centered in the middle of the JPanel.
The red rectangle is drawn after a 45° rotation and is supposed to share the same center as the black one (as shown on the first picture).
My laptop however produces the result shown on the second picture.
Expected result - Windows 7 |
Incorrect result - Windows 10
How can this be?
This is likely because you are violating the contract of the paintComponent() method, where the Javadoc states
If you override this in a subclass you should not make permanent
changes to the passed in Graphics. For example, you should not alter
the clip Rectangle or modify the transform. If you need to do these
operations you may find it easier to create a new Graphics from the
passed in Graphics and manipulate it.
So maybe subtle differences between the Windows 7 and Windows 10 native implementations of the UI is why one breaks and the other doesn't.
To see if this is indeed the cause, try changing the line Graphics2D g2 = (Graphics2D)g; to Graphics2D g2 = (Graphics2D)g.create(); which will make a clone of the Graphics object and leave the original unchanged.
Edit to clarify the correct answer:
The issue was caused by the at.setToRotation call clearing the default scaling transform on the graphics context after drawing the black square and before drawing the rotated red square. The recommended solution is to call the rotate(theta,x,y) form of the method directly on the Graphics2D object which preserves the existing scale transform.
My issues is the following: My actual project (of which the code below is a simplified version of) involves many concentric circles (each with a different colour) and animation utilising a Timer. The circles are drawn using the drawOval method.
My problem is that when these concentric circles are drawn, there appears to be loads of gaps in the outline of these circles, which I'm guessing is something to do with the fact that a circle is composed of pixels and lines as is any shape so the appearance of roundness is an illusion. I say this because when I swap the drawOval method for drawRect the painting looks as you would expect.
When messing around with other people's codes I saw that using RenderingHints somehow solved this problem however slowed down the animation beyond a point that I felt was acceptable.
Below is a screenshot of what is painted. Rather than seeing a solid opaque circle (as all of the circles drawn have the same colour in this example) we see this:
Here is my simplified code:
Test10
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.WindowConstants;
public class Test10 extends JPanel {
Circle[] circles;
public static void main(String[] args) {
new Test10().go();
}
void go() {
JFrame frame = new JFrame("Circle Test");
frame.getContentPane().add(this);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
circles = new Circle[200];
for (int i = 0; i < 200; i++) {
circles[i] = new Circle(i, ((2 * ( 200 - i) + 1)));
}
repaint();
frame.setPreferredSize(new Dimension(500,500));
frame.pack();
frame.setVisible(true);
}
public void paintComponent(Graphics g) {
for (Circle circle : circles ) {
circle.draw(g);
}
}
}
Circle
import java.awt.Graphics;
public class Circle {
int topLeft;
int diameter;
public Circle(int topLeft, int diameter) {
this.topLeft = topLeft;
this.diameter = diameter;
}
void draw(Graphics g) {
g.drawOval(topLeft, topLeft, diameter, diameter);
}
}
Could anyone explain to me a) Why this is happening and b) How to overcome this problem.
UPDATE
Having tried various methods including starting with the outermost circle and using fillOval instead of drawOval, and using a higher stroke value, I still find I have a problem with certain artefacts appearing similar to the screenshot Pavel posted. Here is a screenshot from my full application running the animation, if you look carefully you can see inconsistencies in the colour of mostly any given circle, resulting in these strange results. Their distribution actually follows the same pattern as the screenshot posted above so clearly something fundamental isn't being addressed by these options. Here is my screen shot:
It is impossible to draw perfect circle.
Try using the following method
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setStroke(new BasicStroke(2));
int i = 0;
for (Circle circle : circles ) {
Shape circle2 = new Ellipse2D.Double(i++, i, circle.diameter, circle.diameter);
g2d.draw(circle2);
}
}
You said you tried with RenderingHints, and it slowed your animation, but you haven't give us any code with animation, so maybe try my code (it would be good to see animation implementation). It looked better, but still not what you wanted. Setting stroke to another value will solve this (set to at least 2). Another one is to use .fill() instead of .draw(). I know that it is not perfect, but you may try it.
ANOTHER IDEA
I thought, that maybe you could add some blur to your image, so those artifacts are not visible?
I haven't done it before, but I found this (found HERE):
private class BlurGlass extends JComponent {
private JFrame f;
public BlurGlass(JFrame f) {
this.f = f;
setOpaque(false);
setFocusable(false);
}
public void paintComponent(Graphics g) {
int w = f.getWidth();
int h = f.getHeight();
setLocation(0, 0);
setSize(w, h);
g.setColor(new Color(0, 0, 0, 0.3f));
g.fillRect(0, 0, w, h);
}
}
now somwhere in go() method:
frame.setGlassPane(new BlurGlass(frame));
frame.getGlassPane().setVisible(true);
It looks a lot better for me. Play a bit with this GlassPane color (try changing .3f to some other value).
You might want to make the Stroke bigger. I've had luck with this in situations similar to yours
You can try by adding this line in your Circle class inside draw function:
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//and draw the Oval on g2
Also another solution might be to fill the circles:
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, diameter, diameter);
g2.fill(circle);
That happens because a computer cannot draw a perfect circle.
A computer uses square pixels to approximate a real circle but its just not possible to achieve perfection and that results in some pixels not being shown
Drawing a filled circle will help you
a detailed explanation
Can you please try fillOval method instead of drawOval.
g.fillOval(topLeft, topLeft, diameter, diameter);
Reverse your idea. Start with the outermost circle, then draw the inner circle and so on, finishing with the smallest circle. You should use fillOval in the process.
The other rendering hint that is often useful for circles/ovals is
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE);
See my other answer with more details and example.
I have coded a simple Java game where there are two rectangles on the screen, one of the rectangles moves and the other stays still, the moving Rectangle moves with keyboard arrow input and can move either up, down, left or right. The problem I am having is drawing my rectangles on the screen, I mean I have the 2 rectangles set up with my variables as shown:
Rectangle rectOne = new Rectangle(shiftX, shiftY,90,90);
Rectangle rectTwo = new Rectangle(500 + buckyPositionX, 330 + buckyPositionY, 210, 150);
I have made a render method to draw the things on the screen which I want shown:
public void render(GameContainer gc, StateBasedGame sbg, Graphics g) throws SlickException{
}
The problem I am having is showing my rectangles on the screen by writing the code in the render method, I could do the following:
g.fillRect(x, y,90,90);
g.fillRect(500 + buckyPositionX, 330 + buckyPositionY, 210, 150);
Which makes 2 rectangles on the screen but I need the rectangles to be drawn using the Rectangle code written with the variables, I have been told this can be done using Graphics2D but I am wondering if there is a simpler way of just using the graphics function, if not could you please help me set this up?
Thank you in advance.
You can access attributes of the Rectangle instances easily:
g.fillRect(rectOne.getX(), rectOne.getY(), rectOne.getWidth(), rectOne.getHeight());
In any case mind that usually the Graphics object is a Graphics2D instance at runtime so this could work easily too:
Graphics2D g2d = (Graphics2D)g;
g2d.fill(rectOne);
Just use:
g.fillRect(myRect.getX(), myRect.getY(), myRect.getWidth(), myRect.getHeight());
where myRect is the rectangle you want to draw. You could even make a custom method drawRect(Graphics g, Rectangle myRect); if you have lots of rectangles to draw.
Using Graphics2D is not that difficult as well, as the provide Graphics object normally is a Graphics2D object:
Graphics2D g2d = (Graphics2D) g;
g2d.fill(myRect);
When I try and apply a rotation to the current g2d object, it doesn't rotate it, it renders it in the same place (in my context on top of the other). From what I understand of the rotate method, it applies a transformation to the current graphics context, transforming the pixels of any rendering that comes after it (this might be where I'm going wrong). Here's the code in question:
#Override
public void paint(final Graphics graphics) {
super.paint(graphics);
final Graphics2D g2d = (Graphics2D) graphics;
....
....
g2d.setColor(Color.RED);
g2d.setStroke(new BasicStroke(SMALL_LINE_THICKNESS));
if (isLattice1Drawn) {
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1));
// lattice1 and lattice2 are Polygon objects
g2d.draw(lattice1);
// This fades in the second Polygon over the first
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, alpha));
// This line should rotate it, but doesn't
g2d.rotate(Math.toRadians(210));
g2d.draw(lattice2);
.....
Thanks, Mike
Edit 1
As a suggestion from Jeff, I tried having just the rotation and drawing in paint, leaving me with the following code:
#Override
public void paint(final Graphics graphics) {
super.paint(graphics);
final Graphics2D g2d = (Graphics2D) graphics;
g2d.rotate(Math.toRadians(210));
g2d.draw(lattice2);
return;
// Rest of paint .................
Unfortunately this did not help, any other suggestions would be most welcome.
Edit 2:
When I don't call rotate, the polygon is rendered, however when I do nothing happens. Can anyone explain this?
What I understand from Edit 2 is: the rotation actually works. However, since rotation is around origin the rotated coordinates of the polygon ends up outside of the visible area. You can test this by rotating smaller degrees.
Then, if the desired operation is to rotate a polygon around its center of mass, use the following Graphics2D method instead:
void rotate(double theta, double x, double y)
I want to draw my background image in the middle of the frame. As my image is not as big as the window, I want to put a black background.
Here is the code I use:
public void paint(Graphics g)
{
if(this.background != null)
{
int bounds_top = getHeight() / 2;
int bounds_left = getWidth() / 2;
int half_height = this.background.getHeight(null) / 2;
int half_width = this.background.getWidth(null) / 2;
g.drawImage(this.background, bounds_left - half_width, bounds_top - half_height, this.background.getWidth(null), this.background.getHeight(null), this);
this.setBackground(Color.black);
//this.setOpaque(false);
}
}
If I set the frame to be opaqe, my image is displayed but the background is gray.
If I set opaque to false, my frame is just black, no image is displayed.
So here is my question, how can I display my image and have a back background?
If you are doing this in a JPanel child, call setBackground(Color.black); in the constructor, and implement the code in paintComponent first calling super.paintComponent(g); for the black background.
You are drawing the image to the background, and then setting the background color to black. Try setting the background color to black first, and then draw the image to it. Otherwise it looks like you are drawing black over the image.
I found a litle trick to solve it:
Graphics2D g2 = (Graphics2D) g;
g2.setPaint(Color.BLACK);
g2.fill(new Rectangle2D.Double(0, 0, getWidth(), getHeight()));
g.drawImage(this.background, bounds_left - half_width, bounds_top - half_height, this.background.getWidth(null), this.background.getHeight(null), this);
This works well.