Most efficient way to render a dynamic image - java

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

Related

How can I create a Rectangle with a blend mode using a Graphics2D object in Java?

I am attempting to create a day/night cycle in a simple RPG-style game with Java. I have reached a point where I have included a low-alpha color overlay will change hue over time to simulate this:
// Render the overlay rectangle.
public void render(Graphics2D g) {
// Set the color to yellow with ~50% opacity.
g.setColor(new Color(255, 255, 0, 125));
// Draw the rectangle.
g.fillRect(0, 0, handler.getWidth(), handler.getHeight());
g.dispose();
}
While this works, I would like to introduce blending modes (i.e. add, subtract, screen, soft light) into my code to increase the day/night cycle's realism.
Here's what I have tried, but to no avail:
Researching JavaFX's blending mode options. I don't want to switch to JavaFX.
Creating an all-white image that overlays the screen and uses setComposite() and setXORMode() from a different StackOverflow answer.
Using an image with a BlendingMode and setComposite() from the package org.jdesktop.swingx.graphics.BlendComposite.
My desired code is something similar to what follows, but I will take what I can get.
public void render(Graphics2D g) {
g.setColor(new Color(255, 255, 0, 125));
g.setBlendMode(ADD);
g.fillRect(0, 0, handler.getWidth(), handler.getHeight());
g.dispose();
}
One possibility to solve this would be to buffer your screen in a BufferedImage and then implement your own drawing methods(This definitely isn't a fast way to do this as it throws away all hardware-acceleration. If you need to render a big screen this can be laggy depending on your system and your implementation.):
class MyGraphics {
public enum BlendingMode {
NORMAL, ADD, …
}
public BufferedImage buffer;
private BlendingMode bm = BlendingMode.NORMAL;
private int color = 0;
public MyGraphics(BufferedImage buf) {
buffer = buf; // Initialize it with a bufferedImage of your panel size.
}
public void setBlendMode(BlendingMode b) {
bm = b;
}
private int applyBlendingMode(int imgColor1, int newColor) {
int rImg, rNew, gImg, gNew, bImg, bNew, alphaImg, alphaNew; // You can intialize those using bitshifts from the color integers.
switch(bm) {
// Implement all your blending modes here.
}
// And don't forget to return the result!
}
public void setColor(int c) {
color = c;
}
public void fillRect(int x0, int y0, int width, int height) {
for(int x = x0; x < x0+width; x++) {
for(int y = y0; y < y0+height; y++) {
buffer.setRGB(x, y, applyBlendingMode(buffer.getRGB(x, y), color));
}
}
}
}
If the setRGB and getRGB are working to slow for you, you might speed it up by working directly on the BufferedImage's data array(see here for further info).
Also keep in mind, that if you want to use normal blending mode, you can still do it the normal way using buffer.getGraphics() to get your Graphics object. This should be faster in most cases.
After you are done with implementing your blending-modes and maybe some further functionality to draw more then a rectangle you can just simply draw the underlying BufferedImage on your screen using Graphics.drawImage(buffer, 0, 0);

Graphics.drawImage() consumes a lot of memory drawing an int[] image

I have an ImageViewComponent that extends JComponent. This component is added to a JPanel that is added to a JFrame. From another class I regularly update the int[] image field of ImageViewComponent from another class.
The problem is that this process eats up a lot of memory. It even consumes so much memory (+/- 130 MB after a few seconds according to JProfiler, and eventually it surpasses 1GB) that the entire program undergoes a 'lag spike' during garbage collection (the lag in the program happens at the same time that the memory is cleared).
This is the code of ImageViewComponent:
public class ImageViewComponent extends JComponent {
private int image_width, image_height, updateInterval, updateCounter;
private int[] imageArray;
private BufferedImage currentDisplayedImage;
private Image scaledDisplayedImage;
/**
* #param width The width of this component
* #param height The height of this component
* #param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.currentDisplayedImage = null;
this.scaledDisplayedImage = null;
}
public void setImage(int[] image, int width, int height) {
this.imageArray = image;
this.image_width = width;
this.image_height = height;
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (image_width == 0 || image_height == 0)
return;
else if (updateCounter != updateInterval && currentDisplayedImage != null) {
g.drawImage(scaledDisplayedImage, 0, 0, this);
updateCounter++;
return;
}
this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB);
this.currentDisplayedImage.setRGB(0, 0, image_width, image_height, this.imageArray, 0, image_width);
this.scaledDisplayedImage = this.currentDisplayedImage.getScaledInstance(this.getPreferredSize().width,
this.getPreferredSize().height, BufferedImage.SCALE_DEFAULT);
g.drawImage(scaledDisplayedImage, 0, 0, this);
// reset update counter
updateCounter = 0;
}
}
JProfiler states that 70% of the program its active memory is allocated in this class, 50% is in Graphics.drawImage while 20% is in BufferedImage initialization.
I have tried fixing it by putting the line this.currentDisplayedImage = new BufferedImage(image_width, image_height, BufferedImage.TYPE_INT_RGB) in `setImage' and have it only set it once with a boolean flag but this makes the drawn image turn completely black for short amounts of time once in a while, nor does it fix the memory problem.
I also tried this suggestion, which didn't work either.
How can I fix this memory issue?
There are several issues with the code. Some refer to performance, others to style or best practices, and others (at least potentially) refer to memory consumption.
Performance: The getScaledInstance method is distressingly slow. See https://stackoverflow.com/a/32278737/3182664 and others for better alternatives
Style: It's imageWidth, not image_width
Best practices: For a JComponent, you usually, you override paintComponent and not paint
Memory consumption: That's the main point...:
As MadProgrammer already pointed out: Do things as rarely as possible. The role and purpose of this updateCounter is not entirely clear. I think that the responsibility for updating the image less frequently should be in the class that uses your component - particularly, in the class that calls updateImage (which should simply be done less often). Maintaining this in the paint method is not very reliable.
In your current code, it seems like the currentDisplayedImage is (despite its name) neither displayed nor used in any other way. It may, however, be a good idea to keep it: It will be needed to be filled with the int[] data, and as a source for the scaled image that might have to be created.
One possible implementation of your class might look as follows:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
public class ImageViewComponent extends JComponent {
private int updateInterval, updateCounter;
private BufferedImage fullImage;
private BufferedImage displayedImage;
/**
* #param width The width of this component
* #param height The height of this component
* #param ui The higher, the less frequent the image will be updated
*/
public ImageViewComponent(int width, int height, int ui) {
setPreferredSize(new Dimension(width, height));
this.updateInterval = ui;
this.updateCounter = 0;
this.fullImage = null;
this.displayedImage =
new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
}
public void setImage(int[] image, int width, int height) {
// Note: The updateInvervall/updateCounter stuff COULD
// probably also be done here...
if (fullImage == null ||
fullImage.getWidth() != width ||
fullImage.getHeight() != height)
{
fullImage = new BufferedImage(
width, height, BufferedImage.TYPE_INT_RGB);
}
fullImage.setRGB(0, 0, width, height, image, 0, width);
scaleImage(fullImage, displayedImage);
repaint();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(displayedImage, 0, 0, this);
}
private static BufferedImage scaleImage(
BufferedImage input, BufferedImage output)
{
double scaleX = (double) output.getWidth() / input.getWidth();
double scaleY = (double) output.getHeight() / input.getHeight();
AffineTransform affineTransform =
AffineTransform.getScaleInstance(scaleX, scaleY);
AffineTransformOp affineTransformOp =
new AffineTransformOp(affineTransform, null);
return affineTransformOp.filter(input, output);
}
}
but note that this does not do this "updateInterval" handling, for the reason mentioned above.
And a side note: Maybe you don't even have to scale the image. If your intention is to have the image always being displayed at the size of the component, then you can simply do
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// Draw the FULL image, which, regardless of its size (!)
// is here painted to just fill this component:
g.drawImage(fullImage, 0, 0, getWidth(), getHeight(), null);
}
Usually, drawing a scaled image like this is pretty fast. But depending on many factors, separating the step of scaling and painting the image, like you did, may also be a reasonable option.

