I'm working with a background drawable that I want flipped horizontally (it represents the "back side" of a view). I'd like to do this without making a copy of the original bitmap (memory concerns), and I can't just destroy the original because I'll need it later (when the view is flipped back over).
I tried doing this by extending BitmapDrawable and doing some matrix transformations in the draw(Canvas) method, but that doesn't work when hardware accelerated.
Here's what I'm doing now as part of my Fragment's onCreateView():
BitmapDrawable backgroundDrawable = foo;
Matrix matrix = new Matrix();
matrix.preScale(-1, 1);
Bitmap flipped = Bitmap.createBitmap(
backgroundDrawable.getBitmap(), 0, 0, backgroundDrawable
.getBitmap().getWidth(), backgroundDrawable
.getBitmap().getHeight(), matrix, false);
v.findViewById(R.id.bar).setBackgroundDrawable(
new BitmapDrawable(getResources(), flipped));
It works perfectly except that I now have a duplicate Bitmap in memory. Any way around this?
Related
I'm developing a simple 2D Android game and I want to draw the background of each level when the level begins. The background will be static for each level, it won't animate or move.
At first, I was drawing the background in a custom view using canvas and I was loading that view as the background of the level. That worked, however I noticed that if I draw a lot of lines and shapes, the UI begins to stutter. I solved the problem by drawing the background via canvas and saving it directly into a bitmap, which I then load into a background ImageView as you can see below:
CustomView bgView = new CustomView(this); //this view draws the background
Bitmap bitmap = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
bgView.draw(canvas);
ImageView imageViewBg = (ImageView) findViewById(R.id.imageViewBg);
imageViewBg.setImageBitmap(bitmap);
The problem that I notice with this approach is that I'm creating a bitmap with a size of [screenWidth x screenHeight] which might cause an OutOfMemoryError.
So, what's the best approach for drawing the background of a full-screen game? The problem is that I want to draw it dynamically, I cannot load it directly from the assets. (I could use the Bitmap.Config.RGB_565 profile, but still it will just reduce the size of the bitmap in half, it's not the optimal solution).
I use Bitmap.createScaledBitmap():
Bitmap b = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.background),WIDTH,HEIGHT,false);
Get the width and height of the screen
Then draw it as usual:
canvas.drawBitmap(b, 0, 0, null);
EDIT:
Load the bitmap any way you would like. For the purposes of the example, the Bitmap-object will be called b. So first:
WIDTH = b.getWidth();
HEIGHT = b.getHeight();
Move on to rendering:
public void render(Canvas c){
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
//check if the canvas is null first.
int savedState = c.getSaveCount();
c.scale(scaleFactorX, scaleFactorY);
//draw your bitmap.
c.restoreToCount();
//Do normal rendering
}
Explanation
What I do here is to scale the rendering only for the bitmap. This means anything else rendered will not be affected by this.
We need to get the scale factors to scale the screen to when rendering the Bitmap:
final float scaleFactorX = getWidth()/(WIDTH*1.f);
final float scaleFactorY = getHeight()/(HEIGHT*1.f);
Then save the state of the canvas to reload later.
int savedState = c.getSaveCount();
Now we scale the bitmap using the scaleFactors previously defined
c.scale(scaleFactorX, scaleFactorY);
And restore the bitmap to its original state to draw everything else in whatever type of scale you need. If you do not need to zoom the bitmap(if you have zoom) you can add that scaling after the background-scaling.
c.restoreToCount();
I have created a bitmap image which is a circle and than as I wanted to animate it so i converted it into bitmapdrawable and than added it to animation drawable..But due to this the circle shaped has changed to oval shape...
So what should I do ?
Is there any other method to animate only the bitmap file. ?
Thanks in advance..
If you're using Canvas, I'd suggest holding a pointer to the current Bitmap and loading all other Bitmaps into an array.
Say,
Bitmap[] frames = new Bitmap[10] //10 frames
Bitmap frame[0] = BitmapFactory.decodeResource(getResources(), R.drawable.circlefram1);
Bitmap frame[1] = BitmapFactory.decodeResource(getResources(), R.drawable.circlefram2);
...
Select the currentFrame by pointing at the frame you're interested in.
Bitmap currentBitmap = frame[3]; // 4th frame
So when you call drawBitmap(currentBitmap) it will only draw the frame you are interested in. You can change the bitmap every so many frames, by assigning an fps to the frame animation.
If you just want to scale or rotate the bitmap (rotating a circle?), the best way to resize a bitmap is using createScaledBitmap, and rotating using a matrix.
For Scaling, you load any bitmap into memory like this
Bitmap circleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.circle);
If you would like the circle (or any bitmap) rescaled you would do something like this:
Bitmap scaledCircle = Bitmap.createScaledBitmap(circleBitmap, dstWidth, dstHeight, filter);
Where dstWidth and dstHeight are the target destination width and height, which you can previously calculate by scaling the original width and height.
int scaledHeight = circleBitmap.getHeight()/2;
int scaledWidth = circleBitmap.getWidth()/2;
And finally you would normally draw this Bitmap using a canvas like this
canvas.drawBitmap(bitmap)
For rotating, create a Matrix
Matrix mat;
mat.postRotate(degrees); // Rotate the matrix
Bitmap rotatedBitmap = Bitmap.createBitmap(originalBitmap, x, y, width, height, mat, filter);
and finally
canvas.drawBitmap(rotatedBitmap);
Keep in mind Canvases are slow for games or anything real-time!
Hope it helps.
no, you can not animate the bitmap itself with the android animation framwork. You can directly animate Views or ViewGroups and all the classes that derives from View and ViewGroup.
Call view the ImageView that contains the bitmap.
TranslateAnimation slide = new TranslateAnimation(view.getX(), view.getX() + 100, view.getY(), view.getY() + 100 );
slide.setDuration(1000);
view.startAnimation(slide)
that should translate the ImageView by 100px from the current position
I need to "compute" the resulting bitmap obtained from overlapping two different bitmaps that have an alpha value somewhere between 0 and 255. I need to do this in java code, not xml, because bitmaps are being loaded dinamically and not from resources.
Here is my first try (which always yields a black bitmap...):
private Drawable composeBitmaps(int alpha1, BitmapDrawable bm1,
int alpha2, BitmapDrawable bm2)
{
Canvas c = new Canvas();
Bitmap b1 = bm1.getBitmap();
BitmapDrawable draw1 = new BitmapDrawable(b1.copy(b1.getConfig(), true));
draw1.setAlpha(alpha1);
c.setBitmap(draw1.getBitmap());
Paint p = new Paint();
p.setAlpha(alpha2);
c.drawBitmap(bm2.getBitmap(), 0, 0, p);
return draw1;
}
...
View v = // whatever
v.setBackgroundDrawable(composeBitmaps(100, bdrawable1, 150, bdrawable2));
And the view goes black background. What am I doing wrong?
My code above is absolutely correct. The bug that made the bitmap go black was elsewhere. Found it, fixed it, now it works.
The only thing to note is that my code is slow as hell and it cannot be used to crossfade two fullscreen bitmaps at a reasonable fps rate.
I have 2 ImageViews, 1 for Canvas and other for a ImageBackground.
The ImageViews have the size of the screen.
When I touch a Screen I need to redraw the Canvas like the example:
private void reDraw() {
Display display = getWindowManager().getDefaultDisplay();
bmOverlay = Bitmap.createBitmap(display.getWidth(), display.getHeight(), oBitmap.getConfig());
oCanvas = new Canvas(bmOverlay);
//I need to erase this line
oCanvas.drawBitmap(bmpBackGround, new Matrix(), null);
//this code paint some bitmaps on the canvas
for (ColorBall ball : colorballs) {
oCanvas.drawBitmap(ball.getBitmap(), ball.getX(), ball.getY(), null);
}
//set the canvas on the image view
getImageView2().setImageBitmap(bmOverlay);
}
But, on this example I redraw the Bitmap, when I touch the screen. I need to redraw a transparent Canvas because I don´t need to redraw the Bitmap on the background, because the bitmap never change. But if I don´t redraw the bitmap, the background stay black and the bitmap stay on the back. Any help?
Check to make sure the overlay bitmap you've created is actually transparent.
Create it with config ARGB_8888 explicitly.
http://developer.android.com/reference/android/graphics/Bitmap.Config.html
And you might want to move the creation of the bitmap out of the reDraw method and only create it once instead.
A seemingly simple issue, I have an off-screen bitmap that I perform some transformations to (rotation, scaling, etc) and I'd like to store a copy of the bitmap prior to the transformations, such that in my View's onDraw(), I can display the transformed off-screen bitmap AND a smaller scaled version of the un-transformed bitmap as a thumbnail.
No problem writing the off-screen bitmap in onDraw(), but the copied 'preserved' bitmap is also being transformed. Here is the code where I am making the copy of the bitmap, where mCanvas was created via mCanvas = new Canvas(mBitmap);:
mPreservedBitmap = Bitmap.createBitmap(mBitmap);
// save the canvas
mCanvas.save();
// do some rotations, scaling
mCanvas.rotate(rotation, px, py);
mCanvas.scale(scaleFactor, scaleFactor, scaleFocusX, scaleFocusY);
// draw the bitmaps to the screen
invalidate();
// restore the bitmap
mCanvas.restore();
In onDraw(), I have:
// draw the off-screen bitmap to the on-screen bitmap
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
// draw the preserved image, scaling it to a thumbnail first
canvas.drawBitmap(
Bitmap.createScaledBitmap(mPreservedBitmap, (int) thumbWidth, (int) thumbHeight, true),
null,
thumbnailRectF,
thumbCanvasPaint);
The thumbnail gets scaled to the appropriate size, but the bitmap that is being scaled down to thumbnail size is also rotated and scaled the exact same as mBitmap, which I don't want. I've also tried the Bitmap.copy() method, but with the same results. Any pointers/assitance/advice?
Thanks,
Paul
You are doing it wrong :) First of all you should never keep a reference to a Canvas in a field. There is no guarantee whatsoever that the Canvas instance will be the same in two different calls to onDraw(). Your second problem is how you apply the transformations. You should apply them in onDraw():
canvas.save();
canvas.rotate(rotation, px, py);
canvas.scale(scaleFactor, scaleFactor, scaleFocusX, scaleFocusY);
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
canvas.restore();
canvas.drawBitmap(
Bitmap.createScaledBitmap(mPreservedBitmap, (int) thumbWidth, (int) thumbHeight, true), null, thumbnailRectF, thumbCanvasPaint);
invalidate() is not a synchronous operation, your save()/restore() has no guarantee to work if done outside of onDraw().
Also do not call createScaledBitmap() from onDraw(), it is extremely expensive.
To expand on my comment:
Bitmap.createBitmap(Bitmap) returns an immutable bitmap. The documentation actually says that it might be the same object or use the same data.
You have to create a mutable bitmap, if you actually want to modify it, for example:
mPreservedBitmap = mBitmap;
// Create a new, empty bitmap with the original size.
// Since the image is going to be scaled, this might be to big or to small.
// Rotating might also require additional space (otherwise the corners will be cut off)
// Try calculating the proper size or play around with some other createBitmap Methods, just make sure to
// actually create a mutable bitmap, for example by using copy on an immutable bitmap.
mBitmap = Bitmap.createBitmap(mPreservedBitmap.getWidth(), mPreservedBitmap.getHeight(), mPreservedBitmap.getConfig());
mCanvas = new Canvas(mBitmap);
// do some rotations, scaling
mCanvas.rotate(rotation, px, py);
mCanvas.scale(scaleFactor, scaleFactor, scaleFocusX, scaleFocusY);
// draw the original image to the canvas, applying the matrix modifications
mCanvas.drawBitmap(mPreservedBitmap, 0, 0, null);
And in onDraw:
// draw the off-screen bitmap to the on-screen bitmap
canvas.drawBitmap(mBitmap, 0, 0, mBitmapPaint);
// draw the preserved image, scaling it to a thumbnail
canvas.drawBitmap(mPreservedBitmap, null, thumbnailRectF, thumbCanvasPaint);
My final solution to this was to generate a copy of the canvas Bitmap PRIOR to it being scaled via:
mPreservedBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), null, true);
Then, when the Canvas and the primary Bitmap is scaled, I can draw the non-scaled 'preserved' Bitmap to the Canvas in onDraw() via:
canvas.drawBitmap(mPreservedBitmap, null, thumbnailRectF, thumbCanvasPaint);
Per Romain's comments above, I scale the preserved Bitmap off-screen to improve performance in onDraw().