Make WPF Image load async - wpf

I would like to load Gravatar-Images and set them from code behind to a WPF Image-Control.
So the code looks like
imgGravatar.Source = GetGravatarImage(email);
Where GetGravatarImage looks like:
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri( GravatarImage.GetURL( "http://www.gravatar.com/avatar.php?gravatar_id=" + email) , UriKind.Absolute );
bi.EndInit();
return bi;
Unfortunately this locks the GUI when the network connection is slow. Is there a way to assign the image-source and let it load the image in the background without blocking the UI?
Thanks!

I suggest you to use a Binding on your imgGravatar from XAML. Set IsAsync=true on it and WPF will automatically utilize a thread from the thread pool to pull your image. You could encapsulate the resolving logic into an IValueConverter and simply bind the email as Source
in XAML:
<Window.Resouces>
<local:ImgConverter x:Key="imgConverter" />
</Window.Resource>
...
<Image x:Name="imgGravatar"
Source="{Binding Path=Email,
Converter={StaticResource imgConverter},
IsAsync=true}" />
in Code:
public class ImgConverter : IValueConverter
{
public override object Convert(object value, ...)
{
if (value != null)
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.UriSource = new Uri(
GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" +
value.ToString()) , UriKind.Absolute
);
bi.EndInit();
return bi;
}
else
{
return null;
}
}
}

I can't see why your code would block the UI, as BitmapImage supports downloading image data in the background. That's why it has an IsDownloading property and a DownloadCompleted event.
Anyway, the following code shows a straightforward way to download and create the image entirely in a separate thread (from the ThreadPool). It uses a WebClient instance to download the whole image buffer, before creating a BitmapImage from that buffer. After the BitmapImage is created it calls Freeze to make it accessible from the UI thread. Finally it assigns the Image control's Source property in the UI thread by means of a Dispatcher.BeginInvoke call.
ThreadPool.QueueUserWorkItem(
o =>
{
var url = GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" + email);
var webClient = new WebClient();
var buffer = webClient.DownloadData(url);
var bitmapImage = new BitmapImage();
using (var stream = new MemoryStream(buffer))
{
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = stream;
bitmapImage.EndInit();
bitmapImage.Freeze();
}
Dispatcher.BeginInvoke((Action)(() => image.Source = bitmapImage));
});
EDIT: today you would just use async methods:
var url = GravatarImage.GetURL(
"http://www.gravatar.com/avatar.php?gravatar_id=" + email);
var httpClient = new HttpClient();
var responseStream = await httpClient.GetStreamAsync(url);
var bitmapImage = new BitmapImage();
using (var memoryStream = new MemoryStream())
{
await responseStream.CopyToAsync(memoryStream);
bitmapImage.BeginInit();
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.StreamSource = memoryStream;
bitmapImage.EndInit();
bitmapImage.Freeze();
}
image.Source = bitmapImage;

Related

Creating bitmap in memory using RenderTargetBitmap fails in production WCF Service

