Image Source does not get updated from the second time - wpf

I am facing a peculiar issue wherein the Image does not updated when the source gets modified from second time onward.
my xaml and code behind.
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="140"/>
</Grid.RowDefinitions>
<Grid Grid.Row="2" Margin="0 55 0 0" x:Name="ImageGrid" >
</Grid>
</Grid>
FileSystemWatcher myWatcher;
Dispatcher myDisp;
private int count = 0;
public MainWindow()
{
InitializeComponent();
myWatcher = new FileSystemWatcher();
myWatcher.Path = #"C:\Test";
myWatcher.Filter = "hospital.png";
myWatcher.NotifyFilter = NotifyFilters.LastWrite;
myWatcher.Changed += myWatcher_Changed;
myWatcher.EnableRaisingEvents = true;
myDisp = Dispatcher.CurrentDispatcher;
}
void myWatcher_Changed(object sender, FileSystemEventArgs e)
{
myWatcher.EnableRaisingEvents = false;
string aImgPath = #"C:\Test\hospital.png";
if (File.Exists(aImgPath))
{
BitmapImage src = new BitmapImage();
src.BeginInit();
src.UriSource = new Uri(aImgPath, UriKind.Relative);
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
src.Freeze();
myDisp.Invoke(DispatcherPriority.Normal, new Action<BitmapImage>(UpdateImage), src);
}
myWatcher.EnableRaisingEvents = true;
}
void UpdateImage(BitmapImage theImage)
{
ImageGrid.Children.Clear();
Image aImg = new Image();
aImg.HorizontalAlignment = System.Windows.HorizontalAlignment.Left;
aImg.VerticalAlignment = System.Windows.VerticalAlignment.Top;
aImg.Height = 70;
aImg.Width = 250;
aImg.Stretch = Stretch.Uniform;
aImg.Source = theImage;
ImageGrid.Children.Add(aImg);
}
This code gets executed when ever the file hospital.png gets updated; but the UI still shows the old image from second time onward. Any thing i am doing incorrectly?

The problem is that the image file Uri is cached by WPF. Because it never changes, no new image is loaded.
You may however load the image from a FileStream instead:
if (File.Exists(aImgPath))
{
var src = new BitmapImage();
using (var fs = new FileStream(
aImgPath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
src.BeginInit();
src.StreamSource = fs;
src.CacheOption = BitmapCacheOption.OnLoad;
src.EndInit();
}
src.Freeze();
myDisp.Invoke(DispatcherPriority.Normal, new Action<BitmapImage>(UpdateImage), src);
}

Related

Make WPF Image load async

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;

Draw strokes from SurfaceInkCanvas onto image

I'm trying to create an app that allows the user to draw on an image. I started with the PhotoPaint SDK sample. I understand that the SurfaceInkCanvas is set to transparent and is located on top of the image. When the user is finished drawing I would like to draw the strokes onto the image itself. This is where I'm stuck. Can anyone please point me in the right direction?
Thanks!
Download WriteableBitmap extenstion library from: http://writeablebitmapex.codeplex.com/
Usage:
using System;
using System.IO;
using System.Net;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace TestApp
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void SaveImage(object sender, RoutedEventArgs e)
{
var bi = FromUrl("http://www.dynamicdrive.com/dynamicindex4/lightbox2/horses.jpg", Environment.CurrentDirectory);
if (bi.Format != PixelFormats.Pbgra32)
bi = BitmapFactory.ConvertToPbgra32Format(bi);
WriteableBitmap wrb = new WriteableBitmap(bi);
foreach (var stroke in ink.Strokes)
{
foreach (var point in stroke.StylusPoints)
{
wrb.DrawLine((int)point.X, (int)point.Y, (int)(point.X + 1), (int)(point.Y + 1), Colors.Red);
}
}
var encoder = new JpegBitmapEncoder();
BitmapFrame frame = BitmapFrame.Create(wrb);
encoder.Frames.Add(frame);
using (var stream = File.Create("result.jpg"))
{
encoder.Save(stream);
}
}
public static BitmapSource FromUrl(string url, string workingPath)
{
string[] strArr = url.Split(new char[] { '/' });
string file = workingPath + "\\" + strArr[strArr.Length - 1];
if (!File.Exists(file))
{
using (WebClient client = new WebClient())
{
client.DownloadFile(url, file);
}
}
return BitmapImageFromFile(file);
}
public static BitmapSource BitmapImageFromFile(string file)
{
BitmapImage bi = new BitmapImage();
try
{
bi.BeginInit();
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bi.UriSource = new Uri(file, UriKind.RelativeOrAbsolute);
bi.EndInit();
}
catch { return ToBitmapSource(System.Drawing.Bitmap.FromFile(file)); }
return bi;
}
public static BitmapSource ToBitmapSource(System.Drawing.Image bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Jpeg);
stream.Position = 0;
BitmapImage result = new BitmapImage();
result.BeginInit();
result.CacheOption = BitmapCacheOption.OnLoad;
result.StreamSource = stream;
result.EndInit();
result.Freeze();
return result;
}
}
}
}
XAML:
<Window x:Class="TestApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
Height="350" Width="525">
<Grid>
<Image Source="http://www.dynamicdrive.com/dynamicindex4/lightbox2/horses.jpg"
Name="img"/>
<InkCanvas Background="Transparent"
Name="ink"/>
<Button Content="Save"
HorizontalAlignment="Left"
VerticalAlignment="Top"
Margin="5"
Click="SaveImage"/>
</Grid>
</Window>
maybe you'll find a better way to draw on the WriteableBitmap using this extension, drawing method i have used it's just an experiment.

