I am a beginner in Xamarin.
I tried to write a simple app to save the signature with the help of Signature Pad.
A piece of code from MainPage.xaml
<controls:SignaturePadView x:Name="SignaturePAD"
Grid.Row="1"
StrokeColor="Black"
StrokeWidth="3"
BackgroundColor="Gray"
CaptionTextColor="Black"
PromptTextColor="Black"
SignatureLineColor="Black"
CaptionText="Podpis odbiorcy">
</controls:SignaturePadView>
<Button Grid.Row="2"
x:Name="SaveButton"
Text="Potwierdź"
Clicked="SaveSignature"/>
and a fragment from MainPage.xaml.cs
public partial class MainPage : ContentPage
{
public MainPage()
{
InitializeComponent();
}
public async void SaveSignature(object sender, EventArgs e)
{
Stream image = await SignaturePAD.GetImageStreamAsync(SignatureImageFormat.Png);
}
}
And my question is how can I save it into the my phone gallery?
I will be grateful for any help
the SignaturePad returns a stream - so you can write it to a file using normal C# I/O, like FileStream
Stream image = await SignaturePAD.GetImageStreamAsync(SignatureImageFormat.Png);
using (FileStream file = new FileStream(file_path, FileMode.Create, System.IO.FileAccess.Write))
{
image.CopyTo(file);
}
You might be able to find a plugin somewhere that would do this for you, but I would convert the stream to a byte[] like so:
using (image)
using (var memoryStream = new MemoryStream())
{
await image.CopyToAsync(memoryStream);
var picture = memoryStream.ToArray();
}
Then convert the byte[] to the native picture on each platform.
For Android use:
BitmapFactory.DecodeByteArray(picture, 0, picture.Length);
And for iOS use:
var nsData = NSData.FromArray(picture);
var uiImage = UIImage.LoadFromData(nsData);
Then you would need to add the image to the underlying gallery. Here is an example of how to achieve something like that on iOS: https://developer.xamarin.com/recipes/ios/media/video_and_photos/save_photo_to_album_with_metadata/
For Android, this post might help: Xamarin.Forms Android save image to gallery
Related
Solved: The answer was to update all of the nuget packages and target a newer version of Android. Now images loads as expected. I'm not happy with this as I was using exactly the code that Xamarin provided and targeting newer versions has deprecated some of the items the code relys on. Initial version was Xamarin.Forms v23 and I updated to V25
I have a brand new Xamarin forms project with a simple view in which I'm trying to display an image. I've tried several ways of getting an image to display and I am not having any luck at all.
I'm using <image> and I have also tried FFImageLoader control as well.
<StackLayout Orientation="Vertical">
<ff:CachedImage Source="https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg" WidthRequest="100" HeightRequest="100" />
<Button x:Name="btn" Text="Image" Clicked="Button_Clicked" />
<Frame OutlineColor="Red">
<Image x:Name="StupidImage" Source="{Binding Thumbnail}" Aspect="Fill" HeightRequest="100" WidthRequest="100" />
</Frame>
</StackLayout>
This is the current view. I've also set the Source directly to a value with no result.
I'm able to get a stream for the image. I'm able to read all of the bytes from the stream. I built a debug visualizer to display the bytes as an image. Getting the image from a source is not a problem. Getting the image control(s) to display the image is a problem.
I tried binding with a view model. When that failed, I tried that directly setting the source
StupidImage.Source = ImageSource.FromStream(() => result.Stream);
I also made a copy of the bytes and tried
StupidImage.Source = ImageSource.FromStream(() => new MemoryStream(imageBytes));
I've tried ImageSource.FromFile() and .FromUri. I tried adding an image to the project as a resource. Each try was the same, the resource was read and the bytes were available, but the image control just doesn't display it.
I thought maybe it was a size problem, so I set the size of the control. Nothing. I thought maybe it was a resolution problem, so I used a smaller image. I tried several different images of varying quality.
Then I gave up on the image control and I got the FFImageLoading nuget package and gave it a direct url to an image. Same example that FFImageLoading examples used. Still no image.
I tried the emulator and I tried 2 different physical devices. Same result.
I also tried setting an image on a button using btn.Image = "whatever.jpg" with the same result.
This is the result every time. I'm lost. How do I get images to display?
EDIT:
I did get this to work, but only on the emulator
<Image x:Name="StupidImage" Source="https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg" />
and same for
StupidImage.Source = ImageSource.FromUri(new Uri("https://static.pexels.com/photos/104827/cat-pet-animal-domestic-104827.jpeg"));
EDIT 2 - Clarification
My goal is to allow the user to select a photo from the device and then display a preview of it.
If you want to use images in you app you can load them into your Shared Project, like
Make sure you change the Build Action to Embedded resource
Then in your code
image.Source = ImageSource.FromResource("App5.Images.useravatar.png");
Note the Resource name.
And XAML
<ContentPage.Content>
<StackLayout>
<Image x:Name="image" WidthRequest="50"/>
</StackLayout>
</ContentPage.Content>
Just a few things you can take off the list:
[x] Adding a image from Visual studio :
Right click on the correct folder
select Add >> New File ...
NB: you have to add it with visual studio and not just throw it in the folder. Visual studio needs to know about it
[x] When Adding the image is it in the correct place :
For android: it has to be in
ProjectName.Driod.Resources.drawable folder
For ios: it has to be in
ProjectName.iOS.Resources folder
[x] Naming Convention
Its always best to use .png , all lowercase , no spaces or special char on both android and ios
with ios you normally get 3 images of the same image with the following namting convention
theman.png
theman#2x.png
theman#3x.png
They are all the same image just different sizes
[x] Showing it in xaml :
<StackLayout>
<Image Source="thedog.png" HeightRequest="100" WidthRequest="100" />
</StackLayout>
In your example you used a frame , how about a stacklayout ? a frame has more requirements.
for MVVM you can change Source with the following , dont forget that twoway :)
Source="{Binding Thumbnail, Mode=TwoWay}"
NB This is VERY basic explanations
You can try implementing the CrossMedia Plugin.
Then in your button clicked code section, put the following:
Button_Clicked.Clicked += async (sender, args) =>
{
if ( !CrossMedia.Current.IsPickPhotoSupported )
{
DisplayAlert("Error message here", "More message", "OK");
return;
}
var file = await Plugin.Media.CrossMedia.Current.PickPhotoAsync(new Plugin.Media.Abstractions.PickMediaOptions
{
PhotoSize = Plugin.Media.Abstractions.PhotoSize.Medium
});
if (file == null)
return;
image.Source = ImageSource.FromStream(() =>
{
var stream = file.GetStream();
file.Dispose();
return stream;
});
};
Once the button is clicked, the gallery/directory will be displayed. You can choose the photo you want. Once you hit OK the image will be displayed in the Image control/tag. I'm not sure if this is the solution you are looking for. Hopes it gets you on the right direction.
This may or may not help I'll add some code, one of the surprising things about Xamarin forms and Android and using a memory stream.. is that the device density multiplier is still applied even if you aren't using a resource(If I am remembering correctly) so I would imagine if you are looking at the ADB interface you will see memory issues which is why you cant display an image... I solved this previously via sampling
The way I solved it was creating a new Image subclass -ResourceImage,
public class ResourceImage :Image
{
public enum SourceTypes{
Database,
File,
Function,
}
private bool _LoadAct = false;
public bool LoadAct { get{
return _LoadAct;
}
set{ _LoadAct = value; OnPropertyChanged ("LoadAct");
}
}
public Func<Stream> Func{ get; set; }
public SourceTypes SourceType{ get; set;}
public string ResName{ get; set;}
public ResourceImage ()
{
}
public ResourceImage (string name)
{
ResName = name;
}
public ResourceImage(Func<Stream> func){
SourceType = SourceTypes.Function;
Func = func;
}
}
then in the Android Renderer : I did the following
public class ResourceImageRenderer : ImageRenderer
{
protected override void OnElementChanged (ElementChangedEventArgs<Image> e)
{
base.OnElementChanged (e);
if (e.OldElement == null)
{
var el = (ResourceImage)Element;
if (el.SourceType == ResourceImage.SourceTypes.Database) {
//Ignore for now
} else if (el.SourceType == ResourceImage.SourceTypes.File) {
using (global::Android.Graphics.BitmapFactory.Options options = new global::Android.Graphics.BitmapFactory.Options ()) {
options.InJustDecodeBounds = false;
options.InSampleSize = 1;//calculateInSampleSize (options, outS.X / 4, outS.Y / 4);
var gd = Context.Resources.GetIdentifier (el.ResName.Split (new char[]{ '.' }) [0], "drawable", Context.PackageName);
using (global::Android.Graphics.Rect rt = new global::Android.Graphics.Rect (0, 0, 0, 0)) {
var bitmap = global::Android.Graphics.BitmapFactory.DecodeResource (Context.Resources, gd, options);//DecodeStream (ms, rt, options);
bitmap.Density = global::Android.Graphics.Bitmap.DensityNone;
Control.SetImageDrawable (new global::Android.Graphics.Drawables.BitmapDrawable (bitmap));
}
}
} else if (el.SourceType == ResourceImage.SourceTypes.Function) {
new Task (() => {
var ms = el.Func();
if(ms == null)return;
global::Android.Graphics.BitmapFactory.Options options = new global::Android.Graphics.BitmapFactory.Options ();
options.InJustDecodeBounds = false;
options.InSampleSize = 2;//calculateInSampleSize (options, outS.X / 4, outS.Y / 4);
ms.Position = 0;
Device.BeginInvokeOnMainThread(()=>{
using (global::Android.Graphics.Rect rt = new global::Android.Graphics.Rect (0, 0, 0, 0)) {
try{
var bitmap = global::Android.Graphics.BitmapFactory.DecodeStream (ms, rt, options);
bitmap.Density = global::Android.Graphics.Bitmap.DensityNone;
Control.SetImageDrawable (new global::Android.Graphics.Drawables.BitmapDrawable (bitmap));
}catch(Exception eee){
}
}
});
}).Start();
}
}
}
Looking back at the code(haven't touched it in years.) there are plenty of places for improvement, I had to add the sampling to solve the same issue , users were selecting images to display in a messaging app and it worked perfectly on iOS just never displayed on Android
This is how I allow a user to select an image and then display it on a page.
I call my image service Select Image method passing in a callback method
await _imageService.SelectImage(ImageSelected);
This is my SelectImage method. There is some permission checking at the start. It uses the Media Plugin to display the gallery and allow the user to select an image.
public async Task SelectImage(Action<MediaFile> imageAction)
{
var allowed = await _permissionService.CheckOrRequestStoragePermission();
if (!allowed) return;
if (!_media.IsPickPhotoSupported)
{
throw new GalleryUnavailableException("Gallery unavailable");
}
var file = await _media.PickPhotoAsync(new PickMediaOptions
{
PhotoSize = PhotoSize.Small,
CompressionQuality = 92
});
imageAction(file);
}
It returns a MediaFile
Here is the Image Selected callback method
private void ImageSelected(MediaFile image)
{
if (image == null)
{
return;
}
ChosenImage = new IncidentImage
{
ImageBytes = image.ToByteArray()
};
}
ChosenImage is a Property in my view model
public IncidentImage ChosenImage {get; set;}
I use PropertyChanged.Fody to trigger property changed notifications but you can also use INotifyPropertyChanged.
And IncidentImage is a class I use to both store and display images
public class IncidentImage
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
public int IncidentDetailsId { get; set; }
public byte[] ImageBytes { get; set; }
[Ignore]
public ImageSource ImageSource
{
get
{
ImageSource retval = null;
try
{
if (ImageBytes != null)
{
retval = ImageSource.FromStream(() => new MemoryStream(ImageBytes));
}
}
catch (Exception e)
{
Debug.WriteLine(e);
}
return retval;
}
}
}
And here is the XAML
<Image Source="{Binding ChosenImage.ImageSource}"
Aspect="AspectFit"/>
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.
I am trying to load and render an image using the skia graphics library in my xamarin forms solution. When I try to render the image (running the android project) I get the following error:
Value cannot be null. Parameter name: codec
here is the code:
void OnPainting(object sender, SKPaintSurfaceEventArgs e)
{
var surface = e.Surface;
var canvas = surface.Canvas;
canvas.Clear(SKColors.White);
var filename = "test.jpg";
using (var stream = new SKFileStream(filename))
using (var bitmap = SKBitmap.Decode(stream)) // the error occurs on this line
using (var paint = new SKPaint())
{
canvas.DrawBitmap(bitmap, SKRect.Create(200, 200), paint);
}
}
I cannot find any sample code online for xamarin. Any sample code or links would be much appreciated.
thanks in advance
Value cannot be null. Parameter name: codec
I think it is possible you get a null object here: using (var stream = new SKFileStream(filename)). I tried to created a demo, and it works fine.
XAML:
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:skiaviews="clr-namespace:SkiaSharp.Views.Forms;assembly=SkiaSharp.Views.Forms"
x:Class="FormsIssue6.Page1">
<Grid>
<skiaviews:SKCanvasView x:Name="mycanvas" PaintSurface="OnPainting" />
</Grid>
</ContentPage>
Code behind:
private void OnPainting(object sender, SkiaSharp.Views.Forms.SKPaintSurfaceEventArgs e)
{
var surface = e.Surface;
var canvas = surface.Canvas;
var assembly = typeof(Page1).GetTypeInfo().Assembly;
var fileStream = assembly.GetManifestResourceStream("YOUR-FILE-FULL-NAME");
// clear the canvas / fill with white
canvas.DrawColor(SKColors.White);
// decode the bitmap from the stream
using (var stream = new SKManagedStream(fileStream))
using (var bitmap = SKBitmap.Decode(stream))
using (var paint = new SKPaint())
{
// create the image filter
using (var filter = SKImageFilter.CreateBlur(5, 5))
{
paint.ImageFilter = filter;
// draw the bitmap through the filter
canvas.DrawBitmap(bitmap, SKRect.Create(640, 480), paint);
}
}
}
The file name in the code above should be like "YOUR PROJECT NAMESPACE"."File NAME", and this file is placed in PCL and build action of this file must be "Embedded Resource". For more information about working with file, you can refer to Files.
I cannot find any sample code online for xamarin. Any sample code or links would be much appreciated.
The package itself on Github has a code sample for Xamarin.Forms, you can refer to FormsSample.
In my Windows 8 app I get an image as base64 string. Now I am looking for a method that converts the string so that I can include it in my XAML image, something like this:
public static Image Base64ToImage(string s)
{
// How To?
}
I have seen many solutions but all of them use classes/methods that are not available in Windows 8 store apps. Thanks for your hints.
The method could look like this:
private async Task<BitmapImage> Base64ToImage(string base64)
{
var bitmap = new BitmapImage();
var buffer = Convert.FromBase64String(base64);
using (var stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(buffer.AsBuffer());
stream.Seek(0);
bitmap.SetSource(stream);
}
return bitmap;
}
In my application I am using the below mentioned helper method for binding my Isolated storage image to Image control. I got this helper method from the link "Binding Image stored in the Isolated Storage to Image Control in Windows Phone"
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);
}
}
});
}
}
}
I am using this inside a ListBox control. And if try with default library images everything will work as expected. But if I try with the images with large size( taken through device camera ) the app crashes.
And here is the exception what I am getting
An exception of type 'System.OutOfMemoryException' occurred in System.Windows.ni.dll but was not handled in user code
stack trace
at MS.Internal.FrameworkCallbacks.NotifyManagedDebuggerOnNativeOOM()
at MS.Internal.XcpImports.BitmapSource_SetSource(BitmapSource bitmapSource, CValue& byteStream)
at System.Windows.Media.Imaging.BitmapSource.SetSourceInternal(Stream streamSource)
at System.Windows.Media.Imaging.BitmapImage.SetSourceInternal(Stream streamSource)
at System.Windows.Media.Imaging.BitmapSource.SetSource(Stream streamSource)
at MyaPP.Common.IsoStoreImageSource.<>c__DisplayClass4.<>c__DisplayClass6.b__1(Object _)
The caching within the ListBox might be taking up your memory and this is especially noticeable with larger images. I'm not familiar with the helper method you've posted but try adding this.
if (img != null)
{
BitmapImage bitmapImage = img.Source as BitmapImage;
bitmapImage.UriSource = null;
img.Source = null;
//rest of the code ...
}
Okay, it took some time for me to return to this issue. I'll share my findings here, but I'm not considering them a real answer to the issue, but rather a workaround. However, I hope it will help somebody.
First I want to confirm OutOfMemoryException happens in certain circumstances. But, surprisingly, it depends on the page layout you're using. In fact, if your layout involves StackPanel, you'll have an exception. I guess, it comes down to the fact how MeasureOverride and ArrangeOverride methods are implemented in StackPanel (though I may be completely wrong here). It looks like when ListBox is a child to StackPanel, it tries to load all of the images before display. This, of course, causes the memory leak.
On the other hand, if you use something like Grid as a parent for list of images, then there's no such exceptions, and the memory load is reasonable.
Here's page layout that worked for me:
<Grid>
<ListBox ItemsSource="{Binding IsoStorePics}">
<ListBox.ItemTemplate>
<DataTemplate>
<Image local:IsoStoreImageSource.IsoStoreFileName="{Binding Path}" Margin="5"/>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
</Grid>
This is the best answer I have for you now. Please let me know if it helped.
You can try like this, Stream object will automatically disposed.
using (IsolatedStorageFile iso = IsolatedStorageFile.GetUserStoreForApplication())
{
if (iso.FileExists(imagePath))
{
using (Stream imagestream = new IsolatedStorageFileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read, iso))
{
BitmapImage bmp = new BitmapImage();
bmp.SetSource(imagestream);
imgControl.Source = bmp;
}
}
}