I am using WPF objects to generate an bitmap image in memory. The program that does this resides in a WCF web service. The image renders correctly when I run on locally on IISExpress, and on a test IIS 7 server. However, when running on a server used by QA, the image is not rendered correctly. More specifically, only the top 22px lines of a 250px height image are rendered. The settings on both the test server and the QA server are supposed to be identical (insert skeptical face here).
Question: What possible settings in IIS could be effecting this image rendering? Also, I'm thinking there could possibly be a threading issue since RenderTargetBitmap renders asynchronously, and I do get a partial image.
Here is the code I'm using:
private byte[] RenderGauge(ViewData viewData)
{
double resolution = 4 * ReSize;
double dpi = 96 * resolution;
var view = new Gauge();
var vm = new GuageViewModel(viewData);
view.Measure(new Size(350, 70));
view.Arrange(new Rect(new Size(350, 70)));
var bounds = VisualTreeHelper.GetDescendantBounds(view);
if (bounds != Rect.Empty)
{
height = (int)(Math.Floor(bounds.Height) + 1);
width = (int)(Math.Floor(bounds.Width) + 1);
size = new Size(width, height);
}
var bitmap = new RenderTargetBitmap((int)(width * resolution), (int)(height * resolution), dpi, dpi, PixelFormats.Pbgra32);
var visual = new DrawingVisual();
using (var context = visual.RenderOpen())
{
var brush = new VisualBrush(view);
context.DrawRectangle(brush, null, new Rect(new Point(), bounds.Size));
}
bitmap.Render(visual);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bitmap));
byte[] img;
using (var MS = new MemoryStream())
{
encoder.Save(MS);
img = MS.ToArray();
}
img = img == null ? new byte[0] : img;
return img;
}
So, I'm doing exactly the same thing and I had a number of issues rendering files. I've found that using a binding to a bitmap in the XAML helps. The code from my view model that returns the image source is:
public Uri ImageUri
{
get { return new Uri(ImagePath, UriKind.Absolute); }
}
public BitmapImage ImageSource
{
get
{
try
{
if (string.IsNullOrEmpty(ImagePath) || !File.Exists(ImagePath))
return null;
var image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = ImageUri;
image.EndInit();
return image;
}
catch (Exception e)
{
var logger = LogManager.GetLogger(typeof(ImageDetails));
ExceptionHelper.LogExceptionMessage(logger, e);
}
return null;
}
}
Then in the XAML I bind to the ImageSource property.
I think that most problems with RenderTargetBitmap are related to asynchronous bindings in the XAML becauses the render method is synchronous.

Convert drawing.bitmap to windows.controls.image

I am reading data from a smartcard.
This data contains a picture as well.
Code to get the picture in a class ReadData:
public Bitmap GetPhotoFile()
{
byte[] photoFile = GetFile("photo_file");
Bitmap photo = new Bitmap(new MemoryStream(photoFile));
return photo;
}
Code in the xaml:
imgphoto = ReadData.GetPhotoFile();
Error being generated:
Cannot implicitly convert type 'System.Drawing.Bitmap' to 'System.Windows.Controls.Image'
What is the best approach in this?
Do not create a System.Drawing.Bitmap from that file. Bitmap is WinForms, not WPF.
Instead, create a WPF BitmapImage
public ImageSource GetPhotoFile()
{
var photoFile = GetFile("photo_file");
var photo = new BitmapImage();
using (var stream = new MemoryStream(photoFile))
{
photo.BeginInit();
photo.CacheOption = BitmapCacheOption.OnLoad;
photo.StreamSource = stream;
photo.EndInit();
}
return photo;
}
Then assign the returned ImageSource to the Source property of the Image control:
imgphoto.Source = ReadData.GetPhotoFile();

Save content of a visual Object as a image file in WPF?

I need to save the content of a WPF Object as an Image file. In my application I have a chart drawn on a Canvas object. This is what I need to save. The Canvas with all child objects.
What you're looking for is the RenderTargetBitmap class. There's an example of its use on the MSDN page I linked, and there's another good example that includes saving to a file here:
RenderTargetBitmap by Eric Sinc
Here is the func which creates RenderTargetBitmap object, that will be used in further funcs.
public static RenderTargetBitmap ConvertToBitmap(UIElement uiElement, double resolution)
{
var scale = resolution / 96d;
uiElement.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
var sz = uiElement.DesiredSize;
var rect = new Rect(sz);
uiElement.Arrange(rect);
var bmp = new RenderTargetBitmap((int)(scale * (rect.Width)), (int)(scale * (rect.Height)), scale * 96, scale * 96, PixelFormats.Default);
bmp.Render(uiElement);
return bmp;
}
This functionc creates JPEG string content of file and writes it to a file:
public static void ConvertToJpeg(UIElement uiElement, string path, double resolution)
{
var jpegString = CreateJpeg(ConvertToBitmap(uiElement, resolution));
if (path != null)
{
try
{
using (var fileStream = File.Create(path))
{
using (var streamWriter = new StreamWriter(fileStream, Encoding.Default))
{
streamWriter.Write(jpegString);
streamWriter.Close();
}
fileStream.Close();
}
}
catch (Exception ex)
{
//TODO: handle exception here
}
}
}
This function used above to create JPEG string representation of Image content:
public static string CreateJpeg(RenderTargetBitmap bitmap)
{
var jpeg = new JpegBitmapEncoder();
jpeg.Frames.Add(BitmapFrame.Create(bitmap));
string result;
using (var memoryStream = new MemoryStream())
{
jpeg.Save(memoryStream);
memoryStream.Seek(0, SeekOrigin.Begin);
using (var streamReader = new StreamReader(memoryStream, Encoding.Default))
{
result = streamReader.ReadToEnd();
streamReader.Close();
}
memoryStream.Close();
}
return result;
}
Hope this helps.
With the help of the Eric Sinc tutorial I came to the following solution:
It uses a win32 SaveDialog to choose where the file should go and a PngBitmapEncoder (many other BitmapEncoders available!) to convert it to something we can save.
Note that the element being saved in this example is "cnvClasses" and that the size of the output is, quite deliberately, the same as the control.
SaveFileDialog svDlg = new SaveFileDialog();
svDlg.Filter = "PNG files|*.png|All Files|*.*";
svDlg.Title = "Save diagram as PNG";
if (svDlg.ShowDialog().Value == true)
{
RenderTargetBitmap render = new RenderTargetBitmap((int)this.cnvClasses.ActualWidth, (int)this.cnvClasses.ActualHeight, 96, 96, PixelFormats.Pbgra32);
render.Render(cnvClasses);
PngBitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(render));
using (FileStream fs = new FileStream(svDlg.FileName, FileMode.Create))
encoder.Save(fs);
}

