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);
}
}
}
Related
When I try to set an image every 20ms from TcpClient as a Memory stream the Image source is not set in the Image control in Xamarin, I wonder if there is some sort of limitation the time that takes to set an ImageSource in an an Image control the output from the debugger,:
https://pastebin.com/8FgN5Rai
private Task<ImageSource> GetImageAsync(System.IO.Stream stream)
{
TaskCompletionSource<ImageSource> tcs = new TaskCompletionSource<ImageSource>();
tcs.TrySetResult(ImageSource.FromStream(() => (MemoryStream)formatter.Deserialize(client.GetStream())));
return tcs.Task;
}
private async void ReceiveImage()
{
while (client.Connected)
{
Xamarin.Forms.Device.BeginInvokeOnMainThread(new Action(async () =>
{
imageControl.Source = await GetImageAsync(client.GetStream());
}));
}
}
ok i got to set the image but then it says is not a valid image object
Ok i tried a newer version but the image in the stream isnt valid?
Image loading: Image load failed: System.Runtime.Serialization.SerializationException: The input stream is not a valid binary format. The starting contents (in bytes) are: 10-10-FF-10-10-10-FF-10-10-10-FF-10-10-10-FF-10-10
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
Any executable funtion to convert from base64 to an image using c# windows 8.1************* .
I tried :
public Image byteArrayToImage(byte[] byteArrayIn)
{
Image returnImage = null;
using (MemoryStream ms = new MemoryStream(byteArrayIn))
{
returnImage = Image.FromStream(ms);
}
return returnImage;
}
FromStream not suitable for windows 8.1
First of all, the Windows.UI.Xaml.Controls.Image class is a control object, not an actual bitmap-type object. What you actually want is a BitmapImage. Of course, given this misunderstanding, you're likely to run into other problems with your code; you don't show the caller of this method, but if you've confused the type here, you've probably confused it elsewhere. So that will have to be fixed.
Secondly, as is the case with many things in WinRT, doing what you want is somewhat more complicated than if you were using the desktop API. :(
That said, something like this should work:
public Windows.UI.Xaml.Media.Imaging.BitmapImage byteArrayToImage(byte[] byteArrayIn)
{
using (InMemoryRandomAccessStream stream = new InMemoryRandomAccessStream())
{
await stream.WriteAsync(byteArrayIn.AsBuffer(0, byteArrayIn.Length));
stream.Seek(0);
BitmapImage image = new BitmapImage();
await image.SetSourceAsync(stream);
return image;
}
}
Then you can set that object to the Source property of an Image control object.
Some notes:
In your original example, disposing the source stream is at the very least unconventional. My recollection is that in at least some cases, doing so isn't legal, as the Bitmap object returned requires the Stream instance to remain undisposed. For WinRT, AFAIK disposing the stream after the bitmap has been initialized is fine.
The AsBuffer() method is an extension method. You'll need to include a using System.Runtime.InteropServices.WindowsRuntime; in your code if it's not already there for it to work.
See also:
BitmapSource class
InMemoryRandomAccessStream class
WindowsRuntimeBufferExtensions Class
I'm working on a project using WPF to display the Kinect ColorImageFrame and a skeleton representation. I also have to record those two videos.
I'm able to display and record (using EmguCV) those two images, but I have some performance issues. It seems that this part of my code is the reason of my loss of performance.
private void DrawSkeleton(Skeleton[] skeletons)
{
using (System.Drawing.Bitmap skelBitmap = new System.Drawing.Bitmap(640, 480))
{
foreach (Skeleton S in skeletons)
{
if (S.TrackingState == SkeletonTrackingState.Tracked)
{
DrawBonesAndJoints(S,skelBitmap);
}
else if (S.TrackingState == SkeletonTrackingState.PositionOnly)
{
}
}
_videoArraySkel.Add(ToOpenCVImage<Bgr, Byte>(skelBitmap));
BitmapSource source = ToWpfBitmap(skelBitmap);
this.skeletonStream.Source = source;
}
}
and more precisely from the ToWpfBitmap which allows me to display it in my Window:
public static BitmapSource ToWpfBitmap(System.Drawing.Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
// According to MSDN, "The default OnDemand cache option retains access to the stream until the image is needed."
// Force the bitmap to load right now so we can dispose the stream.
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
return result;
}
}
The loss of performance is characterized by:
- The videos displayed on the Window are not fluent anymore
- The video recording seems to miss some frames which leads to a video going faster/lower than the normal.
Can you help me by telling me where this problem may come from?
Try to use RecyclableMemoryStream instead of MemoryStream. It was designed for solving some issue with memory.
Check out this article for details - Announcing Microsoft.IO.RecycableMemoryStream
Have you tried doing the memory write i/o in a separate thread, while maintaining the data in a buffer like a queue?
I have a data class in my application which maintains a collection of byte arrays representing JPEG images. It's defined as:
private ArrayList FrameList = new ArrayList();
I'm having some troubles with my Image object rendering a blank page (and taking its sweet time to do it as well). When I insert a blank image with a 2K in-memory byte array (byte x[] = { lots of hex values };):
FrameList.Insert(currFrame, x);
and then import a JPEG file over it later on with:
byte[] bytes = File.ReadAllBytes(fspec);
FrameList[currFrame] = bytes;
the array is read correctly into memory and stored in the ArrayList (confirmed with the debugger).
However,I then have a function to get the image:
public BitmapImage getCurrPicture()
{
MemoryStream strm;
BitmapImage bmp = new BitmapImage();
strm = new MemoryStream((byte[])FrameList[currFrame-1]);
bmp.CacheOption = BitmapCacheOption.None;
bmp.BeginInit();
bmp.StreamSource = strm;
bmp.EndInit();
strm.Close();
return bmp;
}
which is called:
imgPicB.Source = data.getCurrPicture();
and it doesn't always render.
imgPicB is defined in my XAML as:
<Image x:Name="imgPicB"
Width="400"
Height="300"
Stretch="Fill"
VerticalAlignment="Top" />
Funny thing is, if I use the exact same JPEG setting the source with setting the source to the file URI directly, it renders fine.
Is there some problem with using in-memory JPEG images in WPF? Is there some extra smarts performed when loading from a file (say auto-detection of the image type)?
Try this:
public BitmapSource GetCurrPicture()
{
var bitmapImage = new BitmapImage();
using (Stream stream = new MemoryStream((byte[])FrameList[currFrame-1]))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
bitmapImage.Freeze();
return bitmapImage;
}
}
This works by having WPF decode the image immediately with OnLoad, and then release the reference to the stream when it is done. This means the stream can be garbage collected when it leaves the function. Otherwise, BitmapImage could hold onto the stream until it is rendered.
In the code posted in the question, the render would fail because:
the decode is delayed; and
the stream has been closed at the point where the decode tries to happen.
Link
DwayneNeed 20 Jun 2008 5:11 PM Comments
Caching BitmapImage will store the decoded bitmap in system memory. You can control when this happens by setting the CacheOption property. BitmapImage also maintains a cache of previous BitmapImage instances (via weak references) so that loading the same Uri multiple times will share the same instance. To avoid this cache, you can include the BitmapCreateOptions.IgnoreImageCache flag in the CreateOptions property.