I have created a test project just for the case. I have one Image control in my .xaml like that:
<Image x:Name="img" />
I have tested the project with 6 pics, and all of them from the same web Site. Size of images which are shown is approximately 50 - 90 KB. And the image which isn't showing is 294 KB.
And I am setting the source of image like that:
img.Source = new BitmapImage(new Uri(imageURI));
What could be a problem?
Thanks.
UPDATE1:
Also, I have ckecked ImageFailed event. It is throwing AG_E_NETWORK_ERROR exception.
UPDATE2:
Here is the source of image which is not showing:
(deleted)
The image in question have hot-link protection on.
That is most likely the culprit preventing you from being able to download it. And given the hotlink protection, I'd guess you don't have the necessary rights to use it in a application either.
If you wish to work around this, use the HttpWebRequest class and set the HttpWebRequest.Referer property.
Thanks to #Claus Jørgensen, I have learned that some web sites can use hot-link protection to prevent other websites from directly linking to files and pictures on your website.
So I create an AttachedProperty for binding the source of Image to URI and download it asynchronously.
Here is .xaml:
<Image AttachedProperties:ImageProperties.SourceWithCustomReferer="{Binding Image, Mode=TwoWay}"/>
And AttachedProperty:
public static class ImageProperties
{
#region SourceWithCustomReferer Property
public static Dictionary<Uri, BitmapImage> imageCache = new Dictionary<Uri, BitmapImage>();
public static readonly DependencyProperty SourceWithCustomRefererProperty =
DependencyProperty.RegisterAttached(
"SourceWithCustomReferer",
typeof(Uri),
typeof(ImageProperties),
new PropertyMetadata(OnSourceWithCustomRefererChanged));
private static void OnSourceWithCustomRefererChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
{
var image = (Image)o;
var uri = (Uri)e.NewValue;
if (DesignerProperties.IsInDesignTool)
{
// for the design surface we just load the image straight up
image.Source = new BitmapImage(uri);
}
else
{
if (imageCache.ContainsKey(uri))
{
image.Source = imageCache[uri];
return;
}
image.Source = null;
HttpWebRequest request = HttpWebRequest.Create(uri) as HttpWebRequest;
request.Headers["Referer"] = "http://www.WEBSITE.com"; // or your custom referer string here
request.BeginGetResponse((result) =>
{
try
{
Stream imageStream = request.EndGetResponse(result).GetResponseStream();
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.CreateOptions = BitmapCreateOptions.BackgroundCreation;
bitmapImage.SetSource(imageStream);
image.Source = bitmapImage;
imageCache.Add(uri, bitmapImage);
});
}
catch (WebException)
{
// add error handling
}
} , null);
}
}
public static Uri GetSourceWithCustomReferer(Image image)
{
if (image == null)
{
throw new ArgumentNullException("Image");
}
return (Uri)image.GetValue(SourceWithCustomRefererProperty);
}
public static void SetSourceWithCustomReferer(Image image, Uri value)
{
if (image == null)
{
throw new ArgumentNullException("Image");
}
image.SetValue(SourceWithCustomRefererProperty, value);
}
#endregion
}
Related
I want to get UIImage from Asset Library Url
I'm an developing app in xamarin ios, trying to get a UIImage Object from a given Asset-Library-Url.
Picked multiple images from gallery using ELCImagePickerViewController.
All image paths are added in string array list.
Now i need to get all the images in Byte[] using the Asset-Library-Url path.
The given Url is:
assets-library://asset/asset.JPG?id=0E126068-14F9-431F-91B5-8BC397727656&ext=JPG
I tried it with the following approach :
static UIImage FromUrl(string uri)
{
using (var url = new NSUrl(uri))
using (var data = NSData.FromUrl(url))
return UIImage.LoadFromData(data);
}
But it always returns null.
Can anybody please tell me what i'm doing wrong here?
These Urls are for the ALAssetsLibrary, you will need to use the ALAssetsLibrary to access them. See example below:
public Byte[] LoadAssetAsByteArray(string uri)
{
var nsUrl = new NSUrl(uri);
var asset = new ALAssetsLibrary();
Byte[] imageByteArray = new Byte[0];
UIImage image;
asset.AssetForUrl(nsUrl,(ALAsset obj) => {
var assetRep = obj.DefaultRepresentation;
var cGImage = assetRep.GetFullScreenImage();
image = new UIImage(cGImage);
// get as Byte[]
imageByteArray = new Byte[image.AsPNG().Length];
//imageView.Image = image;
},
(NSError err) => {
Console.WriteLine(err);
});
return imageByteArray;
}
There is a great recipe using a UIImagePickerController on the Xamarin Site which I beleive would help you
I have a ListView binded to an ObservableCollection. This ListView uses an ItemTemplate which consists of only one Image control, which Source property is binded to a URL string.
For some URLs images fail to load because the remote server would return "Forbidden (403)" - this could be solved by adding a certain header to the HTTP request that gets the image, but the problem is I don't know how I should go about modifying said request.
I tried two different approaches:
Creating an IValueConverter object. I would take the URL and get image data myself - put it into a MemoryStream and use the stream to initialize a BitmapImage object and return that to the Image control. This approach proved to be really slow and it blocked the UI thread.
Creating a new property to bind to that would return a byte array containing image data. This data would be lazily initialized the first time it's called using a Task. When the data would finish downloading, the PropertyChanged event would be fired that would update the Image visually. This approach did not block the UI thread, but was extremely slow for some reason.
I want to know 2 things:
Why are my approaches significantly slower?
How do I directly modify the way Image control gets images from a remote server without affecting the performance/speed that much?
Consider the following example:
public byte[] ImageThumbnail
{
get
{
if (img == null) img = GetImage(ImageUrls.Thumbnail);
return img;
}
}
public byte[] GetImage(string url) {
HttpClient client = new HttpClient();
// add some headers
return client.GetByteArrayAsync(url).Result;
}
Image would be binded to "ImageThumbnail" with IsAsync set to True. The images are downloaded significantly slower in this case compared to just binding the URL directly to Image source.
You should use an asynchronous Binding for the Source property of the Image control in the ItemTemplate:
<ListBox.ItemTemplate>
<DataTemplate>
<Image Source="{Binding Image, IsAsync=True}"/>
</DataTemplate>
</ListBox.ItemTemplate>
Since the Image property getter is now called in a background thread, the returned ImageSource must be made cross-thread accessible by freezing.
public class ImageItem
{
private string url;
public ImageItem(string url)
{
this.url = url;
}
public ImageSource Image
{
get
{
var image = new BitmapImage();
var buffer = new WebClient().DownloadData(url);
using (var stream = new MemoryStream(buffer))
{
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = stream;
image.EndInit();
}
image.Freeze();
return image;
}
}
}
The Image property might be written a little shorter by using BitmapFrame instead of BitmapImage. The BitmapFrame.Create method shown below already returns a frozen BitmapFrame.
public ImageSource Image
{
get
{
var buffer = new WebClient().DownloadData(url);
using (var stream = new MemoryStream(buffer))
{
return BitmapFrame.Create(
stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
}
}
}
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
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
I am trying to fill a rectangle with a file that I have saved to the hard drive. I know that I have to use a ImageBrush and I think I know how to do that if the image is an included resource. In this case the file is created and setting on the hard drive but when I try to use it with the code below the rectangle changes but it changes to show the form back color not the image as I had expected (almost as if the image is invisible).
using (dynamic CommonDialog = AutomationFactory.CreateObject("WIA.CommonDialog"))
{
dynamic imageFile = CommonDialog.ShowAcquireImage();
if (imageFile != null)
{
string filePath = string.Format("d:\\{0}.jpg", Guid.NewGuid());
imageFile.SaveFile(filePath);
rectangle2.Fill = new ImageBrush()
{
ImageSource = new BitmapImage(new Uri(filePath, UriKind.Absolute))
};
}
}
Update: I was able to get this to work by replacing the code block inside the If Then with the following:
{
string filePath = string.Format("d:\\{0}.jpg", Guid.NewGuid());
imageFile.SaveFile(filePath);
BitmapImage bitmapBase = new BitmapImage();
dynamic fileData = imageFile.FileData;
byte[] imageData = fileData.BinaryData;
MemoryStream ms = new MemoryStream(imageData);
bitmapBase.SetSource(ms);
WriteableBitmap writableBitmap = new WriteableBitmap(bitmapBase);
rectangle2.Fill = new ImageBrush() { ImageSource = (writableBitmap) };
}
When using Silverlight 4 you can create an out-of-browser application thatt can access (part of) the local disk.
See here how
You cannot access images, or any files, from a local drive (except for Isolated storage or a stream from a file open dialog). These are all security measures.
As you did not mention Out Of Browser I assume this is just a web/client app.