I want to save a view as a bitmap in high resolution. Unfortunately, Kotlin's drawToBitmap() gives a very small bitmap (low resolution). How can I get a high resolution bitmap instead?
This is my failed attempt:
int tableLayoutId = 1;
float scaleFactor = 4f;
TableLayout tableLayout = new TableLayout(Activity.this);
tableLayout.setId(tableLayoutId);
tableLayout.setLayoutParams(new TableLayout.LayoutParams(TabLayout.LayoutParams.WRAP_CONTENT,
TabLayout.LayoutParams.WRAP_CONTENT));
tableLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
tableLayout.layout(0, 0, tableLayout.getMeasuredWidth(), tableLayout.getMeasuredHeight());
Canvas bitmapCanvas = new Canvas();
Bitmap bitmap = Bitmap.createBitmap(Math.round(tableLayout.getWidth() * scaleFactor), Math.round(tableLayout.getHeight() * scaleFactor), Bitmap.Config.ARGB_8888);
bitmapCanvas.setBitmap(bitmap);
tableLayout.draw(bitmapCanvas);
This code has no effect, so I'm looking for a different elegant solution that works.
Android View.draw method is drawing pixel to pixel, not vectorelly. If you examine that method, it is calculating like pixelOfWidth, pixelOfHeight. So you can't take better image rather than 1/1 (scale unit).
So simply draw best is like this:
public static Bitmap getDrawedBitmap(View v) {
Bitmap b = Bitmap.createBitmap(Math.round(v.getWidth()), Math.round(v.getMeasuredHeight()), Bitmap.Config.ARGB_8888);
Canvas c = new Canvas();
c.setBitmap(b);
v.draw(c);
return b;
}
The only solution is you can increase original view's width and height (for example doubled of maximum width and height) than call to getDrawedBitmap for better image.
Just use this function:
fun getBitmap(view: View): Bitmap {
val bitmap = Bitmap.createBitmap(view.width, view.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
view.draw(canvas)
return bitmap
}
And pass your desired view like so:
val bitmap = getBitmap(myView)
Related
Context & Goal
I'm building a real-time object detection app. To achieve this, I need a bitmap to send to the recognition algorithm. I have an activity displaying the camera output in full screen, but I'm trying to operate the recognition only on the central square of the screen. So I need a bitmap with those dimensions and position, corresponding to the the central square of the phone screen.
What I have tried
#Override
public void onPreviewSizeChosen(final Size size, final int rotation) {
/* some code... */
// Width of the screen (larger than the height), for my phone it's 4032
previewWidth = size.getWidth();
// Height of the screen, for my phone it's 1980
previewHeight = size.getHeight();
sensorOrientation = rotation - getScreenOrientation();
// The bitmap which will be used to the recognition, dimension : full screen of the phone
rgbFrameBitmap = Bitmap.createBitmap(previewWidth, previewHeight, Config.ARGB_8888);
// squared bitmap 320x320, required size for the recognition algorithm
croppedBitmap = Bitmap.createBitmap(cropSize, cropSize, Config.ARGB_8888);
// bitmap to croppedBitmap transform Matrix
frameToCropTransform =
ImageUtils.getTransformationMatrix(
previewWidth, previewHeight,
cropSize, cropSize,
sensorOrientation, MAINTAIN_ASPECT);
cropToFrameTransform = new Matrix();
frameToCropTransform.invert(cropToFrameTransform);
trackingOverlay = (OverlayView) findViewById(R.id.tracking_overlay);
trackingOverlay.addCallback(
new DrawCallback() {
#Override
public void drawCallback(final Canvas canvas) {
tracker.draw(canvas, isDebug());
}
});
tracker.setFrameConfiguration(previewWidth, previewHeight, sensorOrientation);
}
#Override
protected void processImage() {
trackingOverlay.postInvalidate();
if (computingDetection) {
readyForNextImage();
return;
}
computingDetection = true;
// THE LINE I CAN'T FIGURE OUT
// I try to set width and height = previewHeight (1980 for my phone so a square)
// and to move it down by (previewWidth-previewHeight)/2 so it goes in the middle of the screen
rgbFrameBitmap.setPixels(getRgbBytes(), 0, previewWidth, 0, (previewWidth-previewHeight)/2, previewHeight, previewHeight);
readyForNextImage();
final Canvas canvas = new Canvas(croppedBitmap);
canvas.drawBitmap(rgbFrameBitmap, frameToCropTransform, null);
// Saved set to true, so I can check what the bitmap sent to the recognition algorithm looks like
if (SAVE_PREVIEW_BITMAP) {
ImageUtils.saveBitmap(croppedBitmap);
}
runInBackground(
new Runnable() {
#Override
public void run() {
// Here we send the bitmap to the recognition algorithm to perform real-time detection
final List<Classifier.Recognition> results = detector.recognizeImage(croppedBitmap);
/* some code ... */
}
If I replace the (previewWidth-previewHeight)/2 by 0 as the y parameter of rgbFrameBitmap.setPixels() function, I manage to perform real-time recognition in a square on top of the screen, as I want unless it's not centered. But as soon as I set the y parameter to (previewWidth-previewHeight)/2, this is the error I get when it tries to save the bitmap :
W/ImageReader_JNI: Unable to acquire a buffer item, very likely client tried to acquire more than maxImages buffers
How can I get the bitmap to be centered in the middle of the screen ?
Self solved
There are two thing I misunderstood on the above code.
First, I mixed up the x and the y parameters of the rgbFrameBitmap.setPixels() function, but in any case this was just adding some black space to the bitmap. To obtain the center of the screen, the parameter to change is the offset, like this :
int x = (previewWidth-previewHeight)/2;
rgbFrameBitmap.setPixels(getRgbBytes(), x, previewWidth, 0, 0, previewHeight, previewHeight);
Besides, as we send the croppedBitmap to the recognition algorithm, which is the rgbFrameBitmap cropped to 320x320 thanks to the matrix frameToCropTransform, I had to update this :
frameToCropTransform =
ImageUtils.getTransformationMatrix(
previewWidth, previewHeight,
cropSize, cropSize,
sensorOrientation, MAINTAIN_ASPECT);
into this :
frameToCropTransform =
ImageUtils.getTransformationMatrix(
previewHeight, previewHeight,
cropSize, cropSize,
sensorOrientation, MAINTAIN_ASPECT);
as rgbFrameBitmap is now a square of dimensions previewHeightxpreviewHeight
Horizontal Screen
To get the central square area of a screen, compare height and width of the screen in pixels. Usually for desktop systems width is bigger, and I will assume this is the case.
Get the difference width-height, divide by two and call it x.
Finally draw a rectangle from (x, 0) to (x+height, height).
More General
The side length of the square will be length = min(width,height)
The coordinates of the square will be
x = (width-length)/2
y = (height-length)/2
Finally draw a rectangle from (x, y) to (x+length, y+length).
I simply want to rotate an image from min to max. But for that i have used multiple images to show Progress. Can some one suggest me a way to use a single image. which can rotate at an angle from min to max.
I know there are two possible ways to achieve it.
Using Animation Classes
Custom View
I simply want to rotate this image using SeekBar in any number of steps.
How can i achieve this?
To roate an Image
private void rotate(float degree) {
final RotateAnimation rotateAnim = new RotateAnimation(0.0f, degree,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
rotateAnim.setDuration(0);
rotateAnim.setFillAfter(true);
imgview.startAnimation(rotateAnim);
}
Second Approach
imageView.setRotation(angle); // but requires API >= 11
I can use Matrix
Matrix matrix = new Matrix();
imageView.setScaleType(ScaleType.MATRIX); //required
matrix.postRotate((float) angle, pivX, pivY);
imageView.setImageMatrix(matrix);
How can i set Start and end angle to SeekBar min and max respectively. Which approach is better and Whether i must put it in FrameLayout to let it rotate freely.
You can do this way :
// load the origial BitMap (500 x 500 px)
Bitmap bitmapOrg = BitmapFactory.decodeResource(getResources(),
R.drawable.android);
int width = bitmapOrg.width();
int height = bitmapOrg.height();
// createa matrix for the manipulation
Matrix matrix = new Matrix();
// rotate the Bitmap
matrix.postRotate(45);
// recreate the new Bitmap
Bitmap resizedBitmap = Bitmap.createBitmap(bitmapOrg, 0, 0, width, height, matrix, true);
// make a Drawable from Bitmap to allow to set the BitMap
// to the ImageView, ImageButton or what ever
BitmapDrawable bmd = new BitmapDrawable(resizedBitmap);
// set the Drawable on the ImageView
imageView.setImageDrawable(bmd);
For details check this out.
http://www.anddev.org/resize_and_rotate_image_-_example-t621.html
matrix.postRotate(45); //here 45 is the degree of angle to rotate
You can use a Matrix as suggested here
Matrix matrix = new Matrix();
imageView.setScaleType(ScaleType.MATRIX); //required
matrix.postRotate((float) angle, pivX, pivY);
imageView.setImageMatrix(matrix);
This would be an infinite rotation but you could spin it however you want.
This view just spins 360 degrees infinitely.
final RotateAnimation R = new RotateAnimation(0, 360, view.getWidth() / 2.0f, view.getHeight() / 2.0f);
R.setRepeatCount(Animation.INFINITE);
R.setDuration(800);
view.startAnimation(R);
I'm developing an Android application and I would like to blend Bitmap with a filter like "Multiply" or "Difference" from Photoshop. Bitmap1 is translating horizontally and Bitmap2 vertically.
I had few ideas :
1) Create a method which do some calculation to find the intersection part of the two Bitmap (Requires to cross 2 matrix every frame, very heavy, seems impossible ?). Then crop the intersection on the 2 Bitmap, blend them, and finally draw it. Another idea would be to do a ANDing between the two Bitmap, maybe faster than the other method, does thi function already exist ?
2) OpenGL ES ? But I really don't understand how to use it.
3) But recently I have found this sample code which blend two Bitmap on a canvas and use a paint. The result is the one I want BUT there is still no animation. So I've create a new Class (extends View) in addition to this activity, with the method onDraw(Canvas canvas).
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_compo);
Bitmap bm1 = BitmapFactory.decodeResource(getResources(), R.drawable.bm1);
Bitmap bm2 = BitmapFactory.decodeResource(getResources(), R.drawable.bm2);
blend(bm1,bm2);
}
private void blend(Bitmap bm1, Bitmap bm2){
DisplayMetrics metrics = new DisplayMetrics();
this.getWindowManager().getDefaultDisplay().getMetrics(metrics);
int h = metrics.heightPixels;
int w = metrics.widthPixels;
/// BLEND MODE ///
// Create result image
Bitmap.Config conf = Bitmap.Config.ARGB_8888; // see other conf types
Bitmap result = Bitmap.createBitmap(w, h, conf);
Canvas canvas = new Canvas();
canvas.setBitmap(result);
// Get proper display reference
BitmapDrawable drawable = new BitmapDrawable(getResources(), result);
ImageView imageView = (ImageView)findViewById(R.id.photo1);
imageView.setImageDrawable(drawable);
// Draw base
canvas.drawBitmap(bm2, 0, 0, null);
// Draw overlay
Paint paint = new Paint();
paint.setXfermode(new PorterDuffXfermode(Mode.MULTIPLY));
paint.setShader(new BitmapShader(bm1, TileMode.CLAMP, TileMode.CLAMP));
canvas.drawRect(700, 700, bm2.getWidth(), bm2.getHeight(), paint);
}
Here is the result with changing the value in 'canvas.drawRect()' :
Before translation
After translation
Do you have other solutions ? Or suggestions which would help to realize my filter on animate views ? (The perfect solutions would be to add something like android:blendMode="Multiply" in the XML, but it doesn't seem as easy)
Thank you in advance. J'.
Google suggests to load Bitmaps from resources scaled down, depending on the actual ImageView size ("Loading Large Bitmaps Efficiently" at googles developer guides). Therefor, I have to know the width and height of the ImageView before I can decode the bitmap.
My code looks something like the one posted below. decodeSampledBitmapFromResources returns the bitmap as a scaled down version of the one stored in resources.
public void onCreate(Bundle savedInstanceSate)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout)
ImageView imageView = (ImageView) findViewById(R.id.myImageView);
/**
At this point, I need to calculate width and height of the ImageView.
**/
Bitmap bitmap = MyBitmapManager.decodeSampledBitmapFromResource(
getResources(), R.drawable.my_icon, width, height);
imageView.setImageBitmap(bitmap);
}
The problem is, as I am in onCreate, my ImageView does not have any width and height, yet. getWidth() and getHeight() are just returning 0. I stumbled over this code to calculate the size of a view before it is actually drawn:
ImageView v = findViewById(R.id.myImageView);
v.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
int width = v.getMeasuredWidth();
int height = v.getMeasuredHeight();
Is this suitable for my situation? I tried it and the above code returns some values for width and height which seem to be correct, but Im not sure if this is the correct way to do it.
UPDATE:
After some more testing, this seems NOT to work.
In the above example, im using a PNG with a size of 192x192 pixels.
After measureing the ImageView as seen above, i get a measured dimension of 128x128.
If I call getWidth() and getHeight() AFTER the bitmap is set to the imageview, the dimension is 100x100.
So in this scenario, the image is downsized from 192x192 to 128x128, but not to 100x100 as it should be.
It seems that Measurespec.UNSPECIFIED always returns dimensions greater than they are at the end.
Thanks in advance,
danijoo
I got it solved on my own with this:
ViewTreeObserver vto = imageView.getViewTreeObserver();
vto.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
public boolean onPreDraw(){
// at this point, true width and height are already determined
int width = imageView.getMeasuredWidth();
int height = imageView.getMeasuredHeight();
Bitmap bitmap = MyBitmapManager.decodeSampledBitmapFromResource(
getResources(), R.drawable.my_icon, width, height);
imageView.setImageBitmap(bitmap);
// this is important because onPreDrawn is fired multiple times
imageView.getViewTreeObserver().removeOnPreDrawListener(this);
return true;
}
}
The following code defines my Bitmap:
Resources res = context.getResources();
mBackground = BitmapFactory.decodeResource(res, R.drawable.background);
// scale bitmap
int h = 800; // height in pixels
int w = 480; // width in pixels
// Make sure w and h are in the correct order
Bitmap scaled = Bitmap.createScaledBitmap(mBackground, w, h, true);
... And the following code is used to execute/draw it (the unscaled Bitmap):
canvas.drawBitmap(mBackground, 0, 0, null);
My question is, how might I set it to draw the scaled Bitmap returned in the form of Bitmap scaled, and not the original?
Define a new class member variable:
Bitmap mScaledBackground;
Then, assign your newly created scaled bitmap to it:
mScaledBackground = scaled;
Then, call in your draw method:
canvas.drawBitmap(mScaledBackground, 0, 0, null);
Note that it is not a good idea to hard-code screen size in the way you did in your snippet above. Better would be to fetch your device screen size in the following way:
int width = getWindowManager().getDefaultDisplay().getWidth();
int height = getWindowManager().getDefaultDisplay().getHeight();
And it would be probably better not to declare a new bitmap for the only purpose of drawing your original background in a scaled way. Bitmaps consume a lot of precious resources, and usually a phone is limited to a few MB of Bitmaps you can load before your app ungracefully fails. Instead you could do something like this:
Rect src = new Rect(0, 0, bitmap.getWidth() - 1, bitmap.getHeight() - 1);
Rect dest = new Rect(0, 0, width - 1, height - 1);
canvas.drawBitmap(mBackground, src, dest, null);
To draw the scaled bitmap you want save your scaled bitmap in a field somewhere (here called mScaled) and call:
canvas.drawBitmap(mScaled, 0, 0, null);
in your draw method (or wherever you call it right now).