Convert WPF Path to Bitmap File

I want to be able to load a WPF Resource Dictionary of Paths and output them one by one to files (jpg, bmp, it doesn't matter). This will be in a class library that will be accessed by an MVC application to render to a http stream, so I'm doing this purely within code (no XAML pages).
I've been able to load the dictionary and iterate through the paths, but when I save the images to disk, they are blank. I know I'm missing something trivial, like applying the path to a piece of geometry, or adding it into some containing rectangle or something, but my WPF experience is somewhat limited.
I'm using the following code:
I have a WPF Resource Dictionary containing several paths, such as the following:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Path x:Key="Path1" Data="M 100,200 C 100,25 400,350 400,175 H 280" Fill="White" Margin="10,10,10,10" Stretch="Fill"/>
<Path x:Key="Path2" Data="M 10,50 L 200,70" Fill="White" Margin="10,10,10,10" Stretch="Fill"/>
</ResourceDictionary>
And the class to read and output the files:
public class XamlRenderer
{
public void RenderToDisk()
{
ResourceDictionary resource = null;
Thread t = new Thread(delegate()
{
var s = new FileStream(#"C:\Temp\myfile.xaml", FileMode.Open);
resource = (ResourceDictionary)XamlReader.Load(s);
s.Close();
foreach (var item in resource)
{
var resourceItem = (DictionaryEntry)item;
var path = (System.Windows.Shapes.Path)resourceItem.Value;
var panel = new StackPanel();
var greenBrush = new SolidColorBrush {Color = Colors.Green};
path.Stroke = Brushes.Blue;
path.StrokeThickness = 2;
path.Fill = greenBrush;
panel.Children.Add(path);
panel.UpdateLayout();
string filepath = #"C:\Temp\Images\" + resourceItem.Key + ".jpg";
SaveImage(panel, 64, 64, filepath);
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
public void SaveImage(Visual visual, int width, int height, string filePath)
{
var bitmap =
new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(visual);
var image = new PngBitmapEncoder();
image.Frames.Add(BitmapFrame.Create(bitmap));
using (Stream fs = File.Create(filePath))
{
image.Save(fs);
}
}
}
After much googling, and trial and error, I seem to have arrived at a solution. This post pointed me in the right direction.
There were a few issues:
I was now setting the size of the path and the container
The stack panel container has some nuances that were causing issues, so I replaced it with a canvas
Most importantly, Measure() and Arrange() need to be called on the container element. The UpdateLayout() call is not needed.
Once these issues were fixed, the images rendered to disk (although there's an aspect ratio problem I have yet to fix).
Here is the updated code:
public void RenderToDisk()
{
ResourceDictionary resource = null;
Thread t = new Thread(delegate()
{
var s = new FileStream(#"C:\Temp\myfile.xaml", FileMode.Open);
resource = (ResourceDictionary)XamlReader.Load(s);
s.Close();
foreach (var item in resource)
{
var resourceItem = (DictionaryEntry)item;
var path = (System.Windows.Shapes.Path)resourceItem.Value;
path.Margin = new Thickness(10);
path.HorizontalAlignment = HorizontalAlignment.Center;
path.VerticalAlignment = VerticalAlignment.Center;
path.Width = 48;
path.Height = 48;
path.Stroke = Brushes.White;
path.Fill = Brushes.Black;
var canvas = new Canvas();
canvas.Width = 64;
canvas.Height = 64;
canvas.Margin = new Thickness(0);
canvas.Background = Brushes.Transparent;
canvas.Children.Add(path);
canvas.Measure(new Size(canvas.Width, canvas.Height));
canvas.Arrange(new Rect(new Size(canvas.Width, canvas.Height)));
string filepath = #"C:\Temp\Images\" + resourceItem.Key + ".png";
SaveImage(canvas, (int)canvas.Width, (int)canvas.Height, filepath);
}
});
t.SetApartmentState(ApartmentState.STA);
t.Start();
}

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.

WPF - Rotate and Save Image -both for the View and to Disk

I have a View that contains an image control.
<Image
x:Name="img"
Height="100"
Margin="5"
Source="{Binding Path=ImageFullPath Converter={StaticResource ImagePathConverter}}"/>
The binding uses a converter that does nothing interesting except set BitmapCacheOption to "OnLoad", so that the file is unlocked when I attempt to rotate it.
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
// value contains the full path to the image
string val = (string)value;
if (val == null)
return null;
// load the image, specify CacheOption so the file is not locked
BitmapImage image = new BitmapImage();
image.BeginInit();
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriSource = new Uri(val);
image.EndInit();
return image;
}
Here is my code to rotate the image. val is always 90 or -90, and path is the full path to the .tif file I want to rotate.
internal static void Rotate(int val, string path)
{
//cannot use reference to what is being displayed, since that is a thumbnail image.
//must create a new image from existing file.
Image image = new Image();
BitmapImage logo = new BitmapImage();
logo.BeginInit();
logo.CacheOption = BitmapCacheOption.OnLoad;
logo.UriSource = new Uri(path);
logo.EndInit();
image.Source = logo;
BitmapSource img = (BitmapSource)(image.Source);
//rotate tif and save
CachedBitmap cache = new CachedBitmap(img, BitmapCreateOptions.None, BitmapCacheOption.OnLoad);
TransformedBitmap tb = new TransformedBitmap(cache, new RotateTransform(val));
TiffBitmapEncoder encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(tb)); //cache
using (FileStream file = File.OpenWrite(path))
{
encoder.Save(file);
}
}
The issue I am experiencing is when I get the BitmapCacheOption to BitmapCacheOption.OnLoad, the file is not locked however rotate does not always rotate the image (I believe it is using the original cached value each time).
If I use BitmapCacheOption.OnLoad so the file is not locked, how can I update the Image control once the image has been rotated? The original value seems to be cached in memory.
Is there a better alternative for rotating an image that is currently being displayed in the view?
Yet another year later, I needed to do this and found the following solution:
If you have an image element:
<grid>
<stackpanel>
<Image Name="img" Source="01.jpg"/>
<Button Click="Button_Click" Content="CLICK"/>
</stackpanel>
</grid>
you can rotate it in code-behind:
private double rotateAngle = 0.0;
private void RotateButton_Click(object sender, RoutedEventArgs e)
{
img.RenderTransformOrigin = new Point(0.5, 0.5);
img.RenderTransform = new RotateTransform(rotateAngle+=90);
}
Or, assuming your image element is called img:
private double RotateAngle = 0.0;
private void Button_Click(object sender, RoutedEventArgs e)
{
TransformedBitmap TempImage = new TransformedBitmap();
TempImage.BeginInit();
TempImage.Source = MyImageSource; // MyImageSource of type BitmapImage
RotateTransform transform = new RotateTransform(rotateAngle+=90);
TempImage.Transform = transform;
TempImage.EndInit();
img.Source = TempImage ;
}

Resources