Edit file after removing binding - c#

I am trying to stream a desktop screen from one PC to another. I've started with just sending an image first which was really easy. My next thought was to just display the image using WPF and by continuously editing the saved image I would have a live stream of the desktop. After trying this I noticed that editing an image that is currently being used results in an exception.
The process cannot access the file 'D:\Desktop\ConsoleApp1\WpfApp1\img\Bild1.jpeg' because it is being used by another process.
So when I tried using two different images that would just swap every time, the same exception kept appearing.
The code below deletes the unbound image and creates a new file that will be bound to an Image.
// The Image that is currently not binded will be deleted with the code below
if (img1){
if (File.Exists("./../../img/Bild2.jpeg"))
{
File.Delete("./../../img/Bild2.jpeg");
}
}
else
{
if (File.Exists("./../../img/Bild1.jpeg"))
{
File.Delete("./../../img/Bild1.jpeg");
}
}
// This code save the Image
if (File.Exists("../../img/Bild1.jpeg")) // Image1 exists so I am editing image2
{
bmp.Save("../../img/Bild2.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild2.jpeg");
ImageBind = fi.FullName; // Image Bind is a string property which is binded to a Image Tag
img1 = false; // To check which img has been edited last
}
else if (File.Exists("../../img/Bild2.jpeg"))
{
bmp.Save("./../../img/Bild1.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild1.jpeg");
ImageBind = fi.FullName;
img1 = true;
}
else // If neither Image1 or Image2 exists
{
bmp.Save("../../img/Bild1.jpeg", ImageFormat.Jpeg);
ms.Position = 0;
FileInfo fi = new FileInfo("../../img/Bild1.jpeg");
ImageBind = fi.FullName;
img1 = true;
}

I do not think that copying and swapping images is the right approach for live desktop streaming in many ways, but that is another question. I will only focus on the exception that you get.
The image files that you assign by file path to Image are locked until the application is shut down. In order to change that, you have to change caching options of the underlying BitmapImage.
You can create a value converter to do this, so you can keep the ImageBind property as string.
public class PathToBitmapSourceConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var bi = new BitmapImage();
var uriString = (string)value;
bi.BeginInit();
bi.UriSource = new Uri(uriString, UriKind.RelativeOrAbsolute);
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.CreateOptions = BitmapCreateOptions.IgnoreImageCache;
bi.EndInit();
return bi;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
This code will convert your file path to a BitmapImage. The OnLoad and IgnoreImageCache will prevent the image from being locked. From the documentation:
OnLoad - Caches the entire image into memory at load time. All requests for image data are filled from the memory store.
IgnoreImageCache - Loads images without using an existing image cache. This option should only be selected when images in a cache need to be refreshed.
In your XAML you have to create an instance of the converter in any resource dictionary and adapt the binding of your Image to use the converter, assuming its key is PathToBitmapSourceConverter.
<Image Source="{Binding ImageBind, Converter={StaticResource PathToBitmapSourceConverter}}" ... />

Related

Display ImageSharp Image in Avalonia UI

As the title says. It seems that Avalonia Bitmap requires file path, so the only solution that comes to my mind is saving image, and then displaying it. But it's not exactly what I want to do, I would want to directly display the image from memory.
public void ReadAndDisplayImage()
{
ReadWrite rw = new ReadWrite();
var image = rw.ReadImage(ImagePath); //ImageSharp Image
SelectedImage = new Avalonia.Media.Imaging.Bitmap(ImagePath); //constructor accepts only string, unable to display ImageSharp Image directly
}
In this case here displaying from path is fully acceptable, but later I will need to display it without saving.
You can save the Image's data into a memory stream and pass it in to Avalonia's Bitmap.
public void ReadAndDisplayImage()
{
ReadWrite rw = new ReadWrite();
var image = rw.ReadImage(ImagePath);
using (MemoryStream ms = new MemoryStream())
{
image.Save(ms,PngFormat.Instance);
SelectedImage = new Avalonia.Media.Imaging.Bitmap(ms);
}
}

Why Image.Save() in C# does not always give the same serialized result and how to serialize an image in a deterministic way?

