Sprite Sheet - new sprite being drawn on top of old sprites - android

Basically I tried to use a sprite sheet for animation. The aim is to count from 1 to 8 with 1 second interval. The numbers are image on a sprite sheet. The problem is once the number is being drawn to the canvas, it just won't go away. New image just drawn on top of the unwanted old image. Like this:
Is there a way to clear what has been drawn, so that new image can be drawn on a clean canvas?
DrawStripFrame Class:
public class DrawStripFrame extends SurfaceView implements Runnable{
/**variables declared here*/
public DrawStripFrame (Context context){
super (context);
this.context = context;
holder = getHolder();
}
public void setDrawStripFrame(Bitmap bitmap, int width, int height, int columns){
this.bitmap = bitmap;
this.width = width;
this.height = height;
this.columns = columns;
}
}
#Override
public void run(){
while(running){
if(!holder.getSurface().isValid())
continue;
Canvas canvas = holder.lockCanvas();
draw(canvas);
holder.unlockCanvasAndPost(canvas);
}
}
public void draw (Canvas canvas){
update();
/**4 being no. of columns and 2 being no. of row*/
int u = (frame % 4)*(width/4);
int v = (frame / 4)*(height/2);
Rect src = new Rect(u,v,u+(width/4),v+(height/2));
Rect dst = new Rect (0,0, 100,100);
Paint paint = new Paint();
canvas.drawBitmap(bitmap, src, dst, paint);
}
public void resume() {
running = true;
gameloop = new Thread(this);
gameloop.start();
}
public void update (){
frame++;
if (frame>10)
frame = 0;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

In your case you could just redraw the canvas with a color like this:
public void draw (Canvas canvas){
update();
// clearing canvas
canvas.drawColor(Color.BLUE); // whatever color you need it to be
/**4 being no. of columns and 2 being no. of row*/
int u = (frame % 4)*(width/4);
int v = (frame / 4)*(height/2);
Rect src = new Rect(u,v,u+(width/4),v+(height/2));
Rect dst = new Rect (0,0, 100,100);
Paint paint = new Paint();
canvas.drawBitmap(bitmap, src, dst, paint);
}

Related

Android Drawing a Smooth Path

I have code that I need to improve.
Here's what's wrong: it's a little slow and choppy, meaning the lines aren't smooth and the drawing is a bit delayed.
public void touchStarted(Point point) {
if (null == drawingModePath) {
drawingModePath = new Path();
}
drawingModePath.moveTo(point.x, point.y);
}
public void touchMoved(Point point) {
drawingModePath.lineTo(point.x, point.y);
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
mainDrawingView.setImageBitmap(bitmap);
// Path
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
canvas.drawPath(drawingModePath, paint);
}
public void touchEnded(Point point) {
touchMoved(point);
}
In essence what this code does is drawing a path based on touchStarted, touchMoved, and touchEnded. If someone can help me optimize this, I'd be grateful. Perhaps if I don't recreate the bitmap each time touchMoved occurs? Not sure here... not sure... I use a UIBezierPath to perform this code on iOS and it's a bit faster (and smoother). Anyway, I come to you for help. Input appreciated.
you are recreating everything every move. that will affect the performance of drawing a lot. the event triggers every 8ms (or 16ms im not sure), imagine you are reinstantiating everything every 8ms? thats tough.
so this must be in the instantiation part
Bitmap bitmap = Bitmap.createBitmap((int) getWindowManager()
.getDefaultDisplay().getWidth(), (int) getWindowManager()
.getDefaultDisplay().getHeight(), Bitmap.Config.ARGB_8888);
canvas = new Canvas(bitmap);
mainDrawingView.setImageBitmap(bitmap);
paint = new Paint();
paint.setStyle(Paint.Style.STROKE);
paint.setColor(Color.WHITE);
The touchMoved() should only record the new path and call the invalidate() to make the View redraw itself resulting in calling the draw method (onDraw()).
public void touchMoved(Point point) {
drawingModePath.lineTo(point.x, point.y);
invalidate();
}
and then implement onDraw() method to do the drawing
Heres how i do the drawing interface in one of my projects:
public class SignatureView extends View {
public SignatureView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
// instantiating my paint object
paint = new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(5);
path = new Path();
}
#Override
protected void onSizeChanged(int xNew, int yNew, int xOld, int yOld)
{
// this is where i initialize my canvas, because in constructor, the view is not completely instantiated yet, so getting the height and width there will result in null exception.
bitmap = Bitmap.createBitmap(xNew, yNew, Bitmap.Config.ARGB_8888);
background_canvas = new Canvas(bitmap);
}
#Override
protected void onDraw(Canvas canvas)
{
// draw the new path to a buffer canvas
background_canvas.drawPath(path, paint);
// put the buffer in the real canvas
canvas.drawBitmap(bitmap, 0, 0, paint);
}
#Override
public boolean onTouchEvent(MotionEvent ev)
{
//this is like your move event, it just records the new path every move.
int action = ev.getActionMasked();
if ( action == MotionEvent.ACTION_DOWN )
{
path.moveTo(ev.getX(), ev.getY());
}
else if ( action == MotionEvent.ACTION_MOVE )
{
path.lineTo(ev.getX(), ev.getY());
// call invalidate() to make the view redraw itself, resulting in calling the onDraw() method.
invalidate();
}
else if ( action == MotionEvent.ACTION_UP )
{
onDone.method();
}
return true;
}
public void clear()
{
background_canvas.drawColor(Color.WHITE);
path.reset();
invalidate();
}
interface OnDone{
void method();
}
public void setOnDone(OnDone new_onDone)
{
onDone = new_onDone;
}
OnDone onDone;
private Paint paint;
private Bitmap bitmap;
private Canvas background_canvas;
private Path path;
public Bitmap getBitmap()
{
return bitmap;
}
}

