WPF Saving image from Canvas using RenderTargetBitmap [duplicate] - wpf

I have Grid with three columns and layout is like
radio button | Canvas Control | radio button
For some feature of my application I need to take a screenshot of current view of canvas feature and save into a file.
I am using following method to save canvas to bmp,where I measure and arrange canvas.My problem is after image is saved ,canvas position in original grid is shifted to left,how can I re position my canvas back in grid as it was earlier.
private void SaveCanvasContentToImage(string path, Canvas canvasControl)
{
if (path == null && canvasControl != null) return;
// Save current canvas transform
Transform transform = canvasControl.LayoutTransform;
// reset current transform (in case it is scaled or rotated)
canvasControl.LayoutTransform = null;
// Get the size of canvas
Size size = new Size(canvasControl.Width, canvasControl.Height);
// Measure and arrange the canvas
canvasControl.Measure(size);
canvasControl.Arrange(new Rect(size));
// Create a render bitmap and push the canvas to it
RenderTargetBitmap renderBitmap =
new RenderTargetBitmap(
(int)size.Width,
(int)size.Height,
96d,
96d,
PixelFormats.Pbgra32);
renderBitmap.Render(canvasControl);
// Create a file stream for saving image
using (FileStream outStream = new FileStream(path, FileMode.CreateNew,FileAccess.ReadWrite))
{
// Use bitmap encoder for our data
BitmapEncoder encoder = new BmpBitmapEncoder();
// push the rendered bitmap to it
encoder.Frames.Add(BitmapFrame.Create(renderBitmap));
// save the data to the stream
encoder.Save(outStream);
outStream.Close();
outStream.Dispose();
}
// Restore previously saved layout
canvasControl.LayoutTransform = transform;
}

You may avoid the layout problem by using an intermediate DrawingVisual:
var renderTargetBitmap = new RenderTargetBitmap(
(int)canvasControl.ActualWidth, (int)canvasControl.ActualHeight,
96, 96, PixelFormats.Default);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(
new VisualBrush(canvasControl),
null,
new Rect(0, 0, canvasControl.ActualWidth, canvasControl.ActualHeight));
}
renderTargetBitmap.Render(visual);

Related

Unable to capture WPF livechart in bitmap

I'm trying to write a function that will write the results of a test (which have both text output and chart output, stored in a textbox and a livecharts chart respectively) to a pdf file. There are no issues with the text output, but I haven't been able to get the charts to save unless I extract each chart from its parent stackpanel (each test has a stackpanel that is shown when the test is selected) and then display the chart in a separate window. There is a lot of code around the problematic lines, so I'm going to paste a shortened version below.
The issue seems to be calling "_saveWindow.Show()" instead of ".ShowDialog". I need this to open and close without user input, but the "Show()" doesn't draw the chart in the window at all.
Any ideas why this is happening?
foreach (TestSequenceItem tsi in resultsToSave)
{
tsi.Instances[0].ChartStackPanel.Visibility = Visibility.Visible;
for (int i=0; i<tsi.Instances[0].StackChartList.Count; i++)
{
Chart _saveChart = tsi.Instances[0].StackChartList[i];
tsi.Instances[0].ChartStackPanel.Children.Remove(_saveChart);
ScrollViewer panel = new ScrollViewer { Content = _saveChart };
Window _saveWindow = new Window { Content = panel };
_saveWindow.Show();
var encoder = new PngBitmapEncoder();
RenderTargetBitmap bmp = new RenderTargetBitmap((int)tsiChartDoc.DefaultPageSetup.PageWidth, 600, 96, 96, PixelFormats.Pbgra32);
bmp.Render(_saveChart);
encoder.Frames.Add(BitmapFrame.Create(bmp));
using (MemoryStream stm = new MemoryStream())
{
encoder.Save(stm);
string fileName = "base64:" + Convert.ToBase64String(stm.ToArray());
// Adding a heading to the pdf
tsiChartDoc.LastSection.AddParagraph(tsi.Instances[0].StackTitleList[i].Text, "Heading2");
tsiChartDoc.LastSection.AddImage(fileName);
}
_saveWindow.Close();
panel.Content = null;
tsi.Instances[0].ChartStackPanel.Children.Insert(chartIdx, _saveChart);
}
}

How do I export my wpf InkCanvas with the proper size?

