Reputation: 911
Im trying to render a canvas on another thread. Here is my attempt:
// returns path to exported image
private string exportToImage(double width, double height, Visual visual)
{
var filename = string.Format(@"{0}.png", Guid.NewGuid());
var tempFile = Path.Combine(tempDir, filename);
Rect rect = new Rect(0, 0, width, height);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
Thread RENDER_THREAD = new Thread(() =>
{
this.Dispatcher.Invoke((Action)(() =>
{
rtb.Render(visual);
}));
});
RENDER_THREAD.Start();
//endcode as PNG
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
//save to memory stream
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
return tempFile;
}
Im not getting any errors but the rendered result is an empty image. Is the instance of rtb
in the main thread and the new thread the same?
Edit: Here is my latest attempt.
I create the thread on a MouseUp event:
var curLayer = GetItemsPanel((canvasDataBinding.ItemContainerGenerator.ContainerFromIndex(Binding_LayersListView.SelectedIndex)));
Thread RASTERIZE_THREAD = new Thread(() => { exportToImage(curLayer.ActualWidth, curLayer.ActualHeight, curLayer); });
RASTERIZE_THREAD.Start();
and here is my method the new thread uses:
void exportToImage(double width, double height, Visual visual)
{
var filename = string.Format(@"{0}.png", Guid.NewGuid());
var tempFile = Path.Combine(tempDir, filename);
Rect rect = new Rect(0, 0, width, height);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
this.Dispatcher.Invoke(new Action(() =>
{
rtb.Render(visual);
}));
//endcode as PNG
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
//save to memory stream
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
}
So why does it tell me that The calling thread cannot access this object because a different thread owns it on rtb.Render(visual)
? Im not calling exportToImage from anywhere else, so why is the Dispatcher not associated with the thread I created?
Edit: I needed to create the RebderTargetBitmap inside Dispatcher.Invoke().
Upvotes: 3
Views: 2881
Reputation: 13659
here is a code how you can render the same in a thread and make it async
whole idea is
this code in not dependent on Dispatcher so it is able to run full async
void exportToImage(double width, double height, Visual visual)
{
//this line can not be inside a thread because different thread owns the visual
string visualData = XamlWriter.Save(visual);
Thread t = new Thread(() =>
{
var filename = string.Format(@"{0}.png", Guid.NewGuid());
var tempFile = System.IO.Path.Combine("c:\\", filename);
Rect rect = new Rect(0, 0, width, height);
//create a host control to host the visual
ContentControl cc = new ContentControl();
cc.Content = XamlReader.Parse(visualData);
//call Arrange to let control perform layout (important)
cc.Arrange(rect);
RenderTargetBitmap rtb = new RenderTargetBitmap((int)rect.Right,
(int)rect.Bottom, 96d, 96d, System.Windows.Media.PixelFormats.Default);
rtb.Render(cc);
//endcode as PNG
BitmapEncoder pngEncoder = new PngBitmapEncoder();
pngEncoder.Frames.Add(BitmapFrame.Create(rtb));
//save to memory stream
System.IO.MemoryStream ms = new System.IO.MemoryStream();
pngEncoder.Save(ms);
ms.Close();
System.IO.File.WriteAllBytes(tempFile, ms.ToArray());
});
//STA is necessary for XamlReader.Parse, and proper rendering
t.SetApartmentState(ApartmentState.STA);
t.Start();
}
I have successfully tested this code to render some test visuals. but results may vary depending on how you use. as a guess bindings & raster images may have some issues, however in my tests it worked like a charm.
Upvotes: 5