I am working on an application for Windows phone 8.1 in Xamarin with mvvmCross. I need to select multiple images from the phone library and display them. I am using FileOpenPicker.SelectMultipleFilesAndContinue to do so. Now i need to be able to display all these images in the view. One problem is that the minimum amount of images must be 20 and the images could be pretty large.
First i tried making them into byte arrays and used a converter to display them.
public async void SelectFotosCallback(FileOpenPickerContinuationEventArgs args) {
if (args.Files.Count > 0) {
foreach (StorageFile file in args.Files) {
IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
byte[] bytes = null;
using (var reader = new DataReader(fileStream.GetInputStreamAt(0))) {
bytes = new byte[fileStream.Size];
await reader.LoadAsync((uint)fileStream.Size);
reader.ReadBytes(bytes);
}
callback(bytes);
}
}
else {
}
}
This method did seem to work at the first try but as soon as I tried it with 5 images it stopped working. When it was done with the callback the app just quit. No error message or anything. (My guess is an overload in memory.)
After this i found a little solution where i take the byte arrays and make them to Xamarin.Form Images.
public async void SelectFotosCallback(FileOpenPickerContinuationEventArgs args) {
if (args.Files.Count > 0) {
foreach (StorageFile file in args.Files) {
IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
byte[] bytes = null;
using (var reader = new DataReader(fileStream.GetInputStreamAt(0))) {
bytes = new byte[fileStream.Size];
await reader.LoadAsync((uint)fileStream.Size);
reader.ReadBytes(bytes);
}
Image image = new Image();
image.Source = ImageSource.FromStream(() => new MemoryStream(bytes));
var iets = image.Source.BindingContext;
callback(image);
}
}
else {
}
This seemed to take care of the problem for the overload in memory. the only other problem now is that i can not seem to find any way the display these images.
<GridView ItemsSource="{Binding SelectedImages}">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
<Image Style="{StaticResource imageListImage}" Source="{Binding Source}"/>
<Button Style="{StaticResource imageListXButton}">
<Button.Background>
<ImageBrush ImageSource="ms-appx:///Resources/XButton.png"/>
</Button.Background>
</Button>
</Grid>
</DataTemplate>
</GridView.ItemTemplate>
I try to display them with a simple binding. I have not found any way that would work. Does anyone know a way to display these Images and if not what would be the best alternative to use the bytes without using too much memory.
After some deep digging I was able to find the answer. It was a lot simpeler than I thought. I started working with the decodepixel from the BitmapImage. Using a coneverter I set the values.
public object Convert(object value, Type targetType, object parameter, string language) {
BitmapImage image = (BitmapImage)value;
image.DecodePixelType = DecodePixelType.Logical;
if (image.PixelHeight >= image.PixelWidth) {
image.DecodePixelHeight = 100;
}
else {
image.DecodePixelWidth = 100;
}
return image;
}
The weird thing was that it did work some of the time. But for some reason it didn't work on all the image and it sometimes even threw them away.
But after a lot of testing i finally found what I was looking for.
BitmapImage bitmap = new BitmapImage();
BitmapImage temp = new BitmapImage();
bitmap.DecodePixelType = DecodePixelType.Logical;
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read)) {
IRandomAccessStream secondStream = fileStream.CloneStream();
BitmapImage temp = new BitmapImage();
temp.SetSource(fileStream);
if (temp.PixelHeight >= temp.PixelWidth) {
bitmap.DecodePixelHeight = 150;
}
else {
bitmap.DecodePixelWidth = 150;
}
bitmap.SetSource(secondStream);
}
For some reason setting the decodepixel after setting the source makes it inconsitent but setting these values before setting the source actually crops the image right away.
This method works perfectly and completely resolves my outofmemory problem
Related
I am having the issue of "Canvas Drawing too large bitmaps". After a quick search, I found the following thread, which promptly helped me know what is the issue.
The solution is to put the image in drawable-xxhdpi/ instead of simply drawable/. And here lies the issue: the image is not static, it is imported when I need it. As such, I do not chose where the image ends up stored. It store itself in drawable. Is there 1) A solution to chose which folder to use, or 2) a way to tell it not get the image if it's too heavy?
var file = new SmbFile(path, auth);
try
{
if (file.Exists())
{
// Get readable stream.
var readStream = file.GetInputStream();
//Create reading buffer.
MemoryStream memStream = new MemoryStream();
//Get bytes.
((Stream)readStream).CopyTo(memStream);
var stream1 = new MemoryStream(memStream.ToArray());
if (stream1.Length < 120188100)
{
//Save image
ProductImage = ImageSource.FromStream(() => stream1);
//Dispose readable stream.
readStream.Dispose();
InfoColSpan = 1;
}
else
{
Common.AlertError("Image trop lourde pour l'affichage");
}
}
}
I'm trying to get a byte array from the InkCanvas control, but the method i've come up with so far seems a bit long winded.
Currently I use the following:
StorageFolder folder = await Windows.Storage.ApplicationData.Current.LocalFolder.GetFolderAsync("Temp");
StorageFile file = await folder.CreateFileAsync(GenerateString(5)+".zzx", Windows.Storage.CreationCollisionOption.GenerateUniqueName);
using (IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
await SignatureCanvas.InkPresenter.StrokeContainer.SaveAsync(stream);
var array = await IRandomAccessStreamToByteArray(stream);
}
The custom stream reader is as follows.
private async Task<byte[]> IRandomAccessStreamToByteArray(IRandomAccessStream stream)
{
var reader = new DataReader(stream.GetInputStreamAt(0));
var bytes = new byte[stream.Size];
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(bytes);
return bytes;
}
This works, and gives me the byte array that i need, but also leaves me with unwanted images. Was having some access issues due to files still being written when another call wanted to replace the file so decided to go down the multiple images route. Is there a way to skip the image file entirely? It's not too much of an issue to clear out a temp folder, but if it can be avoided that would be preferable.
I had read somewhere already that InkCanvas doesn't support direct to array dumps, so any suggestions would be appreciated!
If you want to save your InkCanvas strokes to bytes Array without create the file, you should be able to use the Win2D.uwp.
To install Win2D.uwp, run the "Install-Package Win2D.uwp" command in the Package Manager Console.
There is a CanvasDrawingSession.DrawInk method to draw a collection of ink strokes. That we should be able to use CanvasBitmap.GetPixelBytes method to get an array of raw byte data for the entire bitmap.
For example:
private byte[] ConvertInkCanvasToByteArray()
{
var canvasStrokes = SignatureCanvas.InkPresenter.StrokeContainer.GetStrokes();
if (canvasStrokes.Count > 0)
{
var width = (int)SignatureCanvas.ActualWidth;
var height = (int)SignatureCanvas.ActualHeight;
var device = CanvasDevice.GetSharedDevice();
device.DeviceLost += DeviceOnDeviceLost;
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Windows.UI.Colors.White);
ds.DrawInk(SignatureCanvas.InkPresenter.StrokeContainer.GetStrokes());
}
return renderTarget.GetPixelBytes();
}
else
{
return null;
}
}
private void DeviceOnDeviceLost(CanvasDevice sender, object args)
{
Debug.WriteLine("DeviceOnDeviceLost");
}
Also if we want to convert the bytes array to image, we should be able to use following code:
WriteableBitmap bitmap = new WriteableBitmap((int)SignatureCanvas.ActualWidth, (int)SignatureCanvas.ActualHeight);
await bitmap.PixelBuffer.AsStream().WriteAsync(mybytes, 0, mybytes.Length);
I have trouble with image loading (just on this pc, on other one its ok)
Im tried a few ways to put image
<Image
MinWidth="190"
Stretch="Fill"
MinHeight="190"
Source="{Binding ImagePath, Converter={StaticResource UriToImageConverter}}">
<!--If you use my way in answer you need replace Source like this-->
Source="{Binding Image}"><!--Bitmapimage property in model-->
</Image>
Its works only if I put path like 'ms-appx' but in target I need use 'Binding' its works on other pc witout any troubles but here I have "transparent" picture on a page
Ok, I just put images to StorageFile's and copied it to local folder and take it from there and put into my Model's property
Before:
this.collageimages = await OpenPicker.PickMultipleFilesAsync();
for (var i = 0; i < this.collageimages.Count; i++)
{
var stream = await collageimages[i].OpenAsync(FileAccessMode.Read);
this.image = new BitmapImage();
await this.image.SetSourceAsync(stream);
}
After:
this.collageimages = await OpenPicker.PickMultipleFilesAsync();
for (var i = 0; i < this.collageimages.Count; i++)
{
await this.collageimages[i].CopyAsync(ApplicationData.Current.LocalFolder, "collageImage_"+i+"", NameCollisionOption.ReplaceExisting);
var tempStorage = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appdata:///local/collageImage_"+i+""));
var stream = await tempStorage.OpenAsync(FileAccessMode.Read);
this.image = new BitmapImage();
await this.image.SetSourceAsync(stream);
}
I do not claim to be the best answer if you know a better way, please write it
Im very new to this site. Its my first question! hope somone knows the answer.
Here is the deal, I have a database with entities that hold a byte array as a picture (one of the entity properties).
here is the code that convets the pic to byte[] before saving it to the DB:
public async Task<byte[]> ImageFileToByteArrayAsync(StorageFile file)
{
IRandomAccessStream stream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read);
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(stream);
PixelDataProvider pixelData = await decoder.GetPixelDataAsync();
return pixelData.DetachPixelData();
}
when I pull the entity from the database I see that it comes with the correct data of the picture as byte[].
Im working on a windows 8.1 app and with mvvmCross. in my viewModel I have a full prop named selectedGuest. this property holds the current Guest. all the info about the guest is succsessfuly transformed to the UI accept the image! here is the xaml of the image:
<Image Grid.Column="2" Source="{Binding SelectedGuest.Image, Converter={StaticResource ByteArrayToImageConverter}}"/>
and here is the converter:
public class ByteArrayToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value == null || !(value is byte[]))
return null;
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
using (DataWriter writer = new DataWriter(stream.GetOutputStreamAt(0)))
{
writer.WriteBytes((byte[])value);
writer.StoreAsync().GetResults();
}
BitmapImage image = new BitmapImage();
image.DecodePixelHeight = 200;
image.DecodePixelWidth = 150;
image.SetSource(stream);
return image;
}
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
when debuging the thread ENTERS the converter and the 'value' is correct! but somhow I cant see the image. nothing apears, the image just wont display.
thanks for the viewers!
You should store the original encoded image buffer (i.e. the binary image file content) in your database, not the raw pixel data:
public async Task<byte[]> ImageFileToByteArrayAsync(StorageFile file)
{
var buffer = await FileIO.ReadBufferAsync(file);
return buffer.ToArray();
}
I have found a very usefull class on this link: images caching - that help me to make logic for caching images. But in my case I have this:
private void DetailView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
SaveAndLoadImage(feedItem);
}
And in this method I save and load image from isolated storage. But I can't load file imidiately because of some permission (Operation not permitted on IsolatedStorageFileStream.). How can I correct my logic to save and load images immediately?
public void SaveAndLoadImage(MediaItemViewModel curItem)
{
string url = string.Empty;
if (!string.IsNullOrEmpty(curItem.ThumbUrl))
{
url = curItem.ThumbUrl;
}
if ((string.IsNullOrEmpty(curItem.ThumbUrl)) && (!string.IsNullOrEmpty(curItem.MediaUrl)))
{
url = curItem.MediaUrl;
}
if ((!string.IsNullOrEmpty(url)) && (CacheImageFile.GetInstance().IsOnStorage(new Uri(url)) == false))
{
CacheImageFile.DownloadFromWeb(new Uri(url));
}
curItem.ImageSource = CacheImageFile.ExtractFromLocalStorage(new Uri(url)) as BitmapImage;
}
have a look at below link
http://www.windowsphonegeek.com/tips/All-about-WP7-Isolated-Storage---Read-and-Save-Images
for loading images from isolated storage. using streams
BitmapImage bi = new BitmapImage();
using (IsolatedStorageFile myIsolatedStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
using (IsolatedStorageFileStream fileStream = myIsolatedStorage.OpenFile("logo.jpg", FileMode.Open, FileAccess.Read))
{
bi.SetSource(fileStream);
this.img.Height = bi.PixelHeight;
this.img.Width = bi.PixelWidth;
}
}
this.img.Source = bi;
You are getting the image asynchronously from the web, but immediately go to the next line to read the file that hasn't even been written to isolated storage. This is what's causing you the exception.
You could try editing the caching library you found on github, using ManualResetEvent. Notice that you will have to make the method calls on another thread!
For example:
public class CacheImageFileConverter : IValueConverter
{
...
private static ManualResetEvent mre = new ManualResetEvent(true);
private static object DownloadFromWeb(Uri imageFileUri)
{
mre.Reset();
WebClient m_webClient = new WebClient(); //Load from internet
BitmapImage bm = new BitmapImage();
m_webClient.OpenReadCompleted += (o, e) =>
{
if (e.Error != null || e.Cancelled) return;
WriteToIsolatedStorage(IsolatedStorageFile.GetUserStoreForApplication(), e.Result, GetFileNameInIsolatedStorage(imageFileUri));
bm.SetSource(e.Result);
e.Result.Close();
mre.Set();
};
m_webClient.OpenReadAsync(imageFileUri);
return bm;
}
private static object ExtractFromLocalStorage(Uri imageFileUri)
{
mre.WaitOne();
string isolatedStoragePath = GetFileNameInIsolatedStorage(imageFileUri); //Load from local storage
using (var sourceFile = _storage.OpenFile(isolatedStoragePath, FileMode.Open, FileAccess.Read))
{
BitmapImage bm = new BitmapImage();
bm.SetSource(sourceFile);
return bm;
}
}
.... other methods
}
Notice the use of Reset, Set and WaitOne for signaling.
You can use JetImageLoader, I created it for application, where we need to load, cache and show big amount of logos, icons and so on.
It can be used as binding converter, so you should not even change your code! Just update your XAMLs!
Please, check out samples in repository, you'll love it ;)
Features:
Caching on disk
Caching in memory
Fully asynchronous
Available as binding converter or programmatically from your code
Fully open source, fork and improve it!
Here is the example:
<Image Source="{Binding ImageUrl, Converter={StaticResource MyAppJetImageLoaderConverter}}"/>
P.S. I am sorry, that I copying my answer from another questions, but image caching on windows phone is huge problem and I want to share my solution, so everybody can use it and improve for developers community