Drawing multiple lines on canvas with paint with shader cause performance issues

Inside SurfaceView in a separate thread on canvas I draw ~50 bezier curves with Paint that previously been set to have gradient like in the following code:
LinearGradient gradient = new LinearGradient(0, 0, 0, height, Color.BLACK, Color.WHITE, Shader.TileMode.MIRROR);
paint.setShader(gradient);
Setting shader to paint causes serious performance issues - view starts lagging. But if I just comment out setting shader to paint it works brilliant, so the issue is definitely in the gradient.
Thread code:
private class DrawingRunnable implements Runnable {
private final SurfaceHolder surfaceHolder;
private final MyView surfaceView;
public DrawingRunnable(SurfaceHolder surfaceHolder, MyView surfaceView) {
this.surfaceHolder = surfaceHolder;
this.surfaceView = surfaceView;
}
#Override
public void run() {
while (isRunning) {
Canvas canvas = null;
try {
canvas = surfaceHolder.lockCanvas(null);
if (canvas != null) {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
synchronized (surfaceHolder) {
surfaceView.drawMyHeavyView(canvas);
}
}
} catch (Throwable e) {
Timber.w(e, "Error occurred while drawing surface view");
} finally {
if (canvas != null) {
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
}
}
}
Drawing on canvas:
protected void drawMyHeavyView(Canvas canvas) {
for (int index = 0; index < 50; index++) {
path.reset();
path.moveTo(x1, y1);
modifySomeAmplitudeRandomValues(index);
path.quadTo(x3, amplitude[index], x2, y2);
canvas.drawPath(path, paint);
}
}
You should assume that those unknown params (e.g. x1, y1, x2, etc.) are parts of view business logic and aren't valuable for current duscussion.
Can you please advise what's wrong?
P.S.: I'm not allocating LinearGradient in onDraw or so.

Smooth Painting in a SurfaceView

I'm working on a paint-like app, but I've run it to some troubles.
I know I have re-draw all the objects each frame, but this in turn makes performance slow further on when many objects are visible.
I've also noticed that clearing the background only once, then adding objects to be drawn to the surface when painting is active makes the screen flash almost to the point of inducing epilepsy.
So what is the best way to make a paint app 100% smooth no matter how many objects that are drawn?
Epilepsy-inducing code:
public void run()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (drawObjectsToAdd.size() == 0)
continue;
drawing = true;
Canvas c = holder.lockCanvas();
if (redrawBackground)
{
c.drawARGB(255, 255, 255, 255);
redrawBackground = false;
}
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
for (DrawObject draw : drawObjects)
{
draw.Draw(c, paint);
}
holder.unlockCanvasAndPost(c);
drawing = false;
}
}
this adds a new object once it's been added outside of the thread, but it makes the screen flash so much it gives me headaches - and I only redraw the background once.
Smooth at first but becomes laggy after a while, probably due to having to add hundreds and hundreds of objects in the end:
public void run()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (drawObjectsToAdd.size() > 0)
{
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
redraw = true;
}
if (clear)
{
drawObjectsToAdd.clear();
drawObjects.clear();
redraw = true;
clear = false;
}
if (!redraw)
continue;
drawing = true;
Canvas c = holder.lockCanvas();
c.drawARGB(255, 255, 255, 255);
for (DrawObject draw : drawObjects)
{
try
{
draw.Draw(c, paint);
}
catch (Exception ex) { }
}
holder.unlockCanvasAndPost(c);
drawing = false;
redraw = false;
}
}
I, at least for this app wanna store all the objects that are added so it doesn't matter how it's painted as long as it's smooth all the way. Preferably, add a circle - it will render a new Bitmap on to the surface, instead of having to redraw lots of objects each frame - instead store them but do not add objects already drawn.
UPDATE
Pseudo-Code of how I want it to be:
If no background is drawn
Draw background color
If new items have been added
Draw only new items to the background
Store new items in objects list
This way, we'll only draw the background once. When a new item is added, only draw that item to the existing surface. When the objects increases, looping through every item will reduce performance greatly and it will not be pleasant to work with.
UPDATE 2:
private void Draw()
{
while (running)
{
if (!holder.getSurface().isValid())
continue;
if (picture == null)
{
picture = new Picture();
Canvas c = picture.beginRecording(getWidth(), getHeight());
c.drawARGB(255, 255, 255, 255);
picture.endRecording();
}
if (drawObjectsToAdd.size() > 0)
{
drawObjects.addAll(drawObjectsToAdd);
drawObjectsToAdd.clear();
Canvas c = picture.beginRecording(getWidth(), getHeight());
for (DrawObject draw : drawObjects)
{
draw.Draw(c, paint);
}
picture.endRecording();
drawObjects.clear();
}
Canvas c2 = holder.lockCanvas();
c2.drawPicture(picture);
holder.unlockCanvasAndPost(c2);
}
}
This last method from Update 2 makes it render all the lines like the "Snake game" when adding circles. Looks like a moving snake on a background, where some of it's circles disappear one frame and others don't the next. If I skip to redraw each frame, it will instead vary which of these circles that are visible.
what about that Picture implementation? increase MAX_DRAWERS to some reasonable value and see how it works
class SV extends SurfaceView implements SurfaceHolder.Callback, Runnable {
private static final int MAX_DRAWERS = 8;
private boolean mRunning = true;
private List<Picture> mPictures = new LinkedList<Picture>();
private List<Drawer> mDrawers = new LinkedList<Drawer>();
private Paint mPaint;
public SV(Context context) {
super(context);
mPaint = new Paint();
mPaint.setColor(0xffffff00);
getHolder().addCallback(this);
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
new Thread(this).start();
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
#Override
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
mRunning = false;
notify();
}
#Override
public synchronized boolean onTouchEvent(MotionEvent event) {
Drawer drawer = new Drawer(event.getX(), event.getY());
mDrawers.add(drawer);
if (mDrawers.size() > MAX_DRAWERS) {
Picture picture = new Picture();
Canvas canvas = picture.beginRecording(getWidth(), getHeight());
mPaint.setAlpha(0xbb);
for (Drawer dr : mDrawers) {
dr.draw(canvas, mPaint);
}
picture.endRecording();
mPaint.setAlpha(0xff);
mPictures.add(picture);
mDrawers.clear();
Log.d(TAG, "onTouchEvent new Picture");
}
notify();
return false;
}
#Override
public synchronized void run() {
SurfaceHolder holder = getHolder();
while (mRunning) {
// Log.d(TAG, "run wait...");
try {
wait();
} catch (InterruptedException e) {
}
if (mRunning) {
// Log.d(TAG, "run woke up");
Canvas canvas = holder.lockCanvas();
canvas.drawColor(0xff0000ff);
for (Picture picture : mPictures) {
picture.draw(canvas);
}
for (Drawer drawer : mDrawers) {
drawer.draw(canvas, mPaint);
}
holder.unlockCanvasAndPost(canvas);
}
}
Log.d(TAG, "run bye bye");
}
class Drawer {
private float x;
private float y;
public Drawer(float x, float y) {
this.x = x;
this.y = y;
}
public void draw(Canvas canvas, Paint paint) {
canvas.drawCircle(x, y, 8, paint);
}
}
}
You generate your list of objects each and every frame, just to discard it after drawing. Why? Generate your list of draw objects once, then draw them.
That's all you need for your drawing thread...
public void run() {
while (running)
{
Canvas c = holder.lockCanvas();
c.drawARGB(255, 255, 255, 255);
for (DrawObject draw : drawObjects)
{
try
{
draw.Draw(c, paint);
}
catch (Exception ex) { }
}
holder.unlockCanvasAndPost(c);
}
}

