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);
Related
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.
I need to resize my image with custom size. The image is taken from device camera or gallery, i tired the below code, but the image is stretched, i need the image in square shape with out any stretch.
public Bitmap decodeSampledBitmapFromFile(Bitmap bm, int boundBoxInDp) {
boundBoxInDp=300;
int height = bm.getHeight();
int width = bm.getWidth();
float scaleWidth = ((float) boundBoxInDp) / width;
float scaleHeight = ((float) boundBoxInDp) / height;
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, false);
return resizedBitmap;
}
If you already have a bitmap, you could use the following code to resize:
Bitmap originalBitmap = <original initialization>;
Bitmap resizedBitmap = Bitmap.createScaledBitmap(originalBitmap, newWidth, newHeight, false);
Or you can use following library for resize image
https://github.com/biokys/cropimage
This won't fit your image in a bounding box (the failure of which is presumably what you're calling "stretch"). It will not handle rectangular bitmaps in your square bounding box, nor will it handle images smaller than the bounding box particularly well. You probably want something like:
public Bitmap decodeSampledBitmapFromFile(Bitmap bm, int boundBoxInDp) {
boundSquareInPx=convertToPixels(300);
int maxDimen = Math.max(bm.getHeight(), bm.getWidth())
float scale = (maxDimen <= boundSquareInPx) ? 1 : boundSquareInPx / (float) maxDimen;
float scaleWidth = scale * bm.getWidth();
float scaleHeight = scale * bm.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height,
matrix, true);
return resizedBitmap;
}
Few notes: if you image is smaller than your bound it won't fit it- obvious modifications do that.
Secondly, dp != px; the Bitmap object returns px, so you're going to have to convert to px from dp (which is well documented elsewhere).
Use postTranslate(...) if you need to center the correspondingly cropped bitmap.
The documentation is here; this is allready the best library I know for resizing in Android- I've never needed anything else, and I've been in the game a while and work with this frequently.
If you need, in my opinion, the best introduction to working with the API efficiently: read the source code to ImageView and Drawable instances; a really worthwhile personal development exercise would be to use the SDK to implement a fading transition drawable that is center cropped, as that's rather annoyingly one of the only things missing from the Android library, and would involve a heck of a lot of the kind of coding you're trying to do above.
NB:
As you'll note, another answerer has pointed out the existence of createScaledBitmap, which is probably much clearer code; I just wanted to point out how what you were doing was basically right and how it could be improved.
Best.
Take a look at https://github.com/coomar2841/image-chooser-library/blob/d27b542d2487132b0150be382f39e9ef95aafe68/src/com/kbeanie/imagechooser/threads/MediaProcessorThread.java.
The method called compressAndSaveImage.
I have tried several hours to rotate a bitmap with no success. I have read numerous articles about this subject on this web site and it seems the prefered solution involves creating a temporary canvas. Well I did this and I still do not see a roated bitmap.
My bitmap is a 40x40 blue square and I am trying to rotate it 45 degrees. Thats not asking for much is it? When the code runs, the bitmap that appears on the screen is the non-rotated original. ( I have also tried a translate with no success as well)
Here is my code:
// Load the bitmap resource
fillBMP2 = BitmapFactory.decodeResource(context.getResources(), R.drawable.test1);
// Copy to a mutable bitmap
mb = fillBMP2.copy(Bitmap.Config.ARGB_8888, true);
// Create canvas to draw to
Canvas offscreenCanvas = new Canvas (mb);
// Create Matrix so I can rotate it
Matrix matrix = new Matrix();
matrix.setRotate (45);
offscreenCanvas.setMatrix (matrix);
// Send the contents of the canvas into a bitmap
offscreenCanvas.setBitmap(mb);
Later in an OnDraw I do the following:
canvas.drawBitmap(mb, 200, 200, null);
Any ideas what I am doing wrong? Seems like it should work.
Thanks
Try using this
Matrix matrix = new Matrix();
matrix.setRotate(15);
canvas.drawBitmap(bmp, matrix, paint);
setRotation method takes in a float representing
the degrees of rotation.
Try this...
Matrix matrix = new Matrix();
float px = 200;
float py = 200;
matrix.postTranslate(-bitmap.getWidth()/2, -bitmap.getWidth()/2);
matrix.postRotate(45);
matrix.postTranslate(px, py);
canvas.drawBitmap(bitmap, matrix, paint);
You definitely want to use transformations: check out this link.
Basically it's this:
// save the canvas
ctx.save();
// move origin to center
ctx.translate(x,y);
// rotate
ctx.rotate(angle * (Math.PI / 180));
// draw image
ctx.drawImage(image, x, y, w, h, .w/2, h/2, w, h);
// restore
ctx.restore();
I have a SurfaceView canvas I'm drawing a scene into.
The canvas is transformed to accomodate the logical scene size which is 1024x768 (while the actual screen is 800x480), and also to support zoom/scroll navigation on it.
Since I did not want black stripes on the sides (fitting 4:3 to 16:9) I'm using a different background bitmap which has "stretched" sides to cover the ratio difference.
Another twist in the plot, is that I need to support both phones and tables. For tablets I'm loading a higher quality (size) image and for phones I'm scaling down the background bitmap when loading it from disk.
Below is code that does what I want (although probably not the best way), and in particular, I have a feeling the background calculations can be cached into a matrix to be used later in canvas.drawBitmap(Bitmap, Matrix, Paint);
(Also, I couldn't get the equivalent positioning of drawBitmap(Bitmap, left, top, Paint) by translating the canvas with these offsets, I'm also curious why).
This is the draw routine:
public void draw(Canvas canvas) {
canvas.save();
float midX = width/2;
float midY = height/2;
// zoom out to fit logical view, and translate for future
// sprite drawing with logical coordinates
// x,y and zoom are calculated in on init and updated in touch events
canvas.translate(x, y);
canvas.scale(zoom, zoom, midX, midY);
// background part
if(back != null && !back.isRecycled()) {
// todo: these can probably be pre-calculated for optimization
// todo: but so far I couldn't get it right..
if(bgMatrix != null) { // this is where I'm thinking of using the cached matrix
canvas.drawBitmap(back, bgMatrix, paint);
}
else {
float offsetW = (width-back.getWidth())/2;
float offsetH = (height-back.getHeight())/2;
canvas.save();
// bgScaleFactor is calculated upon setting the bg bitmap
// it says by how much we need to scale the image to fill the canvas
// taking into account the image (possible) downscale
canvas.scale(bgScaleFactor, bgScaleFactor, midX, midY);
// this doesn't work: canvas.postTranslate(offsetW, offsetH) and use 0,0 for next draw
canvas.drawBitmap(back, offsetW, offsetH, paint);
// todo: here I would like to save a matrix which represents
// how the back bitmap was drawn onto the canvas
// so that next time these calculations can be avoided
// this fails: bgMatrix = canvas.getMatrix();
canvas.restore();
}
// draw scene shapes on transformed canvas
if(shapes != null){
shapes.onDraw(canvas);
}
canvas.restore();
}
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().