I need to save a bitmap to the Gallery using my application name as the album. The path should be:
Gallery->MyAppName->test.png
but the best result that I get looks like:
Gallery->Others->MyAppName->test.png
Here is my code:
using Android.Graphics;
using Android.Media;
using System;
using System.IO;
..
.
.
public static void ExportBitmapAsPNG(Bitmap bitmap)
{
var sdCardPath = Android.OS.Environment.ExternalStorageDirectory.AbsolutePath+"/MyAppName";
if (!Directory.Exists(sdCardPath))
Directory.CreateDirectory(sdCardPath);
var filePath = System.IO.Path.Combine(sdCardPath, "test.png");
var stream = new FileStream(filePath, FileMode.Create);
bitmap.Compress(Bitmap.CompressFormat.Png, 100, stream);
stream.Close();
MediaScannerConnection.ScanFile(Android.App.Application.Context, new string[] { filePath }, null, null);
}
I hope someone could tell me what I'm missing?
P.S.
I tried to use MediaStore, but it's always save in Picture folder and has no overloads to change this.
Android.Provider.MediaStore.Images.Media.InsertImage(ContentResolver, bitmap2, "test", "");
I've rewritten your function to provide that capability. The key difference is how the folder is created. Additionally, I use an Intent for the media scanner. Anyway, I think this should get you where you want to be. Hope this helps!
public void ExportBitmapAsPNG(Bitmap bitmap) {
// Get/Create Album Folder To Save To
var jFolder = new Java.IO.File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "MyAppNamePhotoAlbum");
if (!jFolder.Exists())
jFolder.Mkdirs();
var jFile = new Java.IO.File(jFolder, "MyPhoto.jpg");
// Save File
using (var fs = new FileStream(jFile.AbsolutePath, FileMode.CreateNew)) {
bitmap.Compress(Bitmap.CompressFormat.Png, 100, fs);
}
// Save Picture To Gallery
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(jFile));
StartActivityForResult(intent, 0);
// Request the media scanner to scan a file and add it to the media database
var f = new Java.IO.File(jFile.AbsolutePath);
intent = new Intent(Intent.ActionMediaScannerScanFile);
intent.SetData(Android.Net.Uri.FromFile(f));
Application.Context.SendBroadcast(intent);
}
Hope this helps!
Related
I know that is easy to take a photo and save it to Gallery.
protected async Task<MediaFile> TakePhoto()
{
var storageOptions = new StoreCameraMediaOptions()
{
SaveToAlbum = true,
Directory = pictureAlbumName,
Name = $"test_{DateTime.Now.ToString("HH_mm_ss_ff")}.jpg"
};
return await CrossMedia.Current.TakePhotoAsync(storageOptions);
}
As the result I got the URL that looks like this:
/storage/emulated/0/Android/data/com.companyname.appname/files/Pictures/MyAlbum/photo_18_47_29_69.jpg
But when I tried to save the image from bytes it appears in the folder but never appears in the gallery. After saving the image I tried of course to scan the newly created path but there was no effect
First attempt
File.WriteAllBytes("/storage/emulated/0/Android/data/com.companyname.appname/files/Pictures/MyAlbum/downloaded_image_223213a3as.jpg", immageBytes);
MediaScannerConnection.ScanFile(Application.Context, new string[] { path },null,null);
Second attempt using obsoleted Android methods
Java.IO.File storagePath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures);
string path = System.IO.Path.Combine(storagePath.ToString(), filename);
System.IO.File.WriteAllBytes(path, imageByte);
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(path)));
CurrentContext.SendBroadcast(mediaScanIntent);
Update:
Basically you need to use this method and save it
private void SaveImageToStorage(Bitmap bitmap)
{
Stream imageOutStream;
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
ContentValues values = new ContentValues();
values.Put(MediaStore.IMediaColumns.DisplayName,
"image_screenshot.jpg");
values.Put(MediaStore.IMediaColumns.MimeType, "image/jpeg");
values.Put(MediaStore.IMediaColumns.RelativePath,
Android.OS.Environment.DirectoryPictures + Java.IO.File.PathSeparator + "AppName");
Android.Net.Uri uri = this.ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, values);
imageOutStream = ContentResolver.OpenOutputStream(uri);
}
else
{
String imagesDir =Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures).ToString() + "/AppName";
imageOutStream = File.OpenRead(System.IO.Path.Combine(imagesDir, "image_screenshot.jpg"));
}
bitmap.Compress(Bitmap.CompressFormat.Jpeg, 100, imageOutStream);
imageOutStream.Close();
}
OG Answer:
As far as I know, Only images in your media store provider are visible to your gallery and to add it to the media store you need to use the following:
MediaStore.Images.Media.InsertImage(Activity.ContentResolver, ImgBitmap, yourTitle , yourDescription);
Hope this helps :)
im developing a Xamarin Forms App where the User can take Videos and save them to the public external storage. Im currently saving the filename of the created video.
My way of saving the video:
private readonly string DirectoryName = "KiloFürKilo";
public async Task<string> CaptureVideoAsync()
{
var photo = await MediaPicker.CaptureVideoAsync();
await using var stream = await photo.OpenReadAsync();
await using var memoryStream = new MemoryStream();
await stream.CopyToAsync(memoryStream);
var filename = "KforK" + DateTime.Now + ".mp4";
SaveVideoFromByte(memoryStream.ToArray(), filename);
return filename;
}
private async void SaveVideoFromByte(byte[] imageByte, string filename)
{
var context = CrossCurrentActivity.Current.AppContext;
var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
//Android 10+
if (Android.OS.Build.VERSION.SdkInt > Android.OS.BuildVersionCodes.P)
{
using var resolver = context.ContentResolver;
var contentValues = new ContentValues();
contentValues.Put(MediaStore.IMediaColumns.DisplayName, filename);
contentValues.Put(MediaStore.IMediaColumns.MimeType, "video/mp4");
contentValues.Put(MediaStore.IMediaColumns.RelativePath, "DCIM/" + DirectoryName);
var uri = resolver.Insert(MediaStore.Video.Media.ExternalContentUri, contentValues);
using var stream = resolver.OpenOutputStream(uri);
await stream.WriteAsync(imageByte);
stream.Close();
mediaScanIntent.SetData(uri);
}
else
{
var rootPath =
Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryMovies);
var storagePath = Path.Combine(rootPath.ToString(), DirectoryName);
if (!File.Exists(storagePath))
{
Directory.CreateDirectory(storagePath);
}
string path = Path.Combine(storagePath.ToString(), filename);
File.WriteAllBytes(path, imageByte);
mediaScanIntent.SetData(Android.Net.Uri.FromFile(new Java.IO.File(path)));
}
context.SendBroadcast(mediaScanIntent);
}
I now want to be able to open and play the video, without the user need to pick it from the gallery.
How can i find a video in the external public storage by a filename and retrieve the path to it?
I think i have to use the MediaStore but could not figure out how.
Thanks to anyone who can help me with this problem:)
MediaStore insert() gave you an uri where you wrote the file.
To read/play the file you can use the same uri.
Further your relative path was "DCIM/"+DirectoryName;
And if you need a path then complete path is
"/storage/emulated/0/DCIM/"+DirectoryName+"/"+filename.
I'm pretty new to the Android world, and I'm facing problems with an app written in Xamarin Android Mono environment.
I need to upgrade Android Target API at least to 29 (better 30), and I stuck at "scoped storage" change.
Although I was able to manage my own pictures taken by the camera (introducing the FileProvider), I still have problem with external images taken by the app Microsoft Office Lens (stored in public folder Pictures/Office Lens).
Summarizing, I use to call OfficeLens by an intent from my app:
StartActivity(Android.App.Application.Context.PackageManager.GetLaunchIntentForPackage("com.microsoft.office.officelens"))
checking for new files with the "deprecated" GetExternalStoragePublicDirectory, that is still working for my porpouse (such as read the list of Office Lens file in order to find new ones):
var TmpDir = new File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), "Office Lens");
Read the NewFiles saving the paths into a list:
var LstFilesNew = new List<string>();
...
foreach (var I in TmpDir.ListFiles())
{
if (I.IsFile == true)
{
LstFilesNew.Add(I.Path);
}
}
Whether before simply used to call this function passing the FilePath to get the BitMap:
public static Bitmap LoadBitmap(this string fileName)
{
var options = new BitmapFactory.Options();
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeFile(fileName, options);
}
With scoped storage I'm trying to get the URI, from the URI to get the Bitmap via MediaStore:
foreach (var FileNew in LstFilesNew)
{
if (Build.VERSION.SdkInt >= BuildVersionCodes.Q)
{
ContentValues contentValues = new ContentValues();
contentValues.Put(MediaStore.MediaColumns.DisplayName, FileNew);
contentValues.Put(MediaStore.MediaColumns.MimeType, "image/jpg");
contentValues.Put(MediaStore.MediaColumns.RelativePath, Environment.DirectoryPictures);
Uri imageUri = Application.Context.ContentResolver.Insert(MediaStore.Images.Media.ExternalContentUri, contentValues);
fos = Application.Context.ContentResolver.OpenOutputStream(imageUri);
Android.Graphics.Bitmap finalBitmap = MediaStore.Images.Media.GetBitmap(Application.Context.ContentResolver, imageUri);
finalBitmap.Compress(Android.Graphics.Bitmap.CompressFormat.Jpeg, 100, fos);
fos.Close();
}
}
My URI seems to be ok ("content://media/external/images/media/1563") but I obtain a "finalBitmap" null after calling "MediaStore.Images.Media.GetBitmap".
I guess I'm completely wrong in my approach, but I spent some days to trying and googling without any result.
Any helps about how to get a bitmap from pictures taken by a third party application in Pictures/"xxx" it'll be really appreciated.
Thank you in advance, cheers
Uri uri = Uri.parse("content://media/external/images/media/1563");
InputStream is = getContentResolver().openInputStream(uri);
Bitmap bitmap = BitmapFactory.decodeStream(is);
The GetExternalStoragePublicDirectory is still working targeting Android 10 and Android 11 to retrieve the path for Images/xyz.
Furthermore, it's still possible to get a bitmap like this:
public static Bitmap LoadBitmap(this string filePath)
{
var options = new BitmapFactory.Options();
options.InJustDecodeBounds = false;
return BitmapFactory.DecodeFile(fileName, options);
}
where filePath are read from the result of GetExternalStoragePublicDirectory with the method ListFiles():
var TmpDir = new Java.IO.File(Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures), "xyz");
foreach (var I in TmpDir.ListFiles())
{
if (I.IsFile == true)
{
var Bmp = LoadBitmap(I.Path);
}
}
Thanks to #blackapps for the hits!
How can be compress the image taken in Xamarin.Android using CameraSourcePreview, the byte in the method OnPictureTaken is too big.
Here's one way to do it by converting the Bitmap to a compressed JPG file. Also in this example is how to save the compressed JPG file to the picture gallery and make it immediately available through USB/Windows via an Android Media Scan. Hope this helps!
public void OnPictureTaken(byte[] data, Camera camera) {
var bmp = BitmapFactory.DecodeByteArray(data, 0, data.Length);
SaveBitmapAsJPEG(bmp, 75);
}
public void SaveBitmapAsJPEG(Bitmap pBitmap, int pnQuality = 85) {
Java.IO.File jFolder = GetCreatePhotoAlbumStorageDir("MyPhotoAlbum");
Java.IO.File jFile = new Java.IO.File(jFolder, $"Photo_{DateTime.Now.ToString("yyyyMMddHHmmss")}.jpg");
// "/storage/emulated/0/Pictures/MyPhotoAlbum/Photo_20190526112410.jpg", which is the following via Windows/USB...
// "Internal shared storage\Pictures\MyPhotoAlbum\Photo_20190526112410.jpg"
using (var fs = new FileStream(jFile.AbsolutePath, FileMode.CreateNew)) {
pBitmap.Compress(Bitmap.CompressFormat.Jpeg, pnQuality, fs);
}
SavePictureToGallery(jFile);
Android.Util.Log.Info("MyApp", $"Picture saved using SaveBitmapAsJPEG() at {jFile.AbsolutePath}");
// Request the media scanner to scan a file and add it to the media database (Make file visible/available through USB connection in Windows Explorer)
var f = new Java.IO.File(jFile.AbsolutePath);
var intent = new Intent(Intent.ActionMediaScannerScanFile);
intent.SetData(Android.Net.Uri.FromFile(f));
Application.Context.SendBroadcast(intent);
}
public Java.IO.File GetCreatePhotoAlbumStorageDir(string psAlbumName) {
// Get the directory for the user's public pictures directory. Will create if it doesn't exist.
var dir = new Java.IO.File(Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures), psAlbumName);
if (!dir.Exists())
dir.Mkdirs();
return dir;
}
private void SavePictureToGallery(Java.IO.File pFile) {
var intent = new Intent(MediaStore.ActionImageCapture);
intent.PutExtra(MediaStore.ExtraOutput, Android.Net.Uri.FromFile(pFile));
StartActivityForResult(intent, 0);
}
Note that you could change the format to PNG if desired by changing the "CompressFormat" to .Png, and naming the file accordingly.
I started an Intent
Intent = new Intent();
Intent.SetType("image/*");
Intent.PutExtra(Intent.ExtraAllowMultiple,true);
Intent.SetAction(Intent.ActionGetContent);
StartActivityForResult(Intent.CreateChooser(Intent, "Select Picture"), code);`
I am working with bitmap.Compress(...) function and took the uri from above intent. I want to delete that bitmap file and recreate the file with the same name (in short: Replacing the existing file).
My physical devices is Samsung J5 having Internal storage and external sd card storage like /storage/emulated/0/ and /storage/D660-18BD/ (D668-18BD is not fixed, it changes according to devices).
When I tried to create a new file in the storage/D660-18BD/newfile.jpg, it says Access to the path is denied.
I googled a lot but no success
What I had tried
1. Added Read/Write External Storage (but android take it as Internal storage) in the manifest file
2. Added the above permission at Runtime also alongwith Read/Write URI permission.
Here is a demo in the thread, download it, it is a Xamarin.Forms project,
1) Run it on your phone, then it will create this path:/storage/11E3-2116/Android/data/com.companyname.cropsample/files/Pictures
2) Add this in the MainActivity, under LoadApplication(new App());:
Java.IO.File sdCardPath = Environment.GetExternalStoragePublicDirectory(Environment.DirectoryPictures);
// filePath: /storage/11E3-2116/Android/data/com.companyname.cropsample/files/Pictures
string destPath = Path.Combine("/storage/11E3-2116/Android/data/com.companyname.cropsample/files/Pictures/", "test.png");
string originPath = Path.Combine(sdCardPath.AbsolutePath, "nULSa.png");
Android.Util.Log.Error("lv", destPath);
FileOutputStream fos = new FileOutputStream(destPath, false);
FileInputStream fis = new FileInputStream(originPath);
int b;
byte[] d = new byte[1024 * 1024];
while ((b = fis.Read(d)) != -1)
{
fos.Write(d, 0, b);
}
fos.Flush();
fos.Close();
fis.Close();
storage/D660-18BD/ is wrong path, you can't get the permission, you have the permission only in your package folder. So you need create the path. Sorry I can't find where the path created, maybe you can find it.
This is the result:
The only thing you need to is to create this path: /storage/11E3-2116/Android/data/com.companyname.cropsample/files/Pictures firstly, once it created, then you can write. I hope it will help.
Update:
I find the solution:
using Android.App;
using Android.Widget;
using Android.OS;
using System.IO;
using Java.IO;
using System.Collections.Generic;
using Android.Content;
using Android.OS.Storage;
using Java.Lang.Reflect;
using System;
namespace WritToSd
{
[Activity(Label = "WritToSd", MainLauncher = true)]
public class MainActivity : Activity
{
string s;
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
// Set our view from the "main" layout resource
SetContentView(Resource.Layout.Main);
// important codes start:
List<string> avaliableStorages = getAvaliableStorages(this);
for (int i=0;i<avaliableStorages.Count;i++) {
// because there is only one external sd card, so s is the path we need.
s = avaliableStorages[i];
}
var str=this.GetExternalFilesDir(null).AbsolutePath;
string direction = s + "/Android/data/" + this.PackageName + "/files/Pictures";
Java.IO.File file = new Java.IO.File(direction);
if (!file.Exists())
{
file.Mkdirs();
}
// end
Java.IO.File sdCardPath = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryPictures);
// filePath: /storage/11E3-2116/Android/data/com.companyname.cropsample/files/Pictures
string destPath = Path.Combine(direction, "test.png");
string originPath = Path.Combine(sdCardPath.AbsolutePath, "test.png");
Android.Util.Log.Error("lv", destPath);
FileOutputStream fos = new FileOutputStream(destPath, false);
FileInputStream fis = new FileInputStream(originPath);
int b;
byte[] d = new byte[1024 * 1024];
while ((b = fis.Read(d)) != -1)
{
fos.Write(d, 0, b);
}
fos.Flush();
fos.Close();
fis.Close();
}
public List<string> getAvaliableStorages(Context context)
{
List<string> list = null;
try
{
var storageManager = (Android.OS.Storage.StorageManager)context.GetSystemService(Context.StorageService);
var volumeList = (Java.Lang.Object[])storageManager.Class.GetDeclaredMethod("getVolumeList").Invoke(storageManager);
list = new List<string>();
foreach (var storage in volumeList)
{
Java.IO.File info = (Java.IO.File)storage.Class.GetDeclaredMethod("getPathFile").Invoke(storage);
if ((bool)storage.Class.GetDeclaredMethod("isEmulated").Invoke(storage) == false && info.TotalSpace > 0)
{
list.Add(info.Path);
}
}
}
catch (Exception e)
{
}
return list;
}
}
}
}