canvas zoom in partially

I want to zoom in the canvas board partially as the letter tray button will be fixed and the board will be zoom,
The partially coding is as mention below and the screen is fully draw in canvas which can be viewed as mention at the bottom.. please help and thanks for you concern.
public class BoardView extends SurfaceView implements SurfaceHolder.Callback
{
class DrawingThread extends Thread implements OnTouchListener {
public DrawingThread(SurfaceHolder holder, Handler handler) {
mSurfaceHolder = holder;
public void setRunning(boolean b) {
mRun = b;
}
#Override
public void run() {
while (mRun) {
Canvas c = null;
try {
c = mSurfaceHolder.lockCanvas(null);
synchronized (mSurfaceHolder) {
// System.gc();
// c.scale(canvasScaleX, canvasScaleY);
// c.save();
// c.translate(canvasTranslateX, canvasTranslateY);
doDraw(c);
}
updateGame();
} finally {
if (c != null) {
mSurfaceHolder.unlockCanvasAndPost(c);
}
}
}
private void doDraw(Canvas canvas) {
if (ge == null)
return;
Rect bRect = new Rect(0, 0, dims.getTotalWidth(),
dims.getScoreHeight() + dims.getBoardheight());
Drawable drawable = getResources().getDrawable(R.drawable.board);
drawable.setBounds(bRect);
drawable.draw(canvas);
Rect tRect = new Rect(0, dims.getScoreHeight()
+ dims.getBoardheight(), dims.getTotalWidth(),
dims.getTotalHeight());
canvas.drawRect(tRect, fillTrayPaint);
int topHeight = dims.getScoreHeight() + dims.getBoardheight();
int bottom = (dims.getTotalHeight() + 5)
- (dims.getTotalWidth() / Tray.TRAY_SIZE);
Rect rect = new Rect(0, topHeight, dims.getTotalWidth(), bottom - 7);
Drawable drawableTray = getResources()
.getDrawable(R.drawable.strip);
drawableTray.setBounds(rect);
drawableTray.draw(canvas);
drawTray(canvas);
drawBoard(canvas);
// drawScore(canvas);
drawMovingTile(canvas);
}
public BoardView(Context context, AttributeSet attrs) {
super(context, attrs);
SurfaceHolder holder = getHolder();
holder.addCallback(this);
// endTurn=(ImageButton)findViewById(R.id.endturn_button_horizontal);
thread = new DrawingThread(holder, handler);
}
#Override
public void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
}
#Override
public void surfaceCreated(SurfaceHolder holder) {
final float scale1 = getResources().getDisplayMetrics().density;
defaultFontSize = (int) (MIN_FONT_DIPS * scale1 + 0.5f);
thread.setDefaultFontSize(defaultFontSize);
dimensions = calculateDimensions(getWidth(), getHeight());
thread.setDimensions(dimensions);
thread.setRunning(true);
// if (thread != null && !thread.isAlive())
thread.start();
}
#Override
public void surfaceDestroyed(SurfaceHolder holder) {
boolean retry = true;
thread.setRunning(false);
while (retry) {
try {
thread.join();
retry = false;
} catch (InterruptedException e) {
}
}
}
We can simply do it by canvas.clipRect(rectangle), at this point what we are suppose to do is save the canvas canvas.save() and after canvas.clipRect(rectangle) call canvas.restore().
Consider trying something like this:
Create a bitmap from the canvas you wish to zoom into.
When rendering that bitmap to the screen, you can change the scr rect
(which will "zoom" into the bitmap).
I hope this helps.
I think you need to render your game board into offscreen bitmap and then draw this bitmap at canvas using Matrix, so you will be able to change its position and scale.
I believe the easiest way to do this would be to use Canvas#scale(float scale, int pivotx, int pivoty) method. It essentially grows the entire canvas uniformally which really is what zooming is from a 2D perspective.
canvas.scale(2.0f, getWidth()/2, getHeight()/2) will scale the image by 2 from the center. canvas.scale(1.0f, getWidth()/2, getHeight()/2) will shrink it back.
I have never tried this in this context, so if you decide to try it report back the results please! I'd like to know for future reference.

How to redraw the Android Canvas

Can anyone help me on how to redraw the canvas. I tried many examples and source code from the internet, but it still didn't work on my PC like the invalidate func, canvas.save, canvas.restore, etc. I want to do some translation and scaling for the canvas, but when I follow the step on the internet it shows nothing. This is my source code. (I'm still new to Java/Android programming.)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
drawMaps.j=1;
resources = this.getResources();
try {
GetAttributes("path");
} catch (XmlPullParserException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
SeekBar seekBar = (SeekBar)findViewById(R.id.seekBar1);
panel = new Panel(this);
setContentView(R.layout.main);
panel.onDraw(canvas2);
ImageView image = (ImageView) findViewById(R.id.mapImage);
image.setImageBitmap(bufMaps);
}
class Panel extends View{
Paint paint = new Paint();
public Panel(Context context) {
super(context);
setFocusable(true);
}
public Bitmap quicky_XY(Bitmap bitmap,int pos_x,int pos_y){
Bitmap bufMap = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Config.ARGB_8888);
Canvas canvas = new Canvas(bufMap);
canvas.save();
final Paint paint = new Paint();
width = canvas.getWidth();//start
height = canvas.getHeight();//end
drawMaps.xpos = width / 30;
drawMaps.ypos = height/ 20;
paint.setStrokeWidth(0);
for (int i = 0; i < 30; i++) {
paint.setColor(Color.DKGRAY);
canvas.drawLine(drawMaps.xpos +(drawMaps.xpos*i), 0,
drawMaps.xpos +(drawMaps.xpos*i), height, paint);
//canvas.drawLine(startX, startY, stopX, stopY, paint)
}
for (int i = 0; i < 20; i++) {
paint.setColor(Color.DKGRAY);
canvas.drawLine(0, drawMaps.ypos+(drawMaps.ypos*i),
width, drawMaps.ypos+(drawMaps.ypos*i), paint);
}
canvas.translate(pos_x,pos_y);
drawMaps.addPath(canvas);
canvas.restore();
invalidate();
return bufMap;
}
#Override
public void onDraw(Canvas canvas) {
canvas.save();
bufMaps = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
bufMaps = quicky_XY(emptyBmap,positionX,positionY);
}
}
#Override
public boolean onTouchEvent(MotionEvent event)
{
positionX = (int)event.getRawX();
positionY = (int)event.getRawY();
switch(event.getAction())
{
case MotionEvent.ACTION_DOWN: {
prevX = positionX;
prevY = positionY;
}
break;
case MotionEvent.ACTION_MOVE: {
final int distY = Math.abs(positionY - prevY);
final int distX = Math.abs(positionX - prevX);
if (distX > mTouchSlop || distY > mTouchSlop){
panel.getDrawingCache();
panel.invalidate();
}
Log.e("LSDEBUG", "touch X, " + positionX);
}
break;
}
return true;
}
You do not call onDraw() yourself. Instead, you call to invalidate() and it will make sure onDraw() is called as soon as the it can.
Also, if you are trying to draw on the canvas from outside the onDraw() method, you need to get a reference to the canvas.
Inside your onDraw() the canvas is not being changed. only saved (again, called on invalidate() or whenever the system needs to redraw this View):
#Override
public void onDraw(Canvas canvas) {
canvas.save();
bufMaps = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
bufMaps = quicky_XY(emptyBmap,positionX,positionY);
}
Accessing the canvas from outside the onDraw() is done using a Holder().lockCanvas() to get reference to the canvas. After drawing, you unlock it again, using unlockAndPost() and that's it.
You will also need to implement the Callback.surfaceCreated interface to find out when the Surface is available to use.
Take a look at the android reference for SurfaceHolder.
This post explains it pretty well.

Categories

Resources