RenderToBitmap trouble in WPF - wpf

Actually, I have to print the view from a Viewport3D, obviously, I use a RenderTargetBitmap. The problem is that if the resolution of the rendered image got high, some triangles of my scene don't appear on my final image.
For example, my viewport can be 1024*768 and the resolution I use with my RenderTargetBitmap would be 3 times viewport's resolution.
http://imgur.com/PS2F9D9
I already solved the problem in a certain way... In fact, triangles don't appear when I use a big scale. If I lower the size of my RenderTargetBitmap, it will contain everything.
Actually, I have more or less 1024*768 at 96dpi. If I want to impress at 300dpi, I need to get a huge image so I would like to avoid this last solution.
Some code :
public static RenderTargetBitmap CaptureEcran(Viewport3D p_viewPort, int p_scale)
{
RenderTargetBitmap l_bmp;
p_scale = p_scale > 5 ? 5 : p_scale;
l_bmp = new RenderTargetBitmap(p_scale * Convert.ToInt32(p_viewPort.ActualWidth), p_scale * Convert.ToInt32(p_viewPort.ActualHeight), p_scale * 96.0, p_scale * 96.0, PixelFormats.Pbgra32);
DrawingVisual vis = new DrawingVisual();
DrawingContext dc = vis.RenderOpen();
dc.DrawRectangle(System.Windows.Media.Brushes.White, null, new Rect(0, 0, p_scale * p_viewPort.ActualWidth, p_scale * p_viewPort.ActualHeight));
dc.Close();
l_bmp.Render(vis);
p_viewPort.UpdateLayout();
l_bmp.Render(p_viewPort);
return l_bmp;
}
public static void SaveImage(RenderTargetBitmap renderTargetBitmap, string m_impression)
{
System.Windows.Forms.FolderBrowserDialog l_fBD = new System.Windows.Forms.FolderBrowserDialog();
l_fBD.ShowDialog();
string l_path = l_fBD.SelectedPath + "\\" + CurrentUser() + "__" + CurrentDate() + "__." + m_impression.ToLower();
FileStream stream = new FileStream(l_path, FileMode.Create);
BitmapEncoder l_encoder = null;
switch(m_impression){
case "PNG":
PngBitmapEncoder l_png = new PngBitmapEncoder();
l_encoder = l_png;
break;
case "JPEG":
JpegBitmapEncoder l_jpeg = new JpegBitmapEncoder();
l_jpeg.QualityLevel = 30;
l_encoder = l_jpeg;
break;
}
l_encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap));
l_encoder.Save(stream);
stream.Close();
}
My call is :
SaveImage(CaptureEcran(m_viewPortCourant.ViewPort3D,5), m_impression);
Where m_impression is .png or .jpg

It finally works by using a VisualBrush containing the viewport and drawn into the DrawingContext.
DrawingVisual vis = new DrawingVisual();
DrawingContext dc = vis.RenderOpen();
VisualBrush sourceBrush = new VisualBrush(p_viewPort);
dc.DrawRectangle(System.Windows.Media.Brushes.White, null, new Rect(0, 0, p_viewPort.ActualWidth * p_scale, p_viewPort.ActualHeight * p_scale));
dc.DrawRectangle(sourceBrush, null, new Rect(new System.Windows.Point(0, 0), new Vector(p_viewPort.ActualWidth, p_viewPort.ActualHeight)));
dc.Close();
l_bmp.Render(vis);

Related

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);

Set image position to page center