I'm trying to create a simple sketching application.
But I've ran into a weird problem. I have a Surface Pro 3 that I'm working on, with a DPI of 144, according to some system settings.
When I save the image from my app, using 96 as the dpi, it produces an image that's just a little bit smaller. Which is kind of weird.
Is there a way that I can either a) scale the canvas/strokes up that I'm saving, or tell the RenderTargetBitmap to scale properly? If I just stick 144 in there, I get the proper scale for the strokes, but my canvas size is borked.
My canvas saving code looks like this:
public void saveCanvas(object sender, RoutedEventArgs e){
this.save_filename = this.save_filename ?? this.getSaveFilename();
if (save_filename != null){
var cantwo = new InkCanvas();
cantwo.Strokes.Clear();
cantwo.Strokes.Add(this.canvas.Strokes);
cantwo.Background = this.canvas.Background;
var size = new Size(this.canvas.ActualWidth, this.canvas.ActualHeight);
cantwo.Height = size.Height;
cantwo.Width = size.Width;
cantwo.Measure(size);
cantwo.Arrange(new Rect(size));
var transform = this.canvas.LayoutTransform;
var rtb = new RenderTargetBitmap((int)this.canvas.ActualWidth, (int)this.canvas.ActualHeight, dpiX, dpiY, PixelFormats.Default);
rtb.Render(cantwo);
try {
using(var fs = File.Open(this.save_filename, FileMode.Create)){
var encoder = new JpegBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(rtb));
encoder.Save(fs);
}
}
catch(IOException){
MessageBox.Show("Failed to save image", "ERROR: Save Failed");
}
// Restore transformation if there was one.
this.canvas.LayoutTransform = transform;
}
}
How can I save my image at the same size/resolution/dpi as my canvas? (Or draw on my canvas at the same dpi/scale as I save my image)?
Instead of creating a second InkCanvas, draw a Rectangle that is filled with a VisualBrush into a DrawingVisual:
var rect = new Rect(canvas.RenderSize);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawRectangle(new VisualBrush(canvas), null, rect);
}
var rtb = new RenderTargetBitmap(
(int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default);
rtb.Render(visual);

chart series not saved in the image

In my WPF project I use the Control.Datavisualization.Charting.Chart control to plot some line series.
I also would like to store the Chart into an image, so I do the following (after series creation):
this.chart.Series.Clear();
this.chart.Series.Add(lineCurve);
this.chart.Series.Add(lineBilinear);
//store the image file in folder
DrawUtils.saveChartToPng(this.chart, path);
where saveChartToPng() is as follows:
public static void saveChartToPng(Chart chart, String filename)
{
Rect bounds = VisualTreeHelper.GetDescendantBounds(chart);
double dpi = 96d;
RenderTargetBitmap rtb = new RenderTargetBitmap((int)bounds.Width, (int)bounds.Height, dpi, dpi, System.Windows.Media.PixelFormats.Default);
DrawingVisual dv = new DrawingVisual();
using (DrawingContext dc = dv.RenderOpen())
{
VisualBrush vb = new VisualBrush(chart);
dc.DrawRectangle(vb, null, new Rect(new Point(), bounds.Size));
}
rtb.Render(dv);
//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(filename, ms.ToArray());
return;
}
After the saveChartToPng() the chart is saved to an image, but series are not present in the image. Series are visible in the chart inside the window.
What am I missing before calling the saveChartToPng()?

Creating tiff image at runtime in WPF

I'm trying to generate a simple tiff image at runtime. This image consists of white background and image downloaded from remote server.
Here's the code I've written for reaching that goal:
const string url = "http://localhost/barcode.gif";
var size = new Size(794, 1123);
var drawingVisual = new DrawingVisual();
using (var drawingContext = drawingVisual.RenderOpen())
{
drawingContext.DrawRectangle(new SolidColorBrush(Colors.White), null, new Rect(size));
var image = new BitmapImage(new Uri(url));
drawingContext.DrawImage(image, new Rect(0, 0, 180, 120));
}
var targetBitmap = new RenderTargetBitmap((int)size.Width, (int)size.Height, 96, 96, PixelFormats.Default);
targetBitmap.Render(drawingVisual);
var convertedBitmap = new FormatConvertedBitmap(targetBitmap, PixelFormats.BlackWhite, null, 0);
var encoder = new TiffBitmapEncoder();
encoder.Frames.Add(BitmapFrame.Create(convertedBitmap));
using (var fs = new FileStream("out.tif", FileMode.Create))
{
encoder.Save(fs);
}
The code works and generates "out.tif" file. But the output file has just a white background, without image received from remote server.
What can be the problem? I have tried the following code in a variety of ways, but everytime with no luck.
I found this reading about the FormatConvertedBitmap class. Maybe give it a shot
FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
// BitmapSource objects like FormatConvertedBitmap can only have their properties
// changed within a BeginInit/EndInit block.
newFormatedBitmapSource.BeginInit();
// Use the BitmapSource object defined above as the source for this new
// BitmapSource (chain the BitmapSource objects together).
newFormatedBitmapSource.Source = targetBitmap;
// Set the new format to BlackWhite.
newFormatedBitmapSource.DestinationFormat = PixelFormats.BlackWhite;
newFormatedBitmapSource.EndInit();

Getting a DrawingContext for a wpf WriteableBitmap

Is there a way to get a DrawingContext (or something similar) for a WriteableBitmap? I.e. something to allow you to call simple DrawLine/DrawRectangle/etc kinds of methods, rather than manipulate the raw pixels directly.
I found sixlettervariables' solution the most workable one. However, there's a "drawingContext.Close()" missing. According to MSDN, "A DrawingContext must be closed before its content can be rendered".
The result is the following utility function:
public static BitmapSource CreateBitmap(
int width, int height, double dpi, Action<DrawingContext> render)
{
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
render(drawingContext);
}
RenderTargetBitmap bitmap = new RenderTargetBitmap(
width, height, dpi, dpi, PixelFormats.Default);
bitmap.Render(drawingVisual);
return bitmap;
}
This can then easily be used like this:
BitmapSource image = ImageTools.CreateBitmap(
320, 240, 96,
drawingContext =>
{
drawingContext.DrawRectangle(
Brushes.Green, null, new Rect(50, 50, 200, 100));
drawingContext.DrawLine(
new Pen(Brushes.White, 2), new Point(0, 0), new Point(320, 240));
});
If you don't mind using System.Drawing you could do something like:
var wb = new WriteableBitmap( width, height, dpi, dpi,
PixelFormats.Pbgra32, null );
wb.Lock();
var bmp = new System.Drawing.Bitmap( wb.PixelWidth, wb.PixelHeight,
wb.BackBufferStride,
PixelFormat.Format32bppPArgb,
wb.BackBuffer );
Graphics g = System.Drawing.Graphics.FromImage( bmp ); // Good old Graphics
g.DrawLine( ... ); // etc...
// ...and finally:
g.Dispose();
bmp.Dispose();
wb.AddDirtyRect( ... );
wb.Unlock();
I'm wondering the same thing, as currently I do something like:
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext drawingContext = drawingVisual.RenderOpen())
{
//
// ... draw on the drawingContext
//
RenderTargetBitmap bmp = new RenderTargetBitmap(width, height, dpi, dpi, PixelFormats.Default);
bmp.Render(drawingVisual);
image.Source = bmp;
}
I'm trying to use the WriteableBitmap to allow multithreaded access to the pixel buffer, which is currently not allowed with neither a DrawingContext nor a RenderTargetBitmap. Maybe some sort of WritePixels routine based off of what you've retrieved from the RenderTargetBitmap would work?
It appears the word is no.
For future reference, we plan to use a port of the Writeable Bitmap Extensions for WPF.
For a solution using purely existing code, any of the other suggestions mentioned below will work.
A different way to solve this problem is to use a RenderTargetBitmap as a backing store, just like in the WriteableBitmap example. Then you can create and issue WPF drawing commands to it whenever you want. For example:
// create the backing store in a constructor
var backingStore =
new RenderTargetBitmap(200,200,97,97,PixelFormats.Pbgra32);
myImage.Source = backingStore;
// whenever you want to update the bitmap, do:
var drawingVisual = new DrawingVisual();
var drawingContext = drawingVisual.RenderOpen();
{
// your drawing commands go here
drawingContext.DrawRectangle(
Brushes.Red, new Pen(),
new Rect(this.RenderSize));
}
Render(drawingContext);
drawingContext.Close();
backingStore.Render(drawingVisual);
If you want to redraw this RenderTargetBitmap every frame, you can catch the CompositionTarget.Rendering event, like this:
CompositionTarget.Rendering += MyRenderingHandler;

Resources