Android Imageview: Measure unspecified to calculate Bitmap dimensions - java

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;
}
}

Related

Save view as bitmap in high resolution

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)

Output is unexpected while trying to take a foreground-screenshot

So my problem is the output of this code is this (the square one) instead of what I should expect. Any ideas why this happens?
I'll explain this much further, the output is basically a square like image but not really square but it contains all the view but it is compressed unlike what we should expect, a fullscreen size image with all the views in the proper place:
Code:
private Bitmap getScreenshot(int id) {
Log.d(TAG, "Creating screenshot");
RelativeLayout RL= (RelativeLayout)findViewById(id);
View v1 = getWindow().getDecorView().getRootView(); //or RL.getRootView();
v1.setDrawingCacheEnabled(true);
v1.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
v1.layout(0, 0, v1.getMeasuredWidth(), v1.getMeasuredHeight());
v1.buildDrawingCache(true);
Bitmap bm = Bitmap.createBitmap(v1.getDrawingCache());
v1.setDrawingCacheEnabled(false);
return bm;
}
Image:
The answer to this question is:
Just force the width and the height of the views.
If it is auto scaled like match_parent, the screenshot will not see it like how it is outputted.
Code:
view.getLayoutParams().width = width;
Same goes with the height
https://i.stack.imgur.com/mFuNG.png

Save multiple TextViews as image of large resolution

Situation: I have a picture and user could add texts on it, change there color, size, position, rotation, font size and etc., i need to save all this texts in one image. It's ok, i'm saving them by using drawing cache.
//RelativeLayout layout - layout with textviews
layout.setDrawingCacheEnabled(true);
Bitmap bitmap = null;
if (layout.getDrawingCache() != null)
bitmap = Bitmap.createBitmap(layout.getDrawingCache());
layout.setDrawingCacheEnabled(false);
Problem: Result image could be small due to screen size of the user's device. I need this image in resolution of 1500-2000 px. In case of just resizing this image - text looks fuzzy and not as good as it was on the screen.
Question: Is there're some other ways to save textviews as image without just resizing and loosing quality of text?
Ok, finally i found working solution.
The idea: user add text view on the image with 800x800 px size, do something with it and then i need to get the same image but in 2000x2000 px. The problem was - after resizing text was fuzzy and noisy. But how can i take a screenshot of not rendered view with size bigger than screen?
Here code that i used, it works just fine, i get the same image, text in the same positions, same size and etc. but no resizing noise, text look clear and not fuzzy. Also, this code save bitmap much bigger than screen size and without showing it to user.
private Bitmap makeTextLayer(int maxWidth, int maxHeight, ImageObject imageObject) {
Context c = mContext;
View v = LayoutInflater.from(c).inflate(R.layout.text_view_generator, new LinearLayout(c), false);
RelativeLayout editTexts = (RelativeLayout) v.findViewById(R.id.editTexts);
initView(v, maxWidth, maxHeight);
for (int i = 0; i < imageObject.getEditTexts().size(); ++i) {
ImageObject.TextInImage textInImage = imageObject.getEditTexts().get(i);
//text view in relative layout - init his size, in my case it's as big as image
CustomEditText editText = new CustomEditText(c);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.FILL_PARENT);
params.addRule(RelativeLayout.CENTER_HORIZONTAL, RelativeLayout.TRUE);
// don't forget to add your view to layout, this view will be saved as screenshot
editTexts.addView(editText, params);
editText.getLayoutParams().width = maxWidth;
editText.getLayoutParams().height = maxHeight;
editText.loadTextParams(textInImage);
editText.loadSizeAndRotation(textInImage);
// this is important, without new init - position of text will be wrong
initView(v, maxWidth, maxHeight);
// and here i configure position
editText.loadPosition();
}
Bitmap result = getViewBitmap(v, maxWidth, maxHeight);
return result;
}
Bitmap getViewBitmap(View v, int maxWidth, int maxHeight) {
//Get the dimensions of the view so we can re-layout the view at its current size
//and create a bitmap of the same size
int width = v.getWidth();
int height = v.getHeight();
int measuredWidth = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
int measuredHeight = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
//Cause the view to re-layout
v.measure(measuredWidth, measuredHeight);
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
//Create a bitmap backed Canvas to draw the view into
Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);
//Now that the view is laid out and we have a canvas, ask the view to draw itself into the canvas
v.draw(c);
return b;
}
private void initView(View view, int maxWidth, int maxHeight){
ViewGroup.LayoutParams vParams = view.getLayoutParams();
//If the View hasn't been attached to a layout, or had LayoutParams set
//return null, or handle this case however you want
if (vParams == null) {
return;
}
int wSpec = measureSpecFromDimension(vParams.width, maxWidth);
int hSpec = measureSpecFromDimension(vParams.height, maxHeight);
view.measure(wSpec, hSpec);
int width = view.getMeasuredWidth();
int height = view.getMeasuredHeight();
//Cannot make a zero-width or zero-height bitmap
if (width == 0 || height == 0) {
return;
}
view.layout(0, 0, width, height);
}
private int measureSpecFromDimension(int dimension, int maxDimension) {
switch (dimension) {
case ViewGroup.LayoutParams.MATCH_PARENT:
return View.MeasureSpec.makeMeasureSpec(maxDimension, View.MeasureSpec.EXACTLY);
case ViewGroup.LayoutParams.WRAP_CONTENT:
return View.MeasureSpec.makeMeasureSpec(maxDimension, View.MeasureSpec.AT_MOST);
default:
return View.MeasureSpec.makeMeasureSpec(dimension, View.MeasureSpec.EXACTLY);
}
}
I would like to thank the authors of the comments in these posts:
Converting a view to Bitmap without displaying it in Android?
Taking a "screenshot" of a specific layout in Android
Take a screenshot of a whole View
Capture whole scrollview bigger than screen
How to screenshot or snapshot a view before it's rendered?
I found my solution when read them, if my solution will not work for you - check out this posts.

