Threading WriteableBitmap to Image control - wpf

I am using OpenGL to render a static picture in WPF, on a .NET Core 3.1 platform. The context is initially displayed in a hidden GLFW window. Then I am using glReadPixels to read the context and display it on an Image control on the UI, using the code shown below. The code works fine when not using threading. However, when calling the rendering code through a different thread the image is not displayed. As an intermediate check, I created the using (FileStream... code, which reads the WriteableBitmap and saves it locally and appears to be working correctly; this means that the WriteableBitmap is created correctly. So, I guess the problem is how to transmit a WriteableBitmap to the Image control, both of which were created on different threads. Dispatcher.BeginInvoke does not work for some strange reason.
byte[] imageBuffer = new byte[imageWidth * imageHeight * 4];
glReadPixels(0, 0, imageWidth, imageHeight, GL_BGRA, GL_UNSIGNED_BYTE, imageBuffer);
imageBuffer = ImageReverse(imageBuffer, imageWidth);
PixelFormat pixelFormat = PixelFormats.Pbgra32;
int rawStride = (imageWidth * pixelFormat.BitsPerPixel) / 8;
BitmapSource bitmapSource = BitmapSource.Create(imageWidth, imageHeight, 96, 96, pixelFormat, null, imageBuffer, rawStride);
WriteableBitmap writeableBitmap = new WriteableBitmap(bitmapSource);
//This is a check whether the writeableBitmap has been created correctly
using (FileStream fileStream = new FileStream(#"C:\Temp\Image1.bmp", FileMode.Create))
{
BmpBitmapEncoder encoder = new BmpBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(writeableBitmap));
encoder.Save(fileStream);
}
Action action = new Action(() => {
_imgSoilProfile.Source = writeableBitmap;
});
_imgSoilProfile.Dispatcher.BeginInvoke(action, DispatcherPriority.Send);

Try to call w.Freeze() on the background thread before you set the Source of the Image:
using (FileStream fileStream = new FileStream(#"C:\Temp\Image1.bmp", FileMode.Create))
{
...
}
w.Freeze();
Action action = new Action(() => {
_imgSoilProfile.Source = writeableBitmap;
});
From the docs:
A frozen Freezable can also be shared across threads, while an unfrozen Freezable cannot.

Related

Delete image created by Interop Powerpoint in WPF Application

I wrote a WPF Application that opens a PowerPoint presentation and save the slides as images to be displayed in a PictureBox. The PictureBox is bounded to ImageBitmap property.
PowerPoint.Application app = new PowerPoint.Application();
PowerPoint.Presentations pres = app.Presentations;
p = pres.Open(FileName, ofalse, ofalse, otrue);
p.Slides[0].Export(ImagePath, "png", 640, 480);
ImageBitmap = new BitmapImage(new Uri(ImagePath));
When the application is closed, I would like to delete all the image files created, but instead I got an error: "“The process cannot access the file because it is being used by another process”.
I have determined (using ProcessExplorer) that the files were being used only by my application.
I read some suggestions that using the code below will allow the image file (located in ImagePath) to be deleted, but I think this will break MVVM since the viewModel now knows about the View.
Image im = GetCopyImage(#"D:\Temp\AAAA.jpg");
myPictureBox.Image = im;
File.Delete(#"D:\Temp\AAAA.jpg");
}
private Image GetCopyImage(string path)
{
using (Image im = Image.FromFile(path))
{
Bitmap bm = new Bitmap(im);
return bm;
}
}
You may set the BitmapCacheOption.OnLoad option during initialization of the BitmapImage. This lets WPF close the image file right after loading the image.
var bitmap = new BitmapImage();
bitmap.BeginInit();
bitmap.CacheOption = BitmapCacheOption.OnLoad;
bitmap.UriSource = new Uri(ImagePath);
bitmap.EndInit();
ImageBitmap = bitmap;

No image displayed when setting Image.Source to valid data

I'm receiving byte[] image-data (from a webcam source, transmitted over network) and want to display it in my WPF Image control.
If I set the resolution to 160x120, I see a nice tiny image being displayed.
For any other resolution though I see nada, nothing, zilch, squat.
If I write the raw bytes to disk I can see that the data is indeed a valid jpg-image.
Just to make sure it's not jpeg-incompatibility I've tested it with png- and bmp-encoding/decoding as well with the same results; still no image.
Anyone got a bright idea ?
private bool OnImage(byte[] inJpg)
{
this.Dispatch(() =>
{
//File.WriteAllBytes("h:\\tmp\\test" + sImageNum++ + ".jpg", inJpg);
using (MemoryStream ms = new MemoryStream(inJpg))
{
BitmapDecoder decoder = new JpegBitmapDecoder(ms, BitmapCreateOptions.None, BitmapCacheOption.Default);
ImageRemoteVideo.Source = decoder.Frames[0];
}
}
);
}
Assuming ImageRemoteVideo is an instance of a WPF Image control, you may be getting bit by some lazy resolve issues. The Image control has "BeginInit" and "EndInit" methods that you should probably be using. Put the BeginInit method before you set the stream, the other after you set it. Also, if I remember right, the Image class supports a StreamSource. You could try setting your MemoryStream (without disposing of it and without using the JpegBitmapDecoder) to that Property. If that works, then see if you can dispose it after the EndInit call.
I have just found a way to make it work, but I'd love it if someone can explain WHY it works :)
First off, I'm using the raw bytestream to create a BitmapImage.
BUT - if I use a "using" clause around the MemoryStream it doesn't work, if I don't it works.
So this doesn't work :
using (MemoryStream byteStream = new MemoryStream(inJpg))
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = byteStream;
image.EndInit();
ImageRemoteVideo.Source = image;
}
But this does :
MemoryStream byteStream = new MemoryStream(inJpg);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = byteStream;
image.EndInit();
ImageRemoteVideo.Source = image;
Found it. It's like Brannon said a case of delayed loading.
Specifying "CacheOption = BitmapCacheOption.OnLoad;" fixes it !
Cheers !
Working version :
MemoryStream byteStream = new MemoryStream(inJpg);
{
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = byteStream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
ImageRemoteVideo.BeginInit();
ImageRemoteVideo.Source = image;
ImageRemoteVideo.EndInit();
}

Loading an image in a background thread in WPF

There are a bunch of questions about this already on this site and other forums, but I've yet to find a solution that actually works.
Here's what I want to do:
In my WPF app, I want to load an image.
The image is from an arbitrary URI on the web.
The image could be in any format.
If I load the same image more than once, I want to use the standard windows internet cache.
Image loading and decoding should happen synchronously, but not on the UI Thread.
In the end I should end up with something that I can apply to an <Image>'s source property.
Things I have tried:
Using WebClient.OpenRead() on a BackgroundWorker. Works fine, but doesn't use the cache. WebClient.CachePolicy only affects that particular WebClient instance.
Using WebRequest on the Backgroundworker instead of WebClient, and setting WebRequest.DefaultCachePolicy. This uses the cache properly, but I've not seen an example that doesn't give me corrupted-looking images half the time.
Creating a BitmapImage in a BackgroundWorker, setting BitmapImage.UriSource and trying to handle BitmapImage.DownloadCompleted. This seems to use the cache if BitmapImage.CacheOption is set, but there doesn't seem to be away to handle DownloadCompleted since the BackgroundWorker returns immediately.
I've been struggling with this off-and-on for literally months and I'm starting to think it's impossible, but you're probably smarter than me. What do you think?
I have approached this problem in several ways, including with WebClient and just with BitmapImage.
EDIT: Original suggestion was to use the BitmapImage(Uri, RequestCachePolicy) constructor, but I realized my project where I tested this method was only using local files, not web. Changing guidance to use my other tested web technique.
You should run the download and decoding on a background thread because during loading, whether synchronous or after download the image, there is a small but significant time required to decode the image. If you are loading many images, this can cause the UI thread to stall. (There are a few other intricacies here like DelayCreation but they don't apply to your question.)
There are a couple ways to load an image, but I've found for loading from the web in a BackgroundWorker, you'll need to download the data yourself using WebClient or a similar class.
Note that BitmapImage internally uses a WebClient, plus it has a lot of error handling and settings of credentials and other things that we'd have to figure out for different situations. I'm providing this snippet but it has only been tested in a limited number of situations. If you are dealing with proxies, credentials, or other scenarios you'll have to massage this a bit.
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
using (WebClient webClient = new WebClient())
{
webClient.Proxy = null; //avoids dynamic proxy discovery delay
webClient.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
try
{
byte[] imageBytes = null;
imageBytes = webClient.DownloadData(uri);
if (imageBytes == null)
{
e.Result = null;
return;
}
MemoryStream imageStream = new MemoryStream(imageBytes);
BitmapImage image = new BitmapImage();
image.BeginInit();
image.StreamSource = imageStream;
image.CacheOption = BitmapCacheOption.OnLoad;
image.EndInit();
image.Freeze();
imageStream.Close();
e.Result = image;
}
catch (WebException ex)
{
//do something to report the exception
e.Result = ex;
}
}
};
worker.RunWorkerCompleted += (s, e) =>
{
BitmapImage bitmapImage = e.Result as BitmapImage;
if (bitmapImage != null)
{
myImage.Source = bitmapImage;
}
worker.Dispose();
};
worker.RunWorkerAsync(imageUri);
I tested this in a simple project and it works fine. I'm not 100% about whether it is hitting the cache, but from what I could tell from MSDN, other forum questions, and Reflectoring into PresentationCore it should be hitting the cache. WebClient wraps WebRequest, which wraps HTTPWebRequest, and so on, and the cache settings are passed down each layer.
The BitmapImage BeginInit/EndInit pair ensures that you can set the settings you need at the same time and then during EndInit it executes. If you need to set any other properties, you should use the empty constructor and write out the BeginInit/EndInit pair like above, setting what you need before calling EndInit.
I typically also set this option, which forces it to load the image into memory during EndInit:
image.CacheOption = BitmapCacheOption.OnLoad;
This will trade off possible higher memory usage for better runtime performance. If you do this, then the BitmapImage will be loaded synchronously within EndInit, unless the BitmapImage requires async downloading from a URL.
Further notes:
BitmapImage will async download if the UriSource is an absolute Uri and is an http or https scheme. You can tell whether it is downloading by checking the BitmapImage.IsDownloading property after EndInit. There are DownloadCompleted, DownloadFailed, and DownloadProgress events, but you have to be extra tricky to get them to fire on the background thread. Since BitmapImage only exposes an asynchronous approach, you would have to add a while loop with the WPF equivalent of DoEvents() to keep the thread alive until the download is complete. This thread shows code for DoEvents that works in this snippet:
worker.DoWork += (s, e) =>
{
Uri uri = e.Argument as Uri;
BitmapImage image = new BitmapImage();
image.BeginInit();
image.UriSource = uri;
image.CacheOption = BitmapCacheOption.OnLoad;
image.UriCachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
image.EndInit();
while (image.IsDownloading)
{
DoEvents(); //Method from thread linked above
}
image.Freeze();
e.Result = image;
};
While the above approach works, it has a code smell because of DoEvents(), and it doesn't let you configure the WebClient proxy or other things that might help with better performance. The first example above is recommended over this one.
The BitmapImage needs async support for all of its events and internals. Calling Dispatcher.Run() on the background thread will...well run the dispatcher for the thread. (BitmapImage inherits from DispatcherObject so it needs a dispatcher. If the thread that created the BitmapImage doesn't already have a dispatcher a new one will be created on demand. cool.).
Important safety tip: The BitmapImage will NOT raise any events if it is pulling data from cache (rats).
This has been working very well for me....
var worker = new BackgroundWorker() { WorkerReportsProgress = true };
// DoWork runs on a brackground thread...no thouchy uiy.
worker.DoWork += (sender, args) =>
{
var uri = args.Argument as Uri;
var image = new BitmapImage();
image.BeginInit();
image.DownloadProgress += (s, e) => worker.ReportProgress(e.Progress);
image.DownloadFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DecodeFailed += (s, e) => Dispatcher.CurrentDispatcher.InvokeShutdown();
image.DownloadCompleted += (s, e) =>
{
image.Freeze();
args.Result = image;
Dispatcher.CurrentDispatcher.InvokeShutdown();
};
image.UriSource = uri;
image.EndInit();
// !!! if IsDownloading == false the image is cached and NO events will fire !!!
if (image.IsDownloading == false)
{
image.Freeze();
args.Result = image;
}
else
{
// block until InvokeShutdown() is called.
Dispatcher.Run();
}
};
// ProgressChanged runs on the UI thread
worker.ProgressChanged += (s, args) => progressBar.Value = args.ProgressPercentage;
// RunWorkerCompleted runs on the UI thread
worker.RunWorkerCompleted += (s, args) =>
{
if (args.Error == null)
{
uiImage.Source = args.Result as BitmapImage;
}
};
var imageUri = new Uri(#"http://farm6.static.flickr.com/5204/5275574073_1c5b004117_b.jpg");
worker.RunWorkerAsync(imageUri);

How to save BitmapImage / WriteableBitmap in png format (Silverlight/windows phone)?

How can i save an image (BitmapImage / WriteableBitmap) in png format using Silverlight for windows phone?
Take a look at ImageTools on codeplex. That supports silverlight encoding PNG. I'm not sure whether this will work with or can be compiled for windows phone 7.
You could try the following code.
This code worked for me. Before you try, make sure that your writablebitmap has a transparent background (You can check by assigning to a image controller image source). If not, make the background transparent from the controller it was coming from.
var localFolder = Windows.Storage.ApplicationData.Current.LocalFolder;
var file = await localFolder.CreateFileAsync("temp.png", CreationCollisionOption.ReplaceExisting);
using (var ras = await file.OpenAsync(FileAccessMode.ReadWrite, StorageOpenOptions.None))
{
WriteableBitmap bitmap = imageSource;
var stream = bitmap.PixelBuffer.AsStream();
byte[] buffer = new byte[stream.Length];
await stream.ReadAsync(buffer, 0, buffer.Length);
BitmapEncoder encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.PngEncoderId, ras);
encoder.SetPixelData(BitmapPixelFormat.Bgra8, BitmapAlphaMode.Straight, (uint)bitmap.PixelWidth, (uint)bitmap.PixelHeight, 96.0, 96.0, buffer);
await encoder.FlushAsync();
}

How to render an <image> in a background WPF process?

I'm taking Silverlight XAML and sending it to a web service that renders the XAML in a STA thread and generates a PNG image. All the XAML renders correctly except the <image> entries which 'appear' to load correctly when their Source property is set in WPF but the PNG does not show the referenced image - what am I doing wrong ?
The core of the code that I am using is as below. The value object is a DTO from Silverlight that contains the XAML in the string that ends up as the sXAML local property and the Image URI is in the value.ImageURL property.
var canvas = (FrameworkElement)XamlReader.Load(new XmlTextReader(new StringReader(sXAML)));
var obj = canvas.FindName("BGImage");
Image img = null;
if (obj != null)
{
img = obj as Image;
img.ImageFailed += img_ImageFailed;
img.Source = new BitmapImage(new Uri(value.ImageURL, UriKind.Absolute));
}
canvas.Arrange(new Rect(new Size(463d, 381d)));
canvas.UpdateLayout();
var mem = new MemoryStream();
var bmp = new RenderTargetBitmap(463, 381, 96d, 96d, PixelFormats.Pbgra32);
bmp.Render(canvas);
var encoder = new PngBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(bmp));
encoder.Save(mem);
FileStream fs = new FileStream("D:\\out.png", FileMode.Create);
mem.WriteTo(fs);
fs.Close();
NB: The img_ImageFailed event handler is never invoked indicating that the img Source assignment was successful in some way.
Things I would try:
1) In your WPF app, if you 'render' your dynamically loaded Canvas to display in a Window, does it all work (images included)?
2) Have you tried attaching a Loaded handler to the img object to see when the image is actually loaded?
3) Assuming #1 works - where are the images located (are they on the internet/local web server)? If you put a breakpoint in the code on
bmp.Render(canvas);
and wait a while before stepping on - does the image then appear in the rendered output?
I suspect the image is being 'downloaded' asynchronously and you are rendering the Canvas too early, before the Image object has resolved its source.
[UPDATE 29-Jan-09]
possible solution
I copied you code down exactly and gave it a try. When I used a 'local' image location (eg. "c:\images\splash.jpg") the image was rendered fine in the output jpeg. When I used a URL (eg. "http://localhost/images/splash.jpg") it did NOT appear (as you describe).
So I modified your code as follows (to try and FORCE the image to be downloaded - will only work for http: image references) --
if (obj != null)
{
img = obj as Image;
// new stuff
BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.StreamSource = getCachedURLStream(new Uri(ImageURL));
bi.EndInit();
img.BeginInit();
img.Source = bi;
img.EndInit();
//img.ImageFailed += img_ImageFailed;
//img.Loaded += new RoutedEventHandler(img_Loaded);
//img.Source = new BitmapImage(new Uri(ImageURL, UriKind.Absolute));
}
public static Stream getCachedURLStream(Uri url)
{
HttpWebRequest webrequest = (HttpWebRequest)WebRequest.Create(url);
WebResponse response;
webrequest.CachePolicy = new RequestCachePolicy(RequestCacheLevel.Default);
response = webrequest.GetResponse();
Stream s = response.GetResponseStream();
BufferedStream bs = new BufferedStream(s, 8192);
return bs;
}
(the getCachedURLStream method is from synchronous image loading - it's kinda extraneous but I just wanted a quick chunk of code to test) and it seemed to then work (image visible in JPEG). Maybe that will work in your scenario?

Resources