WPF loading serialized image

In an app I need to serialize an image through a binarywriter, and to get it in an other app to display it.
Here is a part of my "serialization" code :
FileStream fs = new FileStream(file, FileMode.Create, FileAccess.Write);
BinaryWriter bin = new BinaryWriter(fs);
bin.Write((short)this.Animations.Count);
for (int i = 0; i < this.Animations.Count; i++)
{
MemoryStream stream = new MemoryStream();
BitmapEncoder encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(Animations[i].Image));
encoder.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
bin.Write((int)stream.GetBuffer().Length);
bin.Write(stream.GetBuffer());
stream.Close();
}
bin.Close();
And here is the part of my deserialization that load the image :
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
BitmapSource img;
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
img = new BitmapImage();
MemoryStream stream = new MemoryStream(imageBytes);
BitmapDecoder dec = new PngBitmapDecoder(stream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);
img = dec.Frames[0];
stream.Close();
}
bin.Close();
When I use this method, I load the image (it seems to be stored in the "img" object) but it can't be displayed.
Has somedy an idea?
Thanks
KiTe
UPD :
I already do this : updating my binding, or even trying to affect it directly through the window code behing. None of these approaches work :s
However, when I add this :
private void CreateFile(byte[] bytes)
{
FileStream fs = new FileStream(Environment.CurrentDirectory + "/" + "testeuh.png", FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);
fs.Close();
}
at the end of you function, it perfectly create the file, which can be read without any problems ... So I don't know where the problem can be.
UPD 2 :
A weird things happens.
Here is the binding I use :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding ElementName=lstAnims, Path=SelectedItem.Image}" Stretch="Uniform" />
(the list is itself binded on an observableCollection).
When I create manually the animation through the app, it works (the image is displayed).
But when I load it, it is not displayed (I look at my code, there are not any "new" which could break up my binding, since there are others properties binded the same way and they does not fail).
Moreover, I've put a breakpoint on the getter/setter of the properties Image of my animation.
When it is created, no problems, it has the good informations.
But when it is retreived through the getter, it return a good image the first time, and then and image with pixelWidth, pixelHeight, width, height of only 1 but without going through the setter anymore !
Any idea?
UPD3
tried what you said like this :
Added a property TEST in my viewModel :
private BitmapSource test;
public BitmapSource TEST { get { return test; } set { test = value; RaisePropertyChanged("TEST"); } }
then did it :
img = getBmpSrcFromBytes(bin.ReadBytes(imageBytesLenght));
TEST = img;
(in the code showed and modified before)
and my binding :
<Image x:Name="imgSelectedAnim" Width="150" Height="150" Source="{Binding Path=TEST}" Stretch="Uniform" />
(datacontext is set to my ViewModel)
And it still doesn't work and does the weird image modification (pixW, pixH, W and H set to 1)
FINAL UPD:
It seems I finally solved the problem. Here is simply what I did :
byte[] bytes = bin.ReadBytes(imageBytesLenght);
MemoryStream mem = new MemoryStream(bytes);
img = new BitmapImage();
img.BeginInit();
img.StreamSource = mem;
img.EndInit();
the strange thing is that it didn't work the first time, maybe it is due to an architectural modification within my spriteanimation class, but I don't think it is.
Well, thank you a lot to Eugene Cheverda for his help
At the first, I think you should store your images in a list of BitmapSource and provide reverse encoding of image for using stream as BitmapImage.StreamSource:
FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read);
BinaryReader bin = new BinaryReader(fs);
int animCount = bin.ReadInt16();
int imageBytesLenght;
byte[] imageBytes;
List<BitmapSource> bitmaps = new List<BitmapSource>();
for (int i = 0; i < animCount; i++)
{
imageBytesLenght = bin.ReadInt32();
imageBytes = bin.ReadBytes(imageBytesLenght);
bitmaps.Add(getBmpSrcFromBytes(imageBytes));
}
bin.Close();
UPD
In code above use func written below:
private BitmapSource getBmpSrcFromBytes(byte[] bytes)
{
using (var srcStream = new MemoryStream(bytes))
{
var dec = new PngBitmapDecoder(srcStream, BitmapCreateOptions.PreservePixelFormat,
BitmapCacheOption.Default);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(dec.Frames[0]);
BitmapImage bitmapImage = null;
bool isCreated;
try
{
using (var ms = new MemoryStream())
{
encoder.Save(ms);
bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = ms;
bitmapImage.EndInit();
isCreated = true;
}
}
catch
{
isCreated = false;
}
return isCreated ? bitmapImage : null;
}
}
Hope this helps.
UPD #2
Your binding is incorrect. You may be should bind selected item to for example CurrentImageSource. And then this CurrentImageSource bind to Image control.
Code:
public class MyViewModel : INotifyPropertyChanged
{
public ObservableCollection<BitmapSource> ImagesCollection { get; set; }
private BitmapSource _currentImage;
public BitmapSource CurrentImage
{
get { return _currentImage; }
set
{
_currentImage = value;
raiseOnPropertyChanged("CurrentImage");
}
}
private void raiseOnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged;
}
Then:
<ListBox ItemsSource="{Binding ImagesCollection}" SelectedItem="{Binding CurrentImage}"/>
<Image Source="{Binding CurrentImage}"/>
ADDED
Here is the sample project in which implemented technique as I described above.
It creates animations collection and represent them in listbox, selected animation is displayed in Image control using its Image property.
What I want to say is that if you cannot obtain image as it should be, you need to search problems in serialization/deserialization.

