package rabbitescape.render;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.CoreMatchers.*;
import java.util.ArrayList;
import java.util.List;
import rabbitescape.render.androidlike.Bitmap;
import rabbitescape.render.androidlike.Canvas;
import rabbitescape.render.androidlike.Paint;
import rabbitescape.render.androidlike.Path;
import rabbitescape.render.androidlike.Rect;
import org.junit.*;
public class TestRendering
{
@Test
public void Bitmaps_are_not_rescaled_every_render()
{
TrackingBitmapScaler scaler = new TrackingBitmapScaler();
FakeBitmapLoader loader = new FakeBitmapLoader( 12, 12 );
BitmapCache<FakeBitmap> cache = new BitmapCache<FakeBitmap>(
loader, scaler, Runtime.getRuntime().maxMemory() / 8 );
List<Sprite> sprites1 = sprites( "x", 1, 1, 6, 4 );
List<Sprite> sprites2 = sprites( "x", 1, 1, 6, 4 );
TrackingCanvas output = new TrackingCanvas( 200, 200, loader, scaler );
Renderer<FakeBitmap, FakePaint> renderer =
new Renderer<FakeBitmap, FakePaint>( 0, 0, 16, cache );
// Sanity: no calls to scale yet
assertThat( scaler.scaleCalls.size(), equalTo( 0 ) );
renderer.render( output, sprites1, null );
// Sanity: we had to scale the bitmap
assertThat( scaler.scaleCalls.size(), equalTo( 1 ) );
assertThat( scaler.scaleCalls.get( 0 ), equalTo( 0.5 ) );
// This is what we are testing: render new sprite, same bitmap
renderer.render( output, sprites2, null );
// No new scale call was made
assertThat( scaler.scaleCalls.size(), equalTo( 1 ) );
}
@Test
public void Bitmaps_are_drawn_if_they_overlap_the_canvas()
{
// Exact top left
assertDrawnAt( 0f, 0f, 0, 0, 0, 0, 0, 0, 64, 64, 32, 32, 32 );
assertDrawnAt( 0f, 0f, 1, 1, 0, 0, -32, -32, 64, 64, 32, 32, 32 );
assertDrawnAt( 0f, 0f, 3, 3, 0, 0, -48, -48, 64, 64, 32, 32, 16 );
assertDrawnAt( 0f, 0f, 1, 1, -32, -32, 0, 0, 64, 64, 32, 32, 32 );
assertDrawnAt( 0f, 0f, 3, 2, -96, -64, 0, 0, 64, 64, 32, 32, 48 );
assertDrawnAt( 0f, 0f, 4, 2, -96, -64, -48, 0, 64, 64, 32, 32, 48 );
// Overlap from top left
assertDrawnAt( -6f, -6f, 0, 0, -6, -6, 0, 0, 64, 64, 32, 32, 32 );
assertDrawnAt( -6f, -6f, 0, 0, -4, -4, 0, 0, 64, 64, 32, 32, 48 );
// Overlap from left
assertDrawnAt( -6f, 0f, 0, 0, 0, 0, -6, 0, 64, 64, 32, 32, 32 );
assertDrawnAt( -6f, 0f, 0, 0, -12, 0, 0, 0, 64, 64, 32, 32, 16 );
// Overlap from top
assertDrawnAt( 0f, -6f, 0, 0, 0, 0, 0, -6, 64, 64, 32, 32, 32 );
assertDrawnAt( 0f, -6f, 0, 0, -0, -12, 0, 0, 64, 64, 32, 32, 16 );
// Overlap to bottom right
assertDrawnAt( 0f, 0f, 0, 0, 0, 0, 0, 0, 64, 64, 96, 96, 32 );
assertDrawnAt( 32f, 32f, 1, 1, 0, 0, 0, 0, 64, 64, 64, 64, 32 );
assertDrawnAt( 62f, 62f, 0, 0, 0, 0, 62, 62, 64, 64, 64, 64, 64 );
}
@Test
public void Bitmaps_are_not_drawn_if_they_do_not_overlap_the_canvas()
{
// Just on top left, then off top left
assertDrawnAt( -31f, -31f, 0, 0, 0, 0, -31, -31, 64, 64, 32, 32, 32 );
assertNotDrawn( 0, 0, 0, 0, -32, -32, 64, 64, 32, 32, 32 );
// Again, larger image
assertDrawnAt( -63f, -63f, 0, 0, -32, -32, -31, -31, 64, 64, 64, 64, 32 );
assertNotDrawn( 0, 0, -32, -32, -32, -32, 64, 64, 64, 64, 32 );
// Just on left, then off left
assertDrawnAt( -31f, 0f, 0, 0, 0, 0, -31, 0, 64, 64, 32, 32, 32 );
assertNotDrawn( 0, 0, 0, 0, -32, 0, 64, 64, 32, 32, 32 );
// Just on top, then off top
assertDrawnAt( 0f, -31f, 0, 0, 0, -31, 0, 0, 64, 64, 32, 32, 32 );
assertNotDrawn( 0, 0, 0, -32, 0, 0, 64, 64, 32, 32, 32 );
// Just on bottom right, then off botton right
assertDrawnAt( 63f, 63f, 0, 0, 0, 0, 63, 63, 64, 64, 32, 32, 32 );
assertNotDrawn( 0, 0, 0, 0, 64, 64, 64, 64, 32, 32, 32 );
// Just on bottom right, then off botton right (again)
assertDrawnAt( 63f, 63f, 0, 0, 15, 15, 48, 48, 64, 64, 32, 32, 32 );
assertNotDrawn( 0, 0, 16, 16, 48, 48, 64, 64, 32, 32, 32 );
// Just on bottom right, then off botton right (again)
assertDrawnAt( 63f, 63f, 3, 3, 16, 16, 7, 7, 64, 64, 32, 32, 16 );
assertNotDrawn( 3, 3, 16, 16, 8, 8, 64, 64, 32, 32, 16 );
}
// ---
private void assertNotDrawn(
int tileX,
int tileY,
int spriteOffset32X,
int spriteOffset32Y,
int rendererOffsetX,
int rendererOffsetY,
int canvasSizeX,
int canvasSizeY,
int bitmapWidth,
int bitmapHeight,
int tileSize
)
{
TrackingCanvas output = draw( tileX, tileY, spriteOffset32X,
spriteOffset32Y, rendererOffsetX, rendererOffsetY, canvasSizeX,
canvasSizeY, bitmapWidth, bitmapHeight, tileSize );
assertThat( output.drawCalls.size(), equalTo( 0 ) );
// TODO: they are still loaded at the moment, so that
// we can measure their size. We could at least avoid
// loading them when they are off the right or bottom.
// assertThat( output.loadCalls.size(), equalTo( 0 ) );
}
private void assertDrawnAt(
float expectedDrawX,
float expectedDrawY,
int tileX,
int tileY,
int spriteOffset32X,
int spriteOffset32Y,
int rendererOffsetX,
int rendererOffsetY,
int canvasSizeX,
int canvasSizeY,
int bitmapWidth,
int bitmapHeight,
int tileSize
)
{
TrackingCanvas output = draw( tileX, tileY, spriteOffset32X,
spriteOffset32Y, rendererOffsetX, rendererOffsetY, canvasSizeX,
canvasSizeY, bitmapWidth, bitmapHeight, tileSize );
assertThat( output.drawCalls.get( 0 ).left, equalTo( expectedDrawX ) );
assertThat( output.drawCalls.get( 0 ).top, equalTo( expectedDrawY ) );
assertThat( output.drawCalls.size(), equalTo( 1 ) );
int imgSize = new FakeBitmapLoader( 0, 0 ).sizeFor( tileSize );
assertThat( output.loadCalls.get( 0 ).fileName, equalTo( "x" ) );
assertThat( output.loadCalls.get( 0 ).tileSize, equalTo( imgSize ) );
assertThat( output.loadCalls.size(), equalTo( 1 ) );
if ( tileSize == imgSize )
{
assertThat( output.scaleCalls.size(), equalTo( 0 ) );
}
else
{
assertThat(
output.scaleCalls.get( 0 ),
equalTo( ( double )tileSize / imgSize )
);
assertThat( output.scaleCalls.size(), equalTo( 1 ) );
}
}
private TrackingCanvas draw(
int tileX,
int tileY,
int spriteOffset32X,
int spriteOffset32Y,
int rendererOffsetX,
int rendererOffsetY,
int canvasSizeX,
int canvasSizeY,
int bitmapWidth,
int bitmapHeight,
int tileSize )
{
TrackingBitmapScaler scaler = new TrackingBitmapScaler();
FakeBitmapLoader loader = new FakeBitmapLoader( bitmapWidth, bitmapHeight );
BitmapCache<FakeBitmap> cache = new BitmapCache<FakeBitmap>(
loader, scaler, Runtime.getRuntime().maxMemory() / 8 );
TrackingCanvas output = new TrackingCanvas(
canvasSizeX, canvasSizeY, loader, scaler );
new Renderer<FakeBitmap, FakePaint>(
rendererOffsetX, rendererOffsetY, tileSize, cache
).render(
output,
sprites(
"x",
tileX,
tileY,
spriteOffset32X,
spriteOffset32Y
),
null
);
return output;
}
private List<Sprite> sprites(
String bitmapName,
int tileX,
int tileY,
int offset32X,
int offset32Y
)
{
List<Sprite> ret = new ArrayList<Sprite>();
ret.add( new Sprite(
bitmapName, null, tileX, tileY, offset32X, offset32Y ) );
return ret;
}
private static class FakeBitmap implements Bitmap
{
private final int width;
private final int height;
public FakeBitmap( int width, int height )
{
this.width = width;
this.height = height;
}
@Override
public String name()
{
return null;
}
@Override
public int width()
{
return width;
}
@Override
public int height()
{
return height;
}
@Override
public void recycle()
{
}
@Override
public long getByteCount()
{
return 1;
}
}
private static class FakePaint implements Paint
{
}
private static class TrackingBitmapScaler
implements BitmapScaler<FakeBitmap>
{
public List<Double> scaleCalls = new ArrayList<Double>();
@Override
public FakeBitmap scale( FakeBitmap originalBitmap, double scale )
{
scaleCalls.add( scale );
return originalBitmap;
}
}
public static class TrackingCanvas implements Canvas<FakeBitmap, FakePaint>
{
private static class DrawCall
{
public final float left;
public final float top;
public DrawCall( float left, float top )
{
this.left = left;
this.top = top;
}
}
public final List<DrawCall> drawCalls = new ArrayList<DrawCall>();
public final List<FakeBitmapLoader.LoadCall> loadCalls;
public final List<Double> scaleCalls;
private final int width;
private final int height;
public TrackingCanvas(
int width,
int height,
FakeBitmapLoader loader,
TrackingBitmapScaler scaler
)
{
this.width = width;
this.height = height;
this.loadCalls = loader.loadCalls;
this.scaleCalls = scaler.scaleCalls;
}
@Override
public void drawBitmap(
FakeBitmap bitmap,
float left,
float top,
FakePaint paint
)
{
drawCalls.add( new DrawCall( left, top ) );
}
@Override
public int width()
{
return width;
}
@Override
public int height()
{
return height;
}
@Override
public void drawColor( FakePaint paint )
{
}
@Override
public void drawLine(
float startX,
float startY,
float stopX,
float stopY,
FakePaint paint
)
{
}
@Override
public void drawPath( Path path, FakePaint paint )
{
}
@Override
public void drawRect( Rect rect, FakePaint paint )
{
}
@Override
public void drawText( String text, float x, float y, FakePaint paint )
{
}
}
private static class FakeBitmapLoader implements BitmapLoader<FakeBitmap>
{
private static class LoadCall
{
private final String fileName;
private final int tileSize;
public LoadCall( String fileName, int tileSize )
{
this.fileName = fileName;
this.tileSize = tileSize;
}
}
public final List<LoadCall> loadCalls;
private final int width;
private final int height;
public FakeBitmapLoader( int width, int height )
{
this.loadCalls = new ArrayList<LoadCall>();
this.width = width;
this.height = height;
}
@Override
public FakeBitmap load( String fileName, int tileSize )
{
loadCalls.add( new LoadCall( fileName, tileSize ) );
return new FakeBitmap( width, height );
}
@Override
public int sizeFor( int tileSize )
{
return 32;
}
}
}