I have been struggling with setting an image (that i fetch using the uri) into an ImageView.
What i am doing ?
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
private final WeakReference<Attachment> imageViewReference;
public BitmapWorkerTask(Attachment imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<Attachment>(imageView);
}
// Decode image in background.
#Override
protected Bitmap doInBackground(Integer... params) {
return ImageResizer.decodeSampledBitmapFromFile(imageViewReference.get().getPath(), 200, 100);
}
// Once complete, see if ImageView is still around and set bitmap.
#Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final Attachment imageView = imageViewReference.get();
if (imageView != null) {
profilePic.setImageBitmap(bitmap);
}
else {
Log.v("BLAH[Inner]", (imageViewReference ==null) +""+(bitmap == null));
}
}
else {
Log.v("BLAH", (imageViewReference ==null) +""+(bitmap == null));
}
}
}
I have confirmed that the uri is correct , and the absolute path to the image is also correct(which is set in the path property of the Attachment object).Plus the bitmap is not null.
But the imageView is still not showing the image.
UPDATE
The image is not being shown on the first time but works everytime after that.Weird thing is that nothing shows up on the logcat as well.
Caution :
Bad variable name used(refactoring went wrong)
Default image(set in the xml) does show however.Take a look
I used hierarchy viewer to check into the layout.
I hope you have added permissions in manifest:
<uses-permission android:name="android.permission.INTERNET" />
Sometimes we miss small things.
You are using setImageBitmap which works always but you can use the following snippet code.
BitmapDrawable ob = new BitmapDrawable(getResources(), bitmap)
imageView.setBackgroundDrawable(ob);
Change
profilePic.setImageBitmap(bitmap)
to
imageView.setImageBitmap(bitmap)
From the information you provide, it seems that either the file has problem, or it is the ImageResizer.decodeSampledBitmapFromFile not working, returned an empty or transparent Bitmap.
Can you add this code in onPostExecute and post the result?
int width = bitmap.getWidth();
int height = bitmap.getHeight();
Log.v("BLAH", "width : " + width);
Log.v("BLAH", "height : " + height);
if(width > 0 && height > 0) {
Log.v("BLAH", "pixel : " + bitmap.getPixel(width/2,height/2));
}
Related
I'm looking to populate an imageview depending on which source contains the data. The holder.imgImage could have either a bitmap source or a drawable path but I only want one to be displayed depending on which image is present. I have tried if (image !=null) but doesnt seeem to work.
#Override
public void onBindViewHolder(#NonNull ViewHolder holder, int position) {
holder.myTextView1.setText(categoryList.get(position).getRecipe_name());
holder.myTextView2.setText(categoryList.get(position).getCategory_name());
String image2 = categoryList.get(position).getImage2();
Bitmap myBitmap = BitmapFactory.decodeFile(image2);
holder.imgImage.setImageBitmap(myBitmap);
holder.imgImage.setImageResource(categoryList.get(position).getImage());
}
maybe check if created Bitmap isn't null?
Bitmap myBitmap = BitmapFactory.decodeFile(image2);
if (myBitmap != null)
holder.imgImage.setImageBitmap(myBitmap);
else
holder.imgImage.setImageResource(categoryList.get(position).getImage());
maybe there is a case when getImage2() returns null or empty string?
String image2 = categoryList.get(position).getImage2();
Bitmap myBitmap = (image2 != null && image2.length()) > 0 ?
BitmapFactory.decodeFile(image2) : null;
Background
Since Android API 21, it's possible for apps to take screenshots globally and record the screen.
The problem
I've made a sample code out of all I've found on the Internet, but it has a few issues:
It's quite slow. Maybe it's possible to avoid it, at least for multiple screenshots, by avoiding the notification of it being removed till really not needed.
It has black margins on left and right, meaning something might be wrong with the calculations :
What I've tried
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ID = 1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.checkIfPossibleToRecordButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.requestScreenshotPermission(MainActivity.this, REQUEST_ID);
}
});
findViewById(R.id.takeScreenshotButton).setOnClickListener(new OnClickListener() {
#Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.takeScreenshot(MainActivity.this);
}
});
}
#Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ID)
ScreenshotManager.INSTANCE.onActivityResult(resultCode, data);
}
}
layout/activity_main.xml
<LinearLayout
android:id="#+id/rootView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="#+id/checkIfPossibleToRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="request if possible"/>
<Button
android:id="#+id/takeScreenshotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="take screenshot"/>
</LinearLayout>
ScreenshotManager
public class ScreenshotManager {
private static final String SCREENCAP_NAME = "screencap";
private static final int VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
public static final ScreenshotManager INSTANCE = new ScreenshotManager();
private Intent mIntent;
private ScreenshotManager() {
}
public void requestScreenshotPermission(#NonNull Activity activity, int requestId) {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
activity.startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), requestId);
}
public void onActivityResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && data != null)
mIntent = data;
else mIntent = null;
}
#UiThread
public boolean takeScreenshot(#NonNull Context context) {
if (mIntent == null)
return false;
final MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mIntent);
if (mediaProjection == null)
return false;
final int density = context.getResources().getDisplayMetrics().densityDpi;
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final int width = size.x, height = size.y;
// start capture reader
final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
final VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(SCREENCAP_NAME, width, height, density, VIRTUAL_DISPLAY_FLAGS, imageReader.getSurface(), null, null);
imageReader.setOnImageAvailableListener(new OnImageAvailableListener() {
#Override
public void onImageAvailable(final ImageReader reader) {
Log.d("AppLog", "onImageAvailable");
mediaProjection.stop();
new AsyncTask<Void, Void, Bitmap>() {
#Override
protected Bitmap doInBackground(final Void... params) {
Image image = null;
Bitmap bitmap = null;
try {
image = reader.acquireLatestImage();
if (image != null) {
Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride(), rowStride = planes[0].getRowStride(), rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
return bitmap;
}
} catch (Exception e) {
if (bitmap != null)
bitmap.recycle();
e.printStackTrace();
}
if (image != null)
image.close();
reader.close();
return null;
}
#Override
protected void onPostExecute(final Bitmap bitmap) {
super.onPostExecute(bitmap);
Log.d("AppLog", "Got bitmap?" + (bitmap != null));
}
}.execute();
}
}, null);
mediaProjection.registerCallback(new Callback() {
#Override
public void onStop() {
super.onStop();
if (virtualDisplay != null)
virtualDisplay.release();
imageReader.setOnImageAvailableListener(null, null);
mediaProjection.unregisterCallback(this);
}
}, null);
return true;
}
}
The questions
Well it's about the problems:
Why is it so slow? Is there a way to improve it?
How can I avoid, between taking screenshots, the removal of the notification of them? When can I remove the notification? Does the notification mean it constantly takes screenshots?
Why does the output bitmap (currently I don't do anything with it, because it's still POC) have black margins in it? What's wrong with the code in this matter?
NOTE: I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.
EDIT: I've noticed that on CommonsWare website (here), it is said that the output bitmap is larger for some reason, but as opposed to what I've noticed (black margin in beginning AND end), it says it's supposed to be in the end:
For inexplicable reasons, it will be a bit larger, with excess unused
pixels on each row on the end.
I've tried what was offered there, but it crashes with the exception "java.lang.RuntimeException: Buffer not large enough for pixels" .
Why does the output bitmap (currently I don't do anything with it, because it's still POC) have black margins in it? What's wrong with the code in this matter?
You have black margins around your screenshot because you are not using realSize of the window you're in. To solve this:
Get the real size of the window:
final Point windowSize = new Point();
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
windowManager.getDefaultDisplay().getRealSize(windowSize);
Use that to create your image reader:
imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
This third step may not be required but I have seen otherwise in my app's production code (which runs on a variety of android devices out there). When you acquire an image for ImageReader and create a bitmap out of it. Crop that bitmap using the window size using below code.
// fix the extra width from Image
Bitmap croppedBitmap;
try {
croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, windowSize.x, windowSize.y);
} catch (OutOfMemoryError e) {
Timber.d(e, "Out of memory when cropping bitmap of screen size");
croppedBitmap = bitmap;
}
if (croppedBitmap != bitmap) {
bitmap.recycle();
}
I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.
To capture screen/take screenshot you need an object of MediaProjection. To create such object, you need pair of resultCode (int) and Intent. You already know how these objects are acquired and cache those in your ScreenshotManager class.
Coming back to taking screenshots of any app, you need to follow the same procedure of getting these variables resultCode and Intent but instead of caching it locally in your class variables, start a background service and pass these variables to the same like any other normal parameters. Take a look at how Telecine does it here. When this background service is started it can provide a trigger (a notification button) to the user which when clicked, will perform the same operations of capturing screen/taking screenshot as you are doing in your ScreenshotManager class.
Why is it so slow? Is there a way to improve it?
How much slow is it to your expectations? My use case for Media projection API is to take a screenshot and present it to the user for editing. For me the speed is decent enough. One thing I feel worth mentioning is that the ImageReader class can accept a Handler to a thread in setOnImageAvailableListener. If you provide a handler there, onImageAvailable callback will be triggered on the handler thread instead of the one that created the ImageReader. This will help you in NOT creating a AsyncTask (and starting it) when an image is available instead the callback itself will happen on a background thread. This is how I create my ImageReader:
private void createImageReader() {
startBackgroundThread();
imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
ImageHandler imageHandler = new ImageHandler(context, domainModel, windowSize, this, notificationManager, analytics);
imageReader.setOnImageAvailableListener(imageHandler, backgroundHandler);
}
private void startBackgroundThread() {
backgroundThread = new HandlerThread(NAME_VIRTUAL_DISPLAY);
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
I am displaying an image in a RecyclerView whose source is is a bitmap taken from an MMS message. The problem is that the image is not displaying. Absolutely nothing is displayed. Here is my onBindView:
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
final String name = mDataset.get(position).getContact() ;
final MMSMessage message = mDataset.get(position);
holder.txtHeader.setText(name);
DateTime dateTime = new DateTime(message.getDate());
holder.txtDate.setText(dateTime.toString(Globals.generalSQLFormatterDT));
holder.txtText.setText(message.getBody());
holder.txtText.setVisibility(View.VISIBLE);
Bitmap bitmap = message.getBitmap();
if (bitmap != null) {
//bitmap is not null and I can see an image using Android Studio
bitmap =Bitmap.createScaledBitmap(bitmap, 120, 120, false);
holder.imgMMS.setImageBitmap(bitmap);
} else {
holder.imgMMS.setVisibility(View.GONE);
}
}
The xml for the ImageView:
<ImageView
android:layout_below="#+id/thirdLine"
android:id="#+id/imageMMS"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginRight="6dip"
android:contentDescription="TODO"
/>
I looked here and tried to scale down the image to an arbitrary small size. I don't think it's an out of memory error - I tried putting in the launcher icon as a test. What am I doing wrong?
if (bitmap != null) {
//bitmap is not null and I can see an image using Android Studio
bitmap =Bitmap.createScaledBitmap(bitmap, 120, 120, false);
holder.imgMMS.setImageBitmap(bitmap);
holder.imgMMS.setVisibility(View.GONE);
} else {
holder.imgMMS.setVisibility(View.GONE);
}
You are setting visibility to GONE. My guess is that the RecyclerView is recycling the views, and when it does the view is GONE since you are not setting it to Visible. Try adding holder.imgMMS.setVisibility(View.VISIBLE); for when bitmap is not null, like so:
if (bitmap != null) {
//bitmap is not null and I can see an image using Android Studio
bitmap =Bitmap.createScaledBitmap(bitmap, 120, 120, false);
holder.imgMMS.setImageBitmap(bitmap);
holder.imgMMS.setVisibility(View.VISIBLE);
} else {
holder.imgMMS.setVisibility(View.GONE);
}
The problem is as it is stated in question title. In fact I want to load images which I have their url in my records into RecyclerView and at the same time persist downloaded image to database. I am using realm.io and Glide and my RecyclerViewAdapter is as below:
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
final ProductModel obj = getData().get(position);
holder.data = obj;
holder.title.setText(obj.getTitle());
if (obj.getImage() == null) {
Glide
.with(context)
.load(obj.getImageUrl())
.fitCenter()
.placeholder(R.drawable.bronteel_logo)
.into(new GlideDrawableImageViewTarget(holder.icon) {
#Override
protected void setResource(GlideDrawable resource) {
// this.getView().setImageDrawable(resource); is about to be called
super.setResource(resource);
// here you can be sure it's already set
((ProductsFragment) mFragment).saveImage(obj, resource);
}
});
} else {
byte[] data = obj.getImage();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;
Bitmap bmp = BitmapFactory.decodeByteArray(data, 0, data.length, options);
holder.icon.setImageBitmap(bmp);
}
}
class MyViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
public TextView title;
public ImageView icon;
public ProductModel data;
public MyViewHolder(View view) {
super(view);
title = (TextView) view.findViewById(R.id.textView);
icon = (ImageView) view.findViewById(R.id.imageView);
view.setOnClickListener(this);
}
#Override
public void onClick(View view) {
if (data.getImage() != null)
activity.startActivity(new Intent(activity, ProductActivity.class).putExtra("id", data.getId()));
}
}
And here's how I save images:
public void saveImage(final ProductModel data, Drawable drw) {
new AsyncImagePersister(data).execute(drw);
}
private class AsyncImagePersister extends AsyncTask<Drawable, Void, byte[]> {
private final ProductModel data;
AsyncImagePersister(ProductModel data) {
this.data = data;
}
#Override
protected byte[] doInBackground(Drawable... drawables) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
Bitmap bmp = drawableToBitmap(drawables[0]);
bmp.compress(Bitmap.CompressFormat.PNG, 100, stream);
return stream.toByteArray();
}
#Override
protected void onPostExecute(final byte[] bytes) {
super.onPostExecute(bytes);
realm.executeTransaction(new Realm.Transaction() {
#Override
public void execute(Realm realm) {
data.setImage(bytes);
}
});
}
public Bitmap drawableToBitmap (Drawable drawable) {
Bitmap bitmap = null;
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if(bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
if(drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
}
However, when loading the images for the first time from internet (using Glide) it shows wrong pictures for different places and on the other hand after it fetches all images, the saved images to realm are in their correct place.
So what am I doing wrong? Please help. Thanks.
The misplaced images is due to views are being recycled, So the loaded bitmap does not necessarily belong to the current position, And another thing to consider is that using AsyncTask inside a RecyclerView won't play nice and will cause lags in your UI, And for the final point, saving the byte[] array in your model might end up to a OOM exception!
If you want do some long running task inside your adapter, think of using a Service, IntentService or ThreadHandler, so you will be sending tasks one by one and the'd be queued and executed one by one.
About having offline access to images:
One option could be using Glide.diskCacheStrategy method and use DiskCacheStrategy.ALL so the original image size will be cached and you can use later in offline mode
Second option is to use Picasso instead of Glide!
so that you can use a custom RequestHandler and download the image and save it somewhere so you can access it later, consider memory management is all on your side and you should handle it!
here's a hint for your second option:
create class which extends from RequestHandler:
CustomReqHandler : RequestHandler() {
Then you should override two methods: canHandleRequest(), load()
in canHandleRequest() you should determine whether you want to handle current request or not, so define a custom scheme for these requests and check if this is one of them like:
val scheme:String = data.uri.scheme
the 2nd method is load() which is executed on a background thread and returns a Result object, download the image, save it somewhere, and return Result object!
You don't actually have to save the loaded images in your database when you're using Glide for this purpose. Glide caches the images loaded once automatically and efficiently. The caching is a complex system and if you want to read more about the caching with Glide, you might have a look here.
Now, about the images loaded in wrong place - this should not happen. I found no serious bug in your onBindViewHolder but hence as I suggest you not to save the images locally you might consider loading the images simply with Glide like this.
Glide
.with(context)
.load(obj.getImageUrl())
.fitCenter()
.placeholder(R.drawable.bronteel_logo)
.into(holder.icon);
Just you need to make sure if the obj.getImageUrl() is returning proper url.
I have specified an area for the imageView in the layout, in which I should display an image by referring to its path. Now, I got the image displayed on its respective imageview but
(1) the image is smaller than the allotted are for the imageView. And I want the imgae
to fit exactly the imageView
(2) the image is rotated 90 degrees anticlockwise. I want it to be
displayed normally without rotation?
Please help me to achieve the aforementioned points
Last Update
Java_Code:
private void displayMeetinPointImageInsideImageView(DataBaseRow dataBaseRow, ImageView imgSpaceIv) {
// TODO Auto-generated method stub
if (dataBaseRow == null) {
Log.w(TAG, "#displayMeetinPointImageInsideImageView(): The Object Of The DataBaseRow Class Is NULL, Maybe "
+ "It Was Not Instantiated.");
return;
} else if (imgSpaceIv == null) {
Log.w(TAG, "#displayMeetinPointImageInsideImageView(): The Variable Of Type ImageView Is NULL, Maybe It Was "
+ "Not Instantiated.");
return;
} else if (dataBaseRow.getImgPath().isEmpty()) {
Log.w(TAG, "#displayMeetinPointImageInsideImageView(): dataBaseRow.getImgPath() Is Empty");
return;
} else {
String imgPathString = dataBaseRow.getImgPath();
Options options = new Options();
options.inSampleSize = 3;
Bitmap bitmap = BitmapFactory.decodeFile(imgPathString);
Bitmap imgbitmap = BitmapFactory.decodeFile(imgPathString, options);
imgSpaceIv.setImageBitmap(imgbitmap);
}
}
Look at your logs:
dataBaseRow.getImgPath()= /storage/emulated/0/DCIM/MPL/5th guy hinging.jpeg
There is space in your filename.