How achieve Image.Clone() in WPF?

I obtain an array of byte (byte[]) from db and render into a Image Control using the following method :
public Image BinaryImageFromByteConverter(byte[] valueImage)
{
Image img = new Image();
byte[] bytes = valueImage as byte[];
MemoryStream stream = new MemoryStream(bytes);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = stream;
image.EndInit();
img.Source = image;
img.Height = 240;
img.Width = 240;
return img;
}
So now that is rendered, I want to "copy" the Image.Source from Image (Control) to another element, for example : Paragraph..
paragraph1.Inlines.Add(new InlineUIContainer(ImageOne));
but nothings appears, I try to create a new Image using ImageOne.Source but I just found this example with Uri(#"path"), I cant apply this method cause my BitmapImage comes from a byte[] type
Image img = new Image();
img.Source = new BitmapImage(new Uri(#"c:\icons\A.png"));
Helps with this issue please, thanks!
Just create a new Image element and set its source to the same BitmapImage:
byte[] imageInfo = File.ReadAllBytes("IMG_0726.JPG");
BitmapImage image;
using (MemoryStream imageStream = new MemoryStream(imageInfo))
{
image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.StreamSource = imageStream;
image.EndInit();
}
this.mainImage.Source = image;
this.secondaryImage.Source = image;
It also works if you just copy one source to the other:
this.mainImage.Source = image;
this.secondaryImage.Source = this.mainImage.Source;

Resources