Good practice for drawing and zooming image?

I have a JScrollPane with a JPanel where I can draw by mouse and code.
I need the possibility to zoom on details in my drawing.
But soon I get a outOfMemoryError. I think because I make my drawing to big while zooming.
This is my code:
private BufferedImage _bufferedImage;
private int _panelWidth = 2000, _panelHeight = 1500;
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
if(_bufferedImage != null){
g.drawImage(_bufferedImage, 0, 0, this);
}
}
public void draw(float zoomFactor){
try {
int width = (int)(_panelWidth * zoomFactor);
int height = (int)(_panelHeight * zoomFactor);
_bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = _bufferedImage.createGraphics();
g2.setBackground(Color.WHITE);
g2.setPaint(Color.BLACK);
g2.scale(zoomFactor, zoomFactor);
drawHouse(g2); ...
g2.dispose();
}
catch (Exception e) {
e.printStackTrace();
}
repaint();
}
There must be better practice then what I did.
I can just draw the area of the scrollpane, but then I can't use the scrollbars,
then I have to use buttons with arrow up, right, left, down to scroll in my drawing.
Anyone who can me give a hint?
but then I can't use the scrollbars
Scrollbars work when the preferred size of the component is greater than the size of the scrollpane. If you are zooming the image in the paintComponent() method then you would also need to override the getPreferredSize() method to return the appropriate size of the image that takes into account the zooming factor.
So in your case the preferred size would be the size of your image.
If you want to zoom in, I am assuming you are no trying to make "bigger pixels", but to draw the same figures at a higher scale. In that case, you should not be using a BufferedImage at all -- instead, you should draw to a suitably scaled JPanel or similar. You can always take a snapshot of whatever you are rendering whenever you need it; but rendering to a BufferedImage without need is wasteful (of time and memory).
See this answer for details.

