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();
Related
I want to show an image (in a JPanel) that must update quickly (about 30 fps). I also want to keep my CPU usage as low as possible.
Each image update will consist of either:
Moving a block of pixels to a new location
Replacing a block of pixels with a new block
The first solution that came to mind was something like this:
private BufferedImage screen;
public void runBlockUpdate(int x, int y, int pieceWidth, int pieceHeight byte[] piece){
ImageIcon imgPiece = new ImageIcon(piece);
Graphics2D g = screen.createGraphics();
g.drawImage(imgPiece.getImage(), x, y, pieceWidth, pieceHeight, null);
repaint();
}
#Override
public void paintComponent(Graphics g) {
g.drawImage(screen, 0, 0, (int) (screenRect.width * screenScale), (int) (screenRect.height * screenScale), this);
}
My main performance concern regards the paint() method. I want to know if there are any more efficient ways of doing this before I fully implement this technique.
You can try to use a swing timer an call the refresh method(JFrame.repaint()) every 30millisec
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
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
EDIT TWO
To prevent snarky comments and one-line answers missing the point: IFF it is as simple as calling setDoubleBuffered(true), then how do I get access to the current offline buffer so that I can start messing with the BufferedImage's underlying pixel databuffer?
I took the time to write a running piece of code (which looks kinda fun too) so I'd really appreciate answers actually answering (what a shock ;) my question and explaining what/how this is working instead of one-liners and snarky comments ;)
Here's a working piece of code that bounces a square across a JFrame. I'd like to know about the various ways that can be used to transform this piece of code so that it uses double-buffering.
Note that the way I clear the screen and redraw the square ain't the most efficient but this is really not what this question is about (in a way, it's better for the sake of this example that it is somewhat slow).
Basically, I need to constantly modify a lot pixels in a BufferedImage (as to have some kind of animation) and I don't want to see the visual artifacts due to single-buffering on screen.
I've got a JLabel whose Icon is an ImageIcon wrapping a BufferedImage. I want to modify that BufferedImage.
What has to be done so that this becomes double-buffered?
I understand that somehow "image 1" will be shown while I'll be drawing on "image 2". But then once I'm done drawing on "image 2", how do I "quickly" replace "image 1" by "image 2"?
Is this something I should be doing manually, like, say, by swapping the JLabel's ImageIcon myself?
Should I be always drawing in the same BufferedImage then do a fast 'blit' of that BufferedImage's pixels in the JLabel's ImageIcon's BufferedImage? (I guess no and I don't see how I could "synch" this with the monitor's "vertical blank line" [or equivalent in flat-screen: I mean, to 'synch' without interfering with the moment the monitor itselfs refreshes its pixels, as to prevent shearing]).
What about the "repaint" orders? Am I suppose to trigger these myself? Which/when exactly should I call repaint() or something else?
The most important requirement is that I should be modifying pixels directly in the images's pixel databuffer.
import javax.swing.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
public class DemosDoubleBuffering extends JFrame {
private static final int WIDTH = 600;
private static final int HEIGHT = 400;
int xs = 3;
int ys = xs;
int x = 0;
int y = 0;
final int r = 80;
final BufferedImage bi1;
public static void main( final String[] args ) {
final DemosDoubleBuffering frame = new DemosDoubleBuffering();
frame.addWindowListener(new WindowAdapter() {
public void windowClosing( WindowEvent e) {
System.exit(0);
}
});
frame.setSize( WIDTH, HEIGHT );
frame.pack();
frame.setVisible( true );
}
public DemosDoubleBuffering() {
super( "Trying to do double buffering" );
final JLabel jl = new JLabel();
bi1 = new BufferedImage( WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB );
final Thread t = new Thread( new Runnable() {
public void run() {
while ( true ) {
move();
drawSquare( bi1 );
jl.repaint();
try {Thread.sleep(10);} catch (InterruptedException e) {}
}
}
});
t.start();
jl.setIcon( new ImageIcon( bi1 ) );
getContentPane().add( jl );
}
private void drawSquare( final BufferedImage bi ) {
final int[] buf = ((DataBufferInt) bi.getRaster().getDataBuffer()).getData();
for (int i = 0; i < buf.length; i++) {
buf[i] = 0xFFFFFFFF; // clearing all white
}
for (int xx = 0; xx < r; xx++) {
for (int yy = 0; yy < r; yy++) {
buf[WIDTH*(yy+y)+xx+x] = 0xFF000000;
}
}
}
private void move() {
if ( !(x + xs >= 0 && x + xs + r < bi1.getWidth()) ) {
xs = -xs;
}
if ( !(y + ys >= 0 && y + ys + r < bi1.getHeight()) ) {
ys = -ys;
}
x += xs;
y += ys;
}
}
EDIT
This is not for a full-screen Java application, but a regular Java application, running in its own (somewhat small) window.
---- Edited to address per pixel setting ----
The item blow addresses double buffering, but there's also an issue on how to get pixels into a BufferedImage.
If you call
WriteableRaster raster = bi.getRaster()
on the BufferedImage it will return a WriteableRaster. From there you can use
int[] pixels = new int[WIDTH*HEIGHT];
// code to set array elements here
raster.setPixel(0, 0, pixels);
Note that you would probably want to optimize the code to not actually create a new array for each rendering. In addition, you would probably want to optimized the array clearing code to not use a for loop.
Arrays.fill(pixels, 0xFFFFFFFF);
would probably outperform your loop setting the background to white.
---- Edited after response ----
The key is in your original setup of the JFrame and inside the run rendering loop.
First you need to tell SWING to stop Rasterizing whenever it wants to; because, you'll be telling it when you're done drawing to the buffered image you want to swap out in full. Do this with JFrame's
setIgnoreRepaint(true);
Then you'll want to create a buffer strategy. Basically it specifies how many buffers you want to use
createBufferStrategy(2);
Now that you tried to create the buffer strategy, you need to grab the BufferStrategy object as you will need it later to switch buffers.
final BufferStrategy bufferStrategy = getBufferStrategy();
Inside your Thread modify the run() loop to contain:
...
move();
drawSqure(bi1);
Graphics g = bufferStrategy.getDrawGraphics();
g.drawImage(bi1, 0, 0, null);
g.dispose();
bufferStrategy.show();
...
The graphics grabbed from the bufferStrategy will be the off-screen Graphics object, when creating triple buffering, it will be the "next" off-screen Graphics object in a round-robin fashion.
The image and the Graphics context are not related in a containment scenario, and you told Swing you'd do the drawing yourself, so you have to draw the image manually. This is not always a bad thing, as you can specify the buffer flipping when the image is fully drawn (and not before).
Disposing of the graphics object is just a good idea as it helps in garbage collection. Showing the bufferStrategy will flip buffers.
While there might have been a misstep somewhere in the above code, this should get you 90% of the way there. Good luck!
---- Original post follows ----
It might seem silly to refer such a question to a javase tutorial, but have you looked into BufferStrategy and BufferCapatbilites?
The main issue I think you are encountering is that you are fooled by the name of the Image. A BufferedImage has nothing to do with double buffering, it has to do with "buffering the data (typically from disk) in memory." As such, you will need two BufferedImages if you wish to have a "double buffered image"; as it is unwise to alter pixels in image which is being shown (it might cause repainting issues).
In your rendering code, you grab the graphics object. If you set up double buffering according to the tutorial above, this means you will grab (by default) the off-screen Graphics object, and all drawing will be off-screen. Then you draw your image (the right one of course) to the off-screen object. Finally, you tell the strategy to show() the buffer, and it will do the replacement of the Graphics context for you.
Generally we use Canvas class which is suitable for animation in Java. Anyhoo, following is how you achieve double buffering:
class CustomCanvas extends Canvas {
private Image dbImage;
private Graphics dbg;
int x_pos, y_pos;
public CustomCanvas () {
}
public void update (Graphics g) {
// initialize buffer
if (dbImage == null) {
dbImage = createImage (this.getSize().width, this.getSize().height);
dbg = dbImage.getGraphics ();
}
// clear screen in background
dbg.setColor (getBackground ());
dbg.fillRect (0, 0, this.getSize().width, this.getSize().height);
// draw elements in background
dbg.setColor (getForeground());
paint (dbg);
// draw image on the screen
g.drawImage (dbImage, 0, 0, this);
}
public void paint (Graphics g)
{
g.setColor (Color.red);
g.fillOval (x_pos - radius, y_pos - radius, 2 * radius, 2 * radius);
}
}
Now you can update the x_pos and y_pos from a thread, followed by the 'repaint' call on the canvas object. The same technique should work on a JPanel as well.
What you want is basically impossible in windowed mode with Swing. There is no support for raster synchronization for window repaints, this is only available in fullscreen mode (and even then may not be supported by all platforms).
Swing components are double-buffered by default, that is they will do all the rendering to an intermediate buffer and that buffer is then finally copied to the screen, avoiding flicker from background clearing and then painting on top of it.
And thats the only strategy that is reasonable well supported on all underlying platforms. It avoids only repaint flickering, but not visual tearing from moving graphic elements.
A reasonably simple way of having access to the raw pixels of an area fully under you control would be to extend a custom component from JComponent and overwrite its paintComponent()-method to paint the area from a BufferedImage (from memory):
public class PixelBufferComponent extends JComponent {
private BufferedImage bufferImage;
public PixelBufferComponent(int width, int height) {
bufferImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
setPreferredSize(new Dimension(width, height));
}
public void paintComponent(Graphics g) {
g.drawImage(bufferImage, 0, 0, null);
}
}
You can then manipulate you buffered image whichever way you desire. To get your changes made visible on screen, simply call repaint() on it. If you do the pixel manipulation from a thread other than the EDT, you need TWO buffered images to cope with race conditions between the actual repaint and your manipulation thread.
Note that this skeleton will not paint the entire area of the component when used with a layout manager that stretches the component beyond its preferred size.
Note also, the buffered image approach mostly only makes sense if you do real low level pixel manipulation via setRGB(...) on the image or if you directly access the underlying DataBuffer directly. If you can do all the manipulations using Graphics2D's methods, you could do all the stuff in the paintComponent method using the provided graphics (which is actually a Graphics2D and can be simply casted).
Here's a variation in which all drawing takes place on the event dispatch thread.
Addendum:
Basically, I need to constantly modify a lot pixels in a BufferedImage…
This kinetic model illustrates several approaches to pixel animation.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.*;
import java.awt.image.BufferedImage;
/** #see http://stackoverflow.com/questions/4430356 */
public class DemosDoubleBuffering extends JPanel implements ActionListener {
private static final int W = 600;
private static final int H = 400;
private static final int r = 80;
private int xs = 3;
private int ys = xs;
private int x = 0;
private int y = 0;
private final BufferedImage bi;
private final JLabel jl = new JLabel();
private final Timer t = new Timer(10, this);
public static void main(final String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DemosDoubleBuffering());
frame.pack();
frame.setVisible(true);
}
});
}
public DemosDoubleBuffering() {
super(true);
this.setLayout(new GridLayout());
this.setPreferredSize(new Dimension(W, H));
bi = new BufferedImage(W, H, BufferedImage.TYPE_INT_ARGB);
jl.setIcon(new ImageIcon(bi));
this.add(jl);
t.start();
}
#Override
public void actionPerformed(ActionEvent e) {
move();
drawSquare(bi);
jl.repaint();
}
private void drawSquare(final BufferedImage bi) {
Graphics2D g = bi.createGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, W, H);
g.setColor(Color.blue);
g.fillRect(x, y, r, r);
g.dispose();
}
private void move() {
if (!(x + xs >= 0 && x + xs + r < bi.getWidth())) {
xs = -xs;
}
if (!(y + ys >= 0 && y + ys + r < bi.getHeight())) {
ys = -ys;
}
x += xs;
y += ys;
}
}
I have made a custom component (derived from JComponent) which represents
a draggable Bezier-curve.
(looks like a hanging cable, someone might know it
from Bender or Cubase)
My problem is: The curve may become really long,
let's say from top left to bottom right corners of the desktop.
This makes Swing's repaint functionality inefficient:
The area of the curve is perhaps few hundred pixels, but the area of
the component (being mostly 'transparent') is millions of pixels big.
My subjection impression is:
The longer the curve, the more flicker I get when dragging it.
I hope I made myself clear about the problem.
Perhaps it would help when I somehow could choose by myself, which regions
of the component needs repainting at all.
EDIT:
Such a mess! I'm profiling the application using Netbeans, which helps to
find inefficient code normally, but this Swing framework is making hundreds
of nested calls! I just can't figure out, what is slow and why.
By the way, disabling super.paint(...) or super.paintComponent(...) doesn't help.
Check out Filthy Rich Clients by Chet Haase and Romain Guy. They address these very optimizations among others along the way to producing responsive and graphically impressive UI.
Doing all of your bezier mathematics on the paint thread everytime the component is refreshed is (as you've gathered) a bad idea. Does your curve change often? If not then why not paint it to a BufferedImage as and when it changes, and change your paint() code to simply draw the buffered image to the component instead.
class CurveComponent extends JComponent {
private BufferedImage image;
#Override
public void paintComponent( Graphics g ) {
if ( image == null ) {
return;
}
g.drawImage( image, 0, 0, this );
}
private void updateCurve() {
image = new BufferedImage( getWidth(), getHeight(), BufferedImage.ARGB );
Graphics g = image.getGraphics();
// draw the curve onto image using g.
g.dispose();
}
}
Only call updateCurve() when you need to and all that expensive mathematics won't be needlessly repeated. Painting should be pretty responsive, even for a fullscreen window. drawImage() will be doing a straightforward memory copy and should be lightning fast.
Try writing a tiny test app, which consists of nothing except what you need to reproduce this problem. This will make profiling easier. Then post that app here, so we can take a look at possible solutions.
I found your question interesting so I wrote a test app myself. This draws a Bezier curve which is continually resized as you drag. I created a gradient background to ensure this works well with a nasty background. I get good performance and low flicker, although I use top-notch machine.
It pays to read "Filthy Rich Clients" to learn all the tricks of writing custom Swing components that perform really well.
import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.CubicCurve2D;
import java.awt.geom.Point2D;
public class CustomComponent extends JComponent {
private Point2D start = new Point2D.Double(0, 0);
private Point2D end = new Point2D.Double(300, 200);
private CustomComponent() {
this.setOpaque(true);
final MouseAdapter mouseAdapter = new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
setEnd(e.getPoint());
}
};
this.addMouseListener(mouseAdapter);
this.addMouseMotionListener(mouseAdapter);
}
public void setStart(Point2D start) {
this.start = start;
repaint();
}
public void setEnd(Point2D end) {
this.end = end;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
final Graphics2D g2 = (Graphics2D) g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
// draw gradient background
final int width = getWidth();
final int height = getHeight();
g2.setPaint(new GradientPaint(0, 0, Color.WHITE, width, height, Color.YELLOW));
g2.fillRect(0, 0, width, height);
// draw Bezier curve
final Shape shape = new CubicCurve2D.Double(start.getX(), start.getY(), start.getX(), end.getY(), end.getX(), start.getY(), end.getX(), end.getY());
g2.setColor(Color.BLACK);
g2.draw(shape);
g2.drawString("Click and drag to test for flickering", 100, 20);
}
public static void main(String[] args) {
final CustomComponent component = new CustomComponent();
final Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
final Dimension size = new Dimension(screenSize.width - 20, screenSize.height - 100);
component.setPreferredSize(size);
final JFrame frame = new JFrame();
frame.add(component);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
}
Some things to note:
only overwrite paintComponent(Graphics g), not the other paintXXX() methods
set custom component to opaque if possible
only use repaint() to request repainting. Never directly order a repaint directly in your code. This lets Swing handle it well.
There is no efficient way to create lots of small clip rectangles for a diagonal structure which leaves you with two strategies to avoid flickering:
Double buffering. This needs an enormous amount of memory but the memory copy is very fast (it usually happens in the time the "electron beam" goes back from lower right to upper left ... if there was still a beam in your LCD).
Don't call super.paint() (which draws or "erases" the background) and draw the curve a second time with the background color to erase it.
For more details, see this document.
[EDIT] If fillRect() wasn't abstract, you could set a break point :) Set a break point in paint(), check who calls it and whether the background got cleared at that time. It should be since rendering would be completely wrong. Then set break points further up in the call chain.
You can redraw a smaller portion of the screen using repaint(Rectangle r)
http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/JComponent.html#repaint(java.awt.Rectangle)
Then you mention flicker. Since you are using swing, which uses double buffering your flickering must be coming from something else. Are you clearing the screen in paintComponent(...)? I.e. call to fillRect(...)? Don't do that, it's not needed (IIRC).
Which method do yo use to paint your curve? paint or paintComponent?
My solution was a partial re-design:
Now I don't represent each "cable"-element by a component.
Now, cables are just dummy objects (with no involved JComponent).
The repaint takes place "globally", on the content pane of the parent JFrame.
Now it's efficient, and flickers less.
just use getVisibleRect(); inside paintComponent(Graphics g) to get the area you actually need to redraw