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.
Related
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.
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}}" ... />
I have a simple path tracer I've written that I'd like to be able to show a real(ish)-time preview of in my GUI. Currently, I have this working with some hacky solutions:
App is .NET Core 3 based. GUI is .NET Core 3 WPF. The GUI is its own project, while the actual renderer is a separate class library. Currently, I'm using an Image object in the GUI, with its source bound to Renderer.ImageBuffer, which is a Bitmap. I've added some INotifyPropertyChanged code to let the GUI know that it should refresh with a user-set interval. I'm using this converter to get something displayed there:
public class ConvertBitmapToBitmapImage : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (!(value is System.Drawing.Bitmap rawBitmap)) return null;
using var memory = new MemoryStream();
rawBitmap.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
return bitmapImage;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
However, when I enable the update code in the renderer, there's a huge amount of overhead converting from the raw bitmap data to the GUI-friendly BitmapImage format, and it slows the render down substantially. As far as I can tell, there's no way to access the BitmapImage object type directly from the backend without making the class library a WPF project itself in Core 3. Is there a better way that I can be doing this, or a more appropriate control for mapping raw values to something viewable in the GUI?
It is a fact of life that you will, sometimes, have to "interop" with System.Drawing.Bitmap in WPF Applications. You are using a BitmapImage because you would like a BitmapSource, right? You are in luck! WriteableBitmap is supported in .NET Core 3.
Your converting in an IValueConverter instance makes this quite tricky. In any case, the idea is to keep only one WriteableBitmap around and use WritePixels properly, to copy the data without the overhead of re-creating the bitmap. WPF will update at each rendering pass, so you will definitely get your result. I suggest finding a way to "stuff" the WriteableBitmap into your view-model and pass it directly as such, without the converter.
You may (possibly will) have to re-create the source occasionally, because I suspect you might want to allow resizing your application. Whenever the GUI is resized, you will need to new the WriteableBitmap with the new size. You will also have to make sure you get the DPI setting right, if you are developing a DPI-aware application. Thankfully, the WriteableBitmap can manage some of this (note the constructor arguments) for you.
Update
I ran this code (similar to yours)
//Load a bitmap in memory from file. Size is 1896 x 745,
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(#"...");
//Start a stopwatch to measure performance.
Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
//Perform 500 times.
for (int i = 0; i < 500; i++)
{
using (var memory = new MemoryStream())
{
bmp.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp);
memory.Position = 0;
var bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
//testImage is an Image control in the UI.
testImage.Source = bitmapImage;
}
}
watch.Stop();
System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds + " ms.");
//Just one test run of this takes about 7000 ms!!!
This takes ~7 seconds to run.
Then, I run this code:
//Load a bitmap in memory from file. Size is 1896 x 745,
System.Drawing.Bitmap bmp = new System.Drawing.Bitmap(#"...");
//Create the BitmapSource (a WriteableBitmap). 96 is the dpi setting.
WriteableBitmap writeableBMP =
new WriteableBitmap(1896, 745, 96, 96, PixelFormats.Pbgra32, null);
//Start a stopwatch to measure performance.
Stopwatch watch = new System.Diagnostics.Stopwatch();
watch.Start();
//Perform 500 times.
for (int i = 0; i < 500; i++)
{
System.Drawing.Rectangle rect = new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height);
//Lock the bits to copy from the bitmap to the image source.
BitmapData data =
bmp.LockBits(rect, ImageLockMode.ReadOnly, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
//Prepare the buffer size (System.Drawing.Bitmap specific)
int bufferSize = rect.Height * data.Stride;
//Prepare the write rectangle, of course this is the entire image.
Int32Rect writeRectangle = new Int32Rect(0, 0, 1896, 745);
//Write the pixels
writeableBMP.WritePixels(writeRectangle, data.Scan0, bufferSize, data.Stride, 0, 0);
//Unlock the bits, to prepare for another cycle.
bmp.UnlockBits(data);
testImage.Source = writeableBMP;
}
watch.Stop();
System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds + " ms.");
//Multiple test runs of this take ~300 ms!!!
This second piece of code, in all its glorious sloppiness, executes in ~300 ms. It copies the entire bitmap in every step. A 1896 x 745 bitmap, should be relatively average for a screen UI that is "almost" maximized.
My suggestion to use a WriteableBitmap improves the run-time of achieving the same result with the originally posted code by about ~7000/300 > ~20 times in my machine. Would you possibly try this as I have posted it, and let me know if it runs any better for you? Having had exactly the same problem with you (and switching to a WriteableBitmap of course), I am hard pressed to believe that it only improves your running time by 1-2%. You may have to share some more details of your UI rendering code, so that I can try to locate other potential bottlenecks.
Of course some overhead is to be expected by the rest of the WPF rendering pipeline, as I am only measuring a critical part of the code (relatively roughly, no less) but, in general, the WriteableBitmap is close to "as good as it gets" before you abandon software rendering in WPF for any other rendering engine (e.g. hardware).
(I have received 1 downvote and 1 vote to delete this answer so far)
i want to use the singleton pattern to create a buffer where all needed pictures are stored.
Something like that:
public sealed class BaseBuffer
{
private static readonly Dictionary<string, Bitmap> pictures = new Dictionary<string, Bitmap>();
public static Bitmap GetPicture(string name, ref Bitmap output)
{
//In case the pictureBuffer does not contain the element already
if (!pictures.ContainsKey(name))
{
//Try load picture from resources
try
{
Bitmap bmp = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("MyProject.Resources." + name + ".png"));
pictures.Add(name, bmp);
return bmp;
}
catch (Exception ex)
{
Console.WriteLine("Picture {0} cannot be found.", name);
}
}
else
{
return pictures[name];
}
return null;
}
Now im not sure. Does the "GetPicture" method return a copy of the picture or does it return a reference?
The application will need a few pictures which are displayed verry often on different Applications/Forms. Because of that it would be better to have only references to the pictures than copying them around.
Do you have any idea how to do that?
It'll return a reference to the Bitmap (as it is defined as a class) held within the dictionary. So no it doesn't return a distinct copy. Changes to the bitmap returned will be observed in the dictionary also as they will be the same underlying object. I'm not sure what you are doing with your ref Bitmap output as it doesn't appear to be used.
Also, TryGetValue on the dictionary will perform slightly better if this is called often:
Bitmap bmp;
if (!pictures.TryGetValue(name, out bmp))
{
bmp = new Bitmap(System.Reflection.Assembly.GetEntryAssembly().GetManifestResourceStream("MyProject.Resources." + name + ".png"));
pictures.Add(name, bmp);
}
return bmp;
Im not sure about that, butIi guess instead of doing return bmp; you should do output=bmp; and so on. Otherwise you could get rid of the parameter.
I also think, that even return bmp; and so on would return a reference but not a copy of the Bitmap stored in the dict. so either way would be fine...
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.