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.
Related
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)
I have one image (sprite) that contains two images (selected icon and non selected icon). The width of image is same but its height is double because of second image. How can I get these two images separately from one image?
I have solved this with this piece of code:
private Bitmap getCroppedBitmap(boolean isSelected){
Bitmap fullImage = BitmapFactory.decodeResource(getResources(), R.drawable.sprite);
Bitmap piece = null;
int height = fullImage.getHeight()/2;
if(isSelected){
// show first image
piece = Bitmap.createBitmap(fullImage, 0, 0, fullImage.getWidth(), height);
}else{
// show second image
piece = Bitmap.createBitmap(fullImage, 0, height, fullImage.getWidth(), height);
}
return piece;
}
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.
I have a ListView with visibility=gone and height=wrap_content, and i need its height to can make an animation of expand.
I already tried it:
view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int height = view.getMeasuredHeight();
and
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
int height = view.getMeasuredHeight();
and
v.measure(View.MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.MATCH_PARENT, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(ViewGroup.LayoutParams.WRAP_CONTENT, View.MeasureSpec.UNSPECIFIED));
int height = view.getMeasuredHeight();
But it returned me a lesser value.
I am trying to do this after onResume.
The only way that i found was setting the width of the my view from width of a visible header view, then the below code returned me the right value.
int widthSpec = MeasureSpec.makeMeasureSpec(headerView.getWidth(), MeasureSpec.EXACTLY);
int heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
v.measure(widthSpec, heightSpec);
int height = v.getMeasuredHeight();
view.post(new Runnable() {
#Override
public void run() {
view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
int height = view.getMeasuredHeight();
}
});
This worked for me. The method "post" makes sure the view is already added to the screen.
There is only one efficient way to do that if you want to animate height of a view whose visibility is gone
First add this in xml of hidden view
android:animateLayoutChanges="true"
then thats how you can set it to animate
yourView.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
Android will animate any layout change on that specific view
When an object is gone, it no longer is part of the layout. Maybe you mean to set your object to invisible, in which case you'll probably get a sensical value
This works for me
view.measure(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
int height = view.getMeasuredHeight();
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;
}
}