Android: Copying a bitmap - android

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().

Related

Slow perofromance when drawing transformationd bitmap on canvas in Android

I am trying to further process a Camera2 image. Because the cameras in devices have different rotations and flipped based on back and front camera, I use transforms to properly rotate it.
transformationMatrix is that matrix for the front camera that has 270 rotation.
Then from that transformed camera image, I want to copy a scrolling window to another bitmap. I want to retain that bitmap/state and draw a line before drawing finalBitmapWithScanner on the phone screen.
Is there a way to do this more efficiently and fast? The second line takes 200ms to complete which is the main issue here.
Canvas canvas = new Canvas(tempBitmap);
canvas.drawBitmap(cameraBitmap, transformationMatrix, paint); // <= 200ms
Rect src = new Rect((int) lastXPos, 0, (int) mXPos, mViewHeight);
Canvas canvas2 = new Canvas(finalBitmap);
canvas2.drawBitmap(tempBitmap, src, src, paint);
Canvas canvas3 = new Canvas(finalBitmapWithScanner);
canvas3.drawBitmap(finalBitmap, 0, 0, paint);
canvas3.drawLine(mXPos, 0, mXPos, mViewHeight/2, scrollerPaint);
transformationMatrix.reset();
transformationMatrix.setRotate(270, imageHeight, 0);
transformationMatrix.postTranslate(-imageHeight, 0);
transformationMatrix.postScale(scaleFactor, scaleFactor);
transformationMatrix.postScale(-1f, 1f, mViewWidth / 2f, mViewHeight / 2f);
There are bunch of ways you can try to achieve fast rendering:
You can pass parameters "paint" an null.
also you can use function CreateScaledBitmap and notice you have to set scale and size before rendering as see in below:
As you can see in documentation enter link description here; you have to resize and rescale your bitmap before rendering so you can use code below for your BitmapFactory.Options :
mBitmapOptions.inScaled = true;
mBitmapOptions.inDensity = srcWidth;
mBitmapOptions.inTargetDensity = dstWidth;
// will load & resize the image to be 1/inSampleSize dimensions
mCurrentBitmap = BitmapFactory.decodeResources(getResources(),
mImageIDs, mBitmapOptions);
use canvas.restore() after draw func.

Canvas' drawBitmap method causes bad performance after scaling

I'm trying to create a simple translational movement for a Bitmap object. I'm doing this by creating a canvas and passing a framebuffer to it, which had previously been initialized to a resolution of 1080/1920. I then proceeding to draw a bitmap with a call to canvas' method drawBitmap() like so:
this.canvas = new Canvas(frameBuffer);
...
canvas.drawBitmap(bitmap, null, destinationRect, paint);
In a different thread which is set as the Content View for the main activity I'm scaling the framebuffer to fit the actual screen size and then draw it with all its contents with these lines:
Canvas canvas = holder.lockCanvas();
canvas.getClipBounds(dstRect);
...
canvas.drawBitmap(framebuffer, null, dstRect, paint);
holder.unlockCanvasAndPost(canvas);
This results in very poor performance, both measuring manually with delta time and by monitoring with the Android Studio tools. Even visually you can see a trailing after the bitmap accompanied with very low fps.
I can't seem to find the cause of this, but I suspect that the scaling process that drawBitmap executes implicitly is doing something heavy. I tried changing the filter settings without success. I also doubt that I should head for openGL for this simple need.
Should I use bitmaps with different sizes and decide on runtime what size is the closest to my need or is there a more straightforward solution to this problem?
EDIT:
Here's how I gain a few more frames per second with the matrix.postScale() method:
int width = framebuffer.getWidth();
int height = framebuffer.getHeight();
float scaleWidth = ((float) game.size.x) / width;
float scaleHeight = ((float) game.size.y) / height;
// create a matrix for the manipulation
Matrix matrix = new Matrix();
// resize the bit map
matrix.postScale(scaleWidth, scaleHeight);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(framebuffer, 0, 0, width, height, matrix, true);
canvas.drawColor(Color.parseColor("#013501"));
canvas.drawBitmap(resizedBitmap, null, dstRect, paint);
holder.unlockCanvasAndPost(canvas);

How to resize bitmap when drawing in canvas?

In my android app, I have this function that creates a new bitmap from an old one via canvas.
private static Bitmap convert(Bitmap bitmap, Bitmap.Config config, int width, int height) {
Bitmap convertedBitmap = Bitmap.createBitmap(width, height, config);
Canvas canvas = new Canvas(convertedBitmap);
Paint paint = new Paint();
paint.setColor(Color.BLACK);
canvas.drawBitmap(bitmap, 0, 0, paint);
bitmap.recycle();
return convertedBitmap;
}
The problem is when I draw the old bitmap onto the canvas, since the old bitmap is bigger in dimensions than the canvas, only the top left part of the bitmap gets drawn on the canvas. Is there a way I can make it so when it draws the bitmap on the canvas, it scales the bitmap so it fits perfectly in the canvas?
I don't want to resize the bitmap using createScaledBitmap. Is there a faster way?
OR Can I do createScaledBitmap but make it mutable at the same time? That is what I am trying to do overall, resize and make mutable at same time.
Thanks
You can call public void drawBitmap (Bitmap bitmap, Rect src, RectF dst, Paint paint) for this function (Android Doc). The code below should accomplish what you are trying to do:
canvas.drawBitmap(bitmap, null, new RectF(0, 0, canvasWidth, canvasHeight), null);