How to fade in graphics.fill call

I'm doing the following to a Canvas object.
graphics.setColor(BLUE);
graphics.fill(new Rectangle2D.Double(x, y, width, height));
I'd like to fade in the fill colour to create a smooth transition from the canvas background colour to the new colour (and possibly fade out whatever colour was originally there).
I've played with this kind of thing (setting the graphics object's composite to an AlphaComposite which a Timer updating the alpha value every n milliseconds) but I get flickering.
I'm wondering what general concept I'm missing.
Thanks for any pointers.
First of all, how could you be using the AWT? It is quite outdated. I reccomend you switch to swing, mainly because swing has double buffering, which would remove your flicker.
Your application does exactly what you tell it to do. If you want to make a fade-in effect, you have to determine what kind of color changes you want to make, create a function which does it, and implement the fade itself.
I'd approach it like that:
class FadeEffect{
int totalDurationMs;
int elapsedDurationMs;
Color initialColor;
Color finalColor;
Color getColor(int durationDelta) {
elapsedDurationMs += durationDelta;
if (elapsedDurationMs > totalDurationMs) {
return finalColor;
}
double progress = 1.0d*elapsedDurationMs/totalDurationMs;
return new Color( (int)(finalColor.getRed()-initialColor.getRed())*progress,
(int)(finalColor.getGreen()-initialColor.getGreen())*progress,
(int)(finalColor.getBlue()-initialColor.getBlue())*progress);
}
//getters, setters, etc
}
As for the flickering issue: make sure you are using double buffering - either in your component, or by manually drawing on a off-screen buffer (image) and only posting the image to the screen when the drawing is complete.
Here is a sample code from my Graphic2D app doing the double buffering:
private VolatileImage vImg;
#Override
public void paint(Graphics g) {
if (gc==null) gc = this.getGraphicsConfiguration();
do {
boolean sizeChanged = false;
sizeChanged = (vImg!=null&&(vImg.getWidth()!=getWidth()|| vImg.getHeight()!=getHeight()));
if (vImg == null || vImg.validate(gc) == VolatileImage.IMAGE_INCOMPATIBLE
|| sizeChanged) {
vImg = gc.createCompatibleVolatileImage(getWidth(), getHeight());
vImg.setAccelerationPriority(1);
}
final Graphics gimg = vImg.getGraphics();
if (gimg instanceof Graphics2D) {
renderContents((Graphics2D) gimg);
gimg.dispose();
g.drawImage(vImg, 0, 0, null);
} else {
throw new UnsupportedOperationException("Rendering impossible, graphics are not of Graphics2D class");
}
} while (vImg.contentsLost());
updateAnimationNo();
}

Java: how to do double-buffering in Swing?

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;
}
}

Categories