I'm trying to print an image in the center of the page but I can't come up with any idea.
System.Windows.Point printLocation = new System.Windows.Point(50,50);
printLocation.X = pageWidth - 50 / 2; 50 is the margin
imageViewer = ImagePrintAdapter.CreateImageFromBitmapImage(img,printLocation);
printerDialog.PrintVisual(imageViewer, "Identification");
This is the CreateImageFromBitmapImagemethod
public static System.Windows.Controls.Image CreateImageFromBitmapImage(BitmapImage imgSource, System.Windows.Point imgLocation)
{
System.Windows.Controls.Image imageViewer = new System.Windows.Controls.Image();
imageViewer.BeginInit();
imageViewer.Source = imgSource;
imageViewer.Measure(new System.Windows.Size(double.PositiveInfinity, double.PositiveInfinity));
imageViewer.Arrange(new System.Windows.Rect(imgLocation, imageViewer.DesiredSize));
imageViewer.EndInit();
imageViewer.UpdateLayout();
return imageViewer;
}
If I set the printLocation.X to be the half of the pageWidth, shouldn't it start at the center ?
You may simply draw the image into a DrawingVisual and print it. The following simplified example assumes that the bitmap size is smaller than the printable area size:
ImageSource image = ...
var rect = new Rect(
(printDialog.PrintableAreaWidth - image.Width) / 2,
(printDialog.PrintableAreaHeight - image.Height) / 2,
image.Width, image.Height);
var visual = new DrawingVisual();
using (var dc = visual.RenderOpen())
{
dc.DrawImage(bitmap, rect);
}
printDialog.PrintVisual(visual, "");
Note that you may as well use any other size for the Rectangle, i.e. scale the printed image accordingly.

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()?

Get a bitmap from a WPF unshown control

I'd like to create a bitmap from a WPF control. I see some examples in this forum (one above all: Render a "not visible" WPF controls to an bitmap image ), but they can render well only if the WPF control is already shown on the screen.
I have to create a control in a model to associate the bitmap result into an his internal bitmap field.
I followed the example into the above thread, but the result was a bitmap with only a part of the content of the control (as if it was not completely rendered).
How perform a complete render before the image render aquisition?
This is my source code:
if( spChart == null){
String s = "<SparrowChart xmlns=\"http://sparrowtoolkit.codeplex.com/wpf\">" +
" <SparrowChart.XAxis>" +
" <LinearXAxis/>" +
" </SparrowChart.XAxis>" +
" <SparrowChart.YAxis>" +
" <LinearYAxis/>" +
" </SparrowChart.YAxis>" +
"</SparrowChart>";
System.IO.StringReader stringReader = new System.IO.StringReader(s);
System.Xml.XmlReader xmlReader;
xmlReader = System.Xml.XmlReader.Create(stringReader);
spChart = (Sparrow.Chart.SparrowChart)System.Windows.Markup.XamlReader.Load(xmlReader);
spChart.XAxes[0].MinValue = 0;
spChart.XAxes[0].MaxValue = 10;
spChart.YAxes[0].MinValue = 0;
spChart.YAxes[0].MaxValue = 10;
spChart.Series.Clear();
} else {
spChart.Series.Clear();
}
List<System.Drawing.PointF> points = new List<System.Drawing.PointF> {new System.Drawing.PointF(3, 7),
new System.Drawing.PointF(5, 2),
new System.Drawing.PointF(8, 4),
new System.Drawing.PointF(4, 6)};
Sparrow.Chart.SeriesBase LS = new Sparrow.Chart.SplineSeries();
foreach(System.Drawing.PointF x in points) {
Sparrow.Chart.DoublePoint newPoint = new Sparrow.Chart.DoublePoint();
newPoint.Data=x.X;
newPoint.Value=x.Y;
}
spChart.Series.Add(LS);
LS = new Sparrow.Chart.ScatterSeries();
foreach(System.Drawing.PointF x in points) {
Sparrow.Chart.DoublePoint newPoint = new Sparrow.Chart.DoublePoint();
newPoint.Data=x.X;
newPoint.Value=x.Y;
}
spChart.Series.Add(LS);
spChart.Measure(new System.Windows.Size(Double.PositiveInfinity, Double.PositiveInfinity));
spChart.Arrange(new System.Windows.Rect(new System.Windows.Size(1000, 1000)));
spChart.UpdateLayout();
RenderTargetBitmap rtb = new RenderTargetBitmap((int)SpChart.ActualWidth, (int)SpChart.ActualHeight, 96, 96, Windows.Media.PixelFormats.Pbgra32);
rtb.Render(spChart);
PngBitmapEncoder png = new PngBitmapEncoder();
png.Frames.Add(BitmapFrame.Create(rtb));
MemoryStream stream = New MemoryStream();
png.Save(stream);
Bitmap tmpBitmap = new Bitmap(Image.FromStream(stream));
bitmapToRender = MyBitmap.Clone();
MyBitmap.Dispose();
Thank you
Lucio
For me, this one works:
Size size = new Size(432, 460);
frameworkElement.Measure(size);
frameworkElement.Arrange(new Rect(new Point(), size));
frameworkElement.UpdateLayout();
RenderTargetBitmap renderTargetBitmap = new RenderTargetBitmap((int)frameworkElement.ActualWidth, (int)frameworkElement.ActualHeight, 96, 96, PixelFormats.Pbgra32);
renderTargetBitmap.Render(frameworkElement);
FormatConvertedBitmap monoBitmap = new FormatConvertedBitmap(renderTargetBitmap, PixelFormats.BlackWhite, null, 0);
BmpBitmapEncoder bmpImage = new BmpBitmapEncoder();
bmpImage.Frames.Add(BitmapFrame.Create(monoBitmap));
byte[] bmpData;
using (MemoryStream buffer = new MemoryStream())
{
bmpImage.Save(buffer);
bmpData = buffer.ToArray();
}
Sorry for resulting in monochrome bitmap, that's just what I used. This can simply be converted to any bitmap type as a result.