android, ImageView bottom of screen

Trying to put a ImageView on the bottom of the screen, + a fit width.
Fit width are ok, but image are near half its height too lower.
A guess: Are the Y of a Imageview centerBased?
Please don't profite "layout" solution, I look for a X, y, height, width solution. :)
(so simple in iOS )
private void layout_imageBottom(ImageView imageV) {
imageV.setScaleType(ImageView.ScaleType.FIT_XY);
imageV.setX(0);
imageV.setMinimumHeight(imageV.getHeight());
imageV.setMaxHeight(imageV.getHeight());
Integer iS= Settings.getScreenSize(getApplicationContext()).x;
imageV.setMinimumWidth(Settings.getScreenSize(getApplicationContext()).x);
imageV.setMaxWidth(Settings.getScreenSize(getApplicationContext()).x);
imageV.setY(Settings.getScreenSize(getApplicationContext()).y - imageV.getHeight());
}
Why don't you set it like this:
RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)imageV.getLayoutParams();
params.addRule(RelativeLayout.ALIGN_PARENT_BOTTOM);
imageV.setLayoutParams(params);
#Udi Idan was right, but if you still want to use X, Y, width, height solution then maybe you can try this
int width = screenW;
int height = 80;
final ImageView imageView = new ImageView(this);
LayoutParams params = new LayoutParams(width, height);
imageView.setLayoutParams(params);
imageView.setBackgroundResource(R.drawable.ic_launcher);
layout.addView(imageView);
imageView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
imageView.setX(0);
imageView.setY(layout.getMeasuredHeight() - imageView.getMeasuredHeight()
- imageView.getPaddingBottom() - imageView.getPaddingTop());
}
});
P/S: I tried some codes like your sample and get the wrong result, maybe we need to calculate ActionBar's height. You could try.

The View got Pixelated during Animation to Tilt

The view got pixilated during animation I just wanted to attain a little tilt while the I try to scroll. I am using the Universal-Image-Library to hanle the animation. I'd like to attain a 3D look when tilting the view.
The first picture, is what I want.
But this picture below, I what I have. The View below got pixilated.
private void rotateLeftFrag(View af) {
if (af != null) {
ObjectAnimator.ofFloat(af, "rotationY", 5, 0)
.setDuration(100).start();
}
}
ObjectAnimator com.nineoldandroids.animation.ObjectAnimator.ofFloat(Object target, String
propertyName, float... values)
Are there any resolve to this to attain smooth animation or titling of the view? Thanks
Update:
float density = getResources().getDisplayMetrics().density;
float scale = getResources().getDisplayMetrics().density;
af.setCameraDistance(density * scale);
ObjectAnimator.ofFloat(af, "rotationY", .5f, 0).setDuration(500).start();
I think this video could help you:
http://www.youtube.com/watch?v=pMcu35-tVls
At 2:10 the guy talks about adding 1 extra transparent pixel to each side of a rotating rectangle. That should help smoothing out the edges because they would be inside the rectangle, not on the border.
Link to the source code is below the video.
In case you can't see it:
http://developer.android.com/shareables/devbytes/CardFlip.zip
Class you want to see is CardView, method bitmapWithBorder:
private static final int ANTIALIAS_BORDER = 1;
/**
* Adding a 1 pixel transparent border around the bitmap can be used to
* anti-alias the image as it rotates.
*/
private BitmapDrawable bitmapWithBorder(BitmapDrawable bitmapDrawable) {
Bitmap bitmapWithBorder = Bitmap.createBitmap(bitmapDrawable.getIntrinsicWidth() +
ANTIALIAS_BORDER * 2, bitmapDrawable.getIntrinsicHeight() + ANTIALIAS_BORDER * 2,
Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmapWithBorder);
canvas.drawBitmap(bitmapDrawable.getBitmap(), ANTIALIAS_BORDER, ANTIALIAS_BORDER, null);
return new BitmapDrawable(getResources(), bitmapWithBorder);
}
Please try to turn off hardware rendering
if(android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
mHeaderImage.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}

Categories

Resources