I have a JFrame in my Java application that contains a JPanel where I have some drawing objects created at run-time. The problem is while scrolling the JF
There are my little mods on mKorbel's answer, thanks to him and Gilbert Le Blanc :
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.LayoutManager;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.lang.ref.WeakReference;
import java.util.Random;
import javax.swing.Scrollable;
import javax.swing.SwingConstants;
import javax.swing.Timer;
/**
*
* @author leBenj
*/
public class GJPanelBufferedImageTileAdapter extends GJPanelBufferedImageAdapter implements Scrollable
{
protected BufferedImage _image = null;
protected GIPanelListener _parent = null;
private int TILE_SIZE_W = -1;
private int TILE_SIZE_H = -1;
private int TILE_COUNT_W = 32;
private int TILE_COUNT_H = 32;
private int visibleTiles = 10;
private boolean[][] loading;
private WeakReference[][] subs;
private final Random random;
public GJPanelBufferedImageTileAdapter( final GIPanelListener parent , LayoutManager layout , boolean isDoubleBuffered )
{
super( parent , layout , isDoubleBuffered );
this._parent = parent;
resetTiling();
random = new Random();
}
public void resetTiling()
{
loading = new boolean[TILE_COUNT_W][TILE_COUNT_H];
subs = new WeakReference[TILE_COUNT_W][TILE_COUNT_H];
}
private BufferedImage getTile( int x , int y )
{
BufferedImage retour = null;
if( x < TILE_COUNT_W )
{
if( y < TILE_COUNT_H )
{
if( subs[x][y] != null )
{
retour = subs[x][y].get();
}
}
}
return retour;
}
private void setTile( BufferedImage sub , int x , int y )
{
subs[x][y] = new WeakReference( sub );
}
private boolean loadTile( final int x , final int y )
{
boolean canPaint = ( getTile( x , y ) != null );
if( x < TILE_COUNT_W )
{
if( y < TILE_COUNT_H )
{
if( !canPaint && !loading[x][y] )
{
Timer timer = new Timer( random.nextInt( 500 ) , new ActionListener()
{
@Override
public void actionPerformed( ActionEvent e )
{
BufferedImage sub = _image.getSubimage( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
setTile( sub , x , y );
repaint( x * TILE_SIZE_W , y * TILE_SIZE_H , TILE_SIZE_W , TILE_SIZE_H );
}
} );
timer.setRepeats( false );
timer.start();
}
}
}
return canPaint;
}
// using paint(g) instead of paintComponent(g) to start drawing as soon as the panel is ready
@Override
protected void paint( Graphics g )
{
super.paint( g );
Rectangle clip = g.getClipBounds();
int startX = clip.x - ( clip.x % TILE_SIZE_W );
int startY = clip.y - ( clip.y % TILE_SIZE_H );
int endX = clip.x + clip.width /*- TILE_SIZE_W*/;
int endY = clip.y + clip.height /*- TILE_SIZE_H*/;
for( int x = startX ; x < endX ; x += TILE_SIZE_W )
{
for( int y = startY ; y < endY ; y += TILE_SIZE_H )
{
if( loadTile( x / TILE_SIZE_W , y / TILE_SIZE_H ) )
{
BufferedImage tile = getTile( x / TILE_SIZE_W , y / TILE_SIZE_H );
if( tile != null )
{
g.drawImage( subs[x / TILE_SIZE_W][y / TILE_SIZE_H].get() , x , y , this );
}
}
else
{
g.setColor( Color.RED );
g.fillRect( x , y , TILE_SIZE_W - 1 , TILE_SIZE_H - 1 );
}
}
}
g.dispose(); // Without this, the original view area will never be painted
}
/**
* @param image the _image to set
*/
public void setImage( BufferedImage image )
{
this._image = image;
TILE_SIZE_W = _image.getWidth() / TILE_COUNT_W;
TILE_SIZE_H = _image.getHeight() / TILE_COUNT_H;
setPreferredSize( new Dimension( TILE_SIZE_W * TILE_COUNT_W , TILE_SIZE_H * TILE_COUNT_H ) );
}
@Override
public Dimension getPreferredScrollableViewportSize()
{
return new Dimension( visibleTiles * TILE_SIZE_W , visibleTiles * TILE_SIZE_H );
}
@Override
public int getScrollableBlockIncrement( Rectangle visibleRect , int orientation , int direction )
{
if( orientation == SwingConstants.HORIZONTAL )
{
return TILE_SIZE_W * Math.max( 1 , visibleTiles - 1 );
}
else
{
return TILE_SIZE_H * Math.max( 1 , visibleTiles - 1 );
}
}
@Override
public boolean getScrollableTracksViewportHeight()
{
return false;
}
@Override
public boolean getScrollableTracksViewportWidth()
{
return false;
}
@Override
public int getScrollableUnitIncrement( Rectangle visibleRect , int orientation , int direction )
{
if( orientation == SwingConstants.HORIZONTAL )
{
return TILE_SIZE_W;
}
else
{
return TILE_SIZE_H;
}
}
}
Explanations :
There was a little problem on right and bottom scrolls : to avoid ArrayOutOfBoundsException, I implemented tests on x and y.
I splitted the TILE_SIZE in two parts in order to fit the image proportions.
As I mentioned in the link in my previous comment, coupling the tiles with an array of WeakReference regulates memory usage : I replaced the boolean[][] loaded by WeakReference[][] and implemented the tileGet(x,y) function to get the tile.
The setImage() methods initializes the class fields such as the size of tiles.
The this._image field is inherited from a superclass and is implemented as below :
protected BufferedImage _image = null;
I hope this could help someone.