Resized bitmap's width and height are not the same as resized bitmap draw on canvas

I have a image,and now i want to resize this image,so I use matrix to realize it.But when I draw this resized bitmap on canvas,Here is my code...I finally found that the rect I draw is smaller than the resized bitmap draw on canvas.I don't know why :(
Matrix m=new Matrix();
m.setScale(ratio,ratio);
m.postRotate(Angle,bitmap.getWidth()/2,bitmap.getHeight()/2);
newbitmap=Bitmap.createBitmap(bitmap, 0, 0,bitmap.getWidth(), bitmap.getHeight(), m,true);
canvas.drawBitmap(newbitmap,0,0, null);
canvas.drawRect(0,0,newbitmap.getWidth(),newbitmap.getHeight(), paint);
So what you need to to do is try using the same height and width of the created bitmap while drawing it on the canvas.Sometimes it may become right.This piece of code may help you.
Bitmap Rbitmap = Bitmap.createBitmap(bitmap).copy(
Config.ARGB_4444, true);
Canvas canvas = new Canvas(Rbitmap);
canvas.drawBitmap(label, -9, Rbitmap.getHeight()-label.getHeight()-10, null);
canvas.save();

Android Scaling Canvas Bitmap

I have a drawing app that allows the user to draw to a blank canvas. I am attempting to draw a scaled 'thumbnail' of the current bitmap so that when the user has scaled in the View, they can reference the thumbnail to get a sense as to where they are in the overall draw canvas. I have the scaling working, and am displaying the thumbnail in the correct location, but it appears that the thumbnail is not being updated on subsequent onDraws when new lines/shapes are added.
So that I have access to the underlying bitmap for this view (to show the thumbnail, be able to easily save the bitmap to a file, etc) I do the following in onSizeChanged() for the View:
#Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// set the canvas height, panning, etc, based on the now inflated View
mWidth = getWidth();
mHeight = getHeight();
mAspectRatio = mWidth / mHeight;
mPanX = 0;
mPanY = 0;
// create the Bitmap, set it to the canvas
mBitmap = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888);
mCanvas.setBitmap(mBitmap);
draw(mCanvas);
}
Then, when the user draws and invalidate() is called, I do the following in onDraw() to generate the thumbnail:
#Override
protected void onDraw(Canvas canvas) {
<snipped code that draws paths, shapes to canvas>
if (mScaled) {
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int) thumbHeight, false);
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
}
}
The thumbnail gets displayed in the space defined by thumbnailRectF using the thumbCanvasPaint, but in subsequent onDraw() calls, the scaled bitmap has not changed from it's original state, even though the full-sized active canvas shows all of the drawings, etc. Based on some testing, it seems to me that while I am setting the Bitmap with the initial call to draw(mCanvas);, subsequent onDraws are writing to the underlying Bitmap rather than the one specified in onSizeChanged().
So, I guess I am trying to figure out how I tie the onDraw canvas to a Bitmap that I can readliy access to perform re-sizes, save, etc. Looking at this question, I thought that the draw(mCanvas); call would tie the onDraw to the bitmap specified in the mCanvas (in my case, mBitmap), but in practice, it doesn't seem to be working, in so far as updats to the canvas are concerned.
Thanks,
Paul
canvas.drawBitmap(out, null, thumbnailRectF, thumbCanvasPaint);
should change to
canvas.drawBitmap(out, new Rect(0,0,mBitmap.getWidht, mBitmap.getheight), thumbnailRectF, thumbCanvasPaint);
There is no need for
Bitmap out = Bitmap.createScaledBitmap(mBitmap, (int) thumbWidth, (int)....
Also check that mScaled is true all the time when zoom is greater than 1
Scale bitmap by Bitmap.createScaledBitmap then draw will not work
The solution for scale the canvas bitmap is use this function (from the docs)
void drawBitmap (Bitmap bitmap, Rect src, Rect dst, Paint paint)
// dst : Rect: The rectangle that the bitmap will be scaled/translated to fit into
so by changing the size of dst, your bitmap size will change
Here is example if I want to draw a bitmap at top-left and scale it to 100px x 120px
Bitmap bitmap = BitmapFactory.decodeResource(...);//bitmap to be drawn
float left = 0;
float top = 0;
RectF dst = new RectF(left, top, left + 100, top + 120); // width=100, height=120
canvas.drawBitmap(bitmap, null, dst, null);

Categories

Resources