For my Android native application, I need to pick an image from the gallery and save it local app folder. I run the intent to open the image picker and select the image without any problem. After this the callback of registerForActivityResult is called and I get the path of the image. Now I use this code to recreate the bmp and save it locally:
val stream = contentResolver.openInputStream(result.data!!.data!!)
val bm = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
val bmpSource = ImageDecoder.createSource(
applicationContext.contentResolver,
result.data!!.data!!
)
ImageDecoder.decodeBitmap(
bmpSource
)
} else {
MediaStore.Images.Media.getBitmap(
contentResolver,
result.data!!.data!!
)
}
bm!!.saveToLocalStorage(this, "AVATAR_${mainViewModel.selectedDevice.first()!!.qrCode}.jpg")
fun Bitmap.saveToLocalStorage(context: Context, filename: String): Boolean {
return try {
context.openFileOutput("$filename", MODE_PRIVATE).use { stream ->
if (!this.compress(Bitmap.CompressFormat.JPEG, 90, stream)) {
throw IOException("Couldn't save image")
}
stream.close()
}
true
} catch (ex: IOException) {
false
}
}
I tried this code and it works properly on my Samsung (Android 11). The same code gives me problems on a Xiaomi device with Android 10. I thought it could be a problem with the version of Android, but in another device with Android 10 it works. The problem I'm facing in the Xiaomi device is that the image stored in the local app folder is just a black image with few Kb. The problems seems in the decode of the image and not in the saving method. Why do I get this problem? What is the best solution to copy the image locally?
EDIT: I cannot simply copy the picked image in the local app folder since I have to change its size. So I should load the image in memory, then create a scaled bitmap and finally save it to local app folder.
Related
I'm trying to figure out a way to delete images from the external storage automatically.
In my app, the user takes photos for some purposes which are saved in a folder created by the app inside the Pictures folder. After some time, the photos taken are of no use anymore. Therefore, I'm trying to write some code that enables the app to delete these photos by itself.
Before you ask, I'm aware that is not normal for an app to delete files from the app without any command from the user. But my app is used inside the company, only by employees etc.
I'm using ctx.contentResolver.delete(uri, null, null) to delete the image file. I have a catch to recover the SecurityException like this:
catch (e: SecurityException) {
val intentSender = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> {
MediaStore.createDeleteR`your text`equest(ctx.contentResolver, listOf(uri)).intentSender
}
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q -> {
val recoverableSecurityException = e as? RecoverableSecurityException
recoverableSecurityException?.userAction?.actionIntent?.intentSender
}
else -> null
}
intentSender?.let { sender ->
intentSenderLauncher.launch(
IntentSenderRequest.Builder(sender).build()
)
}
}
And everything seems to work fine at a first look as the intent to accept appears -- but no image is shown and nothing gets deleted.
Am I doing something wrong? What could be a way to delete these files?
I am picking an image from the gallery and querying its size via ContentResolver API, it returns 29kb.
However when I check the file via adb using ls -al it is 44kb
here is how I query the size of the image:
private fun getFileInfo(contentResolver: ContentResolver, fileUri: Uri): Pair<String, Long>? {
val projection = arrayOf(MediaStore.MediaColumns.DISPLAY_NAME, MediaStore.MediaColumns.SIZE)
val metaCursor = contentResolver.query(fileUri, projection, null, null, null)
metaCursor?.use {
if (it.moveToFirst()) {
val displayNameIndex = it.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME)
val sizeIndex = it.getColumnIndex(MediaStore.MediaColumns.SIZE)
return Pair(it.getString(displayNameIndex), it.getLong(sizeIndex))
}
}
return null
}
I am using an Oreo Emulator.
I have also checked via emulator's tools, file browser shows as 29kb on the other hand file details shows 45kb.
What is going on?
Here are the images from file browser:
Another side note, above situation can be reproducible every time when using camera app on emulator with Android 26-Oreo emulator, however it is fine with emulator Android 25-Nougat.
I have checked, new Document API also returns 29kb
I had a similar issue. The ContentResolver was underestimating the true filesize by about 400 bytes. I solved it using a similar approach to that explained in How to correctly get the file size of an android.net.Uri? :
ParcelFileDescriptor pfd = getContentResolver().openFileDescriptor(imageUri, "r");
fileLength = pfd.getStatSize();
pfd.close();
Android 8.0 (Oreo) introduced the 'SDCardFS' filesystem, which replaced 'FUSE' on the /sdcard partition.
it is possible that this new filesystem has a large cluster size and is therefore not able to allocate exactly 29kb, but instead the smallest block larger than 29kb, which in your case is 44.*kb.
The difference between ls -al and file details is probably caused by different rounding.
I´m working on a project related with image recognition using MATLAB and I'm currently using an Android app to help with some pre-processing steps. I thought it was going to be easy to work with matrices instead of bitmaps. I finally managed to finish my algorithm and to import it to Eclipse. The problem is that I realize that I don't know how to convert a Bitmap image into something that MATLAB can read in for the purposes of my algorithm.
Do you have any ideas on how I can do this?
If I'm interpreting your question correctly, you have an image stored in the Bitmap class and you want to save this to file locally on your Android device. You then want to load this image into MATLAB for your image recognition algorithm.
Given the fact that your image is in memory via Android, you can use the method compress: http://developer.android.com/reference/android/graphics/Bitmap.html#compress(android.graphics.Bitmap.CompressFormat, int, java.io.OutputStream
You'd then use this and save the image to file, and then you can load it into MATLAB, using imread for example.
Here's some sample code you could write for your Android app. Assuming your Bitmap instance is stored in a variable called bmp, do:
FileOutputStream out = null; // For writing to the device
String filename = "out.png"; // Output file name
// Full path to save
// This accesses the pictures directory of your device and saves the file there
String output = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES), filename);
try {
out = new FileOutputStream(filename); // Open up a new file stream
// Save the Bitmap instance to file
// First param - type of image
// Second param - Compression factor
// Third param - The full path to the file
// Note: PNG is lossless, so the compression factor (100) is ignored
bmp.compress(Bitmap.CompressFormat.PNG, 100, out);
}
// Catch any exceptions that happen
catch (Exception e) {
e.printStackTrace();
}
// Execute this code even if exception happens
finally {
try {
// Close the file if it was open to write
if (out != null)
out.close();
}
// Catch any exceptions with the closing here
catch (IOException e) {
e.printStackTrace();
}
}
The above code will save the image to your default Pictures directory on your device. Once you pull out that image, you can read the image into MATLAB by using imread:
im = imread('out.png');
im would thus be the raw RGB pixels of the image that you can now use for your image recognition algorithm.
I want to copy a remote image, for example "http://example.com/example.jpg" to the android user phone built gallery...How can I do it?
To that, you should download the image and save it in internal memory.
You can download the image by yourself:
public static Bitmap getBitmap(String url) {
try {
InputStream is = (InputStream) new URL(url).getContent();
Bitmap d = BitmapFactory.decodeStream(is);
is.close();
return d;
} catch (Exception e) {
return null;
}
}
Code from here But you will have memory problems with large images. I strongly recommended you to use a build library like Android Universal Image Loader or Picasso from square
Here you can find an example of how to use the Android DownloadManager to download your file.
The destination path can be determined using the contants defined in the Environment class. Constant DIRECTORY_DCIM points to the parent directory under which all Activities can create a custom folder where they store their images. You could make your own child folder as destination folder
When your image finishes downloading, you will notice that it will not be listed in the default gallery application, this is because Android builds an index with all the media files and is still unaware of your new downloaded image. This index is updated each time you boot your Android device, but since it's a bit unconvienient to reboot your device each time a file is added, you can also codewise inform the indexing service that a new file is created and needs indexing using this piece of code:
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(Uri.fromFile(file));
sendBroadcast(intent);
This scanning should also occur after a file has been erased.
I'm saving the screenshot of the app to Gallery via -
//screenshot
private void screenshot()
{
Uri photo = lomography();
String photopath = photo.getPath();
try {
MediaStore.Images.Media.insertImage(getContentResolver(), photopath, "name" , "desc");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
The image stored at the photopath is of high quality:
But when I navigate to Gallery and open the same photo, this is the low JPEG version with several compression artifacts:
Why does the Gallery image a JPEG version when the original file is a PNG?
How can I save the high res version to the gallery?
I read here that the trick is in 'Register image with ContentResolver::insert'.
But I'm using getContentResolver.
To fix this issue, open the delcaration of the "MediaStore.Images.Media.insertImage" method, copy and paste the code you need into your own class, then where its compressing the image at 50% change it to 100%.
This was the answer.
I mean 'this' was the missing part in the code.
Instead of 'MediaStore.Images.Media.insertImage(getContentResolver()'.
I should have used MediaStore.Images.Media.insertImage(this.getContentResolver()
Problem solved.