I'm trying to learn how to make an animated sprite in android and couldn't figure out how to go about organising my bitmaps. I have a sprite sheet of my character walking to the Right: a bitmap of five copies of a character, equally spaced (every 45px), in a walk cycle.
I planned to draw each frame by drawing a tiny section of my sprite sheet bitmap at a time by going:
Rect sourceRect = new Rect(0, 0, 45, 75);
canvas.drawBitmap(spriteSheetBitmap, sourceRect, new Rect(0, 0, 45, 75), null);
Then for the next frames, increment "sourceRect.x" by 45, then redraw and so forth.
However, I'm now not sure how to go about making my sprite walk to the Left. I had initially thought I could just mirror my rectangle that I am drawing from to get a flipped picture. Something like:
sourceRect = new Rect(45, 0, 0, 75);
which doesn't seem to work (not sure what actually happens here, but nothing gets drawn to my surface).
Searching online, it seems I should make a copy of my original bitmap, mirror it with a transform matrix, then use that bitmap for drawing when walking to the left. However I've also found implementations where many smaller bitmap objects get created out of the original sprite sheet, stored (and transformed for the mirrored motion), then used as needed.
So I'm wondering what would be the best in this case or if there is really any difference (performance/memory):
Method 1: Load in my original sprite sheet, make a new bitmap instance, mirror it,, then calculate all the rectangles and use those + two entire sheets to draw from (admittedly there is some extra bitmap space where the sprite sheet is unused).
Method 2: Load in my original sprite sheet, for every frame create a new two bitmap objects (1 mirrored, 1 normal) and store those to draw from.
Method 3: Other better ways?
Method 2 would be way too expensive, and you don't need a canvas to flip a bitmap. Simply create another bitmap with a Matrix applied, like so:
BitmapDrawable flip(BitmapDrawable d)
{
Matrix m = new Matrix();
m.preScale(-1, 1);
Bitmap src = d.getBitmap();
Bitmap dst = Bitmap.createBitmap(src, 0, 0, src.getWidth(), src.getHeight(), m, false);
dst.setDensity(DisplayMetrics.DENSITY_DEFAULT);
return new BitmapDrawable(dst);
}
To mirror your sprite simply apply the following transform on the Canvas: scale(-1, 1). You will have to offset the sprite by its width too.
To draw a vertical mirrored bitmap bmp on a canvas:
Matrix m = new Matrix();
// Mirror is basically a rotation
m.setScale( -1 , 1 );
// so you got to move your bitmap back to it's place. otherwise you will not see it
m.postTranslate(canvas.getWidth(), 0);
canvas.drawBitmap(bmp, m, p);
Related
I am using a GLES20 texture in my app, which I am loading with texImage2D function. Sometimes I have to modify small area of the loaded texture with bitmap that I create on the fly. Bitmap contains transparency (some pixels have alpha < 255). When I call function texSubImage2D the pixels with alpha values < 255 get replaced with black pixels.
My code (I have removed non-relevant parts) looks like this:
// Set blending function
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
// Create bitmap
Bitmap bitmap = Bitmap.createBitmap(20, 20, Config.ARGB_8888);
// Draw on the bitmap
Canvas c = new Canvas(bitmp);
Paint paint = new Paint();
paint.setStyle(Style.FILL_AND_STROKE);
paint.setColor(Color.argb(100, 255, 0, 0));
c.drawRect(0, 0, 20, 20, paint);
// Bind to the texture I want to modify
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId);
// Draw on the texture
GLUtils.texSubImage2D(GLES20.GL_TEXTURE_2D, 0, myX, myY, bitmap);
// Free memory
bitmap.recycle();
If I use 255 alpha (change paint.setColor(Color.argb(100, 255, 0, 0)); to paint.setColor(Color.argb(255, 255, 0, 0));) then everything is OK, but alpha is lost of course.
How can I draw a bitmap with transparent pixels on the GLES20 texture without loosing the transparency?
I think the easiest way is to:
Keep a copy of the whole texture around as a Bitmap and draw onto that using whatever mode you want.
Make a copy of the section of the bitmap that you changed (or update an existing bitmap to avoid allocating memory each time).
Upload that smaller bitmap using glTexSubImage2D with the appropriate x and y offset.
The reason that you will have to copy into a smaller bitmap is that glTexSubImage2D doesn't support uploading a section (rectangle) of a bitmap. It does however allow you to update a section of the texture using the offset parameters.
I am working with large size images and when I try to rotate them (applying matrix on the bitmap) a lot of seconds occurs.
I've seen that android system gallery can accomplish this task in a very fast manner instead. How is it possible?
I thought to perform the rotation on an asyncTask, applying only the ImageView rotation (which doesn't take long time) on the main thread, but if the app is killed before the asyncTask get to the end, the app fall in an inconsistent state.
This is my bitmap rotation code, which takes long time execution for large bitmaps:
Matrix mat = new Matrix();
mat.setRotate(90);
bMap = Bitmap.createBitmap(bMap, 0, 0, bMap.getWidth(), bMap.getHeight(), mat, true);
When doing modifications to a large image, create a low quality bitmap copy of it and use it for showing instant rotation or other editing effects. Once user is stopped motion (or left slider control) replace the low quality bitmap with the original image with same effects applied to it. This will significantly improve user experience. Try and let me know.
This might sound like a deviation from the original requirement- you can draw a rectangle instead of rotating the whole image in real time. In the end, what user needs is an indication of how much his image has rotated. This will be faster and can easily be done on UI thread without lag.
This is the simplest rotation on canvas code
canvas.save(); //save the position of the canvas
canvas.rotate(angle, X + (imageW / 2), Y + (imageH / 2)); //rotate the canvas
canvas.drawBitmap(imageBmp, X, Y, null); //draw the image on the rotated canvas
canvas.restore(); // restore the canvas position.
In my Android project, I have a matrix that I use to draw a bitmap on a canvas. During my drawFrame method, I translate the matrix in the Y direction so it looks like the bitmap is moving up. It does move up, but it is also drawn in the previous position leaving multiples of itself behind. Here is my drawFrame code:
Canvas canvas = null;
canvas = holder.lockCanvas();
Matrix moveMatrix = new Matrix ();
moveMatrix.postTranslate(0, 10);
matrix.preConcat(moveMatrix);
canvas.drawBitmap(bitmap, matrix, null);
holder.unlockCanvasAndPost(canvas);
"matrix" is a global Matrix member.
Does anyone know why this leaves a "streaking" trail of bitmaps across the screen?
Thanks!
I forgot to mention that each previous bitmap position seems to shift up and down in the y direction every time the bitmap moves. So it ends up with a string of bitmaps that look like they are jittering up and down. This would mean that every bitmap would have to be redrawn every frame otherwise they would all be standing still.
You need to clear the Canvas at the previous position of the bitmap :)
While rotating a bitmap using matrix, vertex is not stable..
Matrix matrix = new Matrix();
matrix.postRotate(mDegree,100,100);
mCompasstemp = Bitmap.createBitmap(mCompPic, 0, 0, mCompPic.getWidth(), mCompPic.getHeight(), matrix, true);
mCompassHud.setImageBitmap(mCompasstemp);
Output of my code is like
-bitmap will rotate.
-vertex of my bitmap is not stable.
-Bitmap is resizing
I need disable image resizing and make the rotation stable.Can you please suggest a solution for this?
Rather than creating your new Bitmap directly from the original, another (more straight-forward, imho) option is to create the resultant Bitmap, create a Canvas with that Bitmap, then do your rotation/translation/scaling on the Canvas and draw the original Bitmap onto the new Bitmap via the Canvas.
Basically, you're looking, then, at:
scaledImage = Bitmap.createBitmap (croppedWidth, croppedHeight, Bitmap.Config.ARGB_8888);
Canvas offscreenCanvas = new Canvas (scaledImage);
Matrix matrix = new Matrix();
matrix.setRotate (rotations, centreX, centreY);
matrix.postScale(scaleX, scaleY);
offscreenCanvas.setMatrix (matrix);
offscreenCanvas.drawBitmap (pickedImage, 0, 0, new Paint(Paint.DITHER_FLAG));
Not sure if this is what your looking for but it might help.
Android uses its built in compatibility features to scale and render a bitmap appropriately for screens with different pixel densities. There are two methods of scaling, pre-scaling and auto-scaling.
It will pre-scale bitmaps' from resources and auto-scales when the bitmap is being drawn internally (which is what your doing be using createBitmap).
Go to http://developer.android.com/guide/practices/screens_support.html and check under:
4.Use density and/or size-specific resources:
Pre-scaling and auto-scaling of bitmaps and nine-patches
I have tried this code, and the rotate is stable at the center of the bitmap
matrix.reset();
matrix.setRotate(degree, Xpos+bitmap.getWidth()/2, Ypos+bitmap.getHeight()/2);
and then in canvas doDraw()
canvas.setMatrix(matrix);
canvas.drawBitmap(bitmap, Xpos, Ypos, null);
canvas.setMatrix(null);
The Xpos and Ypos is the X and Y position of the bitmap
The setMatrix(null), set the matrix to null, so that the rotate didn't affect the after bitmap
And it didn't always create new bitmap, so it's great for performance
I hope that help
I know its an old question but, all answers with code imply a canvas, so heres a solution without a canvas that worked for me :
Matrix matrix = new Matrix();
matrix.postRotate(mDegree,100,100);
mCompasstemp = Bitmap.createBitmap(mCompPic, 0, 0, mCompPic.getWidth(),
mCompPic.getHeight(), matrix, true);
mCompasstemp = Bitmap.createScaledBitmap(mCompassTemp, mCompPic.getWidth(),
mCompic.getHeight(), false);
mCompassHud.setImageBitmap(mCompasstemp);
So basically after rotating your bitmap you rescale it to the desired size. Might not be best practice, but at least you dont have to create an extra canvas if you dont want/need to, since the involving Paint() Object is not an inexpensive operation either.
I'm used to handle graphics with old-school libraries (allegro, GD, pygame), where if I want to copy a part of a bitmap into another... I just use blit.
I'm trying to figure out how to do that in android, and I got very confused.
So... we have these Canvas that are write-only, and Bitmaps that are read-only? It seems too stupid to be real, there must be something I'm missing, but I really can't figure it out.
edit: to be more precise... if bitmaps are read only, and canvas are write only, I can't blit A into B, and then B into C?
The code to copy one bitmap into another is like this:
Rect src = new Rect(0, 0, 50, 50);
Rect dst = new Rect(50, 50, 200, 200);
canvas.drawBitmap(originalBitmap, src, dst, null);
That specifies that you want to copy the top left corner (50x50) of a bitmap, and then stretch that into a 150x150 Bitmap and write it 50px offset from the top left corner of your canvas.
You can trigger drawing via invalidate() but I recommend using a SurfaceView if you're doing animation. The problem with invalidate is that it only draws once the thread goes idle, so you can't use it in a loop - it would only draw the last frame. Here are some links to other questions I've answered about graphics, they might be of use to explain what I mean.
How to draw a rectangle (empty or filled, and a few other options)
How to create a custom SurfaceView for animation
Links to the code for an app with randomly bouncing balls on the screen, also including touch control
Some more info about SurfaceView versus Invalidate()
Some difficulties with manually rotating things
In response to the comments, here is more information:
If you get the Canvas from a SurfaceHolder.lockCanvas() then I don't think you can copy the residual data that was in it into a Bitmap. But that's not what that control is for - you only use than when you've sorted everything out and you're ready to draw.
What you want to do is create a canvas that draws into a bitmap using
Canvas canvas = new Canvas(yourBitmap)
You can then do whatever transformations and drawing ops you want. yourBitmap will contain all the newest information. Then you use the surface holder like so:
Canvas someOtherCanvas = surfaceHolder.lockCanvas()
someOtherCanvas.drawBitmap(yourBitmap, ....)
That way you've always got yourBitmap which has whatever information in it you're trying to preserve.
In android you draw to the canvas, and when you want it to update you call invalidate which will the redraw this canvas to the screen. So I'm guessing you have overridden the onDraw method of your view so just add invalidate();
#Override
public void onDraw(Canvas canvas) {
// Draw a bitmap to the canvas at 0,0
canvas.drawBitmap(mBitmap, 0, 0, null);
// Add in your drawing functions here
super.onDraw(canvas);
// Call invalidate to draw to screen
invalidate();
}
The above code simply redraws the bitmap constantly, of course you want to add in extra thing to draw and consider using a timing function that calls invalidate so that it is not constantly running. I'd advice having a look at the lunarlander sources.