Taking a screenshot in Xamarin.Forms - xamarin

I've looked at and tested extensively as this post. Below is the code for a screenshot button on PCL.
async void OnScreenshotButtonClicked(object sender, EventArgs args) {
var imageSender = (Image)sender;
var ScreenShot = DependencyService.Get<IScreenshotManager>().CaptureAsync();
await DisplayAlert("Image Saved", "The image has been saved to your device's picture album.", "OK");
}
So my question is how to turn byte[] into an image and save it to a device... For the life of me, I cannot figure out a way to save the image to the device. Any help would be greatly appreciated!!! Thanks!

Like Jason mentioned in the comments you can extend your code in the platform project by adding a File.WriteAllBytes(); line and save your file from right there.
Or you can decouple it and create another DependencyService. For instance create the IScreenshotService like: DependencyService.Get<IScreenshotService>().SaveScreenshot("MyScreenshot123", ScreenShot);
And create implementations like this:
PCL
public interface IScreenshotService
{
SaveScreenshot(string filename, byte[] imageBytes);`
}
iOS
[assembly: Xamarin.Forms.Dependency(typeof(ScreenshotService_iOS))]
 
namespace YourApp.iOS
{
    public class ScreenshotService_iOS: IScreenshotService
{
        public void SaveScreenshot(string filename, byte[] imageBytes)
        {
            var screenshot = new UIImage(NSData.FromArray(imageBytes));
            screenshot.SaveToPhotosAlbum((image, error) =>
            {
// Catch any error here
                if(error != null)
                    Console.WriteLine(error.ToString());
// If you want you can access the image here from the image parameter
            });
        }
    }
}
Android
[assembly: Xamarin.Forms.Dependency(typeof(Picture_Droid))]
 
namespace YourApp.Droid
{
    public class ScreenshotService_Android : IScreenshotService
{
        public void SaveScreenshot(string filename, byte[] imageBytes)
        {
            var dir = Android.OS.Environment.GetExternalStoragePublicDirectory(Android.OS.Environment.DirectoryDcim);
            
var pictures = dir.AbsolutePath;
            
// You might want to add a unique id like GUID or timestamp to the filename, else you could be overwriting an older image
            string filePath = System.IO.Path.Combine(pictures, filename);
            try
            {
// Write the image
                File.WriteAllBytes(filePath, imageData);
// With this we add the image to the image library on the device
                var mediaScanIntent = new Intent(Intent.ActionMediaScannerScanFile);
                mediaScanIntent.SetData(Uri.FromFile(new File(filePath)));
                Xamarin.Forms.Forms.Context.SendBroadcast(mediaScanIntent);
            }
            catch(Exception e)
            {
                Console.WriteLine(e.ToString());
            }
        }
    }
}
Don't forget to add the appropriate permissions on the Android app (probably WRITE_EXTERNAL_STORAGE is enough)
Lastly you could look at third-party libraries like Splat for example.

Related

Download Photo not saved on local folder Xamarin Forms iOS

Saved image not showing on galary iOS(working fine on Android but not iOS)
Here is my code for saving image to device
public void SavePicture(string name, byte[] data, string location = "Downloads")
{
var documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
documentsPath = Path.Combine(documentsPath, location);
Directory.CreateDirectory(documentsPath);
string filePath = Path.Combine(documentsPath, name);
File.WriteAllBytes(filePath, data); // writes to local storage
}
Also allow for photo permission on info.plist
Privacy - Photo Library Usage Description
after execute this code I cant find the saved photo on iOS device
How to solve this?
Well in iOS to show an image into the gallery you have to write additional code as just using File.WriteAllBytes won't call the native UIImageWriteToSavedPhotosAlbum API to show it in the album.
To do that you will have to call this API as follows:
Make a dependency service:
public interface ISavePhotosToAlbum
{
void SavePhotosWithFilePath(String Path);
}
Now Add a native class and do the following:
[assembly: Dependency(typeof(SaveToAlbum))]
namespace SavePhotosDemo.iOS
{
public class SaveToAlbum : ISavePhotosToAlbum
{
public void SavePhotosWithFilePath(String Path)
{
using (var url = new NSUrl (Path))
using (var data = NSData.FromUrl (url))
var image = UIImage.LoadFromData (data);
image.SaveToPhotosAlbum((img, error) =>
{
if (error == null)
{//Success }
else
{//Failure}
});
}
}
}
Then use this Dependency Service as follows:
DependencyService.Get<ISavePhotosToAlbum>().SavePhotosWithStream(imageUrl);
Also, see to it that you add this dependency service call only for ios by adding it in an OnPlatform if statement.
UPDATE
Use the following code to Add a Custom Photo Album in Photos library:
void AddAssetToAlbum(UIImage image, PHAssetCollection album, string imageName)
{
try
{
PHPhotoLibrary.SharedPhotoLibrary.PerformChanges(() =>
{
// Create asset request
var creationRequest = PHAssetCreationRequest.CreationRequestForAsset();
var options = new PHAssetResourceCreationOptions
{
OriginalFilename = imageName
};
creationRequest.AddResource(PHAssetResourceType.Photo, image.AsJPEG(), options);
// Change asset request (change album by adding photo to it)
var addAssetRequest = PHAssetCollectionChangeRequest.ChangeRequest(album);
addAssetRequest.AddAssets(new PHObject[] { creationRequest.PlaceholderForCreatedAsset });
}, (success, error) =>
{
if (!success)
Console.WriteLine("Error adding asset to album");
});
}
catch (Exception ex)
{
string h = ex.Message;
}
}
See to it that you request authorization using the PHPhotoLibrary.RequestAuthorization method as we need to request access to be able to use the Photos library. Also, As of iOS 10 and above we also need to add an entry for access in the target .plist file for "Privacy - Photo Library Usage Description":
<key>NSPhotoLibraryUsageDescription</key>
<string>Access to photos is needed to provide app features</string>
Update
At the end, adding the following started showing the documents in the files app:
<key>UIFileSharingEnabled</key> <true/> <key>LSSupportsOpeningDocumentsInPlace</key> <true/>

How can i open an image in it's full size in UWP

I am working on a chat app:
If the user taps on an image, it will show in full size. The following method is called:
Handle_Tapped():
void Handle_Tapped(object sender, System.EventArgs e)
{
try
{
Image image = sender as Image;
string filePath = String.Empty;
filePath = image.Source as FileImageSource;
Eva3Toolkit.CommonUtils.openImage(filePath);
}
catch (Exception ex)
{
throw ex;
}
}
Which calls (depending on the OS):
openImage() in Android:
public void openImage(string filePath)
{
Intent sendIntent = new Intent(Intent.ActionView);
sendIntent.SetDataAndType(Android.Net.Uri.Parse("file:" + filePath), "image/*");
Forms.Context.StartActivity(Intent.CreateChooser(sendIntent, "Bild öffnen..."));
}
openImage() in iOS:
public void openImage(string filePath)
{
var firstController = ((UIApplicationDelegate)(UIApplication.SharedApplication.Delegate)).Window.RootViewController.ChildViewControllers[0].ChildViewControllers[1].ChildViewControllers[0];
var navcontroller = firstController as UINavigationController;
var docIC = UIDocumentInteractionController.FromUrl(new NSUrl(filePath, true));
docIC.Delegate = new DocInteractionC(navcontroller);
docIC.PresentPreview(true);
}
Now I want to create a method openImage() in UWP, but I don't know know. I know that I will most likely have to work with the image as a StorageFile instead of the path because only a StorageFile grants me permission to open the image.
Is there a way to open the image in full size in UWP? I highly prefer to not create a new view for this.
A Launcher can open a file with the program that is assigned to the file:
public async void openImageAsync(string filePath)
{
StorageFile storageFile = await StorageFile.GetFileFromPathAsync(filePath);
await Launcher.LaunchFileAsync(storageFile);
}
For my situation it's working that I use filePath because the files are in a local folder.
The output looks like this:

kitkat is not letting me upload images

I currently am using the default "file choose/image picker" in android to select the image i want to upload to my server. But the file chooser is not working with android kitkat. When ever I choose an image using the file chooser the URI or the local address to my image is returned as NULL. My code works perfectly with other android devices starting from android 2.2/2.3 to 4.2/4.3.
What I would love to know is if there is a way around it or is there a custom file chooser or a script that i should be using?
Any help is appreciated since this is my first time on stackoverflow. Thank you
never mind I found it.
Bitmap bitmap;
private static final int READ_REQUEST_CODE = 42;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.activity_main);
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
// Filter to only show results that can be "opened", such as a
// file (as opposed to a list of contacts or timezones)
intent.addCategory(Intent.CATEGORY_OPENABLE);
// Filter to show only images, using the image MIME data type.
// If one wanted to search for ogg vorbis files, the type would be "audio/ogg".
// To search for all documents available via installed storage providers,
// it would be "*/*".
intent.setType("image/*");
startActivityForResult(intent, READ_REQUEST_CODE);
}
#Override
public void onActivityResult(int requestCode, int resultCode,
Intent resultData) {
// The ACTION_OPEN_DOCUMENT intent was sent with the request code
// READ_REQUEST_CODE. If the request code seen here doesn't match, it's the
// response to some other intent, and the code below shouldn't run at all.
if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
// The document selected by the user won't be returned in the intent.
// Instead, a URI to that document will be contained in the return intent
// provided to this method as a parameter.
// Pull that URI using resultData.getData().
Uri uri = null;
if (resultData != null) {
uri = resultData.getData();
try {
bitmap = MediaStore.Images.Media.getBitmap(this.getContentResolver(),uri);
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
ImageView my_img_view = (ImageView ) findViewById (R.id.uploadlayout2);
my_img_view.setImageBitmap(bitmap);
}
}
}
this worked for me.
Just remove the last lines about declaring a string and toasting the uri. And you are good to go.

Getting stream from file absolute path

This may sound very simple but I've lost a lot of time looking for answer
In my Windows phone 8 app I use the PhotoChooserTask to let the user choose the photo, and i get the path of the photo by using
string FileName = e.OriginalFileName;
where e is the PhotoResult argument of the Task. , let's say: FileName=
"C:\Data\SharedData\Comms\Unistore\data\18\k\2000000a00000018700b.dat" (selected from the cloud) or
"D:\Pictures\Camera Roll\WP_20140110_10_40_42_1_Smart.jpg" (from camera roll)
I want to save that string path and open it up and show the image again when the users reopen the app. But I cannot find a method to convert those string into Image data (BitmapImage or Stream)
Any idea?
private void PhotoChooserTaskCompleted(object sender, PhotoResult e)
{
if (e.TaskResult == TaskResult.OK)
{
var image = new BitmapImage();
image.SetSource(e.ChosenPhoto);
SaveImageAsync(image);
}
}
public async void SaveImageAsync(BitmapImage image)
{
await Task.Run(SaveImage(image));
}
public async Task SaveImage(BitmapImage image)
{
IStorageFolder folder = await ApplicationData.Current.LocalFolder
.CreateFolderAsync("Images", CreationCollisionOption.OpenIfExists);
IStorageFile file = await folder.CreateFileAsync(
imageFileName, CreationCollisionOption.ReplaceExisting);
using (Stream stream = await file.OpenStreamForWriteAsync())
{
var wrBitmap = new WriteableBitmap(image);
wrBitmap.SaveJpeg(stream, image.PixelWidth, image.PixelHeight, 100, 100);
}
}
At Windows Phone 8.1 you can try "StorageFolder.GetFolderFromPathAsync" static method (if this API is available at your app flavor) and then get a stream for a necessary file from that folder.

Binding Image stored in the Isolated Storage to Image Control in Windows Phone

Is it possible to bind the image present in the Isolates storage to image control through xaml. I found some implementations like getting the image through the property and binding that into xaml control. But this is not the implementation what I am searching for. My question is like, writing an attach property and helper method to fetch the content from Isolated storage. I found a similar implementation in LowProfileImage class, used in windows phone 7. But I think it is deprecated now. If anyone tried similar implementations please help me to achieve the same. Also if implementation have any performance drains please mention that info too.
Yes, it is possible to use images from isolated storage in the app UI. It requires loading the image from the file into the BitmapImage and then binding ImageSource of your control to that BitmapImage. I'm using the following approach:
First, there's a method to load image asynchronously:
private Task<Stream> LoadImageAsync(string filename)
{
return Task.Factory.StartNew<Stream>(() =>
{
if (filename == null)
{
throw new ArgumentException("one of parameters is null");
}
Stream stream = null;
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(filename))
{
stream = isoStore.OpenFile(filename, System.IO.FileMode.Open, FileAccess.Read);
}
}
return stream;
});
}
Then it can be used like this:
public async Task<BitmapSource> FetchImage()
{
BitmapImage image = null;
using (var imageStream = await LoadImageAsync(doc.ImagePath))
{
if (imageStream != null)
{
image = new BitmapImage();
image.SetSource(imageStream);
}
}
return image;
}
And finally you just assign return value of FetchImage() method to some of your view model's property, to which the UI element is bound. Of course, your view model should properly implement INotifyPropertyChanged interface for this approach to work reliably.
If you want to use attached properties approach, here's how you do it:
public class IsoStoreImageSource : DependencyObject
{
public static void SetIsoStoreFileName(UIElement element, string value)
{
element.SetValue(IsoStoreFileNameProperty, value);
}
public static string GetIsoStoreFileName(UIElement element)
{
return (string)element.GetValue(IsoStoreFileNameProperty);
}
// Using a DependencyProperty as the backing store for IsoStoreFileName. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsoStoreFileNameProperty =
DependencyProperty.RegisterAttached("IsoStoreFileName", typeof(string), typeof(IsoStoreImageSource), new PropertyMetadata("", Changed));
private static void Changed(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
Image img = d as Image;
if (img != null)
{
var path = e.NewValue as string;
SynchronizationContext uiThread = SynchronizationContext.Current;
Task.Factory.StartNew(() =>
{
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
if (isoStore.FileExists(path))
{
var stream = isoStore.OpenFile(path, System.IO.FileMode.Open, FileAccess.Read);
uiThread.Post(_ =>
{
var _img = new BitmapImage();
_img.SetSource(stream);
img.Source = _img;
}, null);
}
}
});
}
}
}
And then in XAML:
<Image local:IsoStoreImageSource.IsoStoreFileName="{Binding Path}" />
Some limitations of this approach:
It only works on Image control, though you can change this to a whichever type you want. It's just not very generic.
Performance-wise, it will use a thread from the threadpool every time image source is changed. It's the only way to do asynchronous read from isolated storage on Windows Phone 8 right now. And you definitely don't want to do this synchronously.
But it has one one important advantage:
It works! :)
I like the above approach but there is a simpler more hacky way of doing it if you are interested.
You can go into your xaml and bind the image source to an string property then put the file path into the property dynamically.
<!-- XAML CODE -->
<Image Source="{Binding imagePath}"/>
//Behind property
public String imagePath { get; set; }
load your path into the image path then bind the image source to the image path string. You might have to do an INotifyPropertyChanged but this method should work with proper binding.

Resources