WPF Clear Region on a Drawing Context?

So I am producing a transparent PNG using a DrawingContext and DrawingVisual.
Inside the DrawingContext, I drew a rectange.
I would now like to "cut out" a circle inside of the rectangle. How do I do this? I did not find any functions in drawing context to clear a region.
You can try using CombinedGeometry to combine 2 geometries (each time). It has GeometryCombineMode allowing you to specify some logic combination. In this case what you need is GeometryCombineMode.Xor. The intersection of the Rect and the Ellipse (cirlce) will be cut out. Here is the simple code demonstrating it:
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen()) {
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
dc.DrawGeometry(Brushes.Blue, null, cb);
}
I hope you know how to render the DrawingVisual. You can use some RenderTargetBitmap to capture it into some kind of BitmapSource and then you have many ways to show this bitmap.
Here is the screenshot:
The Black region means the color is transparent.
In case you want to cut out some complex image (such as drawn text or image). You can turn the CombinedGeometry into some kind of OpacityMask (type of Brush). We can turn it into a DrawingBrush and this brush can be used as OpacityMask which can be passed into DrawingContext.PushOpacityMask method:
DrawingVisual dv = new DrawingVisual();
using (var dc = dv.RenderOpen()) {
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
var mask = new DrawingBrush(new GeometryDrawing(Brushes.Blue, null, cb));
dc.PushOpacityMask(mask);
dc.DrawImage(someImage, rect);
dc.DrawText(new FormattedText("Windows Presentation Foundation",
System.Globalization.CultureInfo.CurrentCulture,
System.Windows.FlowDirection.LeftToRight,
new Typeface("Lucida Bright"), 30, Brushes.Red){
MaxTextWidth = rect.Width,
MaxTextHeight = rect.Height,
TextAlignment = TextAlignment.Center
}, new Point());
}
Note that the rect should have the size of your whole drawing. Then positioning the hole and other drawn stuff will be exact as what you want.
Finally the DrawingVisual also has a useful property called Clip which is a Geometry. So you can prepare some CombinedGeometry and assign it to DrawingVisual.Clip property.
Suppose you already have your DrawingVisual (with some drawn stuff including text, images, ...). The following code will punch a hole through it:
//prepare the geometry, which can be considered as the puncher.
var rect = new Rect(0, 0, 300, 200);
var cb = new CombinedGeometry(GeometryCombineMode.Xor,
new RectangleGeometry(rect),
new EllipseGeometry(new Point(150, 100), 50, 50));
//punch the DrawingVisual
yourDrawingVisual.Clip = cb;

Resources