I make a game and when I animate entity with low velocity I use g.FILL(new Ellipse2D.Float(x, y, r, r)); because it renders smooth and fluent motion.
It work fine (in Example blue left circle). But when I needed just an outline of circle I used g.DRAW(new Ellipse2D.Float(x, y, w, h)); and it didn't work and I absolutely don't know what's wrong. No fluent motion, circle jumps pixel over pixel and it looks ugly (in Example red right circle). Graphics2D.draw(Shape) count float values like int values.
This Example code demonstrate it, don't study it a lot, just import, run and watch.
public class Example extends JFrame {
public static void main(String[] args) { new Example(); }
public Example() {
setBounds(50, 50, 400, 400);
setVisible(true);
while(true) {
x1 += 0.01;
y1 += 0.01;
x2 -= 0.01;
y2 += 0.01;
try {
Thread.sleep(16);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
}
double x1 = 50 , y1 = 50;
double x2 = 250, y2 = 50;
#Override
public void paint(Graphics gg) {
Graphics2D g = (Graphics2D) gg;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.BLUE);
g.fill(new Ellipse2D.Double(x1, y1, 100, 100));
g.setColor(Color.RED);
g.draw(new Ellipse2D.Double(x2, y2, 100, 100));
}
}
So how can I fix it without tricks like filling two cirle or moving image of circle? Thanks for every answer.
I would guess the problem is that you didn't invoke super.paint(...) as the first statement in the method which means you lose some of the default painting functionality.
However, that is NOT the proper solution as you should NOT be overriding the paint() method of a JFrame at all. Custom painting is done by overriding the `paintComponent(...) method of a JPanel and then you add the panel to the frame. Read the section from the Swing tutorial on Custom Painting for more information and working examples.
Also, don't use a while true loop for animation. Instead you should be using a Swing Timer to schedule the animation (the tutorial has a section on Swing Timers). Your code only works because you are NOT creating the GUI correctly. The GUI should be created on the Event Dispatch Thread. The Custom Painting tutorial shows you how to use the invokeLater(...) method to do this. The tutorial also has a section on Concurrency in Swing which explains this concept in more detail.
With drawing circles/ovals/ellipses in Java, the "pure" mode often helps drawing it with subpixel accuracy:
g.setRenderingHint( RenderingHints. KEY_STROKE_CONTROL,
RenderingHints.VALUE_STROKE_PURE)
My other answer has more details: https://stackoverflow.com/a/31221395/1143274
Related
I'm writing a simple game for studying purposes. Everything goes fine except one little thing...
I cant figure out how to rotate square without this ugly jumping
here a simplified version of my program illustrating an issue, here i use one timer, but in original program i have 2 timers one for handle game state and second for repaint:
public class soQuestion extends JLabel {
double r;
#Override
public void paintComponent(Graphics g1) {
Graphics2D g = (Graphics2D) g1;
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
g.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
RenderingHints.VALUE_INTERPOLATION_BICUBIC);
g.setRenderingHint(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
g.clearRect(0,0, getWidth(), getHeight());
g.translate(getWidth()/2, getHeight()/2);
g.rotate(r);
g.translate(-20, -20);
g.fillRect(0, 0, 40, 40);
}
public static void main(String[] args) {
JFrame frame = new JFrame();
soQuestion question = new soQuestion();
frame.add(question);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setSize(200, 200);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
new javax.swing.Timer(10, new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
r += 0.005;
question.repaint();
}
}).start();
}
}
jumping is more visible if rotation delta values is small, for fast rotated objects is less visible..
all rendering hints i used has no effect
PS: Sorry for my english
PPS: i can provide more details if needed, how it looks in full version:
Thanks for all participants!
It is fully my mistake and inattention. I was copied this code section from some source :
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
And before ask a question here i couldn't saw that i used hint related to text rendering.
Special thanks to mr #Pshemo for suggesting of using this rendering hint:
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
I was know about it before asking, but i'm a human and could not figure out where a mistake for an a hour and asked.
PS: sorry for my english
I'm a new contributor, so if its not the best help, sry but i am trying :)
have you tried a solution where you are using Math.toRadiants() in your g.rotate() function? in this video: Java: Rotating and scaling images
the image rotates without jumping, it's at 6:42 in the video.
So in your solution, it would look like that: g.rotate(Math.toRadiants(r += 0.005));
I have a fractal tree generator and I am trying to make a slider control the number of iterations, but I can not get it to work. Also, the layout gets messed up whenever the repaint() method is called. Any thoughts on how to fix this?
public class FractalTree extends JPanel implements ChangeListener {
static JSlider slider = new JSlider(0,12);
static int slideVal=7;
public FractalTree()
{
super();
slider.addChangeListener(this);
}
public void paint(Graphics g)
{
g.setColor(Color.green);
drawTree(g, 400, 750, 200, Math.toRadians(-90), Math.toRadians(45), slideVal); //Don't let # of iterations exceed 12, it is useless
}
private void drawTree(Graphics g, int x1, int y1, double l, double t, double dt, double iterations) {
if (iterations > 0) {
int x2 = x1 + (int) (l * Math.cos(t));
int y2 = y1 + (int) (l * Math.sin(t));
g.drawLine(x1, y1, x2, y2);
drawTree(g, x2, y2, l / 1.5, t + dt, Math.PI / 4, iterations - .5);
drawTree(g, x2, y2, l / 1.5, t - dt, Math.PI / 4, iterations - .5);
}
}
#Override
public void stateChanged(ChangeEvent e) {
slideVal=slider.getValue();
repaint();
}
public static void main(String[] args) {
JFrame t = new JFrame("Some swaggy fractal shit");
FractalTree tree = new FractalTree();
slider.setValue(slideVal);
slider.setMinorTickSpacing(1);
slider.setPaintTicks(true);
slider.setPaintLabels(true);
tree.add(slider);
t.add(tree);
t.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
t.setResizable(false);
t.setLocationByPlatform(true);
t.setSize(800, 800);
t.setBackground(Color.black);
t.setVisible(true);
}
}
Two main problems:
You're overriding paint instead of paintComponent.
You're not calling super.paintComponent(g) (or in your case, super.paint(g)) as the first thing in your overridden method.
This is what you need to have:
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.green);
drawTree(g, 400, 750, 200, Math.toRadians(-90), Math.toRadians(45), slideVal);
}
Other things to consider:
Add the slider to the frame on position BorderLayout.PAGE_START instead of to the panel. If you add it to the panel, you risk drawing where the slider is.
Set the background color on the panel and not on the frame.
No need to call super() in the constructor, it's automatic.
setResizable(false) on the frame should generally be avoided. No need to restrict the user's space.
Call pack() on the frame instead of setSize(...). The latter is too dependent on the local graphics configuration.
You will need to override the panel's getPreferredSize method to return the correct size for the drawing.
Your pixel calculation should be adjusted to that the tree is aligned to the upper left corner of the panel, and not start it from an arbitrary location on the bottom, which causes you to lose a lot of screen real estate:
Response to comments
Why paintComponent should be used?
See these:
paintComponent() vs paint() and JPanel vs Canvas in a paintbrush-type GUI
Difference between paint() and paintcomponent()?
public void paint(Graphics g)
This method actually delegates the work of painting to three protected methods: paintComponent, paintBorder, and paintChildren. They're called in the order listed to ensure that children appear on top of component itself. [...] A subclass that just wants to specialize the UI (look and feel) delegate's paint method should just override paintComponent.
You saw that if you override paint and added the slider to the panel, you got issues with the slider painting because you ignored paintChildren.
What calling the superclass constructor does?
Best to answer is the JLS:
JLS 8.8.7. Constructor Body
If a constructor body does not begin with an explicit constructor invocation and the constructor being declared is not part of the primordial class Object, then the constructor body implicitly begins with a superclass constructor invocation "super();", an invocation of the constructor of its direct superclass that takes no arguments.
So calling super() does nothing.
I am attempting to draw sprites out of a sprite sheet.
I have the following class
public class GTComponent extends JComponent {
Graphics2D g2;
#Override
public void paintComponent(Graphics g){
g2 = (Graphics2D)g;
}
public void drawSpriteFrame(Image source, int x, int y, int frame) {
int frameX = (frame % 12) * 32;
int frameY = (frame / 12) * 32;
g2.drawImage(source, x, y, x + 32, y + 32,
frameX, frameY, frameX + 32, frameY + 32, this);
}
}
That is created as an object in the main class as so
JFrame f = new JFrame();
GTComponent img = new GTComponent();
f.add(img);
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize((int)(i.length * 8.1), (int)(i[0].length * 8.5));
f.setVisible(true);
f.setLocationRelativeTo(null);
BufferedImage test = null;
try {
test = ImageIO.read(new File( /*Image File path*/));
}
catch (IOException e) {
System.out.println("error");
System.exit(0);
}
img.drawSpriteFrame(test, (u * 32 + 1), (z * 32 + 1), c);
The problem im facing is that the following error gets thrown
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
After doing several debugs, setting breakpoints at paintComponent and drawSpriteFrame, i found out that the drawSpriteFrame method gets called before the paintComponent method, thus meaning that g2 = null resulting in that error being thrown.
The question here is what triggers the paintComponent method which allows me to initialise the g2 variable?
You seem to have a broad misconception how drawing in Swing works.
You do not call any rendering methods when you want to. You perform rendering when Swing demands it. When Swing calls paintComponent() thats where you perform all your rendering. The graphics passed to paintComponent should be treated as valid only while you're still in the paintComponent method. What happens to it after the method exits is Swings buisness.
You might want to consult the tutorial on Swing custom painting http://docs.oracle.com/javase/tutorial/uiswing/painting/index.html for example code.
paintComponent() is called automatically from the event dispatch thread. If you want your custom component to be painted as part of the ordinary Swing painting process, you should override paintComponent() to call your drawSpriteFrame() method, not call drawSpriteFrame() directly.
If you want to control the drawing operation yourself, you need to use "active rendering" as described in the Full-Screen Exclusive Mode tutorial -- note that the technique described there also works for windowed applications. Basically you need to ask the window for a Graphics instance (instead of waiting for one to be passed into paintComponent() and then draw to that.
A basic example using double buffering:
// Initial setup
Frame mainFrame = new Frame();
mainFrame.setVisible(true); // you'll also want to set size, location, etc.
mainFrame.createBufferStrategy(2);
BufferStrategy bufferStrategy = mainFrame.getBufferStrategy();
//....
// Inside your draw loop (call once for each frame)
Graphics2D g2 = (Graphics2D) bufferStrategy.getDrawGraphics();
g2.drawImage(...) // etc.
g2.dispose();
bufferStrategy.show();
I'm coding a GUI that will be doing some graphics translations/rotations, etc.
My problem is that when I try to translate my graphics,
(a) The entire screen translates instead of my one little painted area
(b) The old paint stays there, leaving a big paint blob instead of a translated image
(c) If I use the clearRect method to allow me to avoid (b), the entire screen goes white and (a) is still a problem
my DrawPanel class (I called it "LaunchTubeImage" for whatever reason)
private class LaunchTubeImage extends JPanel {
private Color colour;
public LaunchTubeImage(Color color) {
super();
this.colour = color;
}
public void paintComponent(Graphics g) {
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,
tubeImage.getSize().height - 50);
g.setColor(colour);
g.clearRect(0,0,getWidth(),getHeight());
g.fillRect(tubeImage.getSize().width/2,
tubeImage.getSize().height - 100 , 10, 50);
}
}
where this is called in my code
tubeImage = new LaunchTubeImage(Color.MAGENTA);
angle.addChangeListener(new ChangeListener(){
public void stateChanged(ChangeEvent e) {
tubeImage.repaint();
}
});
Case 1: Comment out clearRect in that 1st block of code I posted
http://i58.tinypic.com/2d1l5w2_th.png
Black background as desired. Not rotated yet. Looks good so far.
http://oi60.tinypic.com/1zw1sm.jpg
Rotated it with my JSpinner... you see that the previous location was not removed (and note how my buttons randomly doubled and put themselves at the top of the screen).
Case 2: Keeping in the clearRect method
oi57.tinypic.com/2s84307.jpg
Layout is fine so far, but I wanted the background to be black
oi57.tinypic.com/4rde8x.jpg
Yay! It rotated. But note the weird behavior of that random "15" that appeared in my top right corner
oi58.tinypic.com/vymljm.jpg
And finally... when I resize the window you see that my entire screen was rotated - not just the pink image I wanted to rotate
Tips/fixes/advice? Thanks!! I hope I've provided enough information
(P.s. if you insist on us asking clear/useful questions.... then DON'T limit the number of images you can post... :/ )
The first line of an overridden paintComponent method should usually be super.paintComponent(g). On a JPanel, this will cause the drawing area to be cleared with the background color. If you want to clear the background with a different color, you can do this by manually filling a rectangle (clearRect is discouraged, see the JavaDoc), but of course, this has to be done before applying any transform.
So your method should probably look like this:
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(colour);
g.fillRect(0,0,getWidth(),getHeight());
Graphics2D gg = (Graphics2D)g;
double theta = (double)angle.getValue();
theta = Math.toRadians(theta);
gg.rotate(theta,tubeImage.getSize().width/2 + 10,tubeImage.getSize().height - 50);
gg.fillRect(tubeImage.getSize().width/2,tubeImage.getSize().height - 100 , 10, 50);
}
Heres part of my simple code.I want to achieve moving oval to cursors X axis location after clicking left button.Problem is that I can see only last position of oval (when it already stops).I think repaint method in while block doesnt work as I would like.I would like to see each move of oval as its getting into position of cursor.Thank you for suggestions.
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.*;
public class Testfile extends JPanel implements Runnable,MouseListener{
public static JFrame frame;
public int x;
public int y;
public int pointX;
public int pointY;
public void paintComponent(Graphics g){
super.paintComponent(g);
g.fillOval(x, y, 20, 20);
}
public static void main(String args[])throws InterruptedException{
Testfile z=new Testfile();
z.setBackground(Color.cyan);
frame=new JFrame("Test");
frame.setSize(500,500);
frame.add(z);
frame.addMouseListener(z);
frame.setVisible(true);
}
public void mouseClicked(MouseEvent e){
pointX=(int)MouseInfo.getPointerInfo().getLocation().getX();
pointY=(int)MouseInfo.getPointerInfo().getLocation().getY();
try{
while(x!=pointX){
x=x+1;
Thread.sleep(10);
repaint();
}
}
catch(InterruptedException v){System.out.println(v);}
}
.I think repaint method in while block doesnt work as I would like
Your problem has nothing to do with repaint "not working" and all to do with your tying up the Swing event thread. If you run a long-running process on the Swing Event Dispatch Thread (or EDT), the thread responsible for painting the GUI and interacting with the user, your GUI freezes and won't paint itself or respond until the EDT is released.
Solution: don't use a while (true) loop or Thread.sleep(...) on the Swing event thread. Also:
Use a Swing Timer instead to act as your animation "loop".
Another possible solution is to use a background thread to do the Thread.sleep(...), but in my opinion this is not worth the trouble since a Swing Timer will work so well and is easier to implement correctly.
Also:
Don't add your MouseListener to the JFrame but rather to your drawing JPanel. Otherwise you'll find that you'll be off in the y direction by the height of the title bar.
Use the mousePressed(...) method not mouseClicked(...) since the former is more forgiving.
Get the deltaX and deltaY on mousePressed, the direction that the circle should go by subtracting x from pointX and y from pointY.
I've gotten your code to work by checking the Manhattan distance between x and y and pointX (manHattanDistance = Math.abs(x - pointX) + Math.abs(y - pointY);) and pointY, and stopping the timer if it gets below a minimum. I've also saved the prior Manhattan distance and have checked the differences between the old and new one to make sure that the oval doesn't over-shoot, kind of as a fail-safe.
Use doubles to hold your x, y, pointX, pointY, etc, and cast to int when drawing.
Don't forget to cast your Graphics object to a Graphics2D and use RenderingHints to turn antialiasing on. This will make for prettier graphics.
Avoid "magic" numbers. Use constants instead.
Consider using x and y for the center of your circle rather than the left upper corner.
For example, my paintComponent(...) method could look like:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
// RADIUS is an int const and = 10
g.fillOval((int) x - RADIUS, (int) y - RADIUS, 2 * RADIUS, 2 * RADIUS);
}
As above. you need to do these things on separate threads.
Sometimes you will need to decrement x so check if it's already bigger than the the point
clicked, or the program will keep incrementing it indefinitely. Also you'll probably want to do the same with y