I serialize images using the following code:
public static string SerializeImage(Image image)
{
using (MemoryStream memoryStream = new MemoryStream())
{
image.Save(memoryStream, image.RawFormat);
return Convert.ToBase64String(memoryStream.ToArray());
}
}
and deserialize the images by doing the following
public static Image DeserializeImage(string serializedImage)
{
byte[] imageAsBytes = Convert.FromBase64String(serializedImage);
using (MemoryStream memoryStream = new MemoryStream(imageAsBytes, 0, imageAsBytes.Length))
{
memoryStream.Write(imageAsBytes, 0, imageAsBytes.Length);
return Image.FromStream(memoryStream, true);
}
}
If I have an image and does
string serializedImage1 = SerializeImage(image);
Image deserializedImage = DeserializeImage(serializedImage1);
string serializedImage2 = SerializeImage(deserializedImage );
Then
serializedImage1 == serializedImage2;
as expected. But it is not always the case.
If I serialize an image on Process 1, and then redeserialize and reserialize it on Process 2, then the result of the reserialization on Process 2 is not the same as on the Process 1. Everything works, but a few bytes in the beginning of the serialization are different.
Worst, if I do the same thing on 2 different dll (or thread, I'm not sure), it seems the serialization result is not the same too. Again, the serialization/deserialization works, but a few bytes are different.
The image the first time is loaded with the following function :
public static Image GetImageFromFilePath(string filePath)
{
var uri = new Uri(filePath);
var bitmapImage = new BitmapImage(uri);
bitmapImage.Freeze();
using (var memoryStream = new MemoryStream())
{
var pngBitmapEncoder = new PngBitmapEncoder();
pngBitmapEncoder.Frames.Add(BitmapFrame.Create(bitmapImage));
pngBitmapEncoder.Save(memoryStream);
Image image = Image.FromStream(memoryStream);
return image;
}
}
Note however that it happens even if the image is loaded twice with the DeserializeImage() function.
The tests I have done are with ImageFormat.Jpeg and ImageFormat.Png.
First question, why it does this ? I would have expected the result to be always the same, but I suppose some salt is used when doing the Image.Save().
Second question : I want to have a deterministic way to serialize an image, keeping the image format intact. The goal is to save the image in a DB and also to compare serialized images to know if it already exists in the system where this function is used.
Well, I discovered while trying to solve this that a png or jpeg Image object inside C# has some metadata associated to it and doing what I was doing is just not a reliable way to compare images.
The solution I used was derived from this link
https://insertcode.wordpress.com/2014/05/13/compare-content-of-two-files-images-in-c/
So what I do finally is save the images inside the system with the SerializeImage(Image image) function previously described, and when I want to consume it I deserialize it with the DeserializeImage(string serializedImage) function previously described. But when I want to compare images I use the following functions
public static bool ImagesAreEqual(Image image1, Image image2)
{
string image1Base64Bitmap = GetImageAsBase64Bitmap(image1);
string image2Base64Bitmap = GetImageAsBase64Bitmap(image2);
return image1Base64Bitmap.Equals(image2Base64Bitmap);
}
public static string GetImageAsBase64Bitmap(Image image)
{
using (var memoryStream = new MemoryStream())
{
using (var bitmap = new Bitmap(image))
{
bitmap.Save(memoryStream, System.Drawing.Imaging.ImageFormat.Bmp);
}
return Convert.ToBase64String(memoryStream.ToArray());
}
}
That convert the image to raw bitmap before comparing them.
This does a perfect job for me in all my needed cases : the formats of the images are saved/restored correctly, and I can compare them between them to check if they are the same without having to bother with the possibly different serialization.

Images loading partially

I'm trying to load some 20 images in my WPF application. Only the first image loads completely. Other images are loading partially. When i used breakpoint to debug i tried to load each image after 2 seconds and that worked well.
Code
Images will be loaded like,
foreach (string path in ImagesCollection)
{
DisplayImage = LoadImage(path);
}
Load image method,
MemoryStream mem;
if (!string.IsNullOrEmpty(path) && (File.Exists(path)))
{
FileInfo ImageFile = new FileInfo(path);
ImageFile.Refresh();
if (mem != null)
{
mem.Dispose();
}
using (var stream = ImageFile.OpenRead())
{
mem = new MemoryStream();
stream.CopyTo(mem);
}
mem.Position = 0;
ImageFrame = BitmapFrame.Create(mem);
}
Screenshot:
I believe Dispose or a new instance makes the image doesn't load. Kindly help.
The documentation for BitmapFrame.Create state "The bitmapStream can be closed after the frame is created only when the OnLoad cache option is used. The default OnDemand cache option retains the stream until the frame is needed"
This means you cannot re-use the MemoryStream once you've passed it to the BitmapFrame. This is the source of the error.
For efficiency just pass the FileStream.
Load image method
if (!string.IsNullOrEmpty(path) && (File.Exists(path)))
{
var stream = ImageFile.OpenRead())
ImageFrame = BitmapFrame.Create(stream);
}

ImageSource in a class library won't show

