I'm creating a home made game engine for Android and I'm having some problems with the reuse of a Bitmap canvas:
Visible game objects receive the main Canvas in each frame so they can draw themselves with a static bitmap or with a sprite. So far everything works fine.
The sprite consists in two bitmaps. First contains the complete sequence of drawings, and the second is dynamically drawn through a Canvas(bitmap). The result bitmap and its canvas are created in the sprite constructor to avoid reallocations in the draw loop.
I reuse this sprites for each object that needs the same sequence of drawings. This sprites also have a function that allows to obtain a bitmap containing a different position of the drawings sequence. So, if the object wants the sprite in another position calls its function to get the Bitmap and draws to the main canvas.
So the problem is that all objects using the same sprite end up drawing the same image.
In the visible game object:
if(spriteDelay > 0) img = sprite.getDelayedBitmap(spriteDelay);
else img = sprite.getCurrentBitmap();
canvas.drawBitmap(img, null, focusLocation, null);
In the sprite:
currentBmpCanvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
currentBmpCanvas.drawBitmap(scaledImage, delayedIni, fin, null);
return currentBmp;
If I create a new result bitmap and a new canvas in the returning function all works fine:
currentBmp = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
currentBmpCanvas = new Canvas(currentBmp);
I assume that all bitmaps are not drawn until onDraw() is finished, so is there some way to solve this without reallocating the bitmap in each loop?
Thanks!
After some hours thinking, I opted to implement a simple solution that didn't cross my mind before.
Like the object game receives the main canvas, why not do the same with the sprite?
So basicly this is the code for the game object:
sprite.smashDelayedCanvas(canvas, focusLocation);
And for the sprite:
canvas.drawBitmap(scaledImage, delayedIni, dest, null);
Related
I'm trying to draw a Bitmap to a Canvas in a custom SurfaceView, but it doesn't show up anywhere.
In the SurfaceView, I'm using:
//in the constructor
Bitmap leftUp = BitmapFactory.decodeResources(R.drawable.leftup);
leftCurrent = leftUp; //it's a button - leftUp and leftDown
//in my drawing method
canvas.drawBitmap(leftCurrent, controlsWidth, controlsHeight, new Paint());
It runs OK, no errors, but nothing shows up. Anyone have any ideas?
The problem is with you paint object that you pass to the drawBitmap method. It must be configured properly. Take a look at this
answer.
I am drawing multiple text on the canvas in my app. For two text its working fine but drawing multiple text/bitmap at the same times it's a big headache for everyone.it takes all GPU process and it hangs at some point of time especially when you draw large bitmap/text.
Is there any way to save previous drawn things to single bitmap?
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (CustomTextview.GetDB().size() != 0) {
for (CustomTextview textview : CustomTextview.GetDB()) {
Bitmap b= drawasbitmap(textview.text,textview.X,textview,Y) //creating a bitmap for view
canvas.drawBitmap(b, textview.X, textview.Y, textview.paint);
b.recycle();
}
canvas.restore();
}
}
Lets say i have 5 bitmaps/text in my app.it should draw 5 text at the same time.
If it's 5 bitmap the memory will go away,is there any way to save all previous view (Bitmap-which is drawn before adding the new one )to one Bitmap ,or can i use drawing cache?
P.S- i have multiple views in my app- i want to draw the recent one for editing purpose .everthing should be saved to temp bitmap and shown to use that views are out there.when ever user clicks that particular region that view get selected called in onDraw
My code here receives a value for bmpX and bmpY and uses them to draw a bitmap at that location on the screen. This works exactly as I want it to, but I would rather use the canvas.translate() function to handle the movement of the image, rather than just canvas.drawBitmap(), because I will be applying other draw elements to the canvas and I want them all to move the same amount and direction.
My question is: where can I move the canvas.drawBitmap() in the code so that it just draws onto the canvas at the start, but it is moved any time after that using canvas.translate() instead? Anywhere I place the code still freezes the image at that point where I draw it, regardless of how much I change the position of canvas.translate(). I'm imaging its possible to "stick" the image to the canvas, so to speak, and then move the canvas, which also moves the image with it.
public void run() {
// TODO Auto-generated method stub
while(isRunning) {
if(!surfaceHolder.getSurface().isValid()) {
continue;
}
canvas = surfaceHolder.lockCanvas();
canvas.drawRGB(255, 255, 255);
canvas.drawBitmap(bmp, bmpX-(bmp.getWidth()/2),
bmpY-(bmp.getHeight()/2), null);
//canvas.translate(bmpX, bmpY);
surfaceHolder.unlockCanvasAndPost(canvas);
}
}
I hope I explained my problem clearly enough. Thanks!
Answering my own question here for clarity, afetr info I got offline. UI cannot be done on a thread like this, I needed to be using an android UI thread instead. runOnUIUpdatethread and async tasks
Need a little help here : I'm out of ideas now...
Here's what I need to do :
Render "base" image [ it is created from an ARGB.8888 byte array ]
user clicks 2 points on the screen; I need to perform the the pixel manipulation on a region of pixels around the path taken from one point to another... [ I need to calculate a squre block of pixel for each pixel on the whole path ].
display the modification of the image as the code progresses in animation form.
I am able to display the whole path; I am able to calculate & manipulate the pixels properly .. But what I am unable to do is show the animation as my code is progressing on the path... with the current implementation I am able to display the whole calculated path at the end ...
public void onDraw(Canvas canvas){
canvas.drawColor(Color.WHITE);
Paint p = new Paint();
canvas.drawBitmap(base,0,0,p);
traverse clickEvent1.x -> clickEvent2.x
traverse for clickEvent1.Y -> clickEvent2.Y
{
newBitMap = calculateNewBitMap(base)
// I nee to redraw Canvas with (newBitMap)
// canvas.drawBitMap(newBitMap);
//Doesn't work
//postInvalidate();
//invalidate()
//AnimationDrawable.addFrame(newBitMap)
// I am not calling start here
//but just wanted to let you know that I do call animation start to display the frames stored in it
// animation.start();
}
// obviously wouldn't work here As it is already out of the loop
//invalidate();
}
Please NOTE :
The newBitMap image is generated at runtime, it would not be available to me beforehand...
I tried invalidate() in the loop as well ; but it would only draw the cumulative result after the whole loop has traversed and not the intermediate states of the newBitMaps.
Performance is of critical importance + I am dealing with HUGE image sizes .. so please keep that in mind as well ... if I create multiple bitmaps for temporary storing the JVM crashes due to "OutofMemory" ..
I tried storing the new Images in "AnimationDrawable" form as well; but tht's not solving the problem as well....
AnimationDrawable animDrawable = new AnimationDrawable();
Drawable frame1 = new BitmapDrawable(newCaclBitMap);
animDrawable.addFrame(frame1, 250);
Thanks for any pointers / suggestions ..
It seems like what you are doing is running all of your drawing logic in a single call to onDraw() when I think what you want is to have onDraw() called once for each frame of your animation.
So, instead of something like this in your trace:
onDraw()
drawFrame()
drawFrame()
drawFrame()
...
You would have this:
onDraw()
drawFrame()
onDraw()
drawFrame()
onDraw()
drawFrame()
...
The CubeLiveWallpaper example has an example of this type of thing.
Animation objects are used to animate View objects. What you want to do is animate a canvas. This is more complicated but potentially more powerful. Essentially you need to derive an equation that will govern the movement of you drawing as a function of time. When the animation starts you get the current time stamp and then in you onDraw method you draw what the canvas should look like at that point in time. Basically you have to draw every step.
I have a big spritesheet (3808x1632) composed by 42 frames.
I would present an animation with these frames and I use a thread to load a bitmap array with all the frames, with a splash screen waiting for its end.
I'm not using a SurfaceView (and a draw function of a canvas), I just load frame by frame in an ImageView in my main layout.
My approach is similar to Loading a large number of images from a spritesheet
The completion actually takes almost 15 seconds, not acceptable.
I use this kind of function:
for (int i=0; i<TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
mVectorTeapotBG.add(Bitmap.createBitmap(framesBitmapTeapotBG, xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG));
}
framesBitmapTeapotBG is the big spritesheet.
Looking more deeply, I've read in the logcat that the createBitmap function takes a lot of time, maybe because the spritesheet is too big.
I found somewhere that I could make a window on the big spritesheet, using the rect function and canvas, creating small bitmaps to be loaded in the array, but it was not really clear. I'm talking about that post: cut the portion of bitmap
My question is: how can I speed the spritesheet cut?
Edit:
I'm trying to use this approach but I cannot see the final animation:
for (int i=0; i<TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmFrame);
Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, frameWidthTeapotBG, frameHeightTeapotBG);
Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);
c.drawBitmap(framesBitmapTeapotBG, src, dst, null);
mVectorTeapotBG.add(bmFrame);
}
Probably, the Bitmap bmFrame is not correctly managed.
The short answer is better memory management.
The sprite sheet you're loading is huge, and then you're making a copy of it into a bunch of little bitmaps. Supposing the sprite sheet can't be any smaller, I'd suggest taking one of two approaches:
Use individual bitmaps. This will reduce the memory copies as well as the number of times Dalvik will have to grow the heap. However, these benefits may be limited by the need to load many images off the filesystem instead of just one. This would be the case in a normal computer, but Android systems may get different results since they're run off flash memory.
Blit directly from your sprite sheet. When drawing, just draw straight from sprite sheet using something like Canvas.drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint). This will reduce your file loads to one large allocation that probably only needs to happen once in the lifetime of your activity.
I think the second option is probably the better of the two since it will be easier on the memory system and be less work for the GC.
Thanks to stevehb for the suggestion, I finally got it:
for (int i = 0; i < TotalFramesTeapotBG; i++) {
xStartTeapotBG = (i % framesInRowsTeapotBG) * frameWidthTeapotBG;
yStartTeapotBG = (i / framesInRowsTeapotBG) * frameHeightTeapotBG;
Bitmap bmFrame = Bitmap.createBitmap(frameWidthTeapotBG, frameHeightTeapotBG, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(bmFrame);
Rect src = new Rect(xStartTeapotBG, yStartTeapotBG, xStartTeapotBG+frameWidthTeapotBG, yStartTeapotBG+frameHeightTeapotBG);
Rect dst = new Rect(0, 0, frameWidthTeapotBG, frameHeightTeapotBG);
c.drawBitmap(framesBitmapTeapotBG, src, dst, null);
mVectorTeapotBG.add(bmFrame);
}
The computation time falls incredibly! :)
Use a LevelListDrawable. Cut the sprites into individual frames and drop them in your drawable resource directory. Either programmatically or through an xml based level-list drawable create your drawable. Then use ImageView.setImageLevel() to pick your frame.
I use a method of slicing based on rows and columns. However your sprite sheet is rather huge. You have to think you are putting that whole sheet into memory. 3808x1632x4 is the size of the image in memory.
Anyway, what I do is I take an image (lets say a 128x128) and then tell it there are 4 columns and 2 rows in the Sprite(bitmap, 4, 2) constructor. Then you can slice and dice based on that. bitmap.getWidth() / 4 etc... pretty simple stuff. However if you want to do some real stuff use OpenGL and use textures.
Oh I also forgot to mention there are some onDraw stuff that needs to happen. Basically you keep an index counter and slice a rectangle from the bitmap and draw that from a source rectangle to a destination rectangle on the canvas.