My application links to a class library (.dll). In the class library project, three images were put into Resources.resx. One image needs to be chosen at run time and shown on a button. After googling around, I choose to use a converter to help the binding in xaml:
[ValueConversion(typeof(Side), typeof(ImageSource))]
public class SideToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return ToWpfBitmap(Properties.Resources.LeftArmOnCart);
case Side.Right:
return ToWpfBitmap(Properties.Resources.RightArmOnCart);
case Side.Both:
return ToWpfBitmap(Properties.Resources.RightArmOnCart);
default:
throw new ArgumentException("Current configuration is invalid");
}
}
private static BitmapSource ToWpfBitmap(Bitmap bitmap)
{
using (MemoryStream stream = new MemoryStream())
{
bitmap.Save(stream, ImageFormat.Png);
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 xaml code looks like
<Image Source="{Binding Side, Converter={StaticResource SideToImageConverter}}" .../>
Unfortunately, the image won't show silently without throwing any exception. Debug into the converter code, the bitmap argument in ToWpfBitmap() looks fine (the Width/Height values were correct).
BTW, as another trial, the following code works fine.
[ValueConversion(typeof(Side), typeof(ImageSource))]
public class SideToImageConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return LoadBitmap("LeftArmOnCart.png");
case Side.Right:
return LoadBitmap("RightArmOnCart.png");
case Side.Both:
return LoadBitmap("RightArmOnCart.png");
default:
throw new ArgumentException("Current configuration is invalid");
}
}
private static BitmapSource LoadBitmap(string name)
{
BitmapImage result = new BitmapImage();
result.BeginInit();
string uri = "c:/.../Resources/Images/" + name;
result.CacheOption = BitmapCacheOption.OnLoad;
result.UriSource = new Uri(uri, UriKind.Absolute);
result.EndInit();
return result;
}
}
However, absolute path is not desired. So, do I miss anything?
I understand that it is feasible to use relative path by releasing the three image files. But, is it possible NOT to release the three files but just the class library dll?
Thanks!
There are a few things I think you could read up on to help you with your problem. I would start with Pack URIs in WPF so you can understand how to use a URI to check a specific library's location. Since these images are in another assembly's resource file, read the "Referenced Resource Assembly File" in the link.
I think the other thing worth mentioning is the difference between the two methods is one you provide a UriSource to (bottom) and one you don't (top). Looking at Image.Source, when used in XAML it expects a imageUri. With that hint, we can say it is definitely something to do with URIs probably.
In fact, your images are already saved as a stream inside of the libraries that they exist in, so we don't need to do anything with creating a new bitmap or whatever you're doing, instead we can simply provide a valid pack URI and return a string.
I'm thinking something like this should work, provided the name of the library the images are in is YourApp.Models, in sub folder Resources\Images\:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Side side = (Side)value;
switch (side)
{
case Side.Left:
return "pack://application:,,,/YourApp.Models;component/Resources/Images/LeftArmOnCart.png";
case Side.Right:
case Side.Both:
return "pack://application:,,,/YourApp.Models;component/Resources/Images/RightArmOnCart.png";
default:
throw new ArgumentException("Current configuration is invalid");
}
}
Note: Path names are sensitive to forward/back slash, assembly names, component. Be sure everything is correctly formatted.
This is considered an absolute URI. A relative URI would be just "/Resources/Images/LeftArmOnCart.png" which would only work if that file path existed within the assembly that called it--meaning it wouldn't work for you since you're calling from another assembly.
Hope this helps!
There is already an InteropBitmap class that can be used to convert a GDI bitmap to a WPF ImageSource, though I find it doesn't handle alpha channels very well. This is the mechanism I use:
public static BitmapSource CreateBitmapSourceFromGdiBitmap(Bitmap bitmap)
{
if (bitmap == null)
throw new ArgumentNullException("bitmap");
var rect = new Rectangle(0, 0, bitmap.Width, bitmap.Height);
var bitmapData = bitmap.LockBits(
rect,
ImageLockMode.ReadWrite,
PixelFormat.Format32bppArgb);
try
{
var size = (rect.Width * rect.Height) * 4;
return BitmapSource.Create(
bitmap.Width,
bitmap.Height,
bitmap.HorizontalResolution,
bitmap.VerticalResolution,
PixelFormats.Bgra32,
null,
bitmapData.Scan0,
size,
bitmapData.Stride);
}
finally
{
bitmap.UnlockBits(bitmapData);
}
}
Note that this implementation assumes 32bpp ARGB pixel format. That should be fine if you're using PNG images. Otherwise, you'll need to add a conversion step.

IValueConverter blocks prevents deletion

I have a Listbox with image paths (ObservableCollection as source), and IValueConverter is turning them into the image thumbnails.
When I try to delete any of those files, I get the error:
"The process cannot access the file 'C:\test.jpg' because it is being used by another process."
public class UriToBitmapConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
try
{
BitmapFrame bit = BitmapFrame.Create(new Uri(value.ToString()), BitmapCreateOptions.DelayCreation, BitmapCacheOption.OnDemand);
if (bit.Thumbnail != null)
{
return bit.Thumbnail;
}
else
{
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 200;
bi.UriSource = new Uri(value.ToString());
bi.CacheOption = BitmapCacheOption.OnLoad;
bi.EndInit();
return bi;
}
}
catch
{
return null;
}
}
How do I force-release the image, or force file deletion? Thank you.
You could probably use a FileStream and the StreamSource.
Set the CacheOption property to BitmapCacheOption.OnLoad if you wish to close the stream after the BitmapImage is created. The default OnDemand cache option retains access to the stream until the bitmap is needed, and cleanup is handled